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) 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) 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) set(OpenVDB_${COMPONENT}_FOUND TRUE)
else() else()
set(OpenVDB_${COMPONENT}_FOUND FALSE) set(OpenVDB_${COMPONENT}_FOUND FALSE)
@ -518,12 +521,19 @@ list(REMOVE_DUPLICATES OpenVDB_LIBRARY_DIRS)
foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS}) foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
if(NOT TARGET OpenVDB::${COMPONENT}) 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) add_library(OpenVDB::${COMPONENT} UNKNOWN IMPORTED)
set_target_properties(OpenVDB::${COMPONENT} PROPERTIES set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
INTERFACE_COMPILE_OPTIONS "${OpenVDB_DEFINITIONS}" INTERFACE_COMPILE_OPTIONS "${OpenVDB_DEFINITIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${OpenVDB_INCLUDE_DIR}" INTERFACE_INCLUDE_DIRECTORIES "${OpenVDB_INCLUDE_DIR}"
IMPORTED_LINK_DEPENDENT_LIBRARIES "${_OPENVDB_HIDDEN_DEPENDENCIES}" # non visible deps 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 INTERFACE_COMPILE_FEATURES cxx_std_11
IMPORTED_LOCATION "${OpenVDB_${COMPONENT}_LIBRARY}" IMPORTED_LOCATION "${OpenVDB_${COMPONENT}_LIBRARY}"
) )
@ -531,8 +541,13 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
if (_is_multi) if (_is_multi)
set_target_properties(OpenVDB::${COMPONENT} PROPERTIES set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
IMPORTED_LOCATION_RELEASE "${OpenVDB_${COMPONENT}_LIBRARY_RELEASE}" 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 () endif ()
if (OPENVDB_USE_STATIC_LIBS) 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 name = Creality
# Configuration version of this file. Config file will only be installed, if the config_version differs. # 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. # 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? # Where to get the updates from?
config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Creality/ 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% # 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; 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 // must be co-planar
if( Index a2 = opposite_vertex(shared[0].first, shared[1].first);
A.supporting_plane() != B.supporting_plane() && if (! B.supporting_plane().has_on(A.vertex(a2)))
A.supporting_plane() != B.supporting_plane().opposite())
{
return false; 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 // Since A and B are non-degenerate the intersection must be a polygon
// (triangle). Either // (triangle). Either
// - the vertex of A (B) opposite the shared edge of lies on B (A), or // - 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 // Determine if the vertex opposite edge (a0,a1) in triangle A lies in
// (intersects) triangle B // (intersects) triangle B
const auto & opposite_point_inside = []( 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 -> bool
{ {
// get opposite index return CGAL::do_intersect(A.vertex(a2),B);
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;
}; };
// Determine if edge opposite vertex va in triangle A intersects edge // Determine if edge opposite vertex va in triangle A intersects edge
@ -681,8 +686,8 @@ inline bool igl::copyleft::cgal::SelfIntersectMesh<
}; };
if( if(
!opposite_point_inside(A,shared[0].first,shared[1].first,B) && !opposite_point_inside(A,a2,B) &&
!opposite_point_inside(B,shared[0].second,shared[1].second,A) && !opposite_point_inside(B,b2,A) &&
!opposite_edges_intersect(A,shared[0].first,B,shared[1].second) && !opposite_edges_intersect(A,shared[0].first,B,shared[1].second) &&
!opposite_edges_intersect(A,shared[1].first,B,shared[0].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()); //process_chunk(0, candidate_triangle_pairs.size());
} }
#endif #endif

View file

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

View file

@ -958,7 +958,7 @@ namespace DoExport {
skirts.emplace_back(std::move(s)); skirts.emplace_back(std::move(s));
} }
ooze_prevention.enable = true; 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 #if 0
require "Slic3r/SVG.pm"; require "Slic3r/SVG.pm";
Slic3r::SVG::output( Slic3r::SVG::output(
@ -1091,7 +1091,7 @@ namespace DoExport {
static inline std::vector<const PrintInstance*> sort_object_instances_by_max_z(const Print &print) 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::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; std::vector<const PrintInstance*> instances;
instances.reserve(objects.size()); instances.reserve(objects.size());
for (const PrintObject *object : objects) 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 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 // A shorter C++14 style form of the enable_if metafunction
template<bool B, class T> template<bool B, class T>
using enable_if_t = typename std::enable_if<B, T>::type; 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; 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 } // namespace Slic3r
#endif // MTUTILS_HPP #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)); auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face));
int i = 0; int i = 0;
Vec3crd trface; 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); 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); const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true);
for (const ModelVolume *v : this->volumes) for (const ModelVolume *v : this->volumes)
{
if (v->is_model_part()) if (v->is_model_part())
m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix()));
}
} }
return m_raw_bounding_box; 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) { if (keep_upper) {
upper->set_model(nullptr); upper->set_model(nullptr);
upper->sla_support_points.clear(); upper->sla_support_points.clear();
lower->sla_drain_holes.clear(); upper->sla_drain_holes.clear();
upper->sla_points_status = sla::PointsStatus::NoPoints; upper->sla_points_status = sla::PointsStatus::NoPoints;
upper->clear_volumes(); upper->clear_volumes();
upper->input_file = ""; upper->input_file = "";

View file

@ -674,6 +674,7 @@ public:
set_rotation(Z, rotation); set_rotation(Z, rotation);
set_offset(X, unscale<double>(offs(X))); set_offset(X, unscale<double>(offs(X)));
set_offset(Y, unscale<double>(offs(Y))); set_offset(Y, unscale<double>(offs(Y)));
this->object->invalidate_bounding_box();
} }
protected: 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. // Update the ModelObject instance, possibly invalidate the linked PrintObjects.
assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); 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. // 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 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 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); 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.emplace_back(new ModelInstance(*model_instance));
model_object.instances.back()->set_model_object(&model_object); model_object.instances.back()->set_model_object(&model_object);
} }
} else { } else if (! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(),
// Just synchronize the content of the instances. This avoids memory allocation and it does not invalidate ModelInstance pointers, [](auto l, auto r){ return l->print_volume_state == r->print_volume_state && l->printable == r->printable &&
// which may be accessed by G-code export in the meanwhile to deduce sequential print order. l->get_transformation().get_matrix().isApprox(r->get_transformation().get_matrix()); })) {
auto new_instance = model_object_new.instances.begin(); // 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) { 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)->set_transformation((*new_instance)->get_transformation());
(*old_instance)->print_volume_state = (*new_instance)->print_volume_state; (*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; std::vector<coord_t> object_height;
for (const PrintObject *object : m_objects) 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()); std::sort(object_height.begin(), object_height.end());
// Ignore the tallest *copy* (this is why we repeat height for all of them): // 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. // 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 PrintObject *object : m_objects)
for (const PrintInstance &instance : object->instances()) { for (const PrintInstance &instance : object->instances()) {
bb.merge(instance.shift); bb.merge(instance.shift);
bb.merge(instance.shift + to_2d(object->size)); bb.merge(instance.shift + to_2d(object->size()));
} }
return bb; return bb;
} }

View file

@ -120,17 +120,17 @@ public:
// so that next call to make_perimeters() performs a union() before computing loops // so that next call to make_perimeters() performs a union() before computing loops
bool typed_slices; 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 PrintObjectConfig& config() const { return m_config; }
const LayerPtrs& layers() const { return m_layers; } const LayerPtrs& layers() const { return m_layers; }
const SupportLayerPtrs& support_layers() const { return m_support_layers; } const SupportLayerPtrs& support_layers() const { return m_support_layers; }
const Transform3d& trafo() const { return m_trafo; } const Transform3d& trafo() const { return m_trafo; }
const PrintInstances& instances() const { return m_instances; } 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 // 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 // adds region_id, too, if necessary
void add_region_volume(unsigned int region_id, int volume_id, const t_layer_height_range &layer_range) { 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 combine_infill();
void _generate_support_material(); void _generate_support_material();
// XYZ in scaled coordinates
Vec3crd m_size;
PrintObjectConfig m_config; PrintObjectConfig m_config;
// Translation in Z + Rotation + Scaling / Mirroring. // Translation in Z + Rotation + Scaling / Mirroring.
Transform3d m_trafo = Transform3d::Identity(); Transform3d m_trafo = Transform3d::Identity();

View file

@ -43,7 +43,7 @@ namespace Slic3r {
PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_instances) : PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_instances) :
PrintObjectBaseWithState(print, model_object), PrintObjectBaseWithState(print, model_object),
typed_slices(false), 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 // 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(); const BoundingBoxf3 modobj_bbox = model_object->raw_bounding_box();
m_copies_shift = Point::new_scale(modobj_bbox.min(0), modobj_bbox.min(1)); m_copies_shift = Point::new_scale(modobj_bbox.min(0), modobj_bbox.min(1));
// Scale the object size and store it // 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) { if (add_instances) {
@ -73,6 +73,8 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta
PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances) PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances)
{ {
for (PrintInstance &i : instances)
i.shift += m_copies_shift;
// Invalidate and set copies. // Invalidate and set copies.
PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED; PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED;
bool equal_length = instances.size() == m_instances.size(); 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 }) || if (m_print->invalidate_steps({ psSkirt, psBrim, psGCodeExport }) ||
(! equal_length && m_print->invalidate_step(psWipeTower))) (! equal_length && m_print->invalidate_step(psWipeTower)))
status = PrintBase::APPLY_STATUS_INVALIDATED; status = PrintBase::APPLY_STATUS_INVALIDATED;
m_instances = instances; m_instances = std::move(instances);
for (PrintInstance &i : m_instances) for (PrintInstance &i : m_instances)
i.print_object = this; i.print_object = this;
} }
@ -1448,7 +1450,7 @@ void PrintObject::update_slicing_parameters()
{ {
if (! m_slicing_params.valid) if (! m_slicing_params.valid)
m_slicing_params = SlicingParameters::create_from_config( 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) 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/ray_mesh_intersect.h>
#include <igl/point_mesh_squared_distance.h> #include <igl/point_mesh_squared_distance.h>
#include <igl/remove_duplicate_vertices.h> #include <igl/remove_duplicate_vertices.h>
#include <igl/collapse_small_triangles.h>
#include <igl/signed_distance.h> #include <igl/signed_distance.h>
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma warning(pop) #pragma warning(pop)
@ -194,17 +195,12 @@ public:
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ #endif /* SLIC3R_SLA_NEEDS_WINDTREE */
}; };
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { static const constexpr double MESH_EPS = 1e-6;
static const double dEPS = 1e-6;
void to_eigen_mesh(const TriangleMesh &tmesh, Eigen::MatrixXd &V, Eigen::MatrixXi &F)
{
const stl_file& stl = tmesh.stl; 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); V.resize(3*stl.stats.number_of_facets, 3);
F.resize(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) { 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); F(i, 2) = int(3*i+2);
} }
// We will convert this to a proper 3d mesh with no duplicate points. if (!tmesh.has_shared_vertices())
Eigen::VectorXi SVI, SVJ; {
igl::remove_duplicate_vertices(V, F, dEPS, m_V, SVI, SVJ, m_F); 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 // Build the AABB accelaration tree
m_aabb->init(m_V, m_F); 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; 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::hit_result
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
{ {

View file

@ -12,6 +12,9 @@ namespace sla {
struct Contour3D; 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 /// An index-triangle structure for libIGL functions. Also serves as an
/// alternative (raw) input format for the SLASupportTree. /// alternative (raw) input format for the SLASupportTree.
// Implemented in libslic3r/SLA/Common.cpp // Implemented in libslic3r/SLA/Common.cpp
@ -30,11 +33,15 @@ class EigenMesh3D {
public: public:
EigenMesh3D(const TriangleMesh&); explicit EigenMesh3D(const TriangleMesh&);
explicit EigenMesh3D(const Contour3D &other);
EigenMesh3D(const EigenMesh3D& other); EigenMesh3D(const EigenMesh3D& other);
EigenMesh3D(const Contour3D &other);
EigenMesh3D& operator=(const EigenMesh3D&); EigenMesh3D& operator=(const EigenMesh3D&);
EigenMesh3D(EigenMesh3D &&other);
EigenMesh3D& operator=(EigenMesh3D &&other);
~EigenMesh3D(); ~EigenMesh3D();
inline double ground_level() const { return m_ground_level + m_gnd_offset; } 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_valid() const { return m_mesh != nullptr; }
inline bool is_hit() const { return !std::isinf(m_t); } 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 { inline const Vec3d& normal() const {
assert(is_valid()); assert(is_valid());
return m_normal; return m_normal;

View file

@ -1,3 +1,5 @@
#include <string_view>
#include <libslic3r/SLA/RasterWriter.hpp> #include <libslic3r/SLA/RasterWriter.hpp>
#include "libslic3r/PrintConfig.hpp" #include "libslic3r/PrintConfig.hpp"
@ -12,14 +14,16 @@
namespace Slic3r { namespace sla { 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 = "); std::string out("action = print\njobDir = ");
out += projectname + "\n"; out += projectname + "\n";
write_ini(m_config, out);
for (auto &param : m_config)
out += param.first + " = " + param.second + "\n";
return out; return out;
} }
@ -53,7 +57,12 @@ void RasterWriter::save(Zipper &zipper, const std::string &prjname)
zipper.add_entry("config.ini"); 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++) 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; 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 } // namespace
void RasterWriter::set_config(const DynamicPrintConfig &cfg) 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["printerVariant"] = get_cfg_value(cfg, "printer_variant");
m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id"); m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id");
m_config["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id"); m_config["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id");
m_config["fileCreationTimestamp"] = Utils::utc_timestamp(); m_config["fileCreationTimestamp"] = Utils::utc_timestamp();
m_config["prusaSlicerVersion"] = SLIC3R_BUILD_ID; m_config["prusaSlicerVersion"] = SLIC3R_BUILD_ID;
append_full_config(cfg, m_slicer_config);
} }
void RasterWriter::set_statistics(const PrintStatistics &stats) void RasterWriter::set_statistics(const PrintStatistics &stats)

View file

@ -66,8 +66,10 @@ private:
double m_gamma; double m_gamma;
std::map<std::string, std::string> m_config; 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: public:

View file

@ -166,190 +166,182 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder,
return pc == ABORT; 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( EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect(
const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width) const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width)
{ {
static const size_t SAMPLES = 8; static const size_t SAMPLES = 8;
// method based on: // Move away slightly from the touching point to avoid raycasting on the
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space // 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 // We will shoot multiple rays from the head pinpoint in the direction
// of the pinhead robe (side) surface. The result will be the smallest // of the pinhead robe (side) surface. The result will be the smallest
// hit distance. // hit distance.
// Move away slightly from the touching point to avoid raycasting on the ccr::enumerate(hits.begin(), hits.end(),
// inner surface of the mesh. [&m, &rings, sd](HitResult &hit, size_t i) {
Vec3d v = dir; // Our direction (axis)
Vec3d c = s + width * dir;
const double& sd = m_cfg.safety_distance_mm;
// Two vectors that will be perpendicular to each other and to the // Point on the circle on the pin sphere
// axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a Vec3d ps = rings.pinring(i);
// placeholder. // This is the point on the circle on the back sphere
Vec3d a(0, 1, 0), b; 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 Vec3d n = (p - ps).normalized();
// shoot rays. auto q = m.query_ray_hit(ps + sd * n, n);
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; if (q.is_inside()) { // the hit is inside the model
using HitResult = EigenMesh3D::hit_result; 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 return min_hit(hits);
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;
} }
EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( 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; static const size_t SAMPLES = 8;
PointRing<SAMPLES> ring{dir};
// helper vector calculations using Hit = EigenMesh3D::hit_result;
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;
// Hit results // Hit results
std::array<HitResult, SAMPLES> hits; std::array<Hit, SAMPLES> hits;
ccr::enumerate( ccr::enumerate(hits.begin(), hits.end(),
phis.begin(), phis.end(), [this, r, src, ins_check, &ring, dir] (Hit &hit, size_t i) {
[&m, a, b, sd, dir, r, s, ins_check, &hits] (double phi, size_t i) {
double sinphi = std::sin(phi); const double sd = m_cfg.safety_distance_mm;
double cosphi = std::cos(phi);
// Point on the circle on the pin sphere
// Let's have a safety coefficient for the radiuses. Vec3d p = ring.get(i, src, r + sd);
double rcos = (sd + r) * cosphi;
double rsin = (sd + r) * sinphi; auto hr = m_mesh.query_ray_hit(p + sd * dir, dir);
// Point on the circle on the pin sphere if(ins_check && hr.is_inside()) {
Vec3d p (s(X) + rcos * a(X) + rsin * b(X), if(hr.distance() > 2 * r + sd) hit = Hit(0.0);
s(Y) + rcos * a(Y) + rsin * b(Y), else {
s(Z) + rcos * a(Z) + rsin * b(Z)); // re-cast the ray from the outside of the object
hit = m_mesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir);
auto hr = m.query_ray_hit(p + sd*dir, dir); }
} else hit = hr;
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;
});
auto mit = std::min_element(hits.begin(), hits.end()); return min_hit(hits);
return *mit;
} }
bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, 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 // TODO: This is a workaround to not have a faulty last bridge
while(ej(Z) >= eupper(Z) /*endz*/) { 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); m_builder.add_crossbridge(sj, ej, pillar.r);
was_connected = true; was_connected = true;
@ -430,7 +422,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
Vec3d sjback(ej(X), ej(Y), sj(Z)); Vec3d sjback(ej(X), ej(Y), sj(Z));
Vec3d ejback(sj(X), sj(Y), ej(Z)); Vec3d ejback(sj(X), sj(Y), ej(Z));
if (sjback(Z) <= slower(Z) && ejback(Z) >= eupper(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) { pillar.r) >= bridge_distance) {
// need to check collision for the cross stick // need to check collision for the cross stick
m_builder.add_crossbridge(sjback, ejback, pillar.r); m_builder.add_crossbridge(sjback, ejback, pillar.r);
@ -487,7 +479,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
bridgestart(Z) -= zdiff; bridgestart(Z) -= zdiff;
touchjp(Z) = Zdown; 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 // We can't insert a pillar under the source head to connect
// with the nearby pillar's starting junction // 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; double minz = m_builder.ground_level + 2 * m_cfg.head_width_mm;
if(bridgeend(Z) < minz) return false; if(bridgeend(Z) < minz) return false;
double t = bridge_mesh_intersect(bridgestart, double t = bridge_mesh_distance(bridgestart, dirv(bridgestart, bridgeend), r);
dirv(bridgestart, bridgeend), r);
// Cannot insert the bridge. (further search might not worth the hassle) // Cannot insert the bridge. (further search might not worth the hassle)
if(t < distance(bridgestart, bridgeend)) return false; 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. // 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(); abort_in_shame();
else { else {
// If the new endpoint is below ground, do not make a pillar // 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(); 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); hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
return score; return score;
@ -950,11 +941,11 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir)
{ {
auto hjp = head.junction_point(); auto hjp = head.junction_point();
double r = head.r_back_mm; 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; double d = 0, tdown = 0;
t = std::min(t, m_cfg.max_bridge_length_mm); 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; d += r;
if(!std::isinf(tdown)) return false; if(!std::isinf(tdown)) return false;
@ -989,7 +980,7 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head)
auto oresult = solver.optimize_max( auto oresult = solver.optimize_max(
[this, hjp, r_back](double plr, double azm) { [this, hjp, r_back](double plr, double azm) {
Vec3d n = spheric_to_dir(plr, azm).normalized(); 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 initvals(polar, azimuth), // let's start with what we have
bound(3*PI/4, PI), // Must not exceed the slope limit 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_pillar_index.insert(pp.endpoint(), unsigned(pp.id));
m_builder.add_junction(s, pillar().r); m_builder.add_junction(s, pillar().r);
double t = bridge_mesh_intersect(pillarsp, double t = bridge_mesh_distance(pillarsp, dirv(pillarsp, s),
dirv(pillarsp, s), pillar().r);
pillar().r);
if (distance(pillarsp, s) < t) if (distance(pillarsp, s) < t)
m_builder.add_bridge(pillarsp, s, pillar().r); m_builder.add_bridge(pillarsp, s, pillar().r);
@ -1312,8 +1302,8 @@ void SupportTreeBuildsteps::routing_headless()
Vec3d sj = sp + R * n; // stick start point Vec3d sj = sp + R * n; // stick start point
// This is only for checking // This is only for checking
double idist = bridge_mesh_intersect(sph, DOWN, R, true); double idist = bridge_mesh_distance(sph, DOWN, R, true);
double realdist = ray_mesh_intersect(sj, DOWN); double realdist = ray_mesh_intersect(sj, DOWN).distance();
double dist = realdist; double dist = realdist;
if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level; 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 // When bridging heads to pillars... TODO: find a cleaner solution
ccr::BlockingMutex m_bridge_mutex; ccr::BlockingMutex m_bridge_mutex;
inline double ray_mesh_intersect(const Vec3d& s, inline EigenMesh3D::hit_result ray_mesh_intersect(const Vec3d& s,
const Vec3d& dir) 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 // This function will test if a future pinhead would not collide with the
@ -229,6 +229,11 @@ class SupportTreeBuildsteps {
double r_pin, double r_pin,
double r_back, double r_back,
double width); 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. // Checking bridge (pillar and stick as well) intersection with the model.
// If the function is used for headless sticks, the ins_check parameter // If the function is used for headless sticks, the ins_check parameter
@ -243,6 +248,11 @@ class SupportTreeBuildsteps {
const Vec3d& dir, const Vec3d& dir,
double r, double r,
bool ins_check = false); 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. // Helper function for interconnecting two pillars with zig-zag bridges.
bool interconnect(const Pillar& pillar, const Pillar& nextpillar); bool interconnect(const Pillar& pillar, const Pillar& nextpillar);

View file

@ -678,7 +678,7 @@ void SLAPrint::process()
// We want to first process all objects... // We want to first process all objects...
std::vector<SLAPrintObjectStep> level1_obj_steps = { 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 // 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 // propagate to dependent steps
if (step == slaposHollowing) { if (step == slaposHollowing) {
invalidated |= this->invalidate_all_steps(); invalidated |= this->invalidate_all_steps();
} else if (step == slaposObjectSlice) { } else if (step == slaposDrillHoles) {
invalidated |= this->invalidate_steps({ slaposDrillHolesIfHollowed, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports }); invalidated |= this->invalidate_steps({ slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports });
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
} else if (step == slaposDrillHolesIfHollowed) { } else if (step == slaposObjectSlice) {
invalidated |= this->invalidate_steps({ slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports }); invalidated |= this->invalidate_steps({ slaposSupportPoints, slaposSupportTree, slaposPad, slaposSliceSupports });
invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval);
} else if (step == slaposSupportPoints) { } 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() : const std::vector<ExPolygons>& v = o == soModel? m_po->get_model_slices() :
m_po->get_support_slices(); m_po->get_support_slices();
if(idx >= v.size()) return EMPTY_SLICE;
return idx >= v.size() ? EMPTY_SLICE : v[idx]; return idx >= v.size() ? EMPTY_SLICE : v[idx];
} }

View file

@ -20,8 +20,8 @@ enum SLAPrintStep : unsigned int {
enum SLAPrintObjectStep : unsigned int { enum SLAPrintObjectStep : unsigned int {
slaposHollowing, slaposHollowing,
slaposDrillHoles,
slaposObjectSlice, slaposObjectSlice,
slaposDrillHolesIfHollowed,
slaposSupportPoints, slaposSupportPoints,
slaposSupportTree, slaposSupportTree,
slaposPad, slaposPad,
@ -138,9 +138,9 @@ public:
// Returns the current layer height // Returns the current layer height
float layer_height() const { return m_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. // Methods for setting the indices into the slice vectors.
void set_model_slice_idx(const SLAPrintObject &po, size_t id) { void set_model_slice_idx(const SLAPrintObject &po, size_t id) {

View file

@ -26,9 +26,9 @@ namespace Slic3r {
namespace { namespace {
const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS = { const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS = {
5, // slaposHollowing, 10, // slaposHollowing,
20, // slaposObjectSlice, 10, // slaposDrillHolesIfHollowed
5, // slaposDrillHolesIfHollowed 10, // slaposObjectSlice,
20, // slaposSupportPoints, 20, // slaposSupportPoints,
10, // slaposSupportTree, 10, // slaposSupportTree,
10, // slaposPad, 10, // slaposPad,
@ -38,9 +38,9 @@ const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS = {
std::string OBJ_STEP_LABELS(size_t idx) std::string OBJ_STEP_LABELS(size_t idx)
{ {
switch (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 slaposObjectSlice: return L("Slicing model");
case slaposDrillHolesIfHollowed: return L("Drilling holes into hollowed model.");
case slaposSupportPoints: return L("Generating support points"); case slaposSupportPoints: return L("Generating support points");
case slaposSupportTree: return L("Generating support tree"); case slaposSupportTree: return L("Generating support tree");
case slaposPad: return L("Generating pad"); case slaposPad: return L("Generating pad");
@ -80,70 +80,70 @@ SLAPrint::Steps::Steps(SLAPrint *print)
void SLAPrint::Steps::hollow_model(SLAPrintObject &po) void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
{ {
po.m_hollowing_data.reset(); 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 (! po.m_config.hollowing_enable.getBool()) {
//if (drilling_needed && po.transformed_mesh().needed_repair()) BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";
// throw std::runtime_error(L("The mesh appears to be too broken " return;
// "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();
}
} }
BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!";
// Drill holes into the hollowed/original mesh. double thickness = po.m_config.hollowing_min_thickness.getFloat();
if (! drilling_needed) double quality = po.m_config.hollowing_quality.getFloat();
BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; 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 { else {
BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes."; po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
sla::DrainHoles drainholes = po.transformed_drainhole_points(); po.m_hollowing_data->interior = *meshptr;
auto &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes;
TriangleMesh holes_mesh; hollowed_mesh = po.transformed_mesh();
hollowed_mesh.merge(po.m_hollowing_data->interior);
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."));
}
hollowed_mesh.require_shared_vertices(); 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 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 bottom of the bounding box created around the supported model. So
// the first layer which is usually thicker will be part of the supports // 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 // get polygons for all instances in the object
static ClipperPolygons get_all_polygons( static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o)
const ExPolygons & input_polygons,
const std::vector<SLAPrintObject::Instance> &instances,
bool is_lefthanded)
{ {
namespace sl = libnest2d::sl; namespace sl = libnest2d::sl;
if (!record.print_obj()) return {};
ClipperPolygons polygons; 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()); polygons.reserve(input_polygons.size() * instances.size());
for (const ExPolygon& polygon : input_polygons) { 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; coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs;
for(const SliceRecord& slicerecord : o->get_slice_index()) { 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; coord_t lvlid = slicerecord.print_level() - gndlvl;
// Neat trick to round the layer levels to the grid. // 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); supports_polygons.reserve(c);
for(const SliceRecord& record : layer.slices()) { for(const SliceRecord& record : layer.slices()) {
const SLAPrintObject *po = record.print_obj();
const ExPolygons &modelslices = record.get_slice(soModel); ClipperPolygons modelslices = get_all_polygons(record, soModel);
for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp));
bool is_lefth = record.print_obj()->is_left_handed();
if (!modelslices.empty()) { ClipperPolygons supportslices = get_all_polygons(record, soSupport);
ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth); for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp));
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));
}
} }
model_polygons = polyunion(model_polygons); model_polygons = polyunion(model_polygons);
@ -864,8 +863,8 @@ void SLAPrint::Steps::execute(SLAPrintObjectStep step, SLAPrintObject &obj)
{ {
switch(step) { switch(step) {
case slaposHollowing: hollow_model(obj); break; case slaposHollowing: hollow_model(obj); break;
case slaposDrillHoles: drill_holes(obj); break;
case slaposObjectSlice: slice_model(obj); break; case slaposObjectSlice: slice_model(obj); break;
case slaposDrillHolesIfHollowed: break;
case slaposSupportPoints: support_points(obj); break; case slaposSupportPoints: support_points(obj); break;
case slaposSupportTree: support_tree(obj); break; case slaposSupportTree: support_tree(obj); break;
case slaposPad: generate_pad(obj); break; case slaposPad: generate_pad(obj); break;

View file

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

View file

@ -21,8 +21,10 @@
#include <array> #include <array>
#include <type_traits> #include <type_traits>
#include <algorithm> #include <algorithm>
#include <cmath>
#ifndef NDEBUG #ifndef NDEBUG
#include <ostream>
#include <iostream> #include <iostream>
#endif #endif
@ -63,7 +65,7 @@ namespace implementation {
template<bool B, class T> template<bool B, class T>
using enable_if_t = typename std::enable_if<B, T>::type; 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> template<class T, class O = T>
using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>; using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>;
@ -82,41 +84,15 @@ struct remove_cvref {
template< class T > template< class T >
using remove_cvref_t = typename remove_cvref<T>::type; 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> 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> 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: public:
explicit SymetricMatrix(ArithmeticOnly<T> c = T()) { std::fill(m, m + N, c); } 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 // Make plane
SymetricMatrix(T a, T b, T c, T d) SymetricMatrix(T a, T b, T c, T d)
{ {
@ -140,21 +116,16 @@ public:
return det; 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], for (size_t i = 0; i < N; ++i) m[i] += n[i];
m[4] + n[4], m[5] + n[5], m[6] + n[6], return *this;
m[7] + n[7], m[8] + n[8],
m[9] + n[9]);
} }
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]; SymetricMatrix self = *this;
m[4]+=n[4]; m[5]+=n[5]; m[6]+=n[6]; m[7]+=n[7]; return self += n;
m[8]+=n[8]; m[9]+=n[9];
return *this;
} }
T m[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() template<class Mesh> void SimplifiableMesh<Mesh>::compact_faces()
{ {
auto it = std::remove_if(m_faceinfo.begin(), m_faceinfo.end(), 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> template<class Mesh>
void SimplifiableMesh<Mesh>::simplify_mesh_lossless() template<class Fn> void SimplifiableMesh<Mesh>::simplify_mesh_lossless(Fn &&fn)
{ {
// init // init
for (FaceInfo &fi : m_faceinfo) fi.deleted = false; 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) 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) { for (FaceInfo &fi : m_faceinfo) {
if (fi.err[3] > threshold || fi.deleted || fi.dirty) continue; 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); 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, // Standard "generated by Slic3r version xxx timestamp xxx" header string,
// to be placed at the top of Slic3r generated files. // to be placed at the top of Slic3r generated files.
std::string header_slic3r_generated(); std::string header_slic3r_generated();

View file

@ -231,16 +231,17 @@ static inline bool is_approx(Number value, Number test_value)
} }
template<class...Args> 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; static const size_t INITIAL_LEN = 1024;
std::vector<char> buffer(INITIAL_LEN, '\0'); 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)) { if (bufflen >= int(INITIAL_LEN)) {
buffer.resize(size_t(bufflen) + 1); 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); 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 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() std::string header_slic3r_generated()
{ {
return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp(); 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/Mouse3DController.hpp
GUI/DoubleSlider.cpp GUI/DoubleSlider.cpp
GUI/DoubleSlider.hpp GUI/DoubleSlider.hpp
GUI/ObjectDataViewModel.cpp
GUI/ObjectDataViewModel.hpp
Utils/Http.cpp Utils/Http.cpp
Utils/Http.hpp Utils/Http.hpp
Utils/FixModelByWin10.cpp Utils/FixModelByWin10.cpp

View file

@ -10,7 +10,7 @@
#include "libslic3r/SLAPrint.hpp" #include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Slicing.hpp" #include "libslic3r/Slicing.hpp"
#include "libslic3r/GCode/Analyzer.hpp" #include "libslic3r/GCode/Analyzer.hpp"
#include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/BitmapCache.hpp"
#include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/STL.hpp"
#include "libslic3r/Utils.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) for (unsigned int i = 0; i < colors_count; ++i)
{ {
const std::string& txt_color = config->opt_string("extruder_colour", 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); colors[i].set(txt_color, rgb);
} }
else else
{ {
const std::string& txt_color = config->opt_string("filament_colour", i); 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); colors[i].set(txt_color, rgb);
} }
} }

View file

@ -1,6 +1,9 @@
#include "BitmapCache.hpp" #include "BitmapCache.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "GUI.hpp"
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#if ! defined(WIN32) && ! defined(__APPLE__) #if ! defined(WIN32) && ! defined(__APPLE__)
@ -20,6 +23,16 @@
namespace Slic3r { namespace GUI { 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() void BitmapCache::clear()
{ {
for (std::pair<const std::string, wxBitmap*> &bitmap : m_map) 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); auto it = m_map.find(bitmap_key);
if (it == m_map.end()) { if (it == m_map.end()) {
bitmap = new wxBitmap(width, height); 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; m_map[bitmap_key] = bitmap;
} else { } else {
bitmap = it->second; bitmap = it->second;
@ -100,8 +121,13 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg
size_t width = 0; size_t width = 0;
size_t height = 0; size_t height = 0;
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { 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(); width += bmp->GetWidth();
height = std::max<size_t>(height, bmp->GetHeight()); height = std::max<size_t>(height, bmp->GetHeight());
#endif
} }
#ifdef BROKEN_ALPHA #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) { for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
if (bmp->GetWidth() > 0) if (bmp->GetWidth() > 0)
memDC.DrawBitmap(*bmp, x, 0, true); 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(); x += bmp->GetWidth();
#endif
} }
memDC.SelectObject(wxNullBitmap); memDC.SelectObject(wxNullBitmap);
return bitmap; return bitmap;
@ -175,7 +206,7 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *beg
#endif #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); wxImage image(width, height);
image.InitAlpha(); image.InitAlpha();
@ -192,7 +223,7 @@ wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned w
if (grayscale) if (grayscale)
image = image.ConvertToGreyscale(m_gs, m_gs, m_gs); 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, 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, 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 ? std::string bitmap_key = bitmap_name + ( target_height !=0 ?
"-h" + std::to_string(target_height) : "-h" + std::to_string(target_height) :
"-w" + std::to_string(target_width)) "-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" : ""); + (grayscale ? "-gs" : "");
/* For the Dark mode of any platform, we should draw icons in respect to OS background /* 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) if (image == nullptr)
return 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 svg_scale = target_height != 0 ?
(float)target_height / image->height : target_width != 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); ::nsvgDeleteRasterizer(rast);
::nsvgDelete(image); ::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); wxImage image(width, height);
image.InitAlpha(); image.InitAlpha();
unsigned char* imgdata = image.GetData(); unsigned char* imgdata = image.GetData();
@ -312,7 +348,32 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi
*imgdata ++ = b; *imgdata ++ = b;
*imgalpha ++ = transparency; *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 } // namespace GUI

View file

@ -1,24 +1,22 @@
#ifndef SLIC3R_GUI_BITMAP_CACHE_HPP #ifndef SLIC3R_GUI_BITMAP_CACHE_HPP
#define SLIC3R_GUI_BITMAP_CACHE_HPP #define SLIC3R_GUI_BITMAP_CACHE_HPP
#include <map>
#include <wx/wxprec.h> #include <wx/wxprec.h>
#ifndef WX_PRECOMP #ifndef WX_PRECOMP
#include <wx/wx.h> #include <wx/wx.h>
#endif #endif
#include "libslic3r/libslic3r.h"
#include "libslic3r/Config.hpp"
#include "GUI.hpp"
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
class BitmapCache class BitmapCache
{ {
public: public:
BitmapCache() {} BitmapCache();
~BitmapCache() { clear(); } ~BitmapCache() { clear(); }
void 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; } 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); } 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 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 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(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. // 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); 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. // 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, 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]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); } /*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 */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: private:
std::map<std::string, wxBitmap*> m_map; 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 } // GUI

View file

@ -14,7 +14,9 @@
#include <GL/glew.h> #include <GL/glew.h>
#if !ENABLE_6DOF_CAMERA
static const float GIMBALL_LOCK_THETA_MAX = 180.0f; static const float GIMBALL_LOCK_THETA_MAX = 180.0f;
#endif // !ENABLE_6DOF_CAMERA
// phi / theta angles to orient the camera. // phi / theta angles to orient the camera.
static const float VIEW_DEFAULT[2] = { 45.0f, 45.0f }; 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) switch (m_type)
{ {
case Unknown: case Unknown: return "unknown";
return "unknown"; case Perspective: return "perspective";
case Perspective:
return "perspective";
default: default:
case Ortho: case Ortho: return "orthographic";
return "orthographic";
}; };
} }
@ -88,10 +87,7 @@ void Camera::set_type(EType type)
void Camera::set_type(const std::string& type) void Camera::set_type(const std::string& type)
{ {
if (type == "1") set_type((type == "1") ? Perspective : Ortho);
set_type(Perspective);
else
set_type(Ortho);
} }
void Camera::select_next_type() void Camera::select_next_type()
@ -157,17 +153,17 @@ void Camera::select_view(const std::string& direction)
if (direction == "iso") if (direction == "iso")
set_default_orientation(); set_default_orientation();
else if (direction == "left") 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") 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") 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") 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") 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") 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 #else
bool Camera::select_view(const std::string& direction) 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 void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double far_z) const
{ {
#if !ENABLE_6DOF_CAMERA
set_distance(DefaultDistance); set_distance(DefaultDistance);
#endif // !ENABLE_6DOF_CAMERA
double w = 0.0; double w = 0.0;
double h = 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) while (true)
{ {
m_frustrum_zs = calc_tight_frustrum_zs_around(box); m_frustrum_zs = calc_tight_frustrum_zs_around(box);
#endif // !ENABLE_6DOF_CAMERA
if (near_z > 0.0) 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) if (far_z > 0.0)
m_frustrum_zs.second = std::max(m_frustrum_zs.second, far_z); 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]; w = 0.5 * (double)m_viewport[2];
h = 0.5 * (double)m_viewport[3]; h = 0.5 * (double)m_viewport[3];
if (m_zoom != 0.0) double inv_zoom = get_inv_zoom();
{ w *= inv_zoom;
double inv_zoom = 1.0 / m_zoom; h *= inv_zoom;
w *= inv_zoom;
h *= inv_zoom;
}
switch (m_type) 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) if (m_type == Perspective)
{ {
double fov_deg = Geometry::rad2deg(2.0 * std::atan(h / m_frustrum_zs.first)); 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 else
break; break;
} }
#endif // !ENABLE_6DOF_CAMERA
glsafe(::glMatrixMode(GL_PROJECTION)); glsafe(::glMatrixMode(GL_PROJECTION));
glsafe(::glLoadIdentity()); glsafe(::glLoadIdentity());
@ -331,14 +336,22 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa
} }
#if ENABLE_THUMBNAIL_GENERATOR #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) void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor)
#endif // ENABLE_6DOF_CAMERA
#else #else
void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h) void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h)
#endif // ENABLE_THUMBNAIL_GENERATOR #endif // ENABLE_THUMBNAIL_GENERATOR
{ {
// Calculate the zoom factor needed to adjust the view around the given box. // Calculate the zoom factor needed to adjust the view around the given box.
#if ENABLE_THUMBNAIL_GENERATOR #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); double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h, margin_factor);
#endif // ENABLE_6DOF_CAMERA
#else #else
double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h); double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h);
#endif // ENABLE_THUMBNAIL_GENERATOR #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_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) void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor)
#endif // ENABLE_6DOF_CAMERA
{ {
Vec3d center; 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); double zoom = calc_zoom_to_volumes_factor(volumes, canvas_w, canvas_h, center, margin_factor);
#endif // ENABLE_6DOF_CAMERA
if (zoom > 0.0) if (zoom > 0.0)
{ {
m_zoom = zoom; m_zoom = zoom;
@ -396,6 +417,7 @@ void Camera::debug_render() const
float deltaZ = farZ - nearZ; float deltaZ = farZ - nearZ;
float zoom = (float)m_zoom; float zoom = (float)m_zoom;
float fov = (float)get_fov(); float fov = (float)get_fov();
std::array<int, 4>viewport = get_viewport();
float gui_scale = (float)get_gui_scale(); float gui_scale = (float)get_gui_scale();
ImGui::InputText("Type", type.data(), type.length(), ImGuiInputTextFlags_ReadOnly); 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("Zoom", &zoom, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat("Fov", &fov, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat("Fov", &fov, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::Separator(); 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::InputFloat("GUI scale", &gui_scale, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
imgui.end(); 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) 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; Vec3d target = m_target;
translate_world(-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); 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())); m_view_matrix.rotate(Eigen::AngleAxisd(rotation_rad(2), get_dir_forward()));
translate_world(center); translate_world(center);
} }
#endif // ENABLE_6DOF_CAMERA
double Camera::min_zoom() const 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 #endif // ENABLE_6DOF_CAMERA
}
std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const
{ {
std::pair<double, double> ret; std::pair<double, double> ret;
auto& [near_z, far_z] = ret;
#if !ENABLE_6DOF_CAMERA
while (true) while (true)
{ {
#endif // !ENABLE_6DOF_CAMERA
// box in eye space // box in eye space
BoundingBoxf3 eye_box = box.transformed(m_view_matrix); BoundingBoxf3 eye_box = box.transformed(m_view_matrix);
ret.first = -eye_box.max(2); near_z = -eye_box.max(2);
ret.second = -eye_box.min(2); far_z = -eye_box.min(2);
// apply margin // apply margin
ret.first -= FrustrumZMargin; near_z -= FrustrumZMargin;
ret.second += FrustrumZMargin; far_z += FrustrumZMargin;
// ensure min size // 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; double half_size = 0.5 * FrustrumMinZRange;
ret.first = mid_z - half_size; near_z = mid_z - half_size;
ret.second = 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; break;
// ensure min Near Z // ensure min near z
set_distance(m_distance + FrustrumMinNearZ - ret.first); set_distance(m_distance + FrustrumMinNearZ - near_z);
} }
#endif // ENABLE_6DOF_CAMERA
return ret; return ret;
} }
#if ENABLE_THUMBNAIL_GENERATOR #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 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 #else
double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const
#endif // ENABLE_THUMBNAIL_GENERATOR #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 // 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 // then calculates the vertices coordinate on this plane along the camera xy axes
#if !ENABLE_6DOF_CAMERA
// ensure that the view matrix is updated // ensure that the view matrix is updated
apply_view_matrix(); apply_view_matrix();
#endif // !ENABLE_6DOF_CAMERA
Vec3d right = get_dir_right(); Vec3d right = get_dir_right();
Vec3d up = get_dir_up(); 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; dx *= margin_factor;
dy *= 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); return std::min((double)canvas_w / dx, (double)canvas_h / dy);
#endif // ENABLE_6DOF_CAMERA
} }
#if ENABLE_THUMBNAIL_GENERATOR #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 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()) if (volumes.empty())
return -1.0; 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 // 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 // then calculates the vertices coordinate on this plane along the camera xy axes
#if !ENABLE_6DOF_CAMERA
// ensure that the view matrix is updated // ensure that the view matrix is updated
apply_view_matrix(); apply_view_matrix();
#endif // !ENABLE_6DOF_CAMERA
Vec3d right = get_dir_right(); Vec3d right = get_dir_right();
Vec3d up = get_dir_up(); 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)) if ((dx <= 0.0) || (dy <= 0.0))
return -1.0f; 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); return std::min((double)canvas_w / dx, (double)canvas_h / dy);
#endif // ENABLE_6DOF_CAMERA
} }
#endif // ENABLE_THUMBNAIL_GENERATOR #endif // ENABLE_THUMBNAIL_GENERATOR
void Camera::set_distance(double distance) const 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; m_distance = distance;
apply_view_matrix(); apply_view_matrix();
#endif // ENABLE_6DOF_CAMERA
} }
#if 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_z = (position - target).normalized();
Vec3d unit_x = up.cross(unit_z).normalized(); Vec3d unit_x = up.cross(unit_z).normalized();
Vec3d unit_y = unit_z.cross(unit_x).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); m_view_matrix(0, 0) = unit_x(0);
matrix(0, 1) = unit_x(1); m_view_matrix(0, 1) = unit_x(1);
matrix(0, 2) = unit_x(2); m_view_matrix(0, 2) = unit_x(2);
matrix(0, 3) = -unit_x.dot(position); m_view_matrix(0, 3) = -unit_x.dot(new_position);
matrix(1, 0) = unit_y(0); m_view_matrix(1, 0) = unit_y(0);
matrix(1, 1) = unit_y(1); m_view_matrix(1, 1) = unit_y(1);
matrix(1, 2) = unit_y(2); m_view_matrix(1, 2) = unit_y(2);
matrix(1, 3) = -unit_y.dot(position); m_view_matrix(1, 3) = -unit_y.dot(new_position);
matrix(2, 0) = unit_z(0); m_view_matrix(2, 0) = unit_z(0);
matrix(2, 1) = unit_z(1); m_view_matrix(2, 1) = unit_z(1);
matrix(2, 2) = unit_z(2); m_view_matrix(2, 2) = unit_z(2);
matrix(2, 3) = -unit_z.dot(position); m_view_matrix(2, 3) = -unit_z.dot(new_position);
matrix(3, 0) = 0.0; m_view_matrix(3, 0) = 0.0;
matrix(3, 1) = 0.0; m_view_matrix(3, 1) = 0.0;
matrix(3, 2) = 0.0; m_view_matrix(3, 2) = 0.0;
matrix(3, 3) = 1.0; m_view_matrix(3, 3) = 1.0;
return matrix;
} }
void Camera::set_default_orientation() void Camera::set_default_orientation()

View file

@ -48,11 +48,7 @@ private:
mutable double m_gui_scale; mutable double m_gui_scale;
mutable std::array<int, 4> m_viewport; mutable std::array<int, 4> m_viewport;
#if ENABLE_6DOF_CAMERA
Transform3d m_view_matrix;
#else
mutable Transform3d m_view_matrix; mutable Transform3d m_view_matrix;
#endif // ENABLE_6DOF_CAMERA
mutable Transform3d m_projection_matrix; mutable Transform3d m_projection_matrix;
mutable std::pair<double, double> m_frustrum_zs; mutable std::pair<double, double> m_frustrum_zs;
@ -71,7 +67,11 @@ public:
const Vec3d& get_target() const { return m_target; } const Vec3d& get_target() const { return m_target; }
void set_target(const Vec3d& 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; } double get_distance() const { return m_distance; }
#endif // ENABLE_6DOF_CAMERA
double get_gui_scale() const { return m_gui_scale; } double get_gui_scale() const { return m_gui_scale; }
#if !ENABLE_6DOF_CAMERA #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; void apply_projection(const BoundingBoxf3& box, double near_z = -1.0, double far_z = -1.0) const;
#if ENABLE_THUMBNAIL_GENERATOR #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_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); void zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToVolumesMarginFactor);
#endif // ENABLE_6DOF_CAMERA
#else #else
void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h); void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h);
#endif // ENABLE_THUMBNAIL_GENERATOR #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 // 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; } 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 max_zoom() const { return 100.0; }
double min_zoom() const; double min_zoom() const;
#endif // ENABLE_6DOF_CAMERA
private: private:
// returns tight values for nearZ and farZ plane around the given bounding box // 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 // 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; std::pair<double, double> calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const;
#if ENABLE_THUMBNAIL_GENERATOR #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_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; 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 #else
double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const; double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const;
#endif // ENABLE_THUMBNAIL_GENERATOR #endif // ENABLE_THUMBNAIL_GENERATOR
void set_distance(double distance) const; void set_distance(double distance) const;
#if ENABLE_6DOF_CAMERA #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(); void set_default_orientation();
Vec3d validate_target(const Vec3d& target) const; Vec3d validate_target(const Vec3d& target) const;
#endif // ENABLE_6DOF_CAMERA #endif // ENABLE_6DOF_CAMERA

View file

@ -52,28 +52,26 @@ Control::Control( wxWindow *parent,
if (!is_osx) if (!is_osx)
SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on 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_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_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_on = ScalableBitmap(this, "colorchange_add");
m_bmp_add_tick_off = ScalableBitmap(this, "colorchange_add_f"); m_bmp_add_tick_off = ScalableBitmap(this, "colorchange_add_f");
m_bmp_del_tick_on = ScalableBitmap(this, "colorchange_del"); m_bmp_del_tick_on = ScalableBitmap(this, "colorchange_del");
m_bmp_del_tick_off = ScalableBitmap(this, "colorchange_del_f"); 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_on = ScalableBitmap(this, "lock_closed");
m_bmp_one_layer_lock_off = ScalableBitmap(this, "lock_closed_f"); 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_on = ScalableBitmap(this, "lock_open");
m_bmp_one_layer_unlock_off = ScalableBitmap(this, "lock_open_f"); 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_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_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_selection = ssUndef;
m_ticks.set_pause_print_msg(_utf8(L("Place bearings in slots and resume"))); 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" // Draw icon for "Pause print" or "Custom Gcode"
if (tick.gcode != ColorChangeCode && tick.gcode != ToolChangeCode) if (tick.gcode != ColorChangeCode && tick.gcode != ToolChangeCode)
icon = create_scaled_bitmap(this, tick.gcode == PausePrintCode ? "pause_print" : "edit_gcode"); icon = create_scaled_bitmap(tick.gcode == PausePrintCode ? "pause_print" : "edit_gcode");
else else if (m_ticks.is_conflict_tick(tick, m_mode, m_only_extruder, m_values[tick.tick]))
{ icon = create_scaled_bitmap("error_tick");
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");
}
if (!icon.IsNull()) 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(); 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) { for (auto tick : m_ticks.ticks) {
const wxCoord pos = get_position_from_value(tick.tick); const wxCoord pos = get_position_from_value(tick.tick);
@ -833,7 +824,7 @@ void Control::OnLeftDown(wxMouseEvent& event)
detect_selected_slider(pos); detect_selected_slider(pos);
if (!m_selection) { 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) /* Set current thumb position to the nearest tick (if it is)
* OR to a value corresponding to the mouse click * 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 int tick = m_selection == ssLower ? m_lower_value : m_higher_value;
const auto tick_code_it = m_ticks.ticks.find(TickCode{tick}); 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")) : /* Note: just on OSX!!!
_(L("For add color change use left mouse button click")) ) + "\n" + * Right click event causes a little scrolling.
_(L("For add another code use right mouse button click")) : * So, as a workaround we use Ctrl+LeftMouseClick instead of RightMouseClick
tick_code_it->gcode == ColorChangeCode ? ( m_mode == t_mode::SingleExtruder ? * Show this information in tooltip
_(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()) ): if (tick_code_it == m_ticks.ticks.end()) // tick doesn't exist
tick_code_it->gcode == PausePrintCode ? {
_(L("Delete pause")) : // Show mode as a first string of tooltop
tick_code_it->gcode == ToolChangeCode ? tooltip = " " + _(L("Slider(print) mode")) + ": ";
from_u8((boost::format(_utf8(L("Delete extruder change to \"%1%\""))) % tick_code_it->extruder).str()) : tooltip += (m_mode == t_mode::SingleExtruder ? CustomGCode::SingleExtruderMode :
from_u8((boost::format(_utf8(L("For Delete \"%1%\" code use left mouse button click\n" m_mode == t_mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode :
"For Edit \"%1%\" code use right mouse button click"))) % tick_code_it->gcode ).str()); 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; return tooltip;
@ -988,7 +1029,7 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current
_(L("Change extruder (N/A)")); _(L("Change extruder (N/A)"));
wxMenuItem* change_extruder_menu_item = menu->AppendSubMenu(change_extruder_menu, change_extruder_menu_name, _(L("Use another extruder"))); 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) { 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); }, 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(); const int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt();
if (extruders_cnt > 1) 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(); 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, "", append_menu_item(add_color_change_menu, wxID_ANY, item_name, "",
[this, i](wxCommandEvent&) { add_code_as_tick(ColorChangeCode, i); }, "", menu, [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 ? 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("Switch code to Color change (%1%) for:"))) % ColorChangeCode).str()) :
from_u8((boost::format(_utf8(L("Add 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, ""); 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}); 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; extruders[1] = it->extruder;
while (it != m_ticks.ticks.begin()) { 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. // Get used extruders for tick.
// Means all extruders(toools) will be used during printing from current tick to the end // Means all extruders(tools) which will be used during printing from current tick to the end
std::set<int> Control::get_used_extruders_for_tick(int tick) const 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 // #ys_FIXME: get tool ordering from _correct_ place
const ToolOrdering& tool_ordering = GUI::wxGetApp().plater()->fff_print().get_tool_ordering(); 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; 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) for (; it_layer_tools != tool_ordering.end(); ++it_layer_tools)
{ {
const std::vector<unsigned>& extruders = it_layer_tools->extruders; 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; return used_extruders;
} }
const int default_initial_extruder = m_mode == t_mode::MultiAsSingle ? std::max(m_only_extruder, 1) : 1; const int default_initial_extruder = mode == t_mode::MultiAsSingle ? std::max(only_extruder, 1) : 1;
if (m_ticks.empty()) if (ticks.empty())
return {default_initial_extruder}; return {default_initial_extruder};
std::set<int> used_extruders; std::set<int> used_extruders;
const std::set<TickCode>& ticks = m_ticks.ticks;
auto it_start = ticks.lower_bound(TickCode{tick}); auto it_start = ticks.lower_bound(TickCode{tick});
auto it = it_start; 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")) : append_menu_item(&menu, wxID_ANY, it->gcode == ColorChangeCode ? _(L("Edit color")) :
it->gcode == PausePrintCode ? _(L("Edit pause print message")) : it->gcode == PausePrintCode ? _(L("Edit pause print message")) :
_(L("Edit custom G-code")), "", _(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) if (it->gcode == ColorChangeCode && m_mode == t_mode::MultiAsSingle)
append_change_extruder_menu_item(&menu, true); 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 == ToolChangeCode ? _(L("Delete tool change")) :
it->gcode == PausePrintCode ? _(L("Delete pause print")) : it->gcode == PausePrintCode ? _(L("Delete pause print")) :
_(L("Delete custom G-code")), "", _(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); GUI::wxGetApp().plater()->PopupMenu(&menu);
@ -1379,6 +1420,28 @@ static std::string get_new_color(const std::string& color)
return ""; 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) 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"))) + ":"; 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 // get custom gcode
wxTextEntryDialog dlg(nullptr, msg_text, msg_header, code_in, wxTextEntryDialog dlg(nullptr, msg_text, msg_header, code_in,
wxTextEntryDialogStyle | wxTE_MULTILINE); wxTextEntryDialogStyle | wxTE_MULTILINE);
if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) upgrade_text_entry_dialog(&dlg);
if (dlg.ShowModal() != wxID_OK)
return ""; return "";
return dlg.GetValue().ToStdString(); 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 // get custom gcode
wxTextEntryDialog dlg(nullptr, msg_text, msg_header, from_u8(msg_in), wxTextEntryDialog dlg(nullptr, msg_text, msg_header, from_u8(msg_in),
wxTextEntryDialogStyle); wxTextEntryDialogStyle);
upgrade_text_entry_dialog(&dlg);
if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty())
return ""; return "";
@ -1759,6 +1826,61 @@ bool TickCodeInfo::has_tick_with_code(const std::string& gcode)
return false; 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 } // DoubleSlider
} // Slic3r } // Slic3r

View file

@ -39,6 +39,15 @@ enum IconFocus {
ifCog ifCog
}; };
enum ConflictType
{
ctNone,
ctModeConflict,
ctMeaninglessColorChange,
ctMeaninglessToolChange,
ctRedundant
};
using t_mode = CustomGCode::Mode; using t_mode = CustomGCode::Mode;
struct TickCode struct TickCode
@ -73,7 +82,13 @@ public:
void switch_code(const std::string& code_from, const std::string& code_to); 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); 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); 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_plus (bool suppress) { m_suppress_plus = suppress; }
void suppress_minus(bool suppress) { m_suppress_minus = suppress; } void suppress_minus(bool suppress) { m_suppress_minus = suppress; }
@ -230,7 +245,7 @@ protected:
private: private:
bool is_point_in_rect(const wxPoint& pt, const wxRect& rect); 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(); double get_scroll_step();
wxString get_label(const SelectedSlider& selection) const; wxString get_label(const SelectedSlider& selection) const;
@ -251,10 +266,6 @@ private:
// Use those values to disable selection of active extruders // Use those values to disable selection of active extruders
std::array<int, 2> get_active_extruders_for_tick(int tick) const; 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 = ""); void post_ticks_changed_event(const std::string& gcode = "");
bool check_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_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>); wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>);
wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent);
#if ENABLE_THUMBNAIL_GENERATOR #if ENABLE_THUMBNAIL_GENERATOR
const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25; const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
@ -1710,6 +1711,13 @@ void GLCanvas3D::render()
} }
const Size& cnv_size = get_canvas_size(); 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) if (m_camera.requires_zoom_to_bed)
{ {
@ -2647,6 +2655,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE));
break; break;
case WXK_ESCAPE: { deselect_all(); 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 '0': { select_view("iso"); break; }
case '1': { select_view("top"); break; } case '1': { select_view("top"); break; }
case '2': { select_view("bottom"); break; } case '2': { select_view("bottom"); break; }
@ -3839,8 +3848,13 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
#if ENABLE_6DOF_CAMERA #if ENABLE_6DOF_CAMERA
camera.set_scene_box(scene_bounding_box()); camera.set_scene_box(scene_bounding_box());
#endif // ENABLE_6DOF_CAMERA #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.zoom_to_volumes(visible_volumes, thumbnail_data.width, thumbnail_data.height);
camera.apply_viewport(0, 0, 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(); camera.apply_view_matrix();
double near_z = -1.0; double near_z = -1.0;
@ -4431,8 +4445,10 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h)
// ensures that this canvas is current // ensures that this canvas is current
_set_current(); _set_current();
#if !ENABLE_6DOF_CAMERA
// updates camera // updates camera
m_camera.apply_viewport(0, 0, w, h); 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 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 #if ENABLE_THUMBNAIL_GENERATOR
void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor) 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(); const Size& cnv_size = get_canvas_size();
m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height(), margin_factor); m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height(), margin_factor);
#endif // ENABLE_6DOF_CAMERA
m_dirty = true; m_dirty = true;
} }
#else #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_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>); wxDECLARE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>);
wxDECLARE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); wxDECLARE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent);
class GLCanvas3D class GLCanvas3D
{ {

View file

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

View file

@ -960,8 +960,11 @@ void GUI_App::load_current_presets()
this->plater()->set_printer_technology(printer_technology); this->plater()->set_printer_technology(printer_technology);
for (Tab *tab : tabs_list) for (Tab *tab : tabs_list)
if (tab->supports_printer_technology(printer_technology)) { 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(); 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(); tab->load_current_preset();
} }
} }

View file

@ -90,20 +90,20 @@ ObjectList::ObjectList(wxWindow* parent) :
// see note in PresetBundle::load_compatible_bitmaps() // see note in PresetBundle::load_compatible_bitmaps()
// ptFFF // ptFFF
CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(nullptr, "layers"); CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers");
CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(nullptr, "infill"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill");
CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(nullptr, "support"); CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support");
CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time");
CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel");
CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel"); CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap("funnel");
CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap(nullptr, "funnel"); CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap("funnel");
// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim"); // CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap("skirt+brim");
// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time"); // CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap("time");
CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench"); CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap("wrench");
// ptSLA // ptSLA
CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/); CATEGORY_ICON[L("Supports")] = create_scaled_bitmap("support"/*"sla_supports"*/);
CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad"); CATEGORY_ICON[L("Pad")] = create_scaled_bitmap("pad");
CATEGORY_ICON[L("Hollowing")] = create_scaled_bitmap(nullptr, "hollowing"); CATEGORY_ICON[L("Hollowing")] = create_scaled_bitmap("hollowing");
} }
// create control // 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 // 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(), // 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. // which seems to be working as of now.
this->CallAfter([this](){ this->EnsureVisible(this->GetCurrentItem()); }); this->CallAfter([this](){ ensure_current_item_visible(); });
#else #else
this->EnsureVisible(this->GetCurrentItem()); ensure_current_item_visible();
#endif #endif
e.Skip(); e.Skip();
})); }));
@ -265,7 +265,7 @@ void ObjectList::create_objects_ctrl()
// column ItemName(Icon+Text) of the view control: // column ItemName(Icon+Text) of the view control:
// And Icon can be consisting of several bitmaps // 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)); colName, 20*em, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE));
// column PrintableProperty (Icon) of the view control: // 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() void ObjectList::init_icons()
{ {
m_bmp_solidmesh = ScalableBitmap(nullptr, ADD_VOLUME_MENU_ITEMS[int(ModelVolumeType::MODEL_PART) ].second); m_bmp_solidmesh = ScalableBitmap(this, 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_modifiermesh = ScalableBitmap(this, 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_enforcer = ScalableBitmap(this, 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_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.reserve(4); // bitmaps for different types of parts
m_bmp_vector.push_back(&m_bmp_solidmesh.bmp()); m_bmp_vector.push_back(&m_bmp_solidmesh.bmp());
@ -575,12 +575,12 @@ void ObjectList::init_icons()
m_objects_model->SetVolumeBitmaps(m_bmp_vector); m_objects_model->SetVolumeBitmaps(m_bmp_vector);
// init icon for manifold warning // 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 // Set warning bitmap for the model
m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp()); m_objects_model->SetWarningBitmap(&m_bmp_manifold_warning.bmp());
// init bitmap for "Add Settings" context menu // init bitmap for "Add Settings" context menu
m_bmp_cog = ScalableBitmap(nullptr, "cog"); m_bmp_cog = ScalableBitmap(this, "cog");
} }
void ObjectList::msw_rescale_icons() void ObjectList::msw_rescale_icons()
@ -607,23 +607,20 @@ void ObjectList::msw_rescale_icons()
// Update CATEGORY_ICON according to new scale // 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 // ptFFF
CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(nullptr, "layers"); CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers");
CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(nullptr, "infill"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill");
CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(nullptr, "support"); CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support");
CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time");
CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel");
CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel"); CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap("funnel");
CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap(nullptr, "funnel"); CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap("funnel");
// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim"); // CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap("skirt+brim");
// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time"); // CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap("time");
CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench"); CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap("wrench");
// ptSLA // ptSLA
CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/); CATEGORY_ICON[L("Supports")] = create_scaled_bitmap("support"/*"sla_supports"*/);
CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad"); CATEGORY_ICON[L("Pad")] = create_scaled_bitmap("pad");
} }
} }
@ -1003,14 +1000,13 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event)
const bool mult_sel = multiple_selection(); const bool mult_sel = multiple_selection();
if ((mult_sel && !selected_instances_of_same_object()) || if ((mult_sel && !selected_instances_of_same_object()) ||
(!mult_sel && (GetSelection() != item)) || (!mult_sel && (GetSelection() != item)) ) {
m_objects_model->GetParent(item) == wxDataViewItem(nullptr) ) {
event.Veto(); event.Veto();
return; return;
} }
const ItemType& type = m_objects_model->GetItemType(item); const ItemType& type = m_objects_model->GetItemType(item);
if (!(type & (itVolume | itInstance))) { if (!(type & (itVolume | itObject | itInstance))) {
event.Veto(); event.Veto();
return; return;
} }
@ -1024,11 +1020,13 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event)
for (auto sel : sels ) for (auto sel : sels )
sub_obj_idxs.insert(m_objects_model->GetInstanceIdByItem(sel)); 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), 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), m_objects_model->GetInstanceIdByItem(item),
type); type);
/* Under MSW or OSX, DnD moves an item to the place of another selected item /* 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. * 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 bool ObjectList::can_drop(const wxDataViewItem& item) const
{ {
return (m_dragged_data.type() == itInstance && !item.IsOk()) || // move instance(s) or object on "empty place" of ObjectList
(m_dragged_data.type() == itVolume && item.IsOk() && if ( (m_dragged_data.type() & (itInstance | itObject)) && !item.IsOk() )
m_objects_model->GetItemType(item) == itVolume && return true;
m_dragged_data.obj_idx() == m_objects_model->GetObjectIdByItem(item));
// 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) void ObjectList::OnDropPossible(wxDataViewEvent &event)
@ -1082,9 +1090,6 @@ void ObjectList::OnDrop(wxDataViewEvent &event)
return; 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 // It looks like a fixed in current version of the wxWidgets
// #ifdef __WXGTK__ // #ifdef __WXGTK__
// /* Under GTK, DnD moves an item between another two items. // /* 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"))); 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; if (m_dragged_data.type() & itVolume)
auto delta = to_volume_id < from_volume_id ? -1 : 1; {
int cnt = 0; int from_volume_id = m_dragged_data.sub_obj_idx();
for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++) int to_volume_id = m_objects_model->GetVolumeIdByItem(item);
std::swap(volumes[id], volumes[id + delta]); int delta = to_volume_id < from_volume_id ? -1 : 1;
select_item(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes;
m_objects_model->GetParent(item)));
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()); 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) void ObjectList::create_default_popupmenu(wxMenu*menu)
{ {
wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::INVALID); 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) wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
@ -3162,7 +3187,7 @@ void ObjectList::update_selections()
select_items(sels); select_items(sels);
// Scroll selected Item in the middle of an object list // Scroll selected Item in the middle of an object list
this->EnsureVisible(this->GetCurrentItem()); ensure_current_item_visible();
} }
void ObjectList::update_selections_on_canvas() void ObjectList::update_selections_on_canvas()

View file

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

View file

@ -9,6 +9,8 @@
#include <wx/stattext.h> #include <wx/stattext.h>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <algorithm>
#include "slic3r/GUI/GUI_App.hpp" #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 void GLGizmoCut::set_cut_z(double cut_z) const
{ {
// Clamp the plane to the object's bounding box // 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) void GLGizmoCut::perform_cut(const Selection& selection)

View file

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

View file

@ -500,7 +500,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
processed = true; processed = true;
} }
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports || m_current == Hollow)) 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; processed = true;
else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) 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()) else if (evt.LeftUp() && is_dragging())
{ {
switch (m_current) { switch (m_current) {
case Move : m_parent.do_move(L("Gizmo-Move")); case Move : m_parent.do_move(L("Gizmo-Move")); break;
break; case Scale : m_parent.do_scale(L("Gizmo-Scale")); break;
case Scale : m_parent.do_scale(L("Gizmo-Scale")); case Rotate : m_parent.do_rotate(L("Gizmo-Rotate")); break;
break;
case Rotate : m_parent.do_rotate(L("Gizmo-Rotate"));
break;
default : break; default : break;
} }
@ -779,6 +776,64 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
processed = true; 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) // if (processed)
// m_parent.set_cursor(GLCanvas3D::Standard); // 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, /* Load default preset bitmaps before a tabpanel initialization,
* but after filling of an em_unit value * 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 // initialize tabpanel and menubar
init_tabpanel(); init_tabpanel();
@ -345,7 +345,7 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect)
/* Load default preset bitmaps before a tabpanel initialization, /* Load default preset bitmaps before a tabpanel initialization,
* but after filling of an em_unit value * 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 // update Plater
wxGetApp().plater()->msw_rescale(); 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", 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(); }, _(L("Paste clipboard")), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); },
"paste_menu", nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this); "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 // 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[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] ->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". // 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); rightsizer->Add(btn_sizer, 0, wxALIGN_RIGHT);
if (! bitmap.IsOk()) { 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); logo = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap);
@ -99,7 +99,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg)
btn_ok->SetFocus(); btn_ok->SetFocus();
btn_sizer->Add(btn_ok, 0, wxRIGHT, HORIZ_SPACING); 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())); SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT*wxGetApp().em_unit()));
Fit(); 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_text->SetFont(wxGetApp().small_font());
info_manifold = new wxStaticText(parent, wxID_ANY, ""); info_manifold = new wxStaticText(parent, wxID_ANY, "");
info_manifold->SetFont(wxGetApp().small_font()); 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); auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL);
sizer_manifold->Add(info_manifold_text, 0); sizer_manifold->Add(info_manifold_text, 0);
sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2); sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2);
@ -188,7 +188,7 @@ void ObjectInfo::show_sizer(bool show)
void ObjectInfo::msw_rescale() void ObjectInfo::msw_rescale()
{ {
manifold_warning_icon->SetBitmap(create_scaled_bitmap(nullptr, "exclamation")); manifold_warning_icon->SetBitmap(create_scaled_bitmap("exclamation"));
} }
enum SlicedInfoIdx enum SlicedInfoIdx
@ -258,7 +258,7 @@ void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const w
} }
PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) : 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), preset_type(preset_type),
last_selected(wxNOT_FOUND), last_selected(wxNOT_FOUND),
m_em_unit(wxGetApp().em_unit()) m_em_unit(wxGetApp().em_unit())
@ -1875,6 +1875,7 @@ struct Plater::priv
} }
void export_gcode(fs::path output_path, PrintHostJob upload_job); void export_gcode(fs::path output_path, PrintHostJob upload_job);
void reload_from_disk(); void reload_from_disk();
void reload_all_from_disk();
void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
void set_current_panel(wxPanel* panel); 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_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_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_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: // 3DScene/Toolbar:
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); 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*/) void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/)
{ {
if (obj_idx < 0) if (obj_idx < 0)
@ -5034,6 +5054,11 @@ void Plater::reload_from_disk()
p->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 bool Plater::has_toolpaths_to_export() const
{ {
return p->preview->get_canvas3d()->has_toolpaths_to_export(); return p->preview->get_canvas3d()->has_toolpaths_to_export();
@ -5376,6 +5401,13 @@ void Plater::force_filament_colors_update()
this->p->schedule_background_process(); 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() void Plater::on_activate()
{ {
#ifdef __linux__ #ifdef __linux__
@ -5392,11 +5424,6 @@ void Plater::on_activate()
this->p->show_delayed_error_message(); 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. // 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 std::vector<std::string> Plater::get_extruder_colors_from_plater_config() const
{ {

View file

@ -12,6 +12,7 @@
#include "3DScene.hpp" #include "3DScene.hpp"
#include "GLTexture.hpp" #include "GLTexture.hpp"
#include "wxExtensions.hpp"
class wxButton; class wxButton;
class ScalableButton; class ScalableButton;
@ -49,7 +50,7 @@ using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>;
class Plater; class Plater;
enum class ActionButtonType : int; enum class ActionButtonType : int;
class PresetComboBox : public wxBitmapComboBox class PresetComboBox : public PresetBitmapComboBox
{ {
public: public:
PresetComboBox(wxWindow *parent, Preset::Type preset_type); PresetComboBox(wxWindow *parent, Preset::Type preset_type);
@ -193,6 +194,7 @@ public:
void export_amf(); void export_amf();
void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path());
void reload_from_disk(); void reload_from_disk();
void reload_all_from_disk();
bool has_toolpaths_to_export() const; bool has_toolpaths_to_export() const;
void export_toolpaths_to_obj() const; void export_toolpaths_to_obj() const;
void hollow(); void hollow();
@ -227,9 +229,9 @@ public:
void on_extruders_change(size_t extruders_count); void on_extruders_change(size_t extruders_count);
void on_config_change(const DynamicPrintConfig &config); void on_config_change(const DynamicPrintConfig &config);
void force_filament_colors_update(); void force_filament_colors_update();
void force_print_bed_update();
// On activating the parent window. // On activating the parent window.
void on_activate(); 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_extruder_colors_from_plater_config() const;
std::vector<std::string> get_colors_for_color_print() 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 = { s_opts = {
"printer_technology", "printer_technology",
"bed_shape", "bed_custom_texture", "bed_custom_model", "max_print_height", "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_width", "display_height", "display_pixels_x", "display_pixels_y",
"display_mirror_x", "display_mirror_y", "display_mirror_x", "display_mirror_y",
"display_orientation", "display_orientation",
@ -874,18 +873,14 @@ bool PresetCollection::delete_preset(const std::string& name)
return true; 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() *m_bitmap_main_frame = create_scaled_bitmap(file_name);
(void)window;
*m_bitmap_main_frame = create_scaled_bitmap(nullptr, 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() *m_bitmap_add = create_scaled_bitmap(file_name);
(void)window;
*m_bitmap_add = create_scaled_bitmap(nullptr, file_name);
} }
const Preset* PresetCollection::get_selected_preset_parent() const const Preset* PresetCollection::get_selected_preset_parent() const

View file

@ -313,10 +313,10 @@ public:
bool delete_preset(const std::string& name); bool delete_preset(const std::string& name);
// Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame. // 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. // 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. // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items.
void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; } 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()); 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 *m_bitmapCompatible = create_scaled_bitmap("flag_green");
// a low DPI bitmap, because the wxBitmapComboBox and wxDataViewCtrl don't support *m_bitmapIncompatible = create_scaled_bitmap("flag_red");
// high DPI bitmaps very well, they compute their dimensions wrong. *m_bitmapLock = create_scaled_bitmap("lock_closed");
// TODO: Update this when fixed in wxWidgets *m_bitmapLockOpen = create_scaled_bitmap("lock_open");
// 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");
prints .set_bitmap_compatible(m_bitmapCompatible); prints .set_bitmap_compatible(m_bitmapCompatible);
filaments .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); filament_presets[idx] = Preset::remove_suffix_modified(name);
} }
static inline int hex_digit_to_int(const char c) void PresetBundle::load_default_preset_bitmaps()
{
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)
{ {
// Clear bitmap cache, before load new scaled default preset bitmaps // Clear bitmap cache, before load new scaled default preset bitmaps
m_bitmapCache->clear(); m_bitmapCache->clear();
@ -1570,13 +1539,13 @@ void PresetBundle::load_default_preset_bitmaps(wxWindow *window)
this->sla_materials.clear_bitmap_cache(); this->sla_materials.clear_bitmap_cache();
this->printers.clear_bitmap_cache(); this->printers.clear_bitmap_cache();
this->prints.load_bitmap_default(window, "cog"); this->prints.load_bitmap_default("cog");
this->sla_prints.load_bitmap_default(window, "cog"); this->sla_prints.load_bitmap_default("cog");
this->filaments.load_bitmap_default(window, "spool.png"); this->filaments.load_bitmap_default("spool.png");
this->sla_materials.load_bitmap_default(window, "resin"); this->sla_materials.load_bitmap_default("resin");
this->printers.load_bitmap_default(window, "printer"); this->printers.load_bitmap_default("printer");
this->printers.load_bitmap_add(window, "add.png"); this->printers.load_bitmap_add("add.png");
this->load_compatible_bitmaps(window); this->load_compatible_bitmaps();
} }
void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui) 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]; unsigned char rgb[3];
std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder); 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 is not defined.
extruder_color.clear(); 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 // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so
// set a bitmap height to m_bitmapLock->GetHeight() // 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 = ""; 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. // Paint a red flag for incompatible presets.
bmps.emplace_back(preset.is_compatible ? m_bitmapCache->mkclear(normal_icon_width, icon_height) : *m_bitmapIncompatible); bmps.emplace_back(preset.is_compatible ? m_bitmapCache->mkclear(normal_icon_width, icon_height) : *m_bitmapIncompatible);
// Paint the color bars. // 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)); bmps.emplace_back(m_bitmapCache->mksolid(single_bar ? wide_icon_width : normal_icon_width, icon_height, rgb));
if (! single_bar) { 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)); bmps.emplace_back(m_bitmapCache->mksolid(thin_icon_width, icon_height, rgb));
} }
// Paint a lock at the system presets. // Paint a lock at the system presets.

View file

@ -54,8 +54,7 @@ public:
// There will be an entry for each system profile loaded, // There will be an entry for each system profile loaded,
// and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors. // and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors.
// std::set<VendorProfile> vendors; VendorMap vendors;
VendorMap vendors;
struct ObsoletePresets { struct ObsoletePresets {
std::vector<std::string> prints; std::vector<std::string> prints;
@ -130,9 +129,7 @@ public:
// preset if the current print or filament preset is not compatible. // preset if the current print or filament preset is not compatible.
void update_compatible(bool select_other_if_incompatible); 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();
void load_default_preset_bitmaps(wxWindow *window);
// Set the is_visible flag for printer vendors, printer models and printer variants // Set the is_visible flag for printer vendors, printer models and printer variants
// based on the user configuration. // 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. // 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(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_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_fff_config() const;
DynamicPrintConfig full_sla_config() const; DynamicPrintConfig full_sla_config() const;

View file

@ -114,7 +114,7 @@ void Tab::create_preset_tab()
#endif //__WXOSX__ #endif //__WXOSX__
// preset chooser // 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); 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 printhost_cafile_browse = [this, optgroup] (wxWindow* parent) {
auto btn = new wxButton(parent, wxID_ANY, " " + _(L("Browse"))+" " +dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); auto btn = new wxButton(parent, wxID_ANY, " " + _(L("Browse"))+" " +dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); 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); auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn); sizer->Add(btn);

View file

@ -120,7 +120,7 @@ protected:
Preset::Type m_type; Preset::Type m_type;
std::string m_name; std::string m_name;
const wxString m_title; const wxString m_title;
wxBitmapComboBox* m_presets_choice; PresetBitmapComboBox* m_presets_choice;
ScalableButton* m_btn_save_preset; ScalableButton* m_btn_save_preset;
ScalableButton* m_btn_delete_preset; ScalableButton* m_btn_delete_preset;
ScalableButton* m_btn_hide_incompatible_presets; 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), MsgDialog(nullptr, wxString::Format(_(L("%s incompatibility")), SLIC3R_APP_NAME),
wxString::Format(_(L("%s configuration is incompatible")), SLIC3R_APP_NAME), wxID_NONE) 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( auto *text = new wxStaticText(this, wxID_ANY, wxString::Format(_(L(
"This version of %s is not compatible with currently installed configuration bundles.\n" "This version of %s is not compatible with currently installed configuration bundles.\n"

View file

@ -1,7 +1,7 @@
#include <algorithm> #include <algorithm>
#include <sstream> #include <sstream>
#include "WipeTowerDialog.hpp" #include "WipeTowerDialog.hpp"
#include "PresetBundle.hpp" #include "BitmapCache.hpp"
#include "GUI.hpp" #include "GUI.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include "GUI_App.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) { for (const std::string& color : extruder_colours) {
unsigned char rgb[3]; 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])); 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/checklst.h>
#include <wx/combo.h> #include <wx/combo.h>
#include <wx/dataview.h> #include <wx/dataview.h>
#include <wx/dc.h>
#include <wx/wupdlock.h>
#include <wx/button.h> #include <wx/button.h>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/menu.h> #include <wx/menu.h>
#include <wx/wx.h> #include <wx/bmpcbox.h>
#include <vector> #include <vector>
#include <set>
#include <functional> #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__ #ifdef __WXMSW__
void msw_rescale_menu(wxMenu* menu); 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); void enable_menu_item(wxUpdateUIEvent& evt, std::function<bool()> const cb_condition, wxMenuItem* item, wxWindow* win);
class wxDialog; class wxDialog;
class wxBitmapComboBox;
void edit_tooltip(wxString& tooltip); void edit_tooltip(wxString& tooltip);
void msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector<int>& btn_ids); void msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector<int>& btn_ids);
int em_unit(wxWindow* win); int em_unit(wxWindow* win);
float get_svg_scale_factor(wxWindow* win);
wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name, wxBitmap create_scaled_bitmap(const std::string& bmp_name, wxWindow *win = nullptr,
const int px_cnt = 16, const bool is_horizontal = false, const bool grayscale = false); const int px_cnt = 16, const bool grayscale = false);
std::vector<wxBitmap*> get_extruder_color_icons(bool thin_icon = false); std::vector<wxBitmap*> get_extruder_color_icons(bool thin_icon = false);
void apply_extruder_selector(wxBitmapComboBox** ctrl, void apply_extruder_selector(wxBitmapComboBox** ctrl,
@ -102,6 +91,37 @@ public:
void OnListBoxSelection(wxCommandEvent& evt); 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 *** // *** 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 // ScalableBitmap
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -718,11 +157,14 @@ public:
ScalableBitmap() {}; ScalableBitmap() {};
ScalableBitmap( wxWindow *parent, ScalableBitmap( wxWindow *parent,
const std::string& icon_name = "", const std::string& icon_name = "",
const int px_cnt = 16, const int px_cnt = 16);
const bool is_horizontal = false);
~ScalableBitmap() {} ~ScalableBitmap() {}
wxSize GetBmpSize() const;
int GetBmpWidth() const;
int GetBmpHeight() const;
void msw_rescale(); void msw_rescale();
const wxBitmap& bmp() const { return m_bmp; } const wxBitmap& bmp() const { return m_bmp; }
@ -730,14 +172,12 @@ public:
const std::string& name() const{ return m_icon_name; } const std::string& name() const{ return m_icon_name; }
int px_cnt()const {return m_px_cnt;} int px_cnt()const {return m_px_cnt;}
bool is_horizontal()const {return m_is_horizontal;}
private: private:
wxWindow* m_parent{ nullptr }; wxWindow* m_parent{ nullptr };
wxBitmap m_bmp = wxBitmap(); wxBitmap m_bmp = wxBitmap();
std::string m_icon_name = ""; std::string m_icon_name = "";
int m_px_cnt {16}; int m_px_cnt {16};
bool m_is_horizontal {false};
}; };
@ -821,7 +261,6 @@ private:
// bitmap dimensions // bitmap dimensions
int m_px_cnt{ 16 }; int m_px_cnt{ 16 };
bool m_is_horizontal{ false };
}; };

View file

@ -26,58 +26,6 @@ const char *const SUPPORT_TEST_MODELS[] = {
} // namespace } // 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_CASE("Pillar pairhash should be unique", "[SLASupportGeneration]") {
test_pairhash<int, int>(); test_pairhash<int, int>();
test_pairhash<int, long>(); test_pairhash<int, long>();
@ -225,69 +173,6 @@ TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") {
REQUIRE(raster.pixel_dimensions().h_mm == Approx(pixdim.h_mm)); 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]") { TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") {
sla::Raster::TMirroring mirrorings[] = {sla::Raster::NoMirror, sla::Raster::TMirroring mirrorings[] = {sla::Raster::NoMirror,
sla::Raster::MirrorX, sla::Raster::MirrorX,
@ -301,54 +186,6 @@ TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") {
check_raster_transformations(orientation, mirror); 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]") { TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") {
double disp_w = 120., disp_h = 68.; 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}; std::fstream infile{"extruder_idler_quads.obj", std::ios::in};
cntr.from_obj(infile); cntr.from_obj(infile);
} }
} }

View file

@ -292,3 +292,103 @@ void check_validity(const TriangleMesh &input_mesh, int flags)
REQUIRE(mesh.is_manifold()); 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 // Debug
#include <fstream> #include <fstream>
#include <unordered_set>
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/Format/OBJ.hpp" #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_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 #endif // SLA_TEST_UTILS_HPP