Merge remote-tracking branch 'origin/master' into ys_new_features

This commit is contained in:
YuSanka 2019-06-24 13:56:35 +02:00
commit daac165a44
44 changed files with 2572 additions and 646 deletions

View file

@ -18,7 +18,7 @@ AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
AlwaysBreakTemplateDeclarations: false
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
@ -37,18 +37,18 @@ BraceWrapping:
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: All
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 75
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
CompactNamespaces: true
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4

View file

@ -125,8 +125,8 @@ namespace Slic3r {
trapezoid.distance = distance;
trapezoid.feedrate = feedrate;
float accelerate_distance = estimate_acceleration_distance(feedrate.entry, feedrate.cruise, acceleration);
float decelerate_distance = estimate_acceleration_distance(feedrate.cruise, feedrate.exit, -acceleration);
float accelerate_distance = std::max(0.0f, estimate_acceleration_distance(feedrate.entry, feedrate.cruise, acceleration));
float decelerate_distance = std::max(0.0f, estimate_acceleration_distance(feedrate.cruise, feedrate.exit, -acceleration));
float cruise_distance = distance - accelerate_distance - decelerate_distance;
// Not enough space to reach the nominal feedrate.

View file

@ -1409,6 +1409,63 @@ Transformation Transformation::operator * (const Transformation& other) const
return Transformation(get_matrix() * other.get_matrix());
}
Transformation Transformation::volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox)
{
Transformation out;
if (instance_transformation.is_scaling_uniform()) {
// No need to run the non-linear least squares fitting for uniform scaling.
// Just set the inverse.
out.set_from_transform(instance_transformation.get_matrix(true).inverse());
}
else if (is_rotation_ninety_degrees(instance_transformation.get_rotation()))
{
// Anisotropic scaling, rotation by multiples of ninety degrees.
Eigen::Matrix3d instance_rotation_trafo =
(Eigen::AngleAxisd(instance_transformation.get_rotation().z(), Vec3d::UnitZ()) *
Eigen::AngleAxisd(instance_transformation.get_rotation().y(), Vec3d::UnitY()) *
Eigen::AngleAxisd(instance_transformation.get_rotation().x(), Vec3d::UnitX())).toRotationMatrix();
Eigen::Matrix3d volume_rotation_trafo =
(Eigen::AngleAxisd(-instance_transformation.get_rotation().x(), Vec3d::UnitX()) *
Eigen::AngleAxisd(-instance_transformation.get_rotation().y(), Vec3d::UnitY()) *
Eigen::AngleAxisd(-instance_transformation.get_rotation().z(), Vec3d::UnitZ())).toRotationMatrix();
// 8 corners of the bounding box.
auto pts = Eigen::MatrixXd(8, 3);
pts(0, 0) = bbox.min.x(); pts(0, 1) = bbox.min.y(); pts(0, 2) = bbox.min.z();
pts(1, 0) = bbox.min.x(); pts(1, 1) = bbox.min.y(); pts(1, 2) = bbox.max.z();
pts(2, 0) = bbox.min.x(); pts(2, 1) = bbox.max.y(); pts(2, 2) = bbox.min.z();
pts(3, 0) = bbox.min.x(); pts(3, 1) = bbox.max.y(); pts(3, 2) = bbox.max.z();
pts(4, 0) = bbox.max.x(); pts(4, 1) = bbox.min.y(); pts(4, 2) = bbox.min.z();
pts(5, 0) = bbox.max.x(); pts(5, 1) = bbox.min.y(); pts(5, 2) = bbox.max.z();
pts(6, 0) = bbox.max.x(); pts(6, 1) = bbox.max.y(); pts(6, 2) = bbox.min.z();
pts(7, 0) = bbox.max.x(); pts(7, 1) = bbox.max.y(); pts(7, 2) = bbox.max.z();
// Corners of the bounding box transformed into the modifier mesh coordinate space, with inverse rotation applied to the modifier.
auto qs = pts *
(instance_rotation_trafo *
Eigen::Scaling(instance_transformation.get_scaling_factor().cwiseProduct(instance_transformation.get_mirror())) *
volume_rotation_trafo).inverse().transpose();
// Fill in scaling based on least squares fitting of the bounding box corners.
Vec3d scale;
for (int i = 0; i < 3; ++i)
scale(i) = pts.col(i).dot(qs.col(i)) / pts.col(i).dot(pts.col(i));
out.set_rotation(Geometry::extract_euler_angles(volume_rotation_trafo));
out.set_scaling_factor(Vec3d(std::abs(scale(0)), std::abs(scale(1)), std::abs(scale(2))));
out.set_mirror(Vec3d(scale(0) > 0 ? 1. : -1, scale(1) > 0 ? 1. : -1, scale(2) > 0 ? 1. : -1));
}
else
{
// General anisotropic scaling, general rotation.
// Keep the modifier mesh in the instance coordinate system, so the modifier mesh will not be aligned with the world.
// Scale it to get the required size.
out.set_scaling_factor(instance_transformation.get_scaling_factor().cwiseInverse());
}
return out;
}
Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to)
{
return

View file

@ -258,6 +258,11 @@ public:
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const;
Transformation operator * (const Transformation& other) const;
// Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity
// as possible in least squares norm in regard to the 8 corners of bbox.
// Bounding box is expected to be centered around zero in all axes.
static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox);
};
// Rotation when going from the first coordinate system with rotation rot_xyz_from applied

View file

@ -1,172 +1,218 @@
#ifndef MTUTILS_HPP
#define MTUTILS_HPP
#include <atomic> // for std::atomic_flag and memory orders
#include <mutex> // for std::lock_guard
#include <functional> // for std::function
#include <utility> // for std::forward
#include <atomic> // for std::atomic_flag and memory orders
#include <mutex> // for std::lock_guard
#include <functional> // for std::function
#include <utility> // for std::forward
#include <algorithm>
namespace Slic3r {
/// Handy little spin mutex for the cached meshes.
/// Implements the "Lockable" concept
class SpinMutex {
std::atomic_flag m_flg;
class SpinMutex
{
std::atomic_flag m_flg;
static const /*constexpr*/ auto MO_ACQ = std::memory_order_acquire;
static const /*constexpr*/ auto MO_REL = std::memory_order_release;
public:
inline SpinMutex() { m_flg.clear(MO_REL); }
inline void lock() { while(m_flg.test_and_set(MO_ACQ)); }
inline void lock() { while (m_flg.test_and_set(MO_ACQ)) ; }
inline bool try_lock() { return !m_flg.test_and_set(MO_ACQ); }
inline void unlock() { m_flg.clear(MO_REL); }
};
/// A wrapper class around arbitrary object that needs thread safe caching.
template<class T> class CachedObject {
template<class T> class CachedObject
{
public:
// Method type which refreshes the object when it has been invalidated
using Setter = std::function<void(T&)>;
using Setter = std::function<void(T &)>;
private:
T m_obj; // the object itself
bool m_valid; // invalidation flag
SpinMutex m_lck; // to make the caching thread safe
T m_obj; // the object itself
bool m_valid; // invalidation flag
SpinMutex m_lck; // to make the caching thread safe
// the setter will be called just before the object's const value is
// about to be retrieved.
std::function<void(T &)> m_setter;
// the setter will be called just before the object's const value is about
// to be retrieved.
std::function<void(T&)> m_setter;
public:
// Forwarded constructor
template<class...Args> inline CachedObject(Setter fn, Args&&...args):
m_obj(std::forward<Args>(args)...), m_valid(false), m_setter(fn) {}
template<class... Args>
inline CachedObject(Setter fn, Args &&... args)
: m_obj(std::forward<Args>(args)...), m_valid(false), m_setter(fn)
{}
// invalidate the value of the object. The object will be refreshed at the
// next retrieval (Setter will be called). The data that is used in
// the setter function should be guarded as well during modification so the
// modification has to take place in fn.
inline void invalidate(std::function<void()> fn) {
std::lock_guard<SpinMutex> lck(m_lck); fn(); m_valid = false;
// invalidate the value of the object. The object will be refreshed at
// the next retrieval (Setter will be called). The data that is used in
// the setter function should be guarded as well during modification so
// the modification has to take place in fn.
inline void invalidate(std::function<void()> fn)
{
std::lock_guard<SpinMutex> lck(m_lck);
fn();
m_valid = false;
}
// Get the const object properly updated.
inline const T& get() {
inline const T &get()
{
std::lock_guard<SpinMutex> lck(m_lck);
if(!m_valid) { m_setter(m_obj); m_valid = true; }
if (!m_valid) {
m_setter(m_obj);
m_valid = true;
}
return m_obj;
}
};
/// An std compatible random access iterator which uses indices to the source
/// vector thus resistant to invalidation caused by relocations. It also "knows"
/// its container. No comparison is neccesary to the container "end()" iterator.
/// The template can be instantiated with a different value type than that of
/// the container's but the types must be compatible. E.g. a base class of the
/// contained objects is compatible.
/// An std compatible random access iterator which uses indices to the
/// source vector thus resistant to invalidation caused by relocations. It
/// also "knows" its container. No comparison is neccesary to the container
/// "end()" iterator. The template can be instantiated with a different
/// value type than that of the container's but the types must be
/// compatible. E.g. a base class of the contained objects is compatible.
///
/// For a constant iterator, one can instantiate this template with a value
/// type preceded with 'const'.
template<class Vector, // The container type, must be random access...
template<class Vector, // The container type, must be random access...
class Value = typename Vector::value_type // The value type
>
class IndexBasedIterator {
class IndexBasedIterator
{
static const size_t NONE = size_t(-1);
std::reference_wrapper<Vector> m_index_ref;
size_t m_idx = NONE;
public:
size_t m_idx = NONE;
using value_type = Value;
using pointer = Value *;
using reference = Value &;
using difference_type = long;
public:
using value_type = Value;
using pointer = Value *;
using reference = Value &;
using difference_type = long;
using iterator_category = std::random_access_iterator_tag;
inline explicit
IndexBasedIterator(Vector& index, size_t idx):
m_index_ref(index), m_idx(idx) {}
inline explicit IndexBasedIterator(Vector &index, size_t idx)
: m_index_ref(index), m_idx(idx)
{}
// Post increment
inline IndexBasedIterator operator++(int) {
IndexBasedIterator cpy(*this); ++m_idx; return cpy;
inline IndexBasedIterator operator++(int)
{
IndexBasedIterator cpy(*this);
++m_idx;
return cpy;
}
inline IndexBasedIterator operator--(int) {
IndexBasedIterator cpy(*this); --m_idx; return cpy;
inline IndexBasedIterator operator--(int)
{
IndexBasedIterator cpy(*this);
--m_idx;
return cpy;
}
inline IndexBasedIterator& operator++() {
++m_idx; return *this;
inline IndexBasedIterator &operator++()
{
++m_idx;
return *this;
}
inline IndexBasedIterator& operator--() {
--m_idx; return *this;
inline IndexBasedIterator &operator--()
{
--m_idx;
return *this;
}
inline IndexBasedIterator& operator+=(difference_type l) {
m_idx += size_t(l); return *this;
inline IndexBasedIterator &operator+=(difference_type l)
{
m_idx += size_t(l);
return *this;
}
inline IndexBasedIterator operator+(difference_type l) {
auto cpy = *this; cpy += l; return cpy;
inline IndexBasedIterator operator+(difference_type l)
{
auto cpy = *this;
cpy += l;
return cpy;
}
inline IndexBasedIterator& operator-=(difference_type l) {
m_idx -= size_t(l); return *this;
inline IndexBasedIterator &operator-=(difference_type l)
{
m_idx -= size_t(l);
return *this;
}
inline IndexBasedIterator operator-(difference_type l) {
auto cpy = *this; cpy -= l; return cpy;
inline IndexBasedIterator operator-(difference_type l)
{
auto cpy = *this;
cpy -= l;
return cpy;
}
operator difference_type() { return difference_type(m_idx); }
/// Tesing the end of the container... this is not possible with std
/// iterators.
inline bool is_end() const { return m_idx >= m_index_ref.get().size();}
inline bool is_end() const
{
return m_idx >= m_index_ref.get().size();
}
inline Value & operator*() const {
inline Value &operator*() const
{
assert(m_idx < m_index_ref.get().size());
return m_index_ref.get().operator[](m_idx);
}
inline Value * operator->() const {
inline Value *operator->() const
{
assert(m_idx < m_index_ref.get().size());
return &m_index_ref.get().operator[](m_idx);
}
/// If both iterators point past the container, they are equal...
inline bool operator ==(const IndexBasedIterator& other) {
inline bool operator==(const IndexBasedIterator &other)
{
size_t e = m_index_ref.get().size();
return m_idx == other.m_idx || (m_idx >= e && other.m_idx >= e);
}
inline bool operator !=(const IndexBasedIterator& other) {
inline bool operator!=(const IndexBasedIterator &other)
{
return !(*this == other);
}
inline bool operator <=(const IndexBasedIterator& other) {
inline bool operator<=(const IndexBasedIterator &other)
{
return (m_idx < other.m_idx) || (*this == other);
}
inline bool operator <(const IndexBasedIterator& other) {
inline bool operator<(const IndexBasedIterator &other)
{
return m_idx < other.m_idx && (*this != other);
}
inline bool operator >=(const IndexBasedIterator& other) {
inline bool operator>=(const IndexBasedIterator &other)
{
return m_idx > other.m_idx || *this == other;
}
inline bool operator >(const IndexBasedIterator& other) {
inline bool operator>(const IndexBasedIterator &other)
{
return m_idx > other.m_idx && *this != other;
}
};
/// A very simple range concept implementation with iterator-like objects.
template<class It> class Range {
template<class It> class Range
{
It from, to;
public:
public:
// The class is ready for range based for loops.
It begin() const { return from; }
It end() const { return to; }
@ -175,15 +221,17 @@ public:
using Type = It;
Range() = default;
Range(It &&b, It &&e):
from(std::forward<It>(b)), to(std::forward<It>(e)) {}
Range(It &&b, It &&e)
: from(std::forward<It>(b)), to(std::forward<It>(e))
{}
// Some useful container-like methods...
inline size_t size() const { return end() - begin(); }
inline bool empty() const { return size() == 0; }
inline bool empty() const { return size() == 0; }
};
template<class C> bool all_of(const C &container) {
template<class C> bool all_of(const C &container)
{
return std::all_of(container.begin(),
container.end(),
[](const typename C::value_type &v) {
@ -191,6 +239,15 @@ template<class C> bool all_of(const C &container) {
});
}
template<class X, class Y> inline X ceil_i(X x, Y y)
{
static_assert(std::is_integral<X>::value &&
std::is_integral<Y>::value && sizeof(X) >= sizeof(Y),
"");
return (x % y) ? x / y + 1 : x / y;
}
} // namespace Slic3r
#endif // MTUTILS_HPP

View file

@ -489,21 +489,57 @@ void Model::convert_multipart_object(unsigned int max_extruders)
reset_auto_extruder_id();
bool is_single_object = (this->objects.size() == 1);
for (const ModelObject* o : this->objects)
{
for (const ModelVolume* v : o->volumes)
{
ModelVolume* new_v = object->add_volume(*v);
if (new_v != nullptr)
if (is_single_object)
{
new_v->name = o->name;
new_v->config.set_deserialize("extruder", get_auto_extruder_id_as_string(max_extruders));
new_v->translate(-o->origin_translation);
// If there is only one object, just copy the volumes
ModelVolume* new_v = object->add_volume(*v);
if (new_v != nullptr)
{
new_v->name = o->name;
new_v->config.set_deserialize("extruder", get_auto_extruder_id_as_string(max_extruders));
new_v->translate(-o->origin_translation);
}
}
else
{
// If there are more than one object, put all volumes together
// Each object may contain any number of volumes and instances
// The volumes transformations are relative to the object containing them...
int counter = 1;
for (const ModelInstance* i : o->instances)
{
ModelVolume* new_v = object->add_volume(*v);
if (new_v != nullptr)
{
new_v->name = o->name + "_" + std::to_string(counter++);
new_v->config.set_deserialize("extruder", get_auto_extruder_id_as_string(max_extruders));
new_v->translate(-o->origin_translation);
// ...so, transform everything to a common reference system (world)
new_v->set_transformation(i->get_transformation() * v->get_transformation());
}
}
}
}
}
if (is_single_object)
{
// If there is only one object, keep its instances
for (const ModelInstance* i : this->objects.front()->instances)
{
object->add_instance(*i);
}
}
else
// If there are more than one object, create a single instance
object->add_instance();
for (const ModelInstance* i : this->objects.front()->instances)
object->add_instance(*i);
this->clear_objects();
this->objects.push_back(object);
}

View file

@ -2,6 +2,7 @@
#include "Model.hpp"
#include "Geometry.hpp"
#include "SVG.hpp"
#include "MTUtils.hpp"
#include <libnest2d.h>
@ -820,15 +821,13 @@ bool arrange(Model &model, // The model with the geometries
BoundingBox bbb(bed);
auto& cfn = stopcondition;
coord_t md = ceil_i(min_obj_distance, 2) - SCALED_EPSILON;
auto binbb = Box({
static_cast<libnest2d::Coord>(bbb.min(0)),
static_cast<libnest2d::Coord>(bbb.min(1))
},
{
static_cast<libnest2d::Coord>(bbb.max(0)),
static_cast<libnest2d::Coord>(bbb.max(1))
});
auto binbb = Box({libnest2d::Coord{bbb.min(0)} - md,
libnest2d::Coord{bbb.min(1)} - md},
{libnest2d::Coord{bbb.max(0)} + md,
libnest2d::Coord{bbb.max(1)} + md});
switch(bedhint.type) {
case BedShapeType::BOX: {
@ -916,15 +915,13 @@ void find_new_position(const Model &model,
BedShapeHint bedhint = bedShape(bed);
BoundingBox bbb(bed);
auto binbb = Box({
static_cast<libnest2d::Coord>(bbb.min(0)),
static_cast<libnest2d::Coord>(bbb.min(1))
},
{
static_cast<libnest2d::Coord>(bbb.max(0)),
static_cast<libnest2d::Coord>(bbb.max(1))
});
coord_t md = ceil_i(min_obj_distance, 2) - SCALED_EPSILON;
auto binbb = Box({libnest2d::Coord{bbb.min(0)} - md,
libnest2d::Coord{bbb.min(1)} - md},
{libnest2d::Coord{bbb.max(0)} + md,
libnest2d::Coord{bbb.max(1)} + md});
for(auto it = shapemap.begin(); it != shapemap.end(); ++it) {
if(std::find(toadd.begin(), toadd.end(), it->first) == toadd.end()) {

View file

@ -13,6 +13,8 @@
#define ENABLE_RENDER_SELECTION_CENTER 0
// Shows an imgui dialog with render related data
#define ENABLE_RENDER_STATISTICS 0
// Shows an imgui dialog with camera related data
#define ENABLE_CAMERA_STATISTICS 0
//====================

View file

@ -192,6 +192,7 @@ size_t Index::load(const boost::filesystem::path &path)
{
m_configs.clear();
m_vendor = path.stem().string();
m_path = path;
boost::nowide::ifstream ifs(path.string());
std::string line;

View file

@ -72,6 +72,9 @@ public:
// if the index is valid.
const_iterator recommended() const;
// Returns the filesystem path from which this index has originally been loaded
const boost::filesystem::path& path() const { return m_path; }
// Load all vendor specific indices.
// Throws Slic3r::file_parser_error and the standard std file access exceptions.
static std::vector<Index> load_db();
@ -79,6 +82,7 @@ public:
private:
std::string m_vendor;
std::vector<Version> m_configs;
boost::filesystem::path m_path;
};
} // namespace Config

View file

@ -9,6 +9,7 @@
#include "GUI_App.hpp"
#include "PresetBundle.hpp"
#include "Gizmos/GLGizmoBase.hpp"
#include "GLCanvas3D.hpp"
#include <GL/glew.h>
@ -211,7 +212,7 @@ const double Bed3D::Axes::ArrowLength = 5.0;
Bed3D::Axes::Axes()
: origin(Vec3d::Zero())
, length(Vec3d::Zero())
, length(25.0 * Vec3d::Ones())
{
m_quadric = ::gluNewQuadric();
if (m_quadric != nullptr)
@ -273,6 +274,7 @@ void Bed3D::Axes::render_axis(double length) const
Bed3D::Bed3D()
: m_type(Custom)
, m_requires_canvas_update(false)
#if ENABLE_TEXTURES_FROM_SVG
, m_vbo_id(0)
#endif // ENABLE_TEXTURES_FROM_SVG
@ -290,7 +292,7 @@ bool Bed3D::set_shape(const Pointfs& shape)
m_shape = shape;
m_type = new_type;
calc_bounding_box();
calc_bounding_boxes();
ExPolygon poly;
for (const Vec2d& p : m_shape)
@ -311,7 +313,7 @@ bool Bed3D::set_shape(const Pointfs& shape)
// Set the origin and size for painting of the coordinate system axes.
m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z);
m_axes.length = 0.1 * get_bounding_box().max_size() * Vec3d::Ones();
m_axes.length = 0.1 * m_bounding_box.max_size() * Vec3d::Ones();
// Let the calee to update the UI.
return true;
@ -328,27 +330,26 @@ Point Bed3D::point_projection(const Point& point) const
}
#if ENABLE_TEXTURES_FROM_SVG
void Bed3D::render(float theta, bool useVBOs, float scale_factor) const
void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const
{
m_scale_factor = scale_factor;
EType type = useVBOs ? m_type : Custom;
switch (type)
{
case MK2:
{
render_prusa("mk2", theta > 90.0f);
render_prusa(canvas, "mk2", theta > 90.0f);
break;
}
case MK3:
{
render_prusa("mk3", theta > 90.0f);
render_prusa(canvas, "mk3", theta > 90.0f);
break;
}
case SL1:
{
render_prusa("sl1", theta > 90.0f);
render_prusa(canvas, "sl1", theta > 90.0f);
break;
}
default:
@ -360,7 +361,7 @@ void Bed3D::render(float theta, bool useVBOs, float scale_factor) const
}
}
#else
void Bed3D::render(float theta, bool useVBOs, float scale_factor) const
void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const
{
m_scale_factor = scale_factor;
@ -371,17 +372,17 @@ void Bed3D::render(float theta, bool useVBOs, float scale_factor) const
{
case MK2:
{
render_prusa("mk2", theta, useVBOs);
render_prusa(canvas, "mk2", theta, useVBOs);
break;
}
case MK3:
{
render_prusa("mk3", theta, useVBOs);
render_prusa(canvas, "mk3", theta, useVBOs);
break;
}
case SL1:
{
render_prusa("sl1", theta, useVBOs);
render_prusa(canvas, "sl1", theta, useVBOs);
break;
}
default:
@ -400,13 +401,22 @@ void Bed3D::render_axes() const
m_axes.render();
}
void Bed3D::calc_bounding_box()
void Bed3D::calc_bounding_boxes() const
{
m_bounding_box = BoundingBoxf3();
for (const Vec2d& p : m_shape)
{
m_bounding_box.merge(Vec3d(p(0), p(1), 0.0));
}
m_extended_bounding_box = m_bounding_box;
// extend to contain axes
m_extended_bounding_box.merge(m_axes.length + Axes::ArrowLength * Vec3d::Ones());
// extend to contain model, if any
if (!m_model.get_filename().empty())
m_extended_bounding_box.merge(m_model.get_transformed_bounding_box());
}
void Bed3D::calc_triangles(const ExPolygon& poly)
@ -487,41 +497,51 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const
}
#if ENABLE_TEXTURES_FROM_SVG
void Bed3D::render_prusa(const std::string &key, bool bottom) const
void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom) const
{
std::string tex_path = resources_dir() + "/icons/bed/" + key;
std::string model_path = resources_dir() + "/models/" + key;
// use anisotropic filter if graphic card allows
GLfloat max_anisotropy = 0.0f;
if (glewIsSupported("GL_EXT_texture_filter_anisotropic"))
glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy));
// use higher resolution images if graphic card allows
GLint max_tex_size;
glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size));
// clamp or the texture generation becomes too slow
max_tex_size = std::min(max_tex_size, 8192);
// use higher resolution images if graphic card and opengl version allow
GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size();
std::string filename = tex_path + ".svg";
if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename))
{
if (!m_texture.load_from_svg_file(filename, true, max_tex_size))
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8))
{
render_custom();
return;
}
if (max_anisotropy > 0.0f)
// starts generating the main texture, compression will run asynchronously
if (!m_texture.load_from_svg_file(filename, true, true, true, max_tex_size))
{
glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.get_id()));
glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
render_custom();
return;
}
}
else if (m_texture.unsent_compressed_data_available())
{
// sends to gpu the already available compressed levels of the main texture
m_texture.send_compressed_data_to_gpu();
// the temporary texture is not needed anymore, reset it
if (m_temp_texture.get_id() != 0)
m_temp_texture.reset();
m_requires_canvas_update = true;
}
else if (m_requires_canvas_update && m_texture.all_compressed_data_sent_to_gpu())
{
if (canvas != nullptr)
canvas->stop_keeping_dirty();
m_requires_canvas_update = false;
}
if (!bottom)
{
@ -539,6 +559,9 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const
offset += Vec3d(0.0, 0.0, -0.03);
m_model.center_around(offset);
// update extended bounding box
calc_bounding_boxes();
}
if (!m_model.get_filename().empty())
@ -594,7 +617,12 @@ void Bed3D::render_prusa_shader(bool transparent) const
GLint position_id = m_shader.get_attrib_location("v_position");
GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords");
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_texture.get_id()));
// show the temporary texture while no compressed data is available
GLuint tex_id = (GLuint)m_temp_texture.get_id();
if (tex_id == 0)
tex_id = (GLuint)m_texture.get_id();
glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id));
if (position_id != -1)

View file

@ -13,6 +13,8 @@ typedef class GLUquadric GLUquadricObj;
namespace Slic3r {
namespace GUI {
class GLCanvas3D;
class GeometryBuffer
{
#if ENABLE_TEXTURES_FROM_SVG
@ -85,12 +87,17 @@ public:
private:
EType m_type;
Pointfs m_shape;
BoundingBoxf3 m_bounding_box;
mutable BoundingBoxf3 m_bounding_box;
mutable BoundingBoxf3 m_extended_bounding_box;
Polygon m_polygon;
GeometryBuffer m_triangles;
GeometryBuffer m_gridlines;
#if ENABLE_TEXTURES_FROM_SVG
mutable GLTexture m_texture;
// temporary texture shown until the main texture has still no levels compressed
mutable GLTexture m_temp_texture;
// used to trigger 3D scene update once all compressed textures have been sent to GPU
mutable bool m_requires_canvas_update;
mutable Shader m_shader;
mutable unsigned int m_vbo_id;
#else
@ -117,20 +124,20 @@ public:
// Return true if the bed shape changed, so the calee will update the UI.
bool set_shape(const Pointfs& shape);
const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; }
const BoundingBoxf3& get_bounding_box(bool extended) const { return extended ? m_extended_bounding_box : m_bounding_box; }
bool contains(const Point& point) const;
Point point_projection(const Point& point) const;
void render(float theta, bool useVBOs, float scale_factor) const;
void render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const;
void render_axes() const;
private:
void calc_bounding_box();
void calc_bounding_boxes() const;
void calc_triangles(const ExPolygon& poly);
void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox);
EType detect_type(const Pointfs& shape) const;
#if ENABLE_TEXTURES_FROM_SVG
void render_prusa(const std::string& key, bool bottom) const;
void render_prusa(GLCanvas3D* canvas, const std::string& key, bool bottom) const;
void render_prusa_shader(bool transparent) const;
#else
void render_prusa(const std::string &key, float theta, bool useVBOs) const;

View file

@ -2015,7 +2015,7 @@ bool GLBed::on_init_from_file(const std::string& filename, bool useVBOs)
std::string _3DScene::get_gl_info(bool format_as_html, bool extensions)
{
return s_canvas_mgr.get_gl_info(format_as_html, extensions);
return Slic3r::GUI::GLCanvas3DManager::get_gl_info().to_string(format_as_html, extensions);
}
bool _3DScene::add_canvas(wxGLCanvas* canvas, GUI::Bed3D& bed, GUI::Camera& camera, GUI::GLToolbar& view_toolbar)

View file

@ -555,6 +555,7 @@ public:
const std::string& get_filename() const { return m_filename; }
const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box; }
const BoundingBoxf3& get_transformed_bounding_box() const { return m_volume.transformed_bounding_box(); }
void reset();

View file

@ -73,6 +73,9 @@ void AppConfig::set_defaults()
if (get("custom_toolbar_size").empty())
set("custom_toolbar_size", "100");
if (get("camera_type").empty())
set("camera_type", "1");
// Remove legacy window positions/sizes
erase("", "main_frame_maximized");
erase("", "main_frame_pos");

View file

@ -2,6 +2,8 @@
#include "Camera.hpp"
#include "3DScene.hpp"
#include "GUI_App.hpp"
#include "AppConfig.hpp"
#include <GL/glew.h>
@ -19,32 +21,71 @@ static const float VIEW_REAR[2] = { 180.0f, 90.0f };
namespace Slic3r {
namespace GUI {
const double Camera::DefaultDistance = 1000.0;
double Camera::FrustrumMinZSize = 50.0;
double Camera::FrustrumZMargin = 10.0;
double Camera::FovMinDeg = 5.0;
double Camera::FovMaxDeg = 75.0;
Camera::Camera()
: type(Ortho)
, zoom(1.0f)
, phi(45.0f)
// , distance(0.0f)
: phi(45.0f)
, requires_zoom_to_bed(false)
, inverted_phi(false)
, m_theta(45.0f)
, m_type(Ortho)
, m_target(Vec3d::Zero())
, m_theta(45.0f)
, m_zoom(1.0)
, m_distance(DefaultDistance)
, m_gui_scale(1.0)
, m_view_matrix(Transform3d::Identity())
, m_projection_matrix(Transform3d::Identity())
{
}
std::string Camera::get_type_as_string() const
{
switch (type)
switch (m_type)
{
default:
case Unknown:
return "unknown";
// case Perspective:
// return "perspective";
case Perspective:
return "perspective";
default:
case Ortho:
return "ortho";
return "orthographic";
};
}
void Camera::set_type(EType type)
{
if (m_type != type)
{
m_type = type;
wxGetApp().app_config->set("camera_type", std::to_string(m_type));
wxGetApp().app_config->save();
}
}
void Camera::set_type(const std::string& type)
{
if (!type.empty() && (type != "1"))
{
unsigned char type_id = atoi(type.c_str());
if (((unsigned char)Ortho < type_id) && (type_id < (unsigned char)Num_types))
set_type((Camera::EType)type_id);
}
}
void Camera::select_next_type()
{
unsigned char next = (unsigned char)m_type + 1;
if (next == (unsigned char)Num_types)
next = 1;
set_type((EType)next);
}
void Camera::set_target(const Vec3d& target)
{
m_target = target;
@ -65,9 +106,20 @@ void Camera::set_theta(float theta, bool apply_limit)
}
}
void Camera::set_scene_box(const BoundingBoxf3& box)
void Camera::set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h)
{
m_scene_box = box;
zoom = std::max(std::min(zoom, 4.0), -4.0) / 10.0;
zoom = m_zoom / (1.0 - zoom);
// Don't allow to zoom too far outside the scene.
double zoom_min = calc_zoom_to_bounding_box_factor(max_box, canvas_w, canvas_h);
if (zoom_min > 0.0)
zoom = std::max(zoom, zoom_min * 0.7);
// Don't allow to zoom too close to the scene.
zoom = std::min(zoom, 100.0);
m_zoom = zoom;
}
bool Camera::select_view(const std::string& direction)
@ -99,6 +151,18 @@ bool Camera::select_view(const std::string& direction)
return false;
}
double Camera::get_fov() const
{
switch (m_type)
{
case Perspective:
return 2.0 * Geometry::rad2deg(std::atan(1.0 / m_projection_matrix.matrix()(1, 1)));
default:
case Ortho:
return 0.0;
};
}
void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h) const
{
glsafe(::glViewport(0, 0, w, h));
@ -107,27 +171,268 @@ void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h) const
void Camera::apply_view_matrix() const
{
double theta_rad = Geometry::deg2rad(-(double)m_theta);
double phi_rad = Geometry::deg2rad((double)phi);
double sin_theta = ::sin(theta_rad);
Vec3d camera_pos = m_target + m_distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad));
glsafe(::glMatrixMode(GL_MODELVIEW));
glsafe(::glLoadIdentity());
glsafe(::glRotatef(-m_theta, 1.0f, 0.0f, 0.0f)); // pitch
glsafe(::glRotatef(phi, 0.0f, 0.0f, 1.0f)); // yaw
glsafe(::glTranslated(-m_target(0), -m_target(1), -m_target(2)));
glsafe(::glRotatef(phi, 0.0f, 0.0f, 1.0f)); // yaw
glsafe(::glTranslated(-camera_pos(0), -camera_pos(1), -camera_pos(2)));
glsafe(::glGetDoublev(GL_MODELVIEW_MATRIX, m_view_matrix.data()));
}
void Camera::apply_ortho_projection(float x_min, float x_max, float y_min, float y_max, float z_min, float z_max) const
void Camera::apply_projection(const BoundingBoxf3& box) const
{
m_distance = DefaultDistance;
double w = 0.0;
double h = 0.0;
while (true)
{
m_frustrum_zs = calc_tight_frustrum_zs_around(box);
w = (double)m_viewport[2];
h = (double)m_viewport[3];
double two_zoom = 2.0 * m_zoom;
if (two_zoom != 0.0)
{
double inv_two_zoom = 1.0 / two_zoom;
w *= inv_two_zoom;
h *= inv_two_zoom;
}
switch (m_type)
{
default:
case Ortho:
{
m_gui_scale = 1.0;
break;
}
case Perspective:
{
// scale near plane to keep w and h constant on the plane at z = m_distance
double scale = m_frustrum_zs.first / m_distance;
w *= scale;
h *= scale;
m_gui_scale = scale;
break;
}
}
if (m_type == Perspective)
{
double fov_rad = 2.0 * std::atan(h / m_frustrum_zs.first);
double fov_deg = Geometry::rad2deg(fov_rad);
// adjust camera distance to keep fov in a limited range
if (fov_deg > FovMaxDeg + 0.001)
{
double new_near_z = h / ::tan(0.5 * Geometry::deg2rad(FovMaxDeg));
m_distance += (new_near_z - m_frustrum_zs.first);
apply_view_matrix();
}
else if (fov_deg < FovMinDeg - 0.001)
{
double new_near_z = h / ::tan(0.5 * Geometry::deg2rad(FovMinDeg));
m_distance += (new_near_z - m_frustrum_zs.first);
apply_view_matrix();
}
else
break;
}
else
break;
}
glsafe(::glMatrixMode(GL_PROJECTION));
glsafe(::glLoadIdentity());
glsafe(::glOrtho(x_min, x_max, y_min, y_max, z_min, z_max));
glsafe(::glGetDoublev(GL_PROJECTION_MATRIX, m_projection_matrix.data()));
switch (m_type)
{
default:
case Ortho:
{
glsafe(::glOrtho(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second));
break;
}
case Perspective:
{
glsafe(::glFrustum(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second));
break;
}
}
glsafe(::glGetDoublev(GL_PROJECTION_MATRIX, m_projection_matrix.data()));
glsafe(::glMatrixMode(GL_MODELVIEW));
}
void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h)
{
// Calculate the zoom factor needed to adjust the view around the given box.
double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h);
if (zoom > 0.0)
{
m_zoom = zoom;
// center view around box center
m_target = box.center();
}
}
#if ENABLE_CAMERA_STATISTICS
void Camera::debug_render() const
{
ImGuiWrapper& imgui = *wxGetApp().imgui();
imgui.set_next_window_bg_alpha(0.5f);
imgui.begin(std::string("Camera statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
std::string type = get_type_as_string();
Vec3f position = get_position().cast<float>();
Vec3f target = m_target.cast<float>();
float distance = (float)get_distance();
Vec3f forward = get_dir_forward().cast<float>();
Vec3f right = get_dir_right().cast<float>();
Vec3f up = get_dir_up().cast<float>();
float nearZ = (float)m_frustrum_zs.first;
float farZ = (float)m_frustrum_zs.second;
float deltaZ = farZ - nearZ;
float zoom = (float)m_zoom;
float fov = (float)get_fov();
float gui_scale = (float)get_gui_scale();
ImGui::InputText("Type", const_cast<char*>(type.data()), type.length(), ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat3("Position", position.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat3("Target", target.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat("Distance", &distance, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat3("Forward", forward.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat3("Right", right.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat3("Up", up.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat("Near Z", &nearZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat("Far Z", &farZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat("Delta Z", &deltaZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat("Zoom", &zoom, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat("Fov", &fov, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
ImGui::Separator();
ImGui::InputFloat("GUI scale", &gui_scale, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
imgui.end();
}
#endif // ENABLE_CAMERA_STATISTICS
std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const
{
std::pair<double, double> ret = std::make_pair(DBL_MAX, -DBL_MAX);
Vec3d bb_min = box.min;
Vec3d bb_max = box.max;
// box vertices in world space
std::vector<Vec3d> vertices;
vertices.reserve(8);
vertices.push_back(bb_min);
vertices.emplace_back(bb_max(0), bb_min(1), bb_min(2));
vertices.emplace_back(bb_max(0), bb_max(1), bb_min(2));
vertices.emplace_back(bb_min(0), bb_max(1), bb_min(2));
vertices.emplace_back(bb_min(0), bb_min(1), bb_max(2));
vertices.emplace_back(bb_max(0), bb_min(1), bb_max(2));
vertices.push_back(bb_max);
vertices.emplace_back(bb_min(0), bb_max(1), bb_max(2));
// set the Z range in eye coordinates (negative Zs are in front of the camera)
for (const Vec3d& v : vertices)
{
double z = -(m_view_matrix * v)(2);
ret.first = std::min(ret.first, z);
ret.second = std::max(ret.second, z);
}
// apply margin
ret.first -= FrustrumZMargin;
ret.second += FrustrumZMargin;
// ensure min size
if (ret.second - ret.first < FrustrumMinZSize)
{
double mid_z = 0.5 * (ret.first + ret.second);
double half_size = 0.5 * FrustrumMinZSize;
ret.first = mid_z - half_size;
ret.second = mid_z + half_size;
}
return ret;
}
double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const
{
double max_bb_size = box.max_size();
if (max_bb_size == 0.0)
return -1.0;
// 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
// ensure that the view matrix is updated
apply_view_matrix();
Vec3d right = get_dir_right();
Vec3d up = get_dir_up();
Vec3d forward = get_dir_forward();
Vec3d bb_min = box.min;
Vec3d bb_max = box.max;
Vec3d bb_center = box.center();
// box vertices in world space
std::vector<Vec3d> vertices;
vertices.reserve(8);
vertices.push_back(bb_min);
vertices.emplace_back(bb_max(0), bb_min(1), bb_min(2));
vertices.emplace_back(bb_max(0), bb_max(1), bb_min(2));
vertices.emplace_back(bb_min(0), bb_max(1), bb_min(2));
vertices.emplace_back(bb_min(0), bb_min(1), bb_max(2));
vertices.emplace_back(bb_max(0), bb_min(1), bb_max(2));
vertices.push_back(bb_max);
vertices.emplace_back(bb_min(0), bb_max(1), bb_max(2));
double max_x = 0.0;
double max_y = 0.0;
// margin factor to give some empty space around the box
double margin_factor = 1.25;
for (const Vec3d& v : vertices)
{
// project vertex on the plane perpendicular to camera forward axis
Vec3d pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2));
Vec3d proj_on_plane = pos - pos.dot(forward) * forward;
// calculates vertex coordinate along camera xy axes
double x_on_plane = proj_on_plane.dot(right);
double y_on_plane = proj_on_plane.dot(up);
max_x = std::max(max_x, std::abs(x_on_plane));
max_y = std::max(max_y, std::abs(y_on_plane));
}
if ((max_x == 0.0) || (max_y == 0.0))
return -1.0f;
max_x *= margin_factor;
max_y *= margin_factor;
return std::min((double)canvas_w / (2.0 * max_x), (double)canvas_h / (2.0 * max_y));
}
} // GUI
} // Slic3r

View file

@ -9,44 +9,64 @@ namespace GUI {
struct Camera
{
static const double DefaultDistance;
static double FrustrumMinZSize;
static double FrustrumZMargin;
static double FovMinDeg;
static double FovMaxDeg;
enum EType : unsigned char
{
Unknown,
// Perspective,
Ortho,
Perspective,
Num_types
};
EType type;
float zoom;
float phi;
// float distance;
bool requires_zoom_to_bed;
bool inverted_phi;
private:
EType m_type;
Vec3d m_target;
float m_theta;
double m_zoom;
// Distance between camera position and camera target measured along the camera Z axis
mutable double m_distance;
mutable double m_gui_scale;
mutable std::array<int, 4> m_viewport;
mutable Transform3d m_view_matrix;
mutable Transform3d m_projection_matrix;
mutable std::pair<double, double> m_frustrum_zs;
BoundingBoxf3 m_scene_box;
public:
Camera();
EType get_type() const { return m_type; }
std::string get_type_as_string() const;
void set_type(EType type);
void set_type(const std::string& type);
void select_next_type();
const Vec3d& get_target() const { return m_target; }
void set_target(const Vec3d& target);
double get_distance() const { return m_distance; }
double get_gui_scale() const { return m_gui_scale; }
float get_theta() const { return m_theta; }
void set_theta(float theta, bool apply_limit);
double get_zoom() const { return m_zoom; }
void set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h);
void set_zoom(double zoom) { m_zoom = zoom; }
const BoundingBoxf3& get_scene_box() const { return m_scene_box; }
void set_scene_box(const BoundingBoxf3& box);
void set_scene_box(const BoundingBoxf3& box) { m_scene_box = box; }
bool select_view(const std::string& direction);
@ -60,9 +80,26 @@ public:
Vec3d get_position() const { return m_view_matrix.matrix().inverse().block(0, 3, 3, 1); }
double get_near_z() const { return m_frustrum_zs.first; }
double get_far_z() const { return m_frustrum_zs.second; }
double get_fov() const;
void apply_viewport(int x, int y, unsigned int w, unsigned int h) const;
void apply_view_matrix() const;
void apply_ortho_projection(float x_min, float x_max, float y_min, float y_max, float z_min, float z_max) const;
void apply_projection(const BoundingBoxf3& box) const;
void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h);
#if ENABLE_CAMERA_STATISTICS
void debug_render() const;
#endif // ENABLE_CAMERA_STATISTICS
private:
// returns tight values for nearZ and farZ plane around the given bounding box
// the camera MUST be outside of the bounding box in eye coordinate of the given box
std::pair<double, double> calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const;
double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const;
};
} // GUI

View file

@ -5,11 +5,14 @@
#include <stdexcept>
#include <boost/format.hpp>
#include <boost/asio.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <boost/optional.hpp>
#if _WIN32
#include <regex>
#endif
#include "libslic3r/Utils.hpp"
#include "avrdude/avrdude-slic3r.hpp"
#include "GUI.hpp"
@ -104,7 +107,7 @@ struct FirmwareDialog::priv
// GUI elements
wxComboBox *port_picker;
wxStaticText *port_autodetect;
wxStaticText *txt_port_autodetect;
wxFilePickerCtrl *hex_picker;
wxStaticText *txt_status;
wxGauge *progressbar;
@ -131,6 +134,7 @@ struct FirmwareDialog::priv
// Data
std::vector<SerialPortInfo> ports;
optional<SerialPortInfo> port;
bool port_autodetect;
HexFile hex_file;
// This is a shared pointer holding the background AvrDude task
@ -147,6 +151,7 @@ struct FirmwareDialog::priv
btn_flash_label_flashing(_(L("Cancel"))),
label_status_flashing(_(L("Flashing in progress. Please do not disconnect the printer!"))),
timer_pulse(q),
port_autodetect(false),
progress_tasks_done(0),
progress_tasks_bar(0),
user_cancelled(false),
@ -158,7 +163,8 @@ struct FirmwareDialog::priv
void set_txt_status(const wxString &label);
void flashing_start(unsigned tasks);
void flashing_done(AvrDudeComplete complete);
void enable_port_picker(bool enable);
void set_autodetect(bool autodetect);
void update_flash_enabled();
void load_hex_file(const wxString &path);
void queue_event(AvrdudeEvent aevt, wxString message);
@ -171,6 +177,7 @@ struct FirmwareDialog::priv
void prepare_mk2();
void prepare_mk3();
void prepare_avr109(Avr109Pid usb_pid);
bool get_serial_port();
void perform_upload();
void user_cancel();
@ -211,8 +218,10 @@ void FirmwareDialog::priv::find_serial_ports()
idx = i;
break;
}
if (idx != -1)
if (idx != -1) {
port_picker->SetSelection(idx);
update_flash_enabled();
}
}
}
}
@ -277,20 +286,30 @@ void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete)
}
}
void FirmwareDialog::priv::enable_port_picker(bool enable)
void FirmwareDialog::priv::set_autodetect(bool autodetect)
{
port_picker->Show(enable);
btn_rescan->Show(enable);
port_autodetect->Show(! enable);
port_autodetect = autodetect;
port_picker->Show(!autodetect);
btn_rescan->Show(!autodetect);
txt_port_autodetect->Show(autodetect);
q->Layout();
fit_no_shrink();
}
void FirmwareDialog::priv::update_flash_enabled()
{
const bool hex_exists = wxFileExists(hex_picker->GetPath());
const bool port_valid = port_autodetect || get_serial_port();
btn_flash->Enable(hex_exists && port_valid);
}
void FirmwareDialog::priv::load_hex_file(const wxString &path)
{
hex_file = HexFile(path.wx_str());
const bool auto_lookup = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1;
enable_port_picker(! auto_lookup);
const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1;
set_autodetect(autodetect);
}
void FirmwareDialog::priv::queue_event(AvrdudeEvent aevt, wxString message)
@ -553,6 +572,31 @@ void FirmwareDialog::priv::prepare_avr109(Avr109Pid usb_pid)
}
bool FirmwareDialog::priv::get_serial_port()
{
const int selection = port_picker->GetSelection();
if (selection != wxNOT_FOUND) {
port = this->ports[selection];
} else {
// User has supplied a custom filename
std::string path_u8 = GUI::into_u8(port_picker->GetValue());
#ifdef _WIN32
static const std::regex com_pattern("COM[0-9]+", std::regex::icase);
std::smatch matches;
if (std::regex_match(path_u8, matches, com_pattern)) {
#else
if (fs::is_other(fs::path(path_u8))) {
#endif
port = SerialPortInfo(std::move(path_u8));
} else {
port = boost::none;
}
}
return !!port;
}
void FirmwareDialog::priv::perform_upload()
{
auto filename = hex_picker->GetPath();
@ -560,14 +604,8 @@ void FirmwareDialog::priv::perform_upload()
load_hex_file(filename); // Might already be loaded, but we want to make sure it's fresh
int selection = port_picker->GetSelection();
if (selection != wxNOT_FOUND) {
port = this->ports[selection];
// Verify whether the combo box list selection equals to the combo box edit value.
if (wxString::FromUTF8(port->friendly_name.data()) != port_picker->GetValue()) {
return;
}
if (!port_autodetect && !get_serial_port()) {
return;
}
const bool extra_verbose = false; // For debugging
@ -769,13 +807,13 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
p->port_picker = new wxComboBox(panel, wxID_ANY);
p->port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected")));
p->txt_port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected")));
p->btn_rescan = new wxButton(panel, wxID_ANY, _(L("Rescan")));
auto *port_sizer = new wxBoxSizer(wxHORIZONTAL);
port_sizer->Add(p->port_picker, 1, wxEXPAND | wxRIGHT, SPACING);
port_sizer->Add(p->btn_rescan, 0);
port_sizer->Add(p->port_autodetect, 1, wxEXPAND);
p->enable_port_picker(true);
port_sizer->Add(p->txt_port_autodetect, 1, wxEXPAND);
p->set_autodetect(false);
auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:")));
p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH);
@ -836,10 +874,13 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
p->hex_picker->Bind(wxEVT_FILEPICKER_CHANGED, [this](wxFileDirPickerEvent& evt) {
if (wxFileExists(evt.GetPath())) {
this->p->load_hex_file(evt.GetPath());
this->p->btn_flash->Enable();
}
p->update_flash_enabled();
});
p->port_picker->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { p->update_flash_enabled(); });
p->port_picker->Bind(wxEVT_TEXT, [this](wxCommandEvent &) { p->update_flash_enabled(); });
p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [=](wxCollapsiblePaneEvent &evt) {
if (evt.GetCollapsed()) {
this->SetMinSize(wxSize(p->min_width, p->min_height));

View file

@ -300,22 +300,10 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
const Rect& bar_rect = get_bar_rect_viewport(canvas);
const Rect& reset_rect = get_reset_rect_viewport(canvas);
glsafe(::glDisable(GL_DEPTH_TEST));
// The viewport and camera are set to complete view and glOrtho(-$x / 2, $x / 2, -$y / 2, $y / 2, -$depth, $depth),
// where x, y is the window size divided by $self->_zoom.
glsafe(::glPushMatrix());
glsafe(::glLoadIdentity());
_render_tooltip_texture(canvas, bar_rect, reset_rect);
_render_reset_texture(reset_rect);
_render_active_object_annotations(canvas, bar_rect);
_render_profile(bar_rect);
// Revert the matrices.
glsafe(::glPopMatrix());
glsafe(::glEnable(GL_DEPTH_TEST));
}
float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas)
@ -370,7 +358,7 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas)
float half_w = 0.5f * (float)cnv_size.get_width();
float half_h = 0.5f * (float)cnv_size.get_height();
float zoom = canvas.get_camera().zoom;
float zoom = (float)canvas.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom);
@ -382,7 +370,7 @@ Rect GLCanvas3D::LayersEditing::get_reset_rect_viewport(const GLCanvas3D& canvas
float half_w = 0.5f * (float)cnv_size.get_width();
float half_h = 0.5f * (float)cnv_size.get_height();
float zoom = canvas.get_camera().zoom;
float zoom = (float)canvas.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom);
@ -401,7 +389,7 @@ void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas
if (m_tooltip_texture.get_id() == 0)
{
std::string filename = resources_dir() + "/icons/variable_layer_height_tooltip.png";
if (!m_tooltip_texture.load_from_file(filename, false))
if (!m_tooltip_texture.load_from_file(filename, false, true))
return;
}
@ -413,7 +401,7 @@ void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas
const float width = (float)m_tooltip_texture.get_width() * scale;
const float height = (float)m_tooltip_texture.get_height() * scale;
float zoom = canvas.get_camera().zoom;
float zoom = (float)canvas.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
float gap = 10.0f * inv_zoom;
@ -433,7 +421,7 @@ void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) co
if (m_reset_texture.get_id() == 0)
{
std::string filename = resources_dir() + "/icons/variable_layer_height_reset.png";
if (!m_reset_texture.load_from_file(filename, false))
if (!m_reset_texture.load_from_file(filename, false, true))
return;
}
@ -728,7 +716,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
}
}
_generate(text, canvas, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...)
generate(text, canvas, true, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...)
// save information for rescaling
m_msg_text = text;
@ -789,7 +777,7 @@ static void msw_disable_cleartype(wxFont &font)
}
#endif /* __WXMSW__ */
bool GLCanvas3D::WarningTexture::_generate(const std::string& msg_utf8, const GLCanvas3D& canvas, const bool red_colored/* = false*/)
bool GLCanvas3D::WarningTexture::generate(const std::string& msg_utf8, const GLCanvas3D& canvas, bool compress, bool red_colored/* = false*/)
{
reset();
@ -863,7 +851,10 @@ bool GLCanvas3D::WarningTexture::_generate(const std::string& msg_utf8, const GL
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
if (compress && GLEW_EXT_texture_compression_s3tc)
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
@ -879,12 +870,8 @@ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const
if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0))
{
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
glsafe(::glLoadIdentity());
const Size& cnv_size = canvas.get_canvas_size();
float zoom = canvas.get_camera().zoom;
float zoom = (float)canvas.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
float left = (-0.5f * (float)m_original_width) * inv_zoom;
float top = (-0.5f * (float)cnv_size.get_height() + (float)m_original_height + 2.0f) * inv_zoom;
@ -903,9 +890,6 @@ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const
uvs.right_top = { uv_right, uv_top };
GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs);
glsafe(::glPopMatrix());
glsafe(::glEnable(GL_DEPTH_TEST));
}
}
@ -914,7 +898,7 @@ void GLCanvas3D::WarningTexture::msw_rescale(const GLCanvas3D& canvas)
if (m_msg_text.empty())
return;
_generate(m_msg_text, canvas, m_is_colored_red);
generate(m_msg_text, canvas, true, m_is_colored_red);
}
const unsigned char GLCanvas3D::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 };
@ -957,7 +941,7 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePrevie
}
}
bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors, const GLCanvas3D& canvas)
bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors, const GLCanvas3D& canvas, bool compress)
{
reset();
@ -1146,7 +1130,10 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
if (compress && GLEW_EXT_texture_compression_s3tc)
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
@ -1159,12 +1146,8 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const
{
if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0))
{
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
glsafe(::glLoadIdentity());
const Size& cnv_size = canvas.get_canvas_size();
float zoom = canvas.get_camera().zoom;
float zoom = (float)canvas.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
float left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom;
float top = (0.5f * (float)cnv_size.get_height()) * inv_zoom;
@ -1183,9 +1166,6 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const
uvs.right_top = { uv_right, uv_top };
GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs);
glsafe(::glPopMatrix());
glsafe(::glEnable(GL_DEPTH_TEST));
}
}
@ -1229,6 +1209,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar
#endif // ENABLE_SVG_ICONS
, m_use_clipping_planes(false)
, m_sidebar_field("")
, m_keep_dirty(false)
, m_config(nullptr)
, m_process(nullptr)
, m_model(nullptr)
@ -1446,6 +1427,8 @@ void GLCanvas3D::bed_shape_changed()
m_camera.set_scene_box(scene_bounding_box());
m_camera.requires_zoom_to_bed = true;
m_dirty = true;
if (m_bed.is_prusa())
start_keeping_dirty();
}
void GLCanvas3D::set_color_by(const std::string& value)
@ -1467,7 +1450,7 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const
BoundingBoxf3 GLCanvas3D::scene_bounding_box() const
{
BoundingBoxf3 bb = volumes_bounding_box();
bb.merge(m_bed.get_bounding_box());
bb.merge(m_bed.get_bounding_box(false));
if (m_config != nullptr)
{
@ -1475,6 +1458,7 @@ BoundingBoxf3 GLCanvas3D::scene_bounding_box() const
bb.min(2) = std::min(bb.min(2), -h);
bb.max(2) = std::max(bb.max(2), h);
}
return bb;
}
@ -1550,20 +1534,20 @@ void GLCanvas3D::allow_multisample(bool allow)
void GLCanvas3D::zoom_to_bed()
{
_zoom_to_bounding_box(m_bed.get_bounding_box());
_zoom_to_box(m_bed.get_bounding_box(false));
}
void GLCanvas3D::zoom_to_volumes()
{
m_apply_zoom_to_volumes_filter = true;
_zoom_to_bounding_box(volumes_bounding_box());
_zoom_to_box(volumes_bounding_box());
m_apply_zoom_to_volumes_filter = false;
}
void GLCanvas3D::zoom_to_selection()
{
if (!m_selection.is_empty())
_zoom_to_bounding_box(m_selection.get_bounding_box());
_zoom_to_box(m_selection.get_bounding_box());
}
void GLCanvas3D::select_view(const std::string& direction)
@ -1624,6 +1608,7 @@ void GLCanvas3D::render()
}
m_camera.apply_view_matrix();
m_camera.apply_projection(_max_bounding_box(true));
GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f };
glsafe(::glLightfv(GL_LIGHT1, GL_POSITION, position_cam));
@ -1685,29 +1670,31 @@ void GLCanvas3D::render()
m_rectangle_selection.render(*this);
// draw overlays
_render_gizmos_overlay();
_render_warning_texture();
_render_legend_texture();
#if !ENABLE_SVG_ICONS
_resize_toolbars();
#endif // !ENABLE_SVG_ICONS
_render_toolbar();
_render_view_toolbar();
if ((m_layers_editing.last_object_id >= 0) && (m_layers_editing.object_max_z() > 0.0f))
m_layers_editing.render_overlay(*this);
_render_overlays();
#if ENABLE_RENDER_STATISTICS
ImGuiWrapper& imgui = *wxGetApp().imgui();
imgui.set_next_window_bg_alpha(0.5f);
imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
imgui.text(_(L("Last frame")) +": ");
imgui.text("Last frame: ");
ImGui::SameLine();
imgui.text(std::to_string(m_render_stats.last_frame));
ImGui::SameLine();
imgui.text(" "+_(L("ms")));
imgui.text(" ms");
ImGui::Separator();
imgui.text("Compressed textures: ");
ImGui::SameLine();
imgui.text(GLCanvas3DManager::are_compressed_textures_supported() ? "supported" : "not supported");
imgui.text("Max texture size: ");
ImGui::SameLine();
imgui.text(std::to_string(GLCanvas3DManager::get_gl_info().get_max_tex_size()));
imgui.end();
#endif // ENABLE_RENDER_STATISTICS
#if ENABLE_CAMERA_STATISTICS
m_camera.debug_render();
#endif // ENABLE_CAMERA_STATISTICS
wxGetApp().imgui()->render();
m_canvas->SwapBuffers();
@ -2311,6 +2298,9 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
return;
_refresh_if_shown_on_screen();
if (m_keep_dirty)
m_dirty = true;
}
void GLCanvas3D::on_char(wxKeyEvent& evt)
@ -2407,9 +2397,11 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
case 'B':
case 'b': { zoom_to_bed(); break; }
case 'I':
case 'i': { set_camera_zoom(1.0f); break; }
case 'i': { set_camera_zoom(1.0); break; }
case 'K':
case 'k': { m_camera.select_next_type(); m_dirty = true; break; }
case 'O':
case 'o': { set_camera_zoom(-1.0f); break; }
case 'o': { set_camera_zoom(-1.0); break; }
case 'Z':
case 'z': { m_selection.is_empty() ? zoom_to_volumes() : zoom_to_selection(); break; }
default: { evt.Skip(); break; }
@ -2530,7 +2522,7 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
m_layers_editing.band_width = std::max(std::min(m_layers_editing.band_width * (1.0f + 0.1f * (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()), 10.0f), 1.5f);
if (m_canvas != nullptr)
m_canvas->Refresh();
return;
}
}
@ -2541,8 +2533,7 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
return;
// Calculate the zoom delta and apply it to the current zoom factor
float zoom = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
set_camera_zoom(zoom);
set_camera_zoom((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta());
}
void GLCanvas3D::on_timer(wxTimerEvent& evt)
@ -3294,21 +3285,11 @@ void GLCanvas3D::do_mirror()
post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
void GLCanvas3D::set_camera_zoom(float zoom)
void GLCanvas3D::set_camera_zoom(double zoom)
{
zoom = std::max(std::min(zoom, 4.0f), -4.0f) / 10.0f;
zoom = m_camera.zoom / (1.0f - zoom);
// Don't allow to zoom too far outside the scene.
float zoom_min = _get_zoom_to_bounding_box_factor(_max_bounding_box());
if (zoom_min > 0.0f)
zoom = std::max(zoom, zoom_min * 0.7f);
// Don't allow to zoom too close to the scene.
zoom = std::min(zoom, 100.0f);
m_camera.zoom = zoom;
_refresh_if_shown_on_screen();
const Size& cnv_size = get_canvas_size();
m_camera.set_zoom(zoom, _max_bounding_box(false), cnv_size.get_width(), cnv_size.get_height());
m_dirty = true;
}
void GLCanvas3D::update_gizmos_on_off_state()
@ -3342,8 +3323,7 @@ void GLCanvas3D::update_ui_from_settings()
if (new_scaling != orig_scaling) {
BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Scaling factor: " << new_scaling;
m_camera.zoom /= orig_scaling;
m_camera.zoom *= new_scaling;
m_camera.set_zoom(m_camera.get_zoom() * new_scaling / orig_scaling);
_refresh_if_shown_on_screen();
}
#endif
@ -3390,7 +3370,7 @@ Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos)
double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const
{
return factor * m_bed.get_bounding_box().max_size();
return factor * m_bed.get_bounding_box(false).max_size();
}
void GLCanvas3D::set_cursor(ECursorType type)
@ -3508,7 +3488,7 @@ bool GLCanvas3D::_init_toolbar()
item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]";
item.sprite_id = 4;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy(); };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); };
if (!m_toolbar.add_item(item))
return false;
@ -3519,7 +3499,7 @@ bool GLCanvas3D::_init_toolbar()
item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]";
item.sprite_id = 5;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste(); };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); };
if (!m_toolbar.add_item(item))
return false;
@ -3621,143 +3601,25 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h)
// ensures that this canvas is current
_set_current();
// updates camera
m_camera.apply_viewport(0, 0, w, h);
const BoundingBoxf3& bbox = _max_bounding_box();
switch (m_camera.type)
{
case Camera::Ortho:
{
float w2 = w;
float h2 = h;
float two_zoom = 2.0f * m_camera.zoom;
if (two_zoom != 0.0f)
{
float inv_two_zoom = 1.0f / two_zoom;
w2 *= inv_two_zoom;
h2 *= inv_two_zoom;
}
// FIXME: calculate a tighter value for depth will improve z-fighting
// Set at least some minimum depth in case the bounding box is empty to avoid an OpenGL driver error.
float depth = std::max(1.f, 5.0f * (float)bbox.max_size());
m_camera.apply_ortho_projection(-w2, w2, -h2, h2, -depth, depth);
break;
}
// case Camera::Perspective:
// {
// float bbox_r = (float)bbox.radius();
// float fov = PI * 45.0f / 180.0f;
// float fov_tan = tan(0.5f * fov);
// float cam_distance = 0.5f * bbox_r / fov_tan;
// m_camera.distance = cam_distance;
//
// float nr = cam_distance - bbox_r * 1.1f;
// float fr = cam_distance + bbox_r * 1.1f;
// if (nr < 1.0f)
// nr = 1.0f;
//
// if (fr < nr + 1.0f)
// fr = nr + 1.0f;
//
// float h2 = fov_tan * nr;
// float w2 = h2 * w / h;
// ::glFrustum(-w2, w2, -h2, h2, nr, fr);
//
// break;
// }
default:
{
throw std::runtime_error("Invalid camera type.");
break;
}
}
m_dirty = false;
}
BoundingBoxf3 GLCanvas3D::_max_bounding_box() const
BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_bed_model) const
{
BoundingBoxf3 bb = volumes_bounding_box();
bb.merge(m_bed.get_bounding_box());
bb.merge(m_bed.get_bounding_box(include_bed_model));
return bb;
}
void GLCanvas3D::_zoom_to_bounding_box(const BoundingBoxf3& bbox)
void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box)
{
// Calculate the zoom factor needed to adjust viewport to bounding box.
float zoom = _get_zoom_to_bounding_box_factor(bbox);
if (zoom > 0.0f)
{
m_camera.zoom = zoom;
// center view around bounding box center
m_camera.set_target(bbox.center());
m_dirty = true;
}
}
float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const
{
float max_bb_size = bbox.max_size();
if (max_bb_size == 0.0f)
return -1.0f;
// project the bbox vertices on a plane perpendicular to the camera forward axis
// then calculates the vertices coordinate on this plane along the camera xy axes
// we need the view matrix, we let opengl calculate it (same as done in render())
m_camera.apply_view_matrix();
Vec3d right = m_camera.get_dir_right();
Vec3d up = m_camera.get_dir_up();
Vec3d forward = m_camera.get_dir_forward();
Vec3d bb_min = bbox.min;
Vec3d bb_max = bbox.max;
Vec3d bb_center = bbox.center();
// bbox vertices in world space
std::vector<Vec3d> vertices;
vertices.reserve(8);
vertices.push_back(bb_min);
vertices.emplace_back(bb_max(0), bb_min(1), bb_min(2));
vertices.emplace_back(bb_max(0), bb_max(1), bb_min(2));
vertices.emplace_back(bb_min(0), bb_max(1), bb_min(2));
vertices.emplace_back(bb_min(0), bb_min(1), bb_max(2));
vertices.emplace_back(bb_max(0), bb_min(1), bb_max(2));
vertices.push_back(bb_max);
vertices.emplace_back(bb_min(0), bb_max(1), bb_max(2));
double max_x = 0.0;
double max_y = 0.0;
// margin factor to give some empty space around the bbox
double margin_factor = 1.25;
for (const Vec3d& v : vertices)
{
// project vertex on the plane perpendicular to camera forward axis
Vec3d pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2));
Vec3d proj_on_plane = pos - pos.dot(forward) * forward;
// calculates vertex coordinate along camera xy axes
double x_on_plane = proj_on_plane.dot(right);
double y_on_plane = proj_on_plane.dot(up);
max_x = std::max(max_x, margin_factor * std::abs(x_on_plane));
max_y = std::max(max_y, margin_factor * std::abs(y_on_plane));
}
if ((max_x == 0.0) || (max_y == 0.0))
return -1.0f;
max_x *= 2.0;
max_y *= 2.0;
const Size& cnv_size = get_canvas_size();
return (float)std::min((double)cnv_size.get_width() / max_x, (double)cnv_size.get_height() / max_y);
m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height());
m_dirty = true;
}
void GLCanvas3D::_refresh_if_shown_on_screen()
@ -3943,7 +3805,7 @@ void GLCanvas3D::_render_bed(float theta) const
#if ENABLE_RETINA_GL
scale_factor = m_retina_helper->get_scale_factor();
#endif // ENABLE_RETINA_GL
m_bed.render(theta, m_use_VBOs, scale_factor);
m_bed.render(const_cast<GLCanvas3D*>(this), theta, m_use_VBOs, scale_factor);
}
void GLCanvas3D::_render_axes() const
@ -3972,7 +3834,7 @@ void GLCanvas3D::_render_objects() const
if (m_config != nullptr)
{
const BoundingBoxf3& bed_bb = m_bed.get_bounding_box();
const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(false);
m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height"));
m_volumes.check_outside_state(m_config, nullptr);
}
@ -4054,6 +3916,32 @@ void GLCanvas3D::_render_selection_center() const
}
#endif // ENABLE_RENDER_SELECTION_CENTER
void GLCanvas3D::_render_overlays() const
{
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
glsafe(::glLoadIdentity());
// ensure that the textures are renderered inside the frustrum
glsafe(::glTranslated(0.0, 0.0, -(m_camera.get_near_z() + 0.5)));
// ensure that the overlay fits the frustrum near z plane
double gui_scale = m_camera.get_gui_scale();
glsafe(::glScaled(gui_scale, gui_scale, 1.0));
_render_gizmos_overlay();
_render_warning_texture();
_render_legend_texture();
#if !ENABLE_SVG_ICONS
_resize_toolbars();
#endif // !ENABLE_SVG_ICONS
_render_toolbar();
_render_view_toolbar();
if ((m_layers_editing.last_object_id >= 0) && (m_layers_editing.object_max_z() > 0.0f))
m_layers_editing.render_overlay(*this);
glsafe(::glPopMatrix());
}
void GLCanvas3D::_render_warning_texture() const
{
m_warning_texture.render(*this);
@ -4150,7 +4038,7 @@ void GLCanvas3D::_render_toolbar() const
#endif // ENABLE_RETINA_GL
Size cnv_size = get_canvas_size();
float zoom = m_camera.zoom;
float zoom = (float)m_camera.get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation();
@ -4218,7 +4106,7 @@ void GLCanvas3D::_render_view_toolbar() const
#endif // ENABLE_RETINA_GL
Size cnv_size = get_canvas_size();
float zoom = m_camera.zoom;
float zoom = (float)m_camera.get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
// places the toolbar on the bottom-left corner of the 3d scene
@ -5699,7 +5587,7 @@ std::vector<float> GLCanvas3D::_parse_colors(const std::vector<std::string>& col
void GLCanvas3D::_generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors)
{
m_legend_texture.generate(preview_data, tool_colors, *this);
m_legend_texture.generate(preview_data, tool_colors, *this, true);
}
void GLCanvas3D::_set_warning_texture(WarningTexture::Warning warning, bool state)

View file

@ -376,7 +376,7 @@ class GLCanvas3D
std::vector<Warning> m_warnings;
// Generates the texture with given text.
bool _generate(const std::string& msg, const GLCanvas3D& canvas, const bool red_colored = false);
bool generate(const std::string& msg, const GLCanvas3D& canvas, bool compress, bool red_colored = false);
};
class LegendTexture : public GUI::GLTexture
@ -399,7 +399,7 @@ class GLCanvas3D
void fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas,
std::vector<std::pair<double, double>>& cp_legend_values);
bool generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors, const GLCanvas3D& canvas);
bool generate(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors, const GLCanvas3D& canvas, bool compress);
void render(const GLCanvas3D& canvas) const;
};
@ -443,6 +443,7 @@ private:
bool m_use_clipping_planes;
mutable SlaCap m_sla_caps[2];
std::string m_sidebar_field;
bool m_keep_dirty;
mutable GLVolumeCollection m_volumes;
Selection m_selection;
@ -599,7 +600,7 @@ public:
void do_flatten();
void do_mirror();
void set_camera_zoom(float zoom);
void set_camera_zoom(double zoom);
void update_gizmos_on_off_state();
void reset_all_gizmos() { m_gizmos.reset_all_states(); }
@ -629,6 +630,9 @@ public:
void set_cursor(ECursorType type);
void msw_rescale();
void start_keeping_dirty() { m_keep_dirty = true; }
void stop_keeping_dirty() { m_keep_dirty = false; }
private:
bool _is_shown_on_screen() const;
@ -637,10 +641,9 @@ private:
bool _set_current();
void _resize(unsigned int w, unsigned int h);
BoundingBoxf3 _max_bounding_box() const;
BoundingBoxf3 _max_bounding_box(bool include_bed_model) const;
void _zoom_to_bounding_box(const BoundingBoxf3& bbox);
float _get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const;
void _zoom_to_box(const BoundingBoxf3& box);
void _refresh_if_shown_on_screen();
@ -654,6 +657,7 @@ private:
#if ENABLE_RENDER_SELECTION_CENTER
void _render_selection_center() const;
#endif // ENABLE_RENDER_SELECTION_CENTER
void _render_overlays() const;
void _render_warning_texture() const;
void _render_legend_texture() const;
void _render_volumes_for_picking() const;

View file

@ -15,40 +15,112 @@
#include <string>
#include <iostream>
#ifdef __APPLE__
#include "../Utils/MacDarkMode.hpp"
#endif // __APPLE__
namespace Slic3r {
namespace GUI {
GLCanvas3DManager::GLInfo::GLInfo()
: version("")
, glsl_version("")
, vendor("")
, renderer("")
: m_detected(false)
, m_version("")
, m_glsl_version("")
, m_vendor("")
, m_renderer("")
, m_max_tex_size(0)
, m_max_anisotropy(0.0f)
{
}
void GLCanvas3DManager::GLInfo::detect()
const std::string& GLCanvas3DManager::GLInfo::get_version() const
{
if (!m_detected)
detect();
return m_version;
}
const std::string& GLCanvas3DManager::GLInfo::get_glsl_version() const
{
if (!m_detected)
detect();
return m_glsl_version;
}
const std::string& GLCanvas3DManager::GLInfo::get_vendor() const
{
if (!m_detected)
detect();
return m_vendor;
}
const std::string& GLCanvas3DManager::GLInfo::get_renderer() const
{
if (!m_detected)
detect();
return m_renderer;
}
int GLCanvas3DManager::GLInfo::get_max_tex_size() const
{
if (!m_detected)
detect();
// clamp to avoid the texture generation become too slow and use too much GPU memory
#ifdef __APPLE__
// and use smaller texture for non retina systems
return (Slic3r::GUI::mac_max_scaling_factor() > 1.0) ? std::min(m_max_tex_size, 8192) : std::min(m_max_tex_size / 2, 4096);
#else
// and use smaller texture for older OpenGL versions
return is_version_greater_or_equal_to(3, 0) ? std::min(m_max_tex_size, 8192) : std::min(m_max_tex_size / 2, 4096);
#endif // __APPLE__
}
float GLCanvas3DManager::GLInfo::get_max_anisotropy() const
{
if (!m_detected)
detect();
return m_max_anisotropy;
}
void GLCanvas3DManager::GLInfo::detect() const
{
const char* data = (const char*)::glGetString(GL_VERSION);
if (data != nullptr)
version = data;
m_version = data;
data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION);
if (data != nullptr)
glsl_version = data;
m_glsl_version = data;
data = (const char*)::glGetString(GL_VENDOR);
if (data != nullptr)
vendor = data;
m_vendor = data;
data = (const char*)::glGetString(GL_RENDERER);
if (data != nullptr)
renderer = data;
m_renderer = data;
glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size));
if (GLEW_EXT_texture_filter_anisotropic)
glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &m_max_anisotropy));
m_detected = true;
}
bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
{
if (!m_detected)
detect();
std::vector<std::string> tokens;
boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on);
boost::split(tokens, m_version, boost::is_any_of(" "), boost::token_compress_on);
if (tokens.empty())
return false;
@ -75,6 +147,9 @@ bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int majo
std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool extensions) const
{
if (!m_detected)
detect();
std::stringstream out;
std::string h2_start = format_as_html ? "<b>" : "";
@ -84,10 +159,10 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten
std::string line_end = format_as_html ? "<br>" : "\n";
out << h2_start << "OpenGL installation" << h2_end << line_end;
out << b_start << "GL version: " << b_end << (version.empty() ? "N/A" : version) << line_end;
out << b_start << "Vendor: " << b_end << (vendor.empty() ? "N/A" : vendor) << line_end;
out << b_start << "Renderer: " << b_end << (renderer.empty() ? "N/A" : renderer) << line_end;
out << b_start << "GLSL version: " << b_end << (glsl_version.empty() ? "N/A" : glsl_version) << line_end;
out << b_start << "GL version: " << b_end << (m_version.empty() ? "N/A" : m_version) << line_end;
out << b_start << "Vendor: " << b_end << (m_vendor.empty() ? "N/A" : m_vendor) << line_end;
out << b_start << "Renderer: " << b_end << (m_renderer.empty() ? "N/A" : m_renderer) << line_end;
out << b_start << "GLSL version: " << b_end << (m_glsl_version.empty() ? "N/A" : m_glsl_version) << line_end;
if (extensions)
{
@ -111,6 +186,8 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten
}
GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::MS_Unknown;
bool GLCanvas3DManager::s_compressed_textures_supported = false;
GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info;
GLCanvas3DManager::GLCanvas3DManager()
: m_context(nullptr)
@ -134,7 +211,7 @@ bool GLCanvas3DManager::add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLTo
if (canvas == nullptr)
return false;
if (_get_canvas(canvas) != m_canvases.end())
if (do_get_canvas(canvas) != m_canvases.end())
return false;
GLCanvas3D* canvas3D = new GLCanvas3D(canvas, bed, camera, view_toolbar);
@ -159,7 +236,7 @@ bool GLCanvas3DManager::add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLTo
bool GLCanvas3DManager::remove(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
CanvasesMap::iterator it = do_get_canvas(canvas);
if (it == m_canvases.end())
return false;
@ -190,31 +267,29 @@ void GLCanvas3DManager::init_gl()
if (!m_gl_initialized)
{
glewInit();
m_gl_info.detect();
const AppConfig* config = GUI::get_app_config();
m_use_legacy_opengl = (config == nullptr) || (config->get("use_legacy_opengl") == "1");
m_use_VBOs = !m_use_legacy_opengl && m_gl_info.is_version_greater_or_equal_to(2, 0);
m_use_VBOs = !m_use_legacy_opengl && s_gl_info.is_version_greater_or_equal_to(2, 0);
m_gl_initialized = true;
if (GLEW_EXT_texture_compression_s3tc)
s_compressed_textures_supported = true;
else
s_compressed_textures_supported = false;
}
}
std::string GLCanvas3DManager::get_gl_info(bool format_as_html, bool extensions) const
{
return m_gl_info.to_string(format_as_html, extensions);
}
bool GLCanvas3DManager::init(wxGLCanvas* canvas)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
CanvasesMap::const_iterator it = do_get_canvas(canvas);
if (it != m_canvases.end())
return (it->second != nullptr) ? _init(*it->second) : false;
return (it->second != nullptr) ? init(*it->second) : false;
else
return false;
}
GLCanvas3D* GLCanvas3DManager::get_canvas(wxGLCanvas* canvas)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
CanvasesMap::const_iterator it = do_get_canvas(canvas);
return (it != m_canvases.end()) ? it->second : nullptr;
}
@ -224,29 +299,28 @@ wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow *parent)
if (s_multisample == MS_Unknown)
{
_detect_multisample(attribList);
// debug output
std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl;
detect_multisample(attribList);
// // debug output
// std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl;
}
if (! can_multisample()) {
if (! can_multisample())
attribList[4] = 0;
}
return new wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS);
}
GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas)
GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::do_get_canvas(wxGLCanvas* canvas)
{
return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
}
GLCanvas3DManager::CanvasesMap::const_iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) const
GLCanvas3DManager::CanvasesMap::const_iterator GLCanvas3DManager::do_get_canvas(wxGLCanvas* canvas) const
{
return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
}
bool GLCanvas3DManager::_init(GLCanvas3D& canvas)
bool GLCanvas3DManager::init(GLCanvas3D& canvas)
{
if (!m_gl_initialized)
init_gl();
@ -254,7 +328,7 @@ bool GLCanvas3DManager::_init(GLCanvas3D& canvas)
return canvas.init(m_use_VBOs, m_use_legacy_opengl);
}
void GLCanvas3DManager::_detect_multisample(int* attribList)
void GLCanvas3DManager::detect_multisample(int* attribList)
{
int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER;
const AppConfig* app_config = GUI::get_app_config();

View file

@ -29,21 +29,39 @@ struct Camera;
class GLCanvas3DManager
{
struct GLInfo
public:
class GLInfo
{
std::string version;
std::string glsl_version;
std::string vendor;
std::string renderer;
mutable bool m_detected;
mutable std::string m_version;
mutable std::string m_glsl_version;
mutable std::string m_vendor;
mutable std::string m_renderer;
mutable int m_max_tex_size;
mutable float m_max_anisotropy;
public:
GLInfo();
void detect();
const std::string& get_version() const;
const std::string& get_glsl_version() const;
const std::string& get_vendor() const;
const std::string& get_renderer() const;
int get_max_tex_size() const;
float get_max_anisotropy() const;
bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const;
std::string to_string(bool format_as_html, bool extensions) const;
private:
void detect() const;
};
private:
enum EMultisampleState : unsigned char
{
MS_Unknown,
@ -55,11 +73,12 @@ class GLCanvas3DManager
CanvasesMap m_canvases;
wxGLContext* m_context;
GLInfo m_gl_info;
static GLInfo s_gl_info;
bool m_gl_initialized;
bool m_use_legacy_opengl;
bool m_use_VBOs;
static EMultisampleState s_multisample;
static bool s_compressed_textures_supported;
public:
GLCanvas3DManager();
@ -72,21 +91,24 @@ public:
unsigned int count() const;
void init_gl();
std::string get_gl_info(bool format_as_html, bool extensions) const;
bool init(wxGLCanvas* canvas);
GLCanvas3D* get_canvas(wxGLCanvas* canvas);
static bool can_multisample() { return s_multisample == MS_Enabled; }
static bool are_compressed_textures_supported() { return s_compressed_textures_supported; }
static wxGLCanvas* create_wxglcanvas(wxWindow *parent);
private:
CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas);
CanvasesMap::const_iterator _get_canvas(wxGLCanvas* canvas) const;
static const GLInfo& get_gl_info() { return s_gl_info; }
bool _init(GLCanvas3D& canvas);
static void _detect_multisample(int* attribList);
private:
CanvasesMap::iterator do_get_canvas(wxGLCanvas* canvas);
CanvasesMap::const_iterator do_get_canvas(wxGLCanvas* canvas) const;
bool init(GLCanvas3D& canvas);
static void detect_multisample(int* attribList);
};
} // namespace GUI

View file

@ -68,7 +68,7 @@ namespace GUI {
if (!is_dragging())
return;
float zoom = canvas.get_camera().zoom;
float zoom = (float)canvas.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
Size cnv_size = canvas.get_canvas_size();

View file

@ -12,6 +12,10 @@
#include <vector>
#include <algorithm>
#include <thread>
#define STB_DXT_IMPLEMENTATION
#include "stb_dxt/stb_dxt.h"
#include "nanosvg/nanosvg.h"
#include "nanosvg/nanosvgrast.h"
@ -21,6 +25,99 @@
namespace Slic3r {
namespace GUI {
void GLTexture::Compressor::reset()
{
// force compression completion, if any
m_abort_compressing = true;
// wait for compression completion, if any
while (m_is_compressing) {}
m_levels.clear();
}
void GLTexture::Compressor::add_level(unsigned int w, unsigned int h, const std::vector<unsigned char>& data)
{
m_levels.emplace_back(w, h, data);
}
void GLTexture::Compressor::start_compressing()
{
m_is_compressing = true;
m_abort_compressing = false;
std::thread t(&GLTexture::Compressor::compress, this);
t.detach();
}
bool GLTexture::Compressor::unsent_compressed_data_available() const
{
for (const Level& level : m_levels)
{
if (!level.sent_to_gpu && level.compressed)
return true;
}
return false;
}
void GLTexture::Compressor::send_compressed_data_to_gpu()
{
// this method should be called inside the main thread of Slicer or a new OpenGL context (sharing resources) would be needed
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.m_id));
for (int i = 0; i < (int)m_levels.size(); ++i)
{
Level& level = m_levels[i];
if (!level.sent_to_gpu && level.compressed)
{
glsafe(::glCompressedTexSubImage2D(GL_TEXTURE_2D, (GLint)i, 0, 0, (GLsizei)level.w, (GLsizei)level.h, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)level.compressed_data.size(), (const GLvoid*)level.compressed_data.data()));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (i > 0) ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR));
level.sent_to_gpu = true;
// we are done with the compressed data, we can discard it
level.compressed_data.clear();
level.compressed = false;
}
}
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
}
bool GLTexture::Compressor::all_compressed_data_sent_to_gpu() const
{
for (const Level& level : m_levels)
{
if (!level.sent_to_gpu)
return false;
}
return true;
}
void GLTexture::Compressor::compress()
{
// reference: https://github.com/Cyan4973/RygsDXTc
for (Level& level : m_levels)
{
if (m_abort_compressing)
break;
// stb_dxt library, despite claiming that the needed size of the destination buffer is equal to (source buffer size)/4,
// crashes if doing so, so we start with twice the required size
level.compressed_data = std::vector<unsigned char>(level.w * level.h * 2, 0);
int compressed_size = 0;
rygCompress(level.compressed_data.data(), level.src_data.data(), level.w, level.h, 1, compressed_size);
level.compressed_data.resize(compressed_size);
// we are done with the source data, we can discard it
level.src_data.clear();
level.compressed = true;
}
m_is_compressing = false;
m_abort_compressing = false;
}
GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } };
GLTexture::GLTexture()
@ -28,6 +125,7 @@ GLTexture::GLTexture()
, m_width(0)
, m_height(0)
, m_source("")
, m_compressor(*this)
{
}
@ -36,7 +134,7 @@ GLTexture::~GLTexture()
reset();
}
bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps)
bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, bool compress)
{
reset();
@ -44,12 +142,12 @@ bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps)
return false;
if (boost::algorithm::iends_with(filename, ".png"))
return load_from_png(filename, use_mipmaps);
return load_from_png(filename, use_mipmaps, compress);
else
return false;
}
bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, unsigned int max_size_px)
bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px)
{
reset();
@ -57,12 +155,12 @@ bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps
return false;
if (boost::algorithm::iends_with(filename, ".svg"))
return load_from_svg(filename, use_mipmaps, max_size_px);
return load_from_svg(filename, use_mipmaps, compress, apply_anisotropy, max_size_px);
else
return false;
}
bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector<std::string>& filenames, const std::vector<std::pair<int, bool>>& states, unsigned int sprite_size_px)
bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector<std::string>& filenames, const std::vector<std::pair<int, bool>>& states, unsigned int sprite_size_px, bool compress)
{
reset();
@ -178,7 +276,10 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector<std::stri
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_id));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
if (compress && GLEW_EXT_texture_compression_s3tc)
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
@ -222,6 +323,7 @@ void GLTexture::reset()
m_width = 0;
m_height = 0;
m_source = "";
m_compressor.reset();
}
void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top)
@ -252,7 +354,7 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right,
glsafe(::glDisable(GL_BLEND));
}
unsigned int GLTexture::generate_mipmaps(wxImage& image)
unsigned int GLTexture::generate_mipmaps(wxImage& image, bool compress)
{
int w = image.GetWidth();
int h = image.GetHeight();
@ -284,13 +386,16 @@ unsigned int GLTexture::generate_mipmaps(wxImage& image)
data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
}
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
if (compress && GLEW_EXT_texture_compression_s3tc)
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
}
return (unsigned int)level;
}
bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps)
bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, bool compress)
{
// Load a PNG with an alpha channel.
wxImage image;
@ -335,11 +440,14 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps)
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_id));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
if (compress && GLEW_EXT_texture_compression_s3tc)
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
if (use_mipmaps)
{
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
unsigned int levels_count = generate_mipmaps(image);
unsigned int levels_count = generate_mipmaps(image, compress);
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels_count));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
}
@ -357,8 +465,10 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps)
return true;
}
bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px)
bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px)
{
bool compression_enabled = compress && GLEW_EXT_texture_compression_s3tc;
NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f);
if (image == nullptr)
{
@ -371,6 +481,20 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns
m_width = (int)(scale * image->width);
m_height = (int)(scale * image->height);
if (compression_enabled)
{
// the stb_dxt compression library seems to like only texture sizes which are a multiple of 4
int width_rem = m_width % 4;
int height_rem = m_height % 4;
if (width_rem != 0)
m_width += (4 - width_rem);
if (height_rem != 0)
m_height += (4 - height_rem);
}
int n_pixels = m_width * m_height;
if (n_pixels <= 0)
@ -397,14 +521,32 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_id));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
if (apply_anisotropy)
{
GLfloat max_anisotropy = GLCanvas3DManager::get_gl_info().get_max_anisotropy();
if (max_anisotropy > 1.0f)
glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy));
}
if (compression_enabled)
{
// initializes the texture on GPU
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
// and send the uncompressed data to the compressor
m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data);
}
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
if (use_mipmaps)
{
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
int lod_w = m_width;
int lod_h = m_height;
GLint level = 0;
while ((lod_w > 1) || (lod_h > 1))
// we do not need to generate all levels down to 1x1
while ((lod_w > 16) || (lod_h > 16))
{
++level;
@ -412,12 +554,25 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns
lod_h = std::max(lod_h / 2, 1);
scale /= 2.0f;
data.resize(lod_w * lod_h * 4);
nsvgRasterize(rast, image, 0, 0, scale, data.data(), lod_w, lod_h, lod_w * 4);
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
if (compression_enabled)
{
// initializes the texture on GPU
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
// and send the uncompressed data to the compressor
m_compressor.add_level((unsigned int)lod_w, (unsigned int)lod_h, data);
}
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
}
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
if (!compression_enabled)
{
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
}
}
else
{
@ -430,6 +585,10 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns
m_source = filename;
if (compression_enabled)
// start asynchronous compression
m_compressor.start_compressing();
nsvgDeleteRasterizer(rast);
nsvgDelete(image);

View file

@ -11,6 +11,42 @@ namespace GUI {
class GLTexture
{
class Compressor
{
struct Level
{
unsigned int w;
unsigned int h;
std::vector<unsigned char> src_data;
std::vector<unsigned char> compressed_data;
bool compressed;
bool sent_to_gpu;
Level(unsigned int w, unsigned int h, const std::vector<unsigned char>& data) : w(w), h(h), src_data(data), compressed(false), sent_to_gpu(false) {}
};
GLTexture& m_texture;
std::vector<Level> m_levels;
bool m_is_compressing;
bool m_abort_compressing;
public:
explicit Compressor(GLTexture& texture) : m_texture(texture), m_is_compressing(false), m_abort_compressing(false) {}
void reset();
void add_level(unsigned int w, unsigned int h, const std::vector<unsigned char>& data);
void start_compressing();
bool unsent_compressed_data_available() const;
void send_compressed_data_to_gpu();
bool all_compressed_data_sent_to_gpu() const;
private:
void compress();
};
public:
struct UV
{
@ -33,13 +69,14 @@ namespace GUI {
int m_width;
int m_height;
std::string m_source;
Compressor m_compressor;
public:
GLTexture();
virtual ~GLTexture();
bool load_from_file(const std::string& filename, bool use_mipmaps);
bool load_from_svg_file(const std::string& filename, bool use_mipmaps, unsigned int max_size_px);
bool load_from_file(const std::string& filename, bool use_mipmaps, bool compress);
bool load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px);
// meanings of states: (std::pair<int, bool>)
// first field (int):
// 0 -> no changes
@ -48,7 +85,7 @@ namespace GUI {
// second field (bool):
// false -> no changes
// true -> add background color
bool load_from_svg_files_as_sprites_array(const std::vector<std::string>& filenames, const std::vector<std::pair<int, bool>>& states, unsigned int sprite_size_px);
bool load_from_svg_files_as_sprites_array(const std::vector<std::string>& filenames, const std::vector<std::pair<int, bool>>& states, unsigned int sprite_size_px, bool compress);
void reset();
unsigned int get_id() const { return m_id; }
@ -57,14 +94,21 @@ namespace GUI {
const std::string& get_source() const { return m_source; }
bool unsent_compressed_data_available() const { return m_compressor.unsent_compressed_data_available(); }
void send_compressed_data_to_gpu() { m_compressor.send_compressed_data_to_gpu(); }
bool all_compressed_data_sent_to_gpu() const { return m_compressor.all_compressed_data_sent_to_gpu(); }
static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top);
static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs);
protected:
unsigned int generate_mipmaps(wxImage& image);
unsigned int generate_mipmaps(wxImage& image, bool compress);
private:
bool load_from_png(const std::string& filename, bool use_mipmaps);
bool load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px);
bool load_from_png(const std::string& filename, bool use_mipmaps, bool compress);
bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px);
friend class Compressor;
};
} // namespace GUI

View file

@ -194,7 +194,7 @@ bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const Bac
#endif // ENABLE_SVG_ICONS
if (!background_texture.filename.empty())
res = m_background_texture.texture.load_from_file(path + background_texture.filename, false);
res = m_background_texture.texture.load_from_file(path + background_texture.filename, false, true);
if (res)
m_background_texture.metadata = background_texture;
@ -390,19 +390,12 @@ void GLToolbar::render(const GLCanvas3D& parent) const
generate_icons_texture();
#endif // ENABLE_SVG_ICONS
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
glsafe(::glLoadIdentity());
switch (m_layout.type)
{
default:
case Layout::Horizontal: { render_horizontal(parent); break; }
case Layout::Vertical: { render_vertical(parent); break; }
}
glsafe(::glPopMatrix());
}
bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
@ -614,7 +607,7 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC
{
// NB: mouse_pos is already scaled appropriately
float zoom = parent.get_camera().zoom;
float zoom = (float)parent.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
#if ENABLE_SVG_ICONS
float factor = m_layout.scale * inv_zoom;
@ -719,7 +712,7 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan
{
// NB: mouse_pos is already scaled appropriately
float zoom = parent.get_camera().zoom;
float zoom = (float)parent.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
#if ENABLE_SVG_ICONS
float factor = m_layout.scale * inv_zoom;
@ -836,7 +829,7 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3
{
// NB: mouse_pos is already scaled appropriately
float zoom = parent.get_camera().zoom;
float zoom = (float)parent.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
#if ENABLE_SVG_ICONS
float factor = m_layout.scale * inv_zoom;
@ -919,7 +912,7 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D&
{
// NB: mouse_pos is already scaled appropriately
float zoom = parent.get_camera().zoom;
float zoom = (float)parent.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
#if ENABLE_SVG_ICONS
float factor = m_layout.scale * inv_zoom;
@ -1015,7 +1008,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const
return;
#endif // !ENABLE_SVG_ICONS
float zoom = parent.get_camera().zoom;
float zoom = (float)parent.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
#if ENABLE_SVG_ICONS
float factor = inv_zoom * m_layout.scale;
@ -1170,7 +1163,7 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const
return;
#endif // !ENABLE_SVG_ICONS
float zoom = parent.get_camera().zoom;
float zoom = (float)parent.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
#if ENABLE_SVG_ICONS
float factor = inv_zoom * m_layout.scale;
@ -1338,7 +1331,7 @@ bool GLToolbar::generate_icons_texture() const
states.push_back(std::make_pair(1, true));
}
bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_layout.icons_size * m_layout.scale));
bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_layout.icons_size * m_layout.scale), true);
if (res)
m_icons_texture_dirty = false;

View file

@ -265,10 +265,8 @@ bool GUI_App::on_init_inner()
}
CallAfter([this] {
if (!config_wizard_startup(app_conf_exists)) {
// Only notify if there was no wizard so as not to bother too much ...
preset_updater->slic3r_update_notify();
}
config_wizard_startup(app_conf_exists);
preset_updater->slic3r_update_notify();
preset_updater->sync(preset_bundle);
});
}

View file

@ -1581,66 +1581,6 @@ void ObjectList::load_part( ModelObject* model_object,
}
// Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity
// as possible in least squares norm in regard to the 8 corners of bbox.
// Bounding box is expected to be centered around zero in all axes.
Geometry::Transformation volume_to_bed_transformation(const Geometry::Transformation &instance_transformation, const BoundingBoxf3 &bbox)
{
Geometry::Transformation out;
if (instance_transformation.is_scaling_uniform()) {
// No need to run the non-linear least squares fitting for uniform scaling.
// Just set the inverse.
out.set_from_transform(instance_transformation.get_matrix(true).inverse());
}
else if (Geometry::is_rotation_ninety_degrees(instance_transformation.get_rotation()))
{
// Anisotropic scaling, rotation by multiples of ninety degrees.
Eigen::Matrix3d instance_rotation_trafo =
(Eigen::AngleAxisd(instance_transformation.get_rotation().z(), Vec3d::UnitZ()) *
Eigen::AngleAxisd(instance_transformation.get_rotation().y(), Vec3d::UnitY()) *
Eigen::AngleAxisd(instance_transformation.get_rotation().x(), Vec3d::UnitX())).toRotationMatrix();
Eigen::Matrix3d volume_rotation_trafo =
(Eigen::AngleAxisd(-instance_transformation.get_rotation().x(), Vec3d::UnitX()) *
Eigen::AngleAxisd(-instance_transformation.get_rotation().y(), Vec3d::UnitY()) *
Eigen::AngleAxisd(-instance_transformation.get_rotation().z(), Vec3d::UnitZ())).toRotationMatrix();
// 8 corners of the bounding box.
auto pts = Eigen::MatrixXd(8, 3);
pts(0, 0) = bbox.min.x(); pts(0, 1) = bbox.min.y(); pts(0, 2) = bbox.min.z();
pts(1, 0) = bbox.min.x(); pts(1, 1) = bbox.min.y(); pts(1, 2) = bbox.max.z();
pts(2, 0) = bbox.min.x(); pts(2, 1) = bbox.max.y(); pts(2, 2) = bbox.min.z();
pts(3, 0) = bbox.min.x(); pts(3, 1) = bbox.max.y(); pts(3, 2) = bbox.max.z();
pts(4, 0) = bbox.max.x(); pts(4, 1) = bbox.min.y(); pts(4, 2) = bbox.min.z();
pts(5, 0) = bbox.max.x(); pts(5, 1) = bbox.min.y(); pts(5, 2) = bbox.max.z();
pts(6, 0) = bbox.max.x(); pts(6, 1) = bbox.max.y(); pts(6, 2) = bbox.min.z();
pts(7, 0) = bbox.max.x(); pts(7, 1) = bbox.max.y(); pts(7, 2) = bbox.max.z();
// Corners of the bounding box transformed into the modifier mesh coordinate space, with inverse rotation applied to the modifier.
auto qs = pts *
(instance_rotation_trafo *
Eigen::Scaling(instance_transformation.get_scaling_factor().cwiseProduct(instance_transformation.get_mirror())) *
volume_rotation_trafo).inverse().transpose();
// Fill in scaling based on least squares fitting of the bounding box corners.
Vec3d scale;
for (int i = 0; i < 3; ++ i)
scale(i) = pts.col(i).dot(qs.col(i)) / pts.col(i).dot(pts.col(i));
out.set_rotation(Geometry::extract_euler_angles(volume_rotation_trafo));
out.set_scaling_factor(Vec3d(std::abs(scale(0)), std::abs(scale(1)), std::abs(scale(2))));
out.set_mirror(Vec3d(scale(0) > 0 ? 1. : -1, scale(1) > 0 ? 1. : -1, scale(2) > 0 ? 1. : -1));
}
else
{
// General anisotropic scaling, general rotation.
// Keep the modifier mesh in the instance coordinate system, so the modifier mesh will not be aligned with the world.
// Scale it to get the required size.
out.set_scaling_factor(instance_transformation.get_scaling_factor().cwiseInverse());
}
return out;
}
void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type)
{
const auto obj_idx = get_selected_obj_idx();
@ -1694,7 +1634,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
// Transform the new modifier to be aligned with the print bed.
const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
new_volume->set_transformation(volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb));
new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb));
// Set the modifier position.
auto offset = (type_name == "Slab") ?
// Slab: Lift to print bed

View file

@ -65,7 +65,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent)
if (!m_background_texture.metadata.filename.empty())
{
if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false))
if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false, true))
{
reset();
return false;
@ -531,18 +531,9 @@ void GLGizmosManager::render_overlay(const GLCanvas3D& canvas, const Selection&
generate_icons_texture();
#endif // ENABLE_SVG_ICONS
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
glsafe(::glLoadIdentity());
do_render_overlay(canvas, selection);
glsafe(::glPopMatrix());
}
bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas)
{
bool processed = false;
@ -939,7 +930,7 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio
float cnv_w = (float)canvas.get_canvas_size().get_width();
float cnv_h = (float)canvas.get_canvas_size().get_height();
float zoom = canvas.get_camera().zoom;
float zoom = (float)canvas.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
float height = get_total_overlay_height();
@ -1160,7 +1151,7 @@ bool GLGizmosManager::generate_icons_texture() const
states.push_back(std::make_pair(0, false));
states.push_back(std::make_pair(0, true));
bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_overlay_icons_size * m_overlay_scale));
bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_overlay_icons_size * m_overlay_scale), true);
if (res)
m_icons_texture_dirty = false;

View file

@ -206,7 +206,7 @@ void ImGuiWrapper::new_frame()
}
if (m_font_texture == 0) {
init_font();
init_font(true);
}
ImGui::NewFrame();
@ -383,7 +383,7 @@ bool ImGuiWrapper::want_any_input() const
return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput;
}
void ImGuiWrapper::init_font()
void ImGuiWrapper::init_font(bool compress)
{
destroy_font();
@ -412,7 +412,10 @@ void ImGuiWrapper::init_font()
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
if (compress && GLEW_EXT_texture_compression_s3tc)
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
// Store our identifier
io.Fonts->TexID = (ImTextureID)(intptr_t)m_font_texture;

View file

@ -77,7 +77,7 @@ public:
bool want_any_input() const;
private:
void init_font();
void init_font(bool compress);
void init_input();
void init_style();
void render_draw_data(ImDrawData *draw_data);

View file

@ -147,6 +147,7 @@ void KBShortcutsDialog::fill_shortcuts()
plater_shortcuts.push_back(Shortcut("F", L("Press to scale selection to fit print volume\nin Gizmo scale")));
plater_shortcuts.push_back(Shortcut(alt, L("Press to activate deselection rectangle\nor to scale or rotate selected objects\naround their own center")));
plater_shortcuts.push_back(Shortcut(ctrl, L("Press to activate one direction scaling in Gizmo scale")));
plater_shortcuts.push_back(Shortcut("K", L("Change camera type")));
plater_shortcuts.push_back(Shortcut("B", L("Zoom to Bed")));
plater_shortcuts.push_back(Shortcut("Z", L("Zoom to all objects in scene, if none selected")));
plater_shortcuts.push_back(Shortcut("Z", L("Zoom to selected object")));

View file

@ -505,10 +505,10 @@ void MainFrame::init_menubar()
editMenu->AppendSeparator();
append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C",
_(L("Copy selection to clipboard")), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); },
menu_icon("copy_menu"), nullptr, [this](){return m_plater->can_copy(); }, this);
menu_icon("copy_menu"), nullptr, [this](){return m_plater->can_copy_to_clipboard(); }, this);
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(); },
menu_icon("paste_menu"), nullptr, [this](){return m_plater->can_paste(); }, this);
menu_icon("paste_menu"), nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this);
}
// Window menu

View file

@ -1785,6 +1785,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
q->Layout();
set_current_panel(view3D);
// updates camera type from .ini file
camera.set_type(get_config("camera_type"));
}
void Plater::priv::update(bool force_full_scene_refresh)
@ -4204,30 +4207,14 @@ void Plater::update_object_menu() { p->update_object_menu(); }
void Plater::copy_selection_to_clipboard()
{
p->view3D->get_canvas3d()->get_selection().copy_to_clipboard();
if (can_copy_to_clipboard())
p->view3D->get_canvas3d()->get_selection().copy_to_clipboard();
}
void Plater::paste_from_clipboard()
{
p->view3D->get_canvas3d()->get_selection().paste_from_clipboard();
}
bool Plater::can_paste_from_clipboard() const
{
const Selection& selection = p->view3D->get_canvas3d()->get_selection();
const Selection::Clipboard& clipboard = selection.get_clipboard();
Selection::EMode mode = clipboard.get_mode();
if (clipboard.is_empty())
return false;
if ((mode == Selection::Volume) && !selection.is_from_single_instance())
return false;
if ((mode == Selection::Instance) && (selection.get_mode() != Selection::Instance))
return false;
return true;
if (can_paste_from_clipboard())
p->view3D->get_canvas3d()->get_selection().paste_from_clipboard();
}
void Plater::msw_rescale()
@ -4254,7 +4241,37 @@ bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); }
bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); }
bool Plater::can_arrange() const { return p->can_arrange(); }
bool Plater::can_layers_editing() const { return p->can_layers_editing(); }
bool Plater::can_copy() const { return !is_selection_empty(); }
bool Plater::can_paste() const { return can_paste_from_clipboard(); }
bool Plater::can_paste_from_clipboard() const
{
const Selection& selection = p->view3D->get_canvas3d()->get_selection();
const Selection::Clipboard& clipboard = selection.get_clipboard();
if (clipboard.is_empty())
return false;
if ((wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) && !clipboard.is_sla_compliant())
return false;
Selection::EMode mode = clipboard.get_mode();
if ((mode == Selection::Volume) && !selection.is_from_single_instance())
return false;
if ((mode == Selection::Instance) && (selection.get_mode() != Selection::Instance))
return false;
return true;
}
bool Plater::can_copy_to_clipboard() const
{
if (is_selection_empty())
return false;
const Selection& selection = p->view3D->get_canvas3d()->get_selection();
if ((wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) && !selection.is_sla_compliant())
return false;
return true;
}
}} // namespace Slic3r::GUI

View file

@ -202,7 +202,6 @@ public:
void copy_selection_to_clipboard();
void paste_from_clipboard();
bool can_paste_from_clipboard() const;
bool can_delete() const;
bool can_delete_all() const;
@ -214,8 +213,8 @@ public:
bool can_split_to_volumes() const;
bool can_arrange() const;
bool can_layers_editing() const;
bool can_copy() const;
bool can_paste() const;
bool can_paste_from_clipboard() const;
bool can_copy_to_clipboard() const;
void msw_rescale();

View file

@ -2,6 +2,7 @@
#define PROGRESSSTATUSBAR_HPP
#include <memory>
#include <string>
#include <functional>
#include <string>

View file

@ -47,6 +47,26 @@ Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_trans
{
}
bool Selection::Clipboard::is_sla_compliant() const
{
if (m_mode == Selection::Volume)
return false;
for (const ModelObject* o : m_model.objects)
{
if (o->is_multiparts())
return false;
for (const ModelVolume* v : o->volumes)
{
if (v->is_modifier())
return false;
}
}
return true;
}
Selection::Selection()
: m_volumes(nullptr)
, m_model(nullptr)
@ -385,6 +405,20 @@ bool Selection::is_from_single_object() const
return (0 <= idx) && (idx < 1000);
}
bool Selection::is_sla_compliant() const
{
if (m_mode == Volume)
return false;
for (unsigned int i : m_list)
{
if ((*m_volumes)[i]->is_modifier)
return false;
}
return true;
}
bool Selection::requires_uniform_scale() const
{
if (is_single_full_instance() || is_single_modifier() || is_single_volume())
@ -1893,25 +1927,59 @@ bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const
void Selection::paste_volumes_from_clipboard()
{
int obj_idx = get_object_idx();
if ((obj_idx < 0) || ((int)m_model->objects.size() <= obj_idx))
int dst_obj_idx = get_object_idx();
if ((dst_obj_idx < 0) || ((int)m_model->objects.size() <= dst_obj_idx))
return;
ModelObject* dst_object = m_model->objects[dst_obj_idx];
int dst_inst_idx = get_instance_idx();
if ((dst_inst_idx < 0) || ((int)dst_object->instances.size() <= dst_inst_idx))
return;
ModelObject* src_object = m_clipboard.get_object(0);
if (src_object != nullptr)
{
ModelObject* dst_object = m_model->objects[obj_idx];
ModelInstance* dst_instance = dst_object->instances[dst_inst_idx];
BoundingBoxf3 dst_instance_bb = dst_object->instance_bounding_box(dst_inst_idx);
Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix(true);
Transform3d dst_matrix = dst_instance->get_transformation().get_matrix(true);
bool from_same_object = (src_object->input_file == dst_object->input_file) && src_matrix.isApprox(dst_matrix);
// used to keep relative position of multivolume selections when pasting from another object
BoundingBoxf3 total_bb;
ModelVolumePtrs volumes;
for (ModelVolume* src_volume : src_object->volumes)
{
ModelVolume* dst_volume = dst_object->add_volume(*src_volume);
dst_volume->set_new_unique_id();
double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05);
dst_volume->translate(offset, offset, 0.0);
if (from_same_object)
{
// // if the volume comes from the same object, apply the offset in world system
// double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05);
// dst_volume->translate(dst_matrix.inverse() * Vec3d(offset, offset, 0.0));
}
else
{
// if the volume comes from another object, apply the offset as done when adding modifiers
// see ObjectList::load_generic_subobject()
total_bb.merge(dst_volume->mesh().bounding_box().transformed(src_volume->get_matrix()));
}
volumes.push_back(dst_volume);
}
wxGetApp().obj_list()->paste_volumes_into_list(obj_idx, volumes);
// keeps relative position of multivolume selections
if (!from_same_object)
{
for (ModelVolume* v : volumes)
{
v->set_offset((v->get_offset() - total_bb.center()) + dst_matrix.inverse() * (Vec3d(dst_instance_bb.max(0), dst_instance_bb.min(1), dst_instance_bb.min(2)) + 0.5 * total_bb.size() - dst_instance->get_transformation().get_offset()));
}
}
wxGetApp().obj_list()->paste_volumes_into_list(dst_obj_idx, volumes);
}
}

View file

@ -152,6 +152,8 @@ public:
void reset() { m_model.clear_objects(); }
bool is_empty() const { return m_model.objects.empty(); }
bool is_sla_compliant() const;
ModelObject* add_object() { return m_model.add_object(); }
ModelObject* get_object(unsigned int id) { return (id < (unsigned int)m_model.objects.size()) ? m_model.objects[id] : nullptr; }
const ModelObjectPtrs& get_objects() const { return m_model.objects; }
@ -257,6 +259,7 @@ public:
bool is_mixed() const { return m_type == Mixed; }
bool is_from_single_instance() const { return get_instance_idx() != -1; }
bool is_from_single_object() const;
bool is_sla_compliant() const;
bool contains_volume(unsigned int volume_idx) const { return m_list.find(volume_idx) != m_list.end(); }
bool requires_uniform_scale() const;

View file

@ -1781,6 +1781,9 @@ DoubleSlider::DoubleSlider( wxWindow *parent,
m_bmp_one_layer_unlock_off = ScalableBitmap(this, "one_layer_unlock_off.png");
m_lock_icon_dim = m_bmp_one_layer_lock_on.bmp().GetSize().x;
m_bmp_revert = ScalableBitmap(this, "undo");
m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x;
m_selection = ssUndef;
// slider events
@ -1836,6 +1839,9 @@ void DoubleSlider::msw_rescale()
m_bmp_one_layer_unlock_off.msw_rescale();
m_lock_icon_dim = m_bmp_one_layer_lock_on.bmp().GetSize().x;
m_bmp_revert.msw_rescale();
m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x;
SLIDER_MARGIN = 4 + Slic3r::GUI::wxGetApp().em_unit();
SetMinSize(get_min_size());
@ -2072,8 +2078,11 @@ void DoubleSlider::render()
//draw color print ticks
draw_ticks(dc);
//draw color print ticks
//draw lock/unlock
draw_one_layer_icon(dc);
//draw revert bitmap (if it's shown)
draw_revert_icon(dc);
}
void DoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end)
@ -2300,6 +2309,24 @@ void DoubleSlider::draw_one_layer_icon(wxDC& dc)
m_rect_one_layer_icon = wxRect(x_draw, y_draw, m_lock_icon_dim, m_lock_icon_dim);
}
void DoubleSlider::draw_revert_icon(wxDC& dc)
{
if (m_ticks.empty())
return;
int width, height;
get_size(&width, &height);
wxCoord x_draw, y_draw;
is_horizontal() ? x_draw = width-2 : x_draw = 0.25*SLIDER_MARGIN;
is_horizontal() ? y_draw = 0.25*SLIDER_MARGIN: y_draw = height-2;
dc.DrawBitmap(m_bmp_revert.bmp(), x_draw, y_draw);
//update rect of the lock/unlock icon
m_rect_revert_icon = wxRect(x_draw, y_draw, m_revert_icon_dim, m_revert_icon_dim);
}
void DoubleSlider::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection)
{
const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, m_thumb_size.y);
@ -2316,8 +2343,8 @@ int DoubleSlider::get_value_from_position(const wxCoord x, const wxCoord y)
if (is_horizontal())
return int(double(x - SLIDER_MARGIN) / step + 0.5);
else
return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5);
return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5);
}
void DoubleSlider::detect_selected_slider(const wxPoint& pt)
@ -2367,7 +2394,10 @@ void DoubleSlider::ChangeOneLayerLock()
void DoubleSlider::OnLeftDown(wxMouseEvent& event)
{
if (HasCapture())
return;
this->CaptureMouse();
wxClientDC dc(this);
wxPoint pos = event.GetLogicalPosition(dc);
if (is_point_in_rect(pos, m_rect_tick_action) && m_is_enabled_tick_manipulation) {
@ -2377,6 +2407,7 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event)
m_is_left_down = true;
if (is_point_in_rect(pos, m_rect_one_layer_icon)) {
// switch on/off one layer mode
m_is_one_layer = !m_is_one_layer;
if (!m_is_one_layer) {
SetLowerValue(m_min_value);
@ -2385,20 +2416,36 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event)
m_selection == ssLower ? correct_lower_value() : correct_higher_value();
if (!m_selection) m_selection = ssHigher;
}
else if (is_point_in_rect(pos, m_rect_revert_icon)) {
// discard all color changes
SetLowerValue(m_min_value);
SetHigherValue(m_max_value);
m_selection == ssLower ? correct_lower_value() : correct_higher_value();
if (!m_selection) m_selection = ssHigher;
m_ticks.clear();
wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED));
}
else
detect_selected_slider(pos);
if (!m_selection && m_is_enabled_tick_manipulation) {
const auto tick = is_point_near_tick(pos);
if (tick >= 0)
if (!m_selection) {
const int tick_val = is_point_near_tick(pos);
/* Set current thumb position to the nearest tick (if it is)
* OR to a value corresponding to the mouse click
* */
const int mouse_val = tick_val >= 0 && m_is_enabled_tick_manipulation ? tick_val :
get_value_from_position(pos.x, pos.y);
if (mouse_val >= 0)
{
if (abs(tick - m_lower_value) < abs(tick - m_higher_value)) {
SetLowerValue(tick);
if (abs(mouse_val - m_lower_value) < abs(mouse_val - m_higher_value)) {
SetLowerValue(mouse_val);
correct_lower_value();
m_selection = ssLower;
}
else {
SetHigherValue(tick);
SetHigherValue(mouse_val);
correct_higher_value();
m_selection = ssHigher;
}
@ -2438,9 +2485,13 @@ void DoubleSlider::OnMotion(wxMouseEvent& event)
const wxClientDC dc(this);
const wxPoint pos = event.GetLogicalPosition(dc);
m_is_one_layer_icon_focesed = is_point_in_rect(pos, m_rect_one_layer_icon);
bool is_revert_icon_focused = false;
if (!m_is_left_down && !m_is_one_layer) {
m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action);
is_revert_icon_focused = !m_ticks.empty() && is_point_in_rect(pos, m_rect_revert_icon);
}
else if (m_is_left_down || m_is_right_down) {
if (m_selection == ssLower) {
@ -2460,6 +2511,12 @@ void DoubleSlider::OnMotion(wxMouseEvent& event)
Update();
event.Skip();
// Set tooltips with information for each icon
const wxString tooltip = m_is_one_layer_icon_focesed ? _(L("One layer mode")) :
m_is_action_icon_focesed ? _(L("Add/Del color change")) :
is_revert_icon_focused ? _(L("Discard all color changes")) : "";
this->SetToolTip(tooltip);
if (action)
{
wxCommandEvent e(wxEVT_SCROLL_CHANGED);
@ -2610,7 +2667,9 @@ void DoubleSlider::OnChar(wxKeyEvent& event)
void DoubleSlider::OnRightDown(wxMouseEvent& event)
{
if (HasCapture()) return;
this->CaptureMouse();
const wxClientDC dc(this);
detect_selected_slider(event.GetLogicalPosition(dc));
if (!m_selection)

View file

@ -766,6 +766,7 @@ protected:
void draw_ticks(wxDC& dc);
void draw_colored_band(wxDC& dc);
void draw_one_layer_icon(wxDC& dc);
void draw_revert_icon(wxDC& dc);
void draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection);
void draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection);
void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const;
@ -807,6 +808,7 @@ private:
ScalableBitmap m_bmp_one_layer_lock_off;
ScalableBitmap m_bmp_one_layer_unlock_on;
ScalableBitmap m_bmp_one_layer_unlock_off;
ScalableBitmap m_bmp_revert;
SelectedSlider m_selection;
bool m_is_left_down = false;
bool m_is_right_down = false;
@ -820,9 +822,11 @@ private:
wxRect m_rect_higher_thumb;
wxRect m_rect_tick_action;
wxRect m_rect_one_layer_icon;
wxRect m_rect_revert_icon;
wxSize m_thumb_size;
int m_tick_icon_dim;
int m_lock_icon_dim;
int m_revert_icon_dim;
long m_style;
float m_label_koef = 1.0;

View file

@ -4,6 +4,7 @@
#include <thread>
#include <unordered_map>
#include <ostream>
#include <utility>
#include <stdexcept>
#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>
@ -45,10 +46,25 @@ static const char *INDEX_FILENAME = "index.idx";
static const char *TMP_EXTENSION = ".download";
void copy_file_fix(const fs::path &source, const fs::path &target)
{
static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; // aka 644
BOOST_LOG_TRIVIAL(debug) << boost::format("PresetUpdater: Copying %1% -> %2%") % source % target;
// Make sure the file has correct permission both before and after we copy over it
if (fs::exists(target)) {
fs::permissions(target, perms);
}
fs::copy_file(source, target, fs::copy_option::overwrite_if_exists);
fs::permissions(target, perms);
}
struct Update
{
fs::path source;
fs::path target;
Version version;
std::string vendor;
std::string changelog_url;
@ -61,7 +77,13 @@ struct Update
, changelog_url(std::move(changelog_url))
{}
friend std::ostream& operator<<(std::ostream& os , const Update &self) {
void install() const
{
copy_file_fix(source, target);
}
friend std::ostream& operator<<(std::ostream& os, const Update &self)
{
os << "Update(" << self.source.string() << " -> " << self.target.string() << ')';
return os;
}
@ -115,7 +137,6 @@ struct PresetUpdater::priv
bool enabled_version_check;
bool enabled_config_update;
std::string version_check_url;
bool had_config_update;
fs::path cache_path;
fs::path rsrc_path;
@ -135,13 +156,10 @@ struct PresetUpdater::priv
void check_install_indices() const;
Updates get_config_updates() const;
void perform_updates(Updates &&updates, bool snapshot = true) const;
static void copy_file(const fs::path &from, const fs::path &to);
};
PresetUpdater::priv::priv()
: ver_slic3r(get_slic3r_version())
, had_config_update(false)
, cache_path(fs::path(Slic3r::data_dir()) / "cache")
, rsrc_path(fs::path(resources_dir()) / "profiles")
, vendor_path(fs::path(Slic3r::data_dir()) / "vendor")
@ -273,7 +291,7 @@ void PresetUpdater::priv::sync_config(const std::set<VendorProfile> vendors)
try {
new_index.load(idx_path_temp);
} catch (const std::exception & /* err */) {
BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading a downloaded index %1% for vendor %2%: invalid index?") % idx_path_temp % vendor.name;
BOOST_LOG_TRIVIAL(error) << boost::format("Could not load downloaded index %1% for vendor %2%: invalid index?") % idx_path_temp % vendor.name;
continue;
}
if (new_index.version() < index.version()) {
@ -323,7 +341,7 @@ void PresetUpdater::priv::check_install_indices() const
if (! fs::exists(path_in_cache)) {
BOOST_LOG_TRIVIAL(info) << "Install index from resources: " << path.filename();
copy_file(path, path_in_cache);
copy_file_fix(path, path_in_cache);
} else {
Index idx_rsrc, idx_cache;
idx_rsrc.load(path);
@ -331,7 +349,7 @@ void PresetUpdater::priv::check_install_indices() const
if (idx_cache.version() < idx_rsrc.version()) {
BOOST_LOG_TRIVIAL(info) << "Update index from resources: " << path.filename();
copy_file(path, path_in_cache);
copy_file_fix(path, path_in_cache);
}
}
}
@ -346,6 +364,7 @@ Updates PresetUpdater::priv::get_config_updates() const
for (const auto idx : index_db) {
auto bundle_path = vendor_path / (idx.vendor() + ".ini");
auto bundle_path_idx = vendor_path / idx.path().filename();
if (! fs::exists(bundle_path)) {
BOOST_LOG_TRIVIAL(info) << "Bundle not present for index, skipping: " << idx.vendor();
@ -360,8 +379,31 @@ Updates PresetUpdater::priv::get_config_updates() const
const auto recommended = idx.recommended();
if (recommended == idx.end()) {
BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % idx.vendor();
// XXX: what should be done here?
continue;
}
// Load 'installed' idx, if any.
// 'Installed' indices are kept alongside the bundle in the `vendor` subdir
// for bookkeeping to remember a cancelled update and not offer it again.
if (fs::exists(bundle_path_idx)) {
Index existing_idx;
try {
existing_idx.load(bundle_path_idx);
const auto existing_recommended = existing_idx.recommended();
if (existing_recommended != existing_idx.end() && recommended->config_version == existing_recommended->config_version) {
// The user has already seen (and presumably rejected) this update
BOOST_LOG_TRIVIAL(info) << boost::format("Downloaded index for `%1%` is the same as installed one, not offering an update.") % idx.vendor();
continue;
}
} catch (const std::exception & /* err */) {
BOOST_LOG_TRIVIAL(error) << boost::format("Could nto load installed index %1%") % bundle_path_idx;
}
}
copy_file_fix(idx.path(), bundle_path_idx);
const auto ver_current = idx.find(vp.config_version);
const bool ver_current_found = ver_current != idx.end();
if (! ver_current_found) {
@ -453,10 +495,10 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
for (const auto &update : updates.updates) {
BOOST_LOG_TRIVIAL(info) << '\t' << update;
copy_file(update.source, update.target);
update.install();
PresetBundle bundle;
bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM);
bundle.load_configbundle(update.source.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM);
BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% conflicting presets")
% (bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
@ -491,19 +533,6 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
}
}
void PresetUpdater::priv::copy_file(const fs::path &source, const fs::path &target)
{
static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; // aka 644
// Make sure the file has correct permission both before and after we copy over it
if (fs::exists(target)) {
fs::permissions(target, perms);
}
fs::copy_file(source, target, fs::copy_option::overwrite_if_exists);
fs::permissions(target, perms);
}
PresetUpdater::PresetUpdater() :
p(new priv())
{}
@ -542,11 +571,6 @@ void PresetUpdater::slic3r_update_notify()
{
if (! p->enabled_version_check) { return; }
if (p->had_config_update) {
BOOST_LOG_TRIVIAL(info) << "New Slic3r version available, but there was a configuration update, notification won't be displayed";
return;
}
auto* app_config = GUI::wxGetApp().app_config;
const auto ver_online_str = app_config->get("version_online");
const auto ver_online = Semver::parse(ver_online_str);
@ -594,8 +618,6 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const
incompats_map.emplace(std::make_pair(incompat.vendor, std::move(restrictions)));
}
p->had_config_update = true; // This needs to be done before a dialog is shown because of OnIdle() + CallAfter() in Perl
GUI::MsgDataIncompatible dlg(std::move(incompats_map));
const auto res = dlg.ShowModal();
if (res == wxID_REPLACE) {
@ -620,8 +642,6 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const
updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
}
p->had_config_update = true; // Ditto, see above
GUI::MsgUpdateConfig dlg(updates_msg);
const auto res = dlg.ShowModal();
@ -631,7 +651,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const
// Reload global configuration
auto *app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets();
return R_UPDATE_INSTALLED;
} else {

View file

@ -137,6 +137,11 @@ public:
Semver operator-(const Minor &b) const { Semver res(*this); return res -= b; }
Semver operator-(const Patch &b) const { Semver res(*this); return res -= b; }
// Stream output
friend std::ostream& operator<<(std::ostream& os, const Semver &self) {
os << self.to_string();
return os;
}
private:
semver_t ver;

View file

@ -17,6 +17,9 @@ struct SerialPortInfo {
std::string friendly_name;
bool is_printer = false;
SerialPortInfo() {}
SerialPortInfo(std::string port) : port(port), friendly_name(std::move(port)) {}
bool id_match(unsigned id_vendor, unsigned id_product) const { return id_vendor == this->id_vendor && id_product == this->id_product; }
};

1049
src/stb_dxt/stb_dxt.h Normal file

File diff suppressed because it is too large Load diff