This commit is contained in:
bubnikv 2018-12-17 18:04:37 +01:00
commit 9463351abd
32 changed files with 3358 additions and 342 deletions

View file

@ -0,0 +1,53 @@
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008-2009 Gael Guennebaud <g.gael@free.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef EIGEN_SPARSE_EXTRA_MODULE_H
#define EIGEN_SPARSE_EXTRA_MODULE_H
#include "../../Eigen/Sparse"
#include "../../Eigen/src/Core/util/DisableStupidWarnings.h"
#include <vector>
#include <map>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <fstream>
#include <sstream>
#ifdef EIGEN_GOOGLEHASH_SUPPORT
#include <google/dense_hash_map>
#endif
/**
* \defgroup SparseExtra_Module SparseExtra module
*
* This module contains some experimental features extending the sparse module.
*
* \code
* #include <Eigen/SparseExtra>
* \endcode
*/
#include "src/SparseExtra/DynamicSparseMatrix.h"
#include "src/SparseExtra/BlockOfDynamicSparseMatrix.h"
#include "src/SparseExtra/RandomSetter.h"
#include "src/SparseExtra/MarketIO.h"
#if !defined(_WIN32)
#include <dirent.h>
#include "src/SparseExtra/MatrixMarketIterator.h"
#endif
#include "../../Eigen/src/Core/util/ReenableStupidWarnings.h"
#endif // EIGEN_SPARSE_EXTRA_MODULE_H

View file

@ -0,0 +1,122 @@
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008-2009 Gael Guennebaud <gael.guennebaud@inria.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef EIGEN_SPARSE_BLOCKFORDYNAMICMATRIX_H
#define EIGEN_SPARSE_BLOCKFORDYNAMICMATRIX_H
namespace Eigen {
#if 0
// NOTE Have to be reimplemented as a specialization of BlockImpl< DynamicSparseMatrix<_Scalar, _Options, _Index>, ... >
// See SparseBlock.h for an example
/***************************************************************************
* specialisation for DynamicSparseMatrix
***************************************************************************/
template<typename _Scalar, int _Options, typename _Index, int Size>
class SparseInnerVectorSet<DynamicSparseMatrix<_Scalar, _Options, _Index>, Size>
: public SparseMatrixBase<SparseInnerVectorSet<DynamicSparseMatrix<_Scalar, _Options, _Index>, Size> >
{
typedef DynamicSparseMatrix<_Scalar, _Options, _Index> MatrixType;
public:
enum { IsRowMajor = internal::traits<SparseInnerVectorSet>::IsRowMajor };
EIGEN_SPARSE_PUBLIC_INTERFACE(SparseInnerVectorSet)
class InnerIterator: public MatrixType::InnerIterator
{
public:
inline InnerIterator(const SparseInnerVectorSet& xpr, Index outer)
: MatrixType::InnerIterator(xpr.m_matrix, xpr.m_outerStart + outer), m_outer(outer)
{}
inline Index row() const { return IsRowMajor ? m_outer : this->index(); }
inline Index col() const { return IsRowMajor ? this->index() : m_outer; }
protected:
Index m_outer;
};
inline SparseInnerVectorSet(const MatrixType& matrix, Index outerStart, Index outerSize)
: m_matrix(matrix), m_outerStart(outerStart), m_outerSize(outerSize)
{
eigen_assert( (outerStart>=0) && ((outerStart+outerSize)<=matrix.outerSize()) );
}
inline SparseInnerVectorSet(const MatrixType& matrix, Index outer)
: m_matrix(matrix), m_outerStart(outer), m_outerSize(Size)
{
eigen_assert(Size!=Dynamic);
eigen_assert( (outer>=0) && (outer<matrix.outerSize()) );
}
template<typename OtherDerived>
inline SparseInnerVectorSet& operator=(const SparseMatrixBase<OtherDerived>& other)
{
if (IsRowMajor != ((OtherDerived::Flags&RowMajorBit)==RowMajorBit))
{
// need to transpose => perform a block evaluation followed by a big swap
DynamicSparseMatrix<Scalar,IsRowMajor?RowMajorBit:0> aux(other);
*this = aux.markAsRValue();
}
else
{
// evaluate/copy vector per vector
for (Index j=0; j<m_outerSize.value(); ++j)
{
SparseVector<Scalar,IsRowMajor ? RowMajorBit : 0> aux(other.innerVector(j));
m_matrix.const_cast_derived()._data()[m_outerStart+j].swap(aux._data());
}
}
return *this;
}
inline SparseInnerVectorSet& operator=(const SparseInnerVectorSet& other)
{
return operator=<SparseInnerVectorSet>(other);
}
Index nonZeros() const
{
Index count = 0;
for (Index j=0; j<m_outerSize.value(); ++j)
count += m_matrix._data()[m_outerStart+j].size();
return count;
}
const Scalar& lastCoeff() const
{
EIGEN_STATIC_ASSERT_VECTOR_ONLY(SparseInnerVectorSet);
eigen_assert(m_matrix.data()[m_outerStart].size()>0);
return m_matrix.data()[m_outerStart].vale(m_matrix.data()[m_outerStart].size()-1);
}
// template<typename Sparse>
// inline SparseInnerVectorSet& operator=(const SparseMatrixBase<OtherDerived>& other)
// {
// return *this;
// }
EIGEN_STRONG_INLINE Index rows() const { return IsRowMajor ? m_outerSize.value() : m_matrix.rows(); }
EIGEN_STRONG_INLINE Index cols() const { return IsRowMajor ? m_matrix.cols() : m_outerSize.value(); }
protected:
const typename MatrixType::Nested m_matrix;
Index m_outerStart;
const internal::variable_if_dynamic<Index, Size> m_outerSize;
};
#endif
} // end namespace Eigen
#endif // EIGEN_SPARSE_BLOCKFORDYNAMICMATRIX_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,392 @@
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008-2009 Gael Guennebaud <gael.guennebaud@inria.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef EIGEN_DYNAMIC_SPARSEMATRIX_H
#define EIGEN_DYNAMIC_SPARSEMATRIX_H
namespace Eigen {
/** \deprecated use a SparseMatrix in an uncompressed mode
*
* \class DynamicSparseMatrix
*
* \brief A sparse matrix class designed for matrix assembly purpose
*
* \param _Scalar the scalar type, i.e. the type of the coefficients
*
* Unlike SparseMatrix, this class provides a much higher degree of flexibility. In particular, it allows
* random read/write accesses in log(rho*outer_size) where \c rho is the probability that a coefficient is
* nonzero and outer_size is the number of columns if the matrix is column-major and the number of rows
* otherwise.
*
* Internally, the data are stored as a std::vector of compressed vector. The performances of random writes might
* decrease as the number of nonzeros per inner-vector increase. In practice, we observed very good performance
* till about 100 nonzeros/vector, and the performance remains relatively good till 500 nonzeros/vectors.
*
* \see SparseMatrix
*/
namespace internal {
template<typename _Scalar, int _Options, typename _StorageIndex>
struct traits<DynamicSparseMatrix<_Scalar, _Options, _StorageIndex> >
{
typedef _Scalar Scalar;
typedef _StorageIndex StorageIndex;
typedef Sparse StorageKind;
typedef MatrixXpr XprKind;
enum {
RowsAtCompileTime = Dynamic,
ColsAtCompileTime = Dynamic,
MaxRowsAtCompileTime = Dynamic,
MaxColsAtCompileTime = Dynamic,
Flags = _Options | NestByRefBit | LvalueBit,
CoeffReadCost = NumTraits<Scalar>::ReadCost,
SupportedAccessPatterns = OuterRandomAccessPattern
};
};
}
template<typename _Scalar, int _Options, typename _StorageIndex>
class DynamicSparseMatrix
: public SparseMatrixBase<DynamicSparseMatrix<_Scalar, _Options, _StorageIndex> >
{
typedef SparseMatrixBase<DynamicSparseMatrix> Base;
using Base::convert_index;
public:
EIGEN_SPARSE_PUBLIC_INTERFACE(DynamicSparseMatrix)
// FIXME: why are these operator already alvailable ???
// EIGEN_SPARSE_INHERIT_ASSIGNMENT_OPERATOR(DynamicSparseMatrix, +=)
// EIGEN_SPARSE_INHERIT_ASSIGNMENT_OPERATOR(DynamicSparseMatrix, -=)
typedef MappedSparseMatrix<Scalar,Flags> Map;
using Base::IsRowMajor;
using Base::operator=;
enum {
Options = _Options
};
protected:
typedef DynamicSparseMatrix<Scalar,(Flags&~RowMajorBit)|(IsRowMajor?RowMajorBit:0), StorageIndex> TransposedSparseMatrix;
Index m_innerSize;
std::vector<internal::CompressedStorage<Scalar,StorageIndex> > m_data;
public:
inline Index rows() const { return IsRowMajor ? outerSize() : m_innerSize; }
inline Index cols() const { return IsRowMajor ? m_innerSize : outerSize(); }
inline Index innerSize() const { return m_innerSize; }
inline Index outerSize() const { return convert_index(m_data.size()); }
inline Index innerNonZeros(Index j) const { return m_data[j].size(); }
std::vector<internal::CompressedStorage<Scalar,StorageIndex> >& _data() { return m_data; }
const std::vector<internal::CompressedStorage<Scalar,StorageIndex> >& _data() const { return m_data; }
/** \returns the coefficient value at given position \a row, \a col
* This operation involes a log(rho*outer_size) binary search.
*/
inline Scalar coeff(Index row, Index col) const
{
const Index outer = IsRowMajor ? row : col;
const Index inner = IsRowMajor ? col : row;
return m_data[outer].at(inner);
}
/** \returns a reference to the coefficient value at given position \a row, \a col
* This operation involes a log(rho*outer_size) binary search. If the coefficient does not
* exist yet, then a sorted insertion into a sequential buffer is performed.
*/
inline Scalar& coeffRef(Index row, Index col)
{
const Index outer = IsRowMajor ? row : col;
const Index inner = IsRowMajor ? col : row;
return m_data[outer].atWithInsertion(inner);
}
class InnerIterator;
class ReverseInnerIterator;
void setZero()
{
for (Index j=0; j<outerSize(); ++j)
m_data[j].clear();
}
/** \returns the number of non zero coefficients */
Index nonZeros() const
{
Index res = 0;
for (Index j=0; j<outerSize(); ++j)
res += m_data[j].size();
return res;
}
void reserve(Index reserveSize = 1000)
{
if (outerSize()>0)
{
Index reserveSizePerVector = (std::max)(reserveSize/outerSize(),Index(4));
for (Index j=0; j<outerSize(); ++j)
{
m_data[j].reserve(reserveSizePerVector);
}
}
}
/** Does nothing: provided for compatibility with SparseMatrix */
inline void startVec(Index /*outer*/) {}
/** \returns a reference to the non zero coefficient at position \a row, \a col assuming that:
* - the nonzero does not already exist
* - the new coefficient is the last one of the given inner vector.
*
* \sa insert, insertBackByOuterInner */
inline Scalar& insertBack(Index row, Index col)
{
return insertBackByOuterInner(IsRowMajor?row:col, IsRowMajor?col:row);
}
/** \sa insertBack */
inline Scalar& insertBackByOuterInner(Index outer, Index inner)
{
eigen_assert(outer<Index(m_data.size()) && inner<m_innerSize && "out of range");
eigen_assert(((m_data[outer].size()==0) || (m_data[outer].index(m_data[outer].size()-1)<inner))
&& "wrong sorted insertion");
m_data[outer].append(0, inner);
return m_data[outer].value(m_data[outer].size()-1);
}
inline Scalar& insert(Index row, Index col)
{
const Index outer = IsRowMajor ? row : col;
const Index inner = IsRowMajor ? col : row;
Index startId = 0;
Index id = static_cast<Index>(m_data[outer].size()) - 1;
m_data[outer].resize(id+2,1);
while ( (id >= startId) && (m_data[outer].index(id) > inner) )
{
m_data[outer].index(id+1) = m_data[outer].index(id);
m_data[outer].value(id+1) = m_data[outer].value(id);
--id;
}
m_data[outer].index(id+1) = inner;
m_data[outer].value(id+1) = 0;
return m_data[outer].value(id+1);
}
/** Does nothing: provided for compatibility with SparseMatrix */
inline void finalize() {}
/** Suppress all nonzeros which are smaller than \a reference under the tolerence \a epsilon */
void prune(Scalar reference, RealScalar epsilon = NumTraits<RealScalar>::dummy_precision())
{
for (Index j=0; j<outerSize(); ++j)
m_data[j].prune(reference,epsilon);
}
/** Resize the matrix without preserving the data (the matrix is set to zero)
*/
void resize(Index rows, Index cols)
{
const Index outerSize = IsRowMajor ? rows : cols;
m_innerSize = convert_index(IsRowMajor ? cols : rows);
setZero();
if (Index(m_data.size()) != outerSize)
{
m_data.resize(outerSize);
}
}
void resizeAndKeepData(Index rows, Index cols)
{
const Index outerSize = IsRowMajor ? rows : cols;
const Index innerSize = IsRowMajor ? cols : rows;
if (m_innerSize>innerSize)
{
// remove all coefficients with innerCoord>=innerSize
// TODO
//std::cerr << "not implemented yet\n";
exit(2);
}
if (m_data.size() != outerSize)
{
m_data.resize(outerSize);
}
}
/** The class DynamicSparseMatrix is deprectaed */
EIGEN_DEPRECATED inline DynamicSparseMatrix()
: m_innerSize(0), m_data(0)
{
eigen_assert(innerSize()==0 && outerSize()==0);
}
/** The class DynamicSparseMatrix is deprectaed */
EIGEN_DEPRECATED inline DynamicSparseMatrix(Index rows, Index cols)
: m_innerSize(0)
{
resize(rows, cols);
}
/** The class DynamicSparseMatrix is deprectaed */
template<typename OtherDerived>
EIGEN_DEPRECATED explicit inline DynamicSparseMatrix(const SparseMatrixBase<OtherDerived>& other)
: m_innerSize(0)
{
Base::operator=(other.derived());
}
inline DynamicSparseMatrix(const DynamicSparseMatrix& other)
: Base(), m_innerSize(0)
{
*this = other.derived();
}
inline void swap(DynamicSparseMatrix& other)
{
//EIGEN_DBG_SPARSE(std::cout << "SparseMatrix:: swap\n");
std::swap(m_innerSize, other.m_innerSize);
//std::swap(m_outerSize, other.m_outerSize);
m_data.swap(other.m_data);
}
inline DynamicSparseMatrix& operator=(const DynamicSparseMatrix& other)
{
if (other.isRValue())
{
swap(other.const_cast_derived());
}
else
{
resize(other.rows(), other.cols());
m_data = other.m_data;
}
return *this;
}
/** Destructor */
inline ~DynamicSparseMatrix() {}
public:
/** \deprecated
* Set the matrix to zero and reserve the memory for \a reserveSize nonzero coefficients. */
EIGEN_DEPRECATED void startFill(Index reserveSize = 1000)
{
setZero();
reserve(reserveSize);
}
/** \deprecated use insert()
* inserts a nonzero coefficient at given coordinates \a row, \a col and returns its reference assuming that:
* 1 - the coefficient does not exist yet
* 2 - this the coefficient with greater inner coordinate for the given outer coordinate.
* In other words, assuming \c *this is column-major, then there must not exists any nonzero coefficient of coordinates
* \c i \c x \a col such that \c i >= \a row. Otherwise the matrix is invalid.
*
* \see fillrand(), coeffRef()
*/
EIGEN_DEPRECATED Scalar& fill(Index row, Index col)
{
const Index outer = IsRowMajor ? row : col;
const Index inner = IsRowMajor ? col : row;
return insertBack(outer,inner);
}
/** \deprecated use insert()
* Like fill() but with random inner coordinates.
* Compared to the generic coeffRef(), the unique limitation is that we assume
* the coefficient does not exist yet.
*/
EIGEN_DEPRECATED Scalar& fillrand(Index row, Index col)
{
return insert(row,col);
}
/** \deprecated use finalize()
* Does nothing. Provided for compatibility with SparseMatrix. */
EIGEN_DEPRECATED void endFill() {}
# ifdef EIGEN_DYNAMICSPARSEMATRIX_PLUGIN
# include EIGEN_DYNAMICSPARSEMATRIX_PLUGIN
# endif
};
template<typename Scalar, int _Options, typename _StorageIndex>
class DynamicSparseMatrix<Scalar,_Options,_StorageIndex>::InnerIterator : public SparseVector<Scalar,_Options,_StorageIndex>::InnerIterator
{
typedef typename SparseVector<Scalar,_Options,_StorageIndex>::InnerIterator Base;
public:
InnerIterator(const DynamicSparseMatrix& mat, Index outer)
: Base(mat.m_data[outer]), m_outer(outer)
{}
inline Index row() const { return IsRowMajor ? m_outer : Base::index(); }
inline Index col() const { return IsRowMajor ? Base::index() : m_outer; }
inline Index outer() const { return m_outer; }
protected:
const Index m_outer;
};
template<typename Scalar, int _Options, typename _StorageIndex>
class DynamicSparseMatrix<Scalar,_Options,_StorageIndex>::ReverseInnerIterator : public SparseVector<Scalar,_Options,_StorageIndex>::ReverseInnerIterator
{
typedef typename SparseVector<Scalar,_Options,_StorageIndex>::ReverseInnerIterator Base;
public:
ReverseInnerIterator(const DynamicSparseMatrix& mat, Index outer)
: Base(mat.m_data[outer]), m_outer(outer)
{}
inline Index row() const { return IsRowMajor ? m_outer : Base::index(); }
inline Index col() const { return IsRowMajor ? Base::index() : m_outer; }
inline Index outer() const { return m_outer; }
protected:
const Index m_outer;
};
namespace internal {
template<typename _Scalar, int _Options, typename _StorageIndex>
struct evaluator<DynamicSparseMatrix<_Scalar,_Options,_StorageIndex> >
: evaluator_base<DynamicSparseMatrix<_Scalar,_Options,_StorageIndex> >
{
typedef _Scalar Scalar;
typedef DynamicSparseMatrix<_Scalar,_Options,_StorageIndex> SparseMatrixType;
typedef typename SparseMatrixType::InnerIterator InnerIterator;
typedef typename SparseMatrixType::ReverseInnerIterator ReverseInnerIterator;
enum {
CoeffReadCost = NumTraits<_Scalar>::ReadCost,
Flags = SparseMatrixType::Flags
};
evaluator() : m_matrix(0) {}
evaluator(const SparseMatrixType &mat) : m_matrix(&mat) {}
operator SparseMatrixType&() { return m_matrix->const_cast_derived(); }
operator const SparseMatrixType&() const { return *m_matrix; }
Scalar coeff(Index row, Index col) const { return m_matrix->coeff(row,col); }
Index nonZerosEstimate() const { return m_matrix->nonZeros(); }
const SparseMatrixType *m_matrix;
};
}
} // end namespace Eigen
#endif // EIGEN_DYNAMIC_SPARSEMATRIX_H

View file

@ -0,0 +1,275 @@
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2011 Gael Guennebaud <gael.guennebaud@inria.fr>
// Copyright (C) 2012 Desire NUENTSA WAKAM <desire.nuentsa_wakam@inria.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef EIGEN_SPARSE_MARKET_IO_H
#define EIGEN_SPARSE_MARKET_IO_H
#include <iostream>
namespace Eigen {
namespace internal
{
template <typename Scalar>
inline bool GetMarketLine (std::stringstream& line, Index& M, Index& N, Index& i, Index& j, Scalar& value)
{
line >> i >> j >> value;
i--;
j--;
if(i>=0 && j>=0 && i<M && j<N)
{
return true;
}
else
return false;
}
template <typename Scalar>
inline bool GetMarketLine (std::stringstream& line, Index& M, Index& N, Index& i, Index& j, std::complex<Scalar>& value)
{
Scalar valR, valI;
line >> i >> j >> valR >> valI;
i--;
j--;
if(i>=0 && j>=0 && i<M && j<N)
{
value = std::complex<Scalar>(valR, valI);
return true;
}
else
return false;
}
template <typename RealScalar>
inline void GetVectorElt (const std::string& line, RealScalar& val)
{
std::istringstream newline(line);
newline >> val;
}
template <typename RealScalar>
inline void GetVectorElt (const std::string& line, std::complex<RealScalar>& val)
{
RealScalar valR, valI;
std::istringstream newline(line);
newline >> valR >> valI;
val = std::complex<RealScalar>(valR, valI);
}
template<typename Scalar>
inline void putMarketHeader(std::string& header,int sym)
{
header= "%%MatrixMarket matrix coordinate ";
if(internal::is_same<Scalar, std::complex<float> >::value || internal::is_same<Scalar, std::complex<double> >::value)
{
header += " complex";
if(sym == Symmetric) header += " symmetric";
else if (sym == SelfAdjoint) header += " Hermitian";
else header += " general";
}
else
{
header += " real";
if(sym == Symmetric) header += " symmetric";
else header += " general";
}
}
template<typename Scalar>
inline void PutMatrixElt(Scalar value, int row, int col, std::ofstream& out)
{
out << row << " "<< col << " " << value << "\n";
}
template<typename Scalar>
inline void PutMatrixElt(std::complex<Scalar> value, int row, int col, std::ofstream& out)
{
out << row << " " << col << " " << value.real() << " " << value.imag() << "\n";
}
template<typename Scalar>
inline void putVectorElt(Scalar value, std::ofstream& out)
{
out << value << "\n";
}
template<typename Scalar>
inline void putVectorElt(std::complex<Scalar> value, std::ofstream& out)
{
out << value.real << " " << value.imag()<< "\n";
}
} // end namepsace internal
inline bool getMarketHeader(const std::string& filename, int& sym, bool& iscomplex, bool& isvector)
{
sym = 0;
iscomplex = false;
isvector = false;
std::ifstream in(filename.c_str(),std::ios::in);
if(!in)
return false;
std::string line;
// The matrix header is always the first line in the file
std::getline(in, line); eigen_assert(in.good());
std::stringstream fmtline(line);
std::string substr[5];
fmtline>> substr[0] >> substr[1] >> substr[2] >> substr[3] >> substr[4];
if(substr[2].compare("array") == 0) isvector = true;
if(substr[3].compare("complex") == 0) iscomplex = true;
if(substr[4].compare("symmetric") == 0) sym = Symmetric;
else if (substr[4].compare("Hermitian") == 0) sym = SelfAdjoint;
return true;
}
template<typename SparseMatrixType>
bool loadMarket(SparseMatrixType& mat, const std::string& filename)
{
typedef typename SparseMatrixType::Scalar Scalar;
typedef typename SparseMatrixType::Index Index;
std::ifstream input(filename.c_str(),std::ios::in);
if(!input)
return false;
const int maxBuffersize = 2048;
char buffer[maxBuffersize];
bool readsizes = false;
typedef Triplet<Scalar,Index> T;
std::vector<T> elements;
Index M(-1), N(-1), NNZ(-1);
Index count = 0;
while(input.getline(buffer, maxBuffersize))
{
// skip comments
//NOTE An appropriate test should be done on the header to get the symmetry
if(buffer[0]=='%')
continue;
std::stringstream line(buffer);
if(!readsizes)
{
line >> M >> N >> NNZ;
if(M > 0 && N > 0 && NNZ > 0)
{
readsizes = true;
//std::cout << "sizes: " << M << "," << N << "," << NNZ << "\n";
mat.resize(M,N);
mat.reserve(NNZ);
}
}
else
{
Index i(-1), j(-1);
Scalar value;
if( internal::GetMarketLine(line, M, N, i, j, value) )
{
++ count;
elements.push_back(T(i,j,value));
}
else
std::cerr << "Invalid read: " << i << "," << j << "\n";
}
}
mat.setFromTriplets(elements.begin(), elements.end());
if(count!=NNZ)
std::cerr << count << "!=" << NNZ << "\n";
input.close();
return true;
}
template<typename VectorType>
bool loadMarketVector(VectorType& vec, const std::string& filename)
{
typedef typename VectorType::Scalar Scalar;
std::ifstream in(filename.c_str(), std::ios::in);
if(!in)
return false;
std::string line;
int n(0), col(0);
do
{ // Skip comments
std::getline(in, line); eigen_assert(in.good());
} while (line[0] == '%');
std::istringstream newline(line);
newline >> n >> col;
eigen_assert(n>0 && col>0);
vec.resize(n);
int i = 0;
Scalar value;
while ( std::getline(in, line) && (i < n) ){
internal::GetVectorElt(line, value);
vec(i++) = value;
}
in.close();
if (i!=n){
std::cerr<< "Unable to read all elements from file " << filename << "\n";
return false;
}
return true;
}
template<typename SparseMatrixType>
bool saveMarket(const SparseMatrixType& mat, const std::string& filename, int sym = 0)
{
typedef typename SparseMatrixType::Scalar Scalar;
std::ofstream out(filename.c_str(),std::ios::out);
if(!out)
return false;
out.flags(std::ios_base::scientific);
out.precision(64);
std::string header;
internal::putMarketHeader<Scalar>(header, sym);
out << header << std::endl;
out << mat.rows() << " " << mat.cols() << " " << mat.nonZeros() << "\n";
int count = 0;
for(int j=0; j<mat.outerSize(); ++j)
for(typename SparseMatrixType::InnerIterator it(mat,j); it; ++it)
{
++ count;
internal::PutMatrixElt(it.value(), it.row()+1, it.col()+1, out);
// out << it.row()+1 << " " << it.col()+1 << " " << it.value() << "\n";
}
out.close();
return true;
}
template<typename VectorType>
bool saveMarketVector (const VectorType& vec, const std::string& filename)
{
typedef typename VectorType::Scalar Scalar;
std::ofstream out(filename.c_str(),std::ios::out);
if(!out)
return false;
out.flags(std::ios_base::scientific);
out.precision(64);
if(internal::is_same<Scalar, std::complex<float> >::value || internal::is_same<Scalar, std::complex<double> >::value)
out << "%%MatrixMarket matrix array complex general\n";
else
out << "%%MatrixMarket matrix array real general\n";
out << vec.size() << " "<< 1 << "\n";
for (int i=0; i < vec.size(); i++){
internal::putVectorElt(vec(i), out);
}
out.close();
return true;
}
} // end namespace Eigen
#endif // EIGEN_SPARSE_MARKET_IO_H

View file

@ -0,0 +1,247 @@
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2012 Desire NUENTSA WAKAM <desire.nuentsa_wakam@inria.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef EIGEN_BROWSE_MATRICES_H
#define EIGEN_BROWSE_MATRICES_H
namespace Eigen {
enum {
SPD = 0x100,
NonSymmetric = 0x0
};
/**
* @brief Iterator to browse matrices from a specified folder
*
* This is used to load all the matrices from a folder.
* The matrices should be in Matrix Market format
* It is assumed that the matrices are named as matname.mtx
* and matname_SPD.mtx if the matrix is Symmetric and positive definite (or Hermitian)
* The right hand side vectors are loaded as well, if they exist.
* They should be named as matname_b.mtx.
* Note that the right hand side for a SPD matrix is named as matname_SPD_b.mtx
*
* Sometimes a reference solution is available. In this case, it should be named as matname_x.mtx
*
* Sample code
* \code
*
* \endcode
*
* \tparam Scalar The scalar type
*/
template <typename Scalar>
class MatrixMarketIterator
{
typedef typename NumTraits<Scalar>::Real RealScalar;
public:
typedef Matrix<Scalar,Dynamic,1> VectorType;
typedef SparseMatrix<Scalar,ColMajor> MatrixType;
public:
MatrixMarketIterator(const std::string &folder)
: m_sym(0), m_isvalid(false), m_matIsLoaded(false), m_hasRhs(false), m_hasrefX(false), m_folder(folder)
{
m_folder_id = opendir(folder.c_str());
if(m_folder_id)
Getnextvalidmatrix();
}
~MatrixMarketIterator()
{
if (m_folder_id) closedir(m_folder_id);
}
inline MatrixMarketIterator& operator++()
{
m_matIsLoaded = false;
m_hasrefX = false;
m_hasRhs = false;
Getnextvalidmatrix();
return *this;
}
inline operator bool() const { return m_isvalid;}
/** Return the sparse matrix corresponding to the current file */
inline MatrixType& matrix()
{
// Read the matrix
if (m_matIsLoaded) return m_mat;
std::string matrix_file = m_folder + "/" + m_matname + ".mtx";
if ( !loadMarket(m_mat, matrix_file))
{
std::cerr << "Warning loadMarket failed when loading \"" << matrix_file << "\"" << std::endl;
m_matIsLoaded = false;
return m_mat;
}
m_matIsLoaded = true;
if (m_sym != NonSymmetric)
{
// Check whether we need to restore a full matrix:
RealScalar diag_norm = m_mat.diagonal().norm();
RealScalar lower_norm = m_mat.template triangularView<Lower>().norm();
RealScalar upper_norm = m_mat.template triangularView<Upper>().norm();
if(lower_norm>diag_norm && upper_norm==diag_norm)
{
// only the lower part is stored
MatrixType tmp(m_mat);
m_mat = tmp.template selfadjointView<Lower>();
}
else if(upper_norm>diag_norm && lower_norm==diag_norm)
{
// only the upper part is stored
MatrixType tmp(m_mat);
m_mat = tmp.template selfadjointView<Upper>();
}
}
return m_mat;
}
/** Return the right hand side corresponding to the current matrix.
* If the rhs file is not provided, a random rhs is generated
*/
inline VectorType& rhs()
{
// Get the right hand side
if (m_hasRhs) return m_rhs;
std::string rhs_file;
rhs_file = m_folder + "/" + m_matname + "_b.mtx"; // The pattern is matname_b.mtx
m_hasRhs = Fileexists(rhs_file);
if (m_hasRhs)
{
m_rhs.resize(m_mat.cols());
m_hasRhs = loadMarketVector(m_rhs, rhs_file);
}
if (!m_hasRhs)
{
// Generate a random right hand side
if (!m_matIsLoaded) this->matrix();
m_refX.resize(m_mat.cols());
m_refX.setRandom();
m_rhs = m_mat * m_refX;
m_hasrefX = true;
m_hasRhs = true;
}
return m_rhs;
}
/** Return a reference solution
* If it is not provided and if the right hand side is not available
* then refX is randomly generated such that A*refX = b
* where A and b are the matrix and the rhs.
* Note that when a rhs is provided, refX is not available
*/
inline VectorType& refX()
{
// Check if a reference solution is provided
if (m_hasrefX) return m_refX;
std::string lhs_file;
lhs_file = m_folder + "/" + m_matname + "_x.mtx";
m_hasrefX = Fileexists(lhs_file);
if (m_hasrefX)
{
m_refX.resize(m_mat.cols());
m_hasrefX = loadMarketVector(m_refX, lhs_file);
}
else
m_refX.resize(0);
return m_refX;
}
inline std::string& matname() { return m_matname; }
inline int sym() { return m_sym; }
bool hasRhs() {return m_hasRhs; }
bool hasrefX() {return m_hasrefX; }
bool isFolderValid() { return bool(m_folder_id); }
protected:
inline bool Fileexists(std::string file)
{
std::ifstream file_id(file.c_str());
if (!file_id.good() )
{
return false;
}
else
{
file_id.close();
return true;
}
}
void Getnextvalidmatrix( )
{
m_isvalid = false;
// Here, we return with the next valid matrix in the folder
while ( (m_curs_id = readdir(m_folder_id)) != NULL) {
m_isvalid = false;
std::string curfile;
curfile = m_folder + "/" + m_curs_id->d_name;
// Discard if it is a folder
if (m_curs_id->d_type == DT_DIR) continue; //FIXME This may not be available on non BSD systems
// struct stat st_buf;
// stat (curfile.c_str(), &st_buf);
// if (S_ISDIR(st_buf.st_mode)) continue;
// Determine from the header if it is a matrix or a right hand side
bool isvector,iscomplex=false;
if(!getMarketHeader(curfile,m_sym,iscomplex,isvector)) continue;
if(isvector) continue;
if (!iscomplex)
{
if(internal::is_same<Scalar, std::complex<float> >::value || internal::is_same<Scalar, std::complex<double> >::value)
continue;
}
if (iscomplex)
{
if(internal::is_same<Scalar, float>::value || internal::is_same<Scalar, double>::value)
continue;
}
// Get the matrix name
std::string filename = m_curs_id->d_name;
m_matname = filename.substr(0, filename.length()-4);
// Find if the matrix is SPD
size_t found = m_matname.find("SPD");
if( (found!=std::string::npos) && (m_sym != NonSymmetric) )
m_sym = SPD;
m_isvalid = true;
break;
}
}
int m_sym; // Symmetry of the matrix
MatrixType m_mat; // Current matrix
VectorType m_rhs; // Current vector
VectorType m_refX; // The reference solution, if exists
std::string m_matname; // Matrix Name
bool m_isvalid;
bool m_matIsLoaded; // Determine if the matrix has already been loaded from the file
bool m_hasRhs; // The right hand side exists
bool m_hasrefX; // A reference solution is provided
std::string m_folder;
DIR * m_folder_id;
struct dirent *m_curs_id;
};
} // end namespace Eigen
#endif

View file

@ -0,0 +1,327 @@
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008 Gael Guennebaud <gael.guennebaud@inria.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef EIGEN_RANDOMSETTER_H
#define EIGEN_RANDOMSETTER_H
namespace Eigen {
/** Represents a std::map
*
* \see RandomSetter
*/
template<typename Scalar> struct StdMapTraits
{
typedef int KeyType;
typedef std::map<KeyType,Scalar> Type;
enum {
IsSorted = 1
};
static void setInvalidKey(Type&, const KeyType&) {}
};
#ifdef EIGEN_UNORDERED_MAP_SUPPORT
/** Represents a std::unordered_map
*
* To use it you need to both define EIGEN_UNORDERED_MAP_SUPPORT and include the unordered_map header file
* yourself making sure that unordered_map is defined in the std namespace.
*
* For instance, with current version of gcc you can either enable C++0x standard (-std=c++0x) or do:
* \code
* #include <tr1/unordered_map>
* #define EIGEN_UNORDERED_MAP_SUPPORT
* namespace std {
* using std::tr1::unordered_map;
* }
* \endcode
*
* \see RandomSetter
*/
template<typename Scalar> struct StdUnorderedMapTraits
{
typedef int KeyType;
typedef std::unordered_map<KeyType,Scalar> Type;
enum {
IsSorted = 0
};
static void setInvalidKey(Type&, const KeyType&) {}
};
#endif // EIGEN_UNORDERED_MAP_SUPPORT
#ifdef _DENSE_HASH_MAP_H_
/** Represents a google::dense_hash_map
*
* \see RandomSetter
*/
template<typename Scalar> struct GoogleDenseHashMapTraits
{
typedef int KeyType;
typedef google::dense_hash_map<KeyType,Scalar> Type;
enum {
IsSorted = 0
};
static void setInvalidKey(Type& map, const KeyType& k)
{ map.set_empty_key(k); }
};
#endif
#ifdef _SPARSE_HASH_MAP_H_
/** Represents a google::sparse_hash_map
*
* \see RandomSetter
*/
template<typename Scalar> struct GoogleSparseHashMapTraits
{
typedef int KeyType;
typedef google::sparse_hash_map<KeyType,Scalar> Type;
enum {
IsSorted = 0
};
static void setInvalidKey(Type&, const KeyType&) {}
};
#endif
/** \class RandomSetter
*
* \brief The RandomSetter is a wrapper object allowing to set/update a sparse matrix with random access
*
* \tparam SparseMatrixType the type of the sparse matrix we are updating
* \tparam MapTraits a traits class representing the map implementation used for the temporary sparse storage.
* Its default value depends on the system.
* \tparam OuterPacketBits defines the number of rows (or columns) manage by a single map object
* as a power of two exponent.
*
* This class temporarily represents a sparse matrix object using a generic map implementation allowing for
* efficient random access. The conversion from the compressed representation to a hash_map object is performed
* in the RandomSetter constructor, while the sparse matrix is updated back at destruction time. This strategy
* suggest the use of nested blocks as in this example:
*
* \code
* SparseMatrix<double> m(rows,cols);
* {
* RandomSetter<SparseMatrix<double> > w(m);
* // don't use m but w instead with read/write random access to the coefficients:
* for(;;)
* w(rand(),rand()) = rand;
* }
* // when w is deleted, the data are copied back to m
* // and m is ready to use.
* \endcode
*
* Since hash_map objects are not fully sorted, representing a full matrix as a single hash_map would
* involve a big and costly sort to update the compressed matrix back. To overcome this issue, a RandomSetter
* use multiple hash_map, each representing 2^OuterPacketBits columns or rows according to the storage order.
* To reach optimal performance, this value should be adjusted according to the average number of nonzeros
* per rows/columns.
*
* The possible values for the template parameter MapTraits are:
* - \b StdMapTraits: corresponds to std::map. (does not perform very well)
* - \b GnuHashMapTraits: corresponds to __gnu_cxx::hash_map (available only with GCC)
* - \b GoogleDenseHashMapTraits: corresponds to google::dense_hash_map (best efficiency, reasonable memory consumption)
* - \b GoogleSparseHashMapTraits: corresponds to google::sparse_hash_map (best memory consumption, relatively good performance)
*
* The default map implementation depends on the availability, and the preferred order is:
* GoogleSparseHashMapTraits, GnuHashMapTraits, and finally StdMapTraits.
*
* For performance and memory consumption reasons it is highly recommended to use one of
* the Google's hash_map implementation. To enable the support for them, you have two options:
* - \#include <google/dense_hash_map> yourself \b before Eigen/Sparse header
* - define EIGEN_GOOGLEHASH_SUPPORT
* In the later case the inclusion of <google/dense_hash_map> is made for you.
*
* \see http://code.google.com/p/google-sparsehash/
*/
template<typename SparseMatrixType,
template <typename T> class MapTraits =
#if defined _DENSE_HASH_MAP_H_
GoogleDenseHashMapTraits
#elif defined _HASH_MAP
GnuHashMapTraits
#else
StdMapTraits
#endif
,int OuterPacketBits = 6>
class RandomSetter
{
typedef typename SparseMatrixType::Scalar Scalar;
typedef typename SparseMatrixType::StorageIndex StorageIndex;
struct ScalarWrapper
{
ScalarWrapper() : value(0) {}
Scalar value;
};
typedef typename MapTraits<ScalarWrapper>::KeyType KeyType;
typedef typename MapTraits<ScalarWrapper>::Type HashMapType;
static const int OuterPacketMask = (1 << OuterPacketBits) - 1;
enum {
SwapStorage = 1 - MapTraits<ScalarWrapper>::IsSorted,
TargetRowMajor = (SparseMatrixType::Flags & RowMajorBit) ? 1 : 0,
SetterRowMajor = SwapStorage ? 1-TargetRowMajor : TargetRowMajor
};
public:
/** Constructs a random setter object from the sparse matrix \a target
*
* Note that the initial value of \a target are imported. If you want to re-set
* a sparse matrix from scratch, then you must set it to zero first using the
* setZero() function.
*/
inline RandomSetter(SparseMatrixType& target)
: mp_target(&target)
{
const Index outerSize = SwapStorage ? target.innerSize() : target.outerSize();
const Index innerSize = SwapStorage ? target.outerSize() : target.innerSize();
m_outerPackets = outerSize >> OuterPacketBits;
if (outerSize&OuterPacketMask)
m_outerPackets += 1;
m_hashmaps = new HashMapType[m_outerPackets];
// compute number of bits needed to store inner indices
Index aux = innerSize - 1;
m_keyBitsOffset = 0;
while (aux)
{
++m_keyBitsOffset;
aux = aux >> 1;
}
KeyType ik = (1<<(OuterPacketBits+m_keyBitsOffset));
for (Index k=0; k<m_outerPackets; ++k)
MapTraits<ScalarWrapper>::setInvalidKey(m_hashmaps[k],ik);
// insert current coeffs
for (Index j=0; j<mp_target->outerSize(); ++j)
for (typename SparseMatrixType::InnerIterator it(*mp_target,j); it; ++it)
(*this)(TargetRowMajor?j:it.index(), TargetRowMajor?it.index():j) = it.value();
}
/** Destructor updating back the sparse matrix target */
~RandomSetter()
{
KeyType keyBitsMask = (1<<m_keyBitsOffset)-1;
if (!SwapStorage) // also means the map is sorted
{
mp_target->setZero();
mp_target->makeCompressed();
mp_target->reserve(nonZeros());
Index prevOuter = -1;
for (Index k=0; k<m_outerPackets; ++k)
{
const Index outerOffset = (1<<OuterPacketBits) * k;
typename HashMapType::iterator end = m_hashmaps[k].end();
for (typename HashMapType::iterator it = m_hashmaps[k].begin(); it!=end; ++it)
{
const Index outer = (it->first >> m_keyBitsOffset) + outerOffset;
const Index inner = it->first & keyBitsMask;
if (prevOuter!=outer)
{
for (Index j=prevOuter+1;j<=outer;++j)
mp_target->startVec(j);
prevOuter = outer;
}
mp_target->insertBackByOuterInner(outer, inner) = it->second.value;
}
}
mp_target->finalize();
}
else
{
VectorXi positions(mp_target->outerSize());
positions.setZero();
// pass 1
for (Index k=0; k<m_outerPackets; ++k)
{
typename HashMapType::iterator end = m_hashmaps[k].end();
for (typename HashMapType::iterator it = m_hashmaps[k].begin(); it!=end; ++it)
{
const Index outer = it->first & keyBitsMask;
++positions[outer];
}
}
// prefix sum
Index count = 0;
for (Index j=0; j<mp_target->outerSize(); ++j)
{
Index tmp = positions[j];
mp_target->outerIndexPtr()[j] = count;
positions[j] = count;
count += tmp;
}
mp_target->makeCompressed();
mp_target->outerIndexPtr()[mp_target->outerSize()] = count;
mp_target->resizeNonZeros(count);
// pass 2
for (Index k=0; k<m_outerPackets; ++k)
{
const Index outerOffset = (1<<OuterPacketBits) * k;
typename HashMapType::iterator end = m_hashmaps[k].end();
for (typename HashMapType::iterator it = m_hashmaps[k].begin(); it!=end; ++it)
{
const Index inner = (it->first >> m_keyBitsOffset) + outerOffset;
const Index outer = it->first & keyBitsMask;
// sorted insertion
// Note that we have to deal with at most 2^OuterPacketBits unsorted coefficients,
// moreover those 2^OuterPacketBits coeffs are likely to be sparse, an so only a
// small fraction of them have to be sorted, whence the following simple procedure:
Index posStart = mp_target->outerIndexPtr()[outer];
Index i = (positions[outer]++) - 1;
while ( (i >= posStart) && (mp_target->innerIndexPtr()[i] > inner) )
{
mp_target->valuePtr()[i+1] = mp_target->valuePtr()[i];
mp_target->innerIndexPtr()[i+1] = mp_target->innerIndexPtr()[i];
--i;
}
mp_target->innerIndexPtr()[i+1] = inner;
mp_target->valuePtr()[i+1] = it->second.value;
}
}
}
delete[] m_hashmaps;
}
/** \returns a reference to the coefficient at given coordinates \a row, \a col */
Scalar& operator() (Index row, Index col)
{
const Index outer = SetterRowMajor ? row : col;
const Index inner = SetterRowMajor ? col : row;
const Index outerMajor = outer >> OuterPacketBits; // index of the packet/map
const Index outerMinor = outer & OuterPacketMask; // index of the inner vector in the packet
const KeyType key = internal::convert_index<KeyType>((outerMinor<<m_keyBitsOffset) | inner);
return m_hashmaps[outerMajor][key].value;
}
/** \returns the number of non zero coefficients
*
* \note According to the underlying map/hash_map implementation,
* this function might be quite expensive.
*/
Index nonZeros() const
{
Index nz = 0;
for (Index k=0; k<m_outerPackets; ++k)
nz += static_cast<Index>(m_hashmaps[k].size());
return nz;
}
protected:
HashMapType* m_hashmaps;
SparseMatrixType* mp_target;
Index m_outerPackets;
unsigned char m_keyBitsOffset;
};
} // end namespace Eigen
#endif // EIGEN_RANDOMSETTER_H

View file

@ -1,6 +1,7 @@
#ifndef slic3r_Channel_hpp_
#define slic3r_Channel_hpp_
#include <memory>
#include <deque>
#include <condition_variable>
#include <mutex>
@ -13,32 +14,26 @@ namespace Slic3r {
template<class T> class Channel
{
private:
using UniqueLock = std::unique_lock<std::mutex>;
using Queue = std::deque<T>;
public:
class Guard
using UniqueLock = std::unique_lock<std::mutex>;
template<class Ptr> class Unlocker
{
public:
Guard(UniqueLock lock, const Queue &queue) : m_lock(std::move(lock)), m_queue(queue) {}
Guard(const Guard &other) = delete;
Guard(Guard &&other) = delete;
~Guard() {}
Unlocker(UniqueLock lock) : m_lock(std::move(lock)) {}
Unlocker(const Unlocker &other) noexcept : m_lock(std::move(other.m_lock)) {} // XXX: done beacuse of MSVC 2013 not supporting init of deleter by move
Unlocker(Unlocker &&other) noexcept : m_lock(std::move(other.m_lock)) {}
Unlocker& operator=(const Unlocker &other) = delete;
Unlocker& operator=(Unlocker &&other) { m_lock = std::move(other.m_lock); }
// Access trampolines
size_t size() const noexcept { return m_queue.size(); }
bool empty() const noexcept { return m_queue.empty(); }
typename Queue::const_iterator begin() const noexcept { return m_queue.begin(); }
typename Queue::const_iterator end() const noexcept { return m_queue.end(); }
typename Queue::const_reference operator[](size_t i) const { return m_queue[i]; }
Guard& operator=(const Guard &other) = delete;
Guard& operator=(Guard &&other) = delete;
void operator()(Ptr*) { m_lock.unlock(); }
private:
UniqueLock m_lock;
const Queue &m_queue;
mutable UniqueLock m_lock; // XXX: mutable: see above
};
using Queue = std::deque<T>;
using LockedConstPtr = std::unique_ptr<const Queue, Unlocker<const Queue>>;
using LockedPtr = std::unique_ptr<Queue, Unlocker<Queue>>;
Channel() {}
~Channel() {}
@ -56,7 +51,7 @@ public:
{
{
UniqueLock lock(m_mutex);
m_queue.push_back(std::forward(item));
m_queue.push_back(std::forward<T>(item));
}
if (! silent) { m_condition.notify_one(); }
}
@ -82,19 +77,22 @@ public:
}
}
// Unlocked observers
// Thread unsafe! Keep in mind you need to re-verify the result after acquiring lock!
size_t size() const noexcept { return m_queue.size(); }
bool empty() const noexcept { return m_queue.empty(); }
// Unlocked observers/hints
// Thread unsafe! Keep in mind you need to re-verify the result after locking!
size_t size_hint() const noexcept { return m_queue.size(); }
Guard read() const
LockedConstPtr lock_read() const
{
return Guard(UniqueLock(m_mutex), m_queue);
return LockedConstPtr(&m_queue, Unlocker<const Queue>(UniqueLock(m_mutex)));
}
LockedPtr lock_rw()
{
return LockedPtr(&m_queue, Unlocker<Queue>(UniqueLock(m_mutex)));
}
private:
Queue m_queue;
std::mutex m_mutex;
mutable std::mutex m_mutex;
std::condition_variable m_condition;
};

View file

@ -96,7 +96,6 @@ static void extract_model_from_archive(
const char *model_xml = strstr(scene_xml_data.data(), model_name_tag);
const char *zero_tag = "<zero>";
const char *zero_xml = strstr(scene_xml_data.data(), zero_tag);
float trafo[3][4] = { 0 };
Vec3d instance_rotation = Vec3d::Zero();
Vec3d instance_scaling_factor = Vec3d::Ones();
Vec3d instance_offset = Vec3d::Zero();
@ -124,19 +123,7 @@ static void extract_model_from_archive(
"[%f, %f, %f]", zero, zero+1, zero+2) == 3) {
instance_scaling_factor = Vec3d((double)scale[0], (double)scale[1], (double)scale[2]);
instance_rotation = Vec3d(-(double)rotation[0], -(double)rotation[1], -(double)rotation[2]);
Eigen::Matrix3f mat_rot, mat_scale, mat_trafo;
mat_rot = Eigen::AngleAxisf(-rotation[2], Eigen::Vector3f::UnitZ()) *
Eigen::AngleAxisf(-rotation[1], Eigen::Vector3f::UnitY()) *
Eigen::AngleAxisf(-rotation[0], Eigen::Vector3f::UnitX());
mat_scale = Eigen::Scaling(scale[0], scale[1], scale[2]);
mat_trafo = mat_rot * mat_scale;
for (size_t r = 0; r < 3; ++ r) {
for (size_t c = 0; c < 3; ++ c)
trafo[r][c] += mat_trafo(r, c);
}
instance_offset = Vec3d((double)(position[0] - zero[0]), (double)(position[1] - zero[1]), (double)(position[2] - zero[2]));
// CHECK_ME -> Is the following correct ?
trafo[2][3] = position[2] / (float)instance_scaling_factor(2);
trafo_set = true;
}
const char *group_tag = "<group>";
@ -189,8 +176,6 @@ static void extract_model_from_archive(
// All the faces have been read.
stl_get_size(&stl);
mesh.repair();
// Transform the model.
stl_transform(&stl, &trafo[0][0]);
if (std::abs(stl.stats.min(2)) < EPSILON)
stl.stats.min(2) = 0.;
// Add a mesh to a model.
@ -274,8 +259,6 @@ static void extract_model_from_archive(
memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50);
stl_get_size(&stl);
mesh.repair();
// Transform the model.
stl_transform(&stl, &trafo[0][0]);
// Add a mesh to a model.
if (mesh.facets_count() > 0)
mesh_valid = true;

View file

@ -612,7 +612,9 @@ double ray_mesh_intersect(const Vec3d& s,
const Vec3d& dir,
const EigenMesh3D& m);
PointSet normals(const PointSet& points, const EigenMesh3D& mesh);
PointSet normals(const PointSet& points, const EigenMesh3D& mesh,
double eps = 0.05, // min distance from edges
std::function<void()> throw_on_cancel = [](){});
inline Vec2d to_vec2(const Vec3d& v3) {
return {v3(X), v3(Y)};
@ -1049,7 +1051,7 @@ bool SLASupportTree::generate(const PointSet &points,
tifcl();
// calculate the normals to the triangles belonging to filtered points
auto nmls = sla::normals(filt_pts, mesh);
auto nmls = sla::normals(filt_pts, mesh, cfg.head_front_radius_mm, tifcl);
head_norm.resize(count, 3);
head_pos.resize(count, 3);

View file

@ -1,3 +1,4 @@
#include <cmath>
#include "SLA/SLASupportTree.hpp"
#include "SLA/SLABoilerPlate.hpp"
#include "SLA/SLASpatIndex.hpp"
@ -9,15 +10,8 @@
#include "boost/geometry/index/rtree.hpp"
#include <igl/ray_mesh_intersect.h>
//#if !defined(_MSC_VER) || defined(_WIN64)
#if 1
#define IGL_COMPATIBLE
#endif
#ifdef IGL_COMPATIBLE
#include <igl/point_mesh_squared_distance.h>
#endif
#include <igl/remove_duplicate_vertices.h>
#include "SLASpatIndex.hpp"
#include "ClipperUtils.hpp"
@ -84,33 +78,124 @@ size_t SpatIndex::size() const
return m_impl->m_store.size();
}
PointSet normals(const PointSet& points, const EigenMesh3D& mesh) {
if(points.rows() == 0 || mesh.V.rows() == 0 || mesh.F.rows() == 0) return {};
#ifdef IGL_COMPATIBLE
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
double eps = 0.05)
{
using Line3D = Eigen::ParametrizedLine<double, 3>;
auto line = Line3D::Through(e1, e2);
double d = line.distance(p);
return std::abs(d) < eps;
}
template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
auto p = pp2 - pp1;
return std::sqrt(p.transpose() * p);
}
PointSet normals(const PointSet& points, const EigenMesh3D& emesh,
double eps,
std::function<void()> throw_on_cancel) {
if(points.rows() == 0 || emesh.V.rows() == 0 || emesh.F.rows() == 0)
return {};
Eigen::VectorXd dists;
Eigen::VectorXi I;
PointSet C;
// We need to remove duplicate vertices and have a true index triangle
// structure
EigenMesh3D mesh;
Eigen::VectorXi SVI, SVJ;
igl::remove_duplicate_vertices(emesh.V, emesh.F, 1e-6,
mesh.V, SVI, SVJ, mesh.F);
igl::point_mesh_squared_distance( points, mesh.V, mesh.F, dists, I, C);
PointSet ret(I.rows(), 3);
for(int i = 0; i < I.rows(); i++) {
throw_on_cancel();
auto idx = I(i);
auto trindex = mesh.F.row(idx);
auto& p1 = mesh.V.row(trindex(0));
auto& p2 = mesh.V.row(trindex(1));
auto& p3 = mesh.V.row(trindex(2));
const Vec3d& p1 = mesh.V.row(trindex(0));
const Vec3d& p2 = mesh.V.row(trindex(1));
const Vec3d& p3 = mesh.V.row(trindex(2));
Eigen::Vector3d U = p2 - p1;
Eigen::Vector3d V = p3 - p1;
ret.row(i) = U.cross(V).normalized();
// We should check if the point lies on an edge of the hosting triangle.
// If it does than all the other triangles using the same two points
// have to be searched and the final normal should be some kind of
// aggregation of the participating triangle normals. We should also
// consider the cases where the support point lies right on a vertex
// of its triangle. The procedure is the same, get the neighbor
// triangles and calculate an average normal.
const Vec3d& p = C.row(i);
// mark the vertex indices of the edge. ia and ib marks and edge ic
// will mark a single vertex.
int ia = -1, ib = -1, ic = -1;
if(std::abs(distance(p, p1)) < eps) {
ic = trindex(0);
}
else if(std::abs(distance(p, p2)) < eps) {
ic = trindex(1);
}
else if(std::abs(distance(p, p3)) < eps) {
ic = trindex(2);
}
else if(point_on_edge(p, p1, p2, eps)) {
ia = trindex(0); ib = trindex(1);
}
else if(point_on_edge(p, p2, p3, eps)) {
ia = trindex(1); ib = trindex(2);
}
else if(point_on_edge(p, p1, p3, eps)) {
ia = trindex(0); ib = trindex(2);
}
std::vector<Vec3i> neigh;
if(ic >= 0) { // The point is right on a vertex of the triangle
for(int n = 0; n < mesh.F.rows(); ++n) {
throw_on_cancel();
Vec3i ni = mesh.F.row(n);
if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic))
neigh.emplace_back(ni);
}
}
else if(ia >= 0 && ib >= 0) { // the point is on and edge
// now get all the neigboring triangles
for(int n = 0; n < mesh.F.rows(); ++n) {
throw_on_cancel();
Vec3i ni = mesh.F.row(n);
if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) &&
(ni(X) == ib || ni(Y) == ib || ni(Z) == ib))
neigh.emplace_back(ni);
}
}
if(!neigh.empty()) { // there were neighbors to count with
Vec3d sumnorm(0, 0, 0);
for(const Vec3i& tri : neigh) {
const Vec3d& pt1 = mesh.V.row(tri(0));
const Vec3d& pt2 = mesh.V.row(tri(1));
const Vec3d& pt3 = mesh.V.row(tri(2));
Eigen::Vector3d U = pt2 - pt1;
Eigen::Vector3d V = pt3 - pt1;
sumnorm += U.cross(V).normalized();
}
sumnorm /= neigh.size();
ret.row(i) = sumnorm;
}
else { // point lies safely within its triangle
Eigen::Vector3d U = p2 - p1;
Eigen::Vector3d V = p3 - p1;
ret.row(i) = U.cross(V).normalized();
}
}
return ret;
#else // TODO: do something on 32 bit windows
return {};
#endif
}
double ray_mesh_intersect(const Vec3d& s,
@ -223,7 +308,7 @@ Segments model_boundary(const EigenMesh3D& emesh, double offs)
pp.emplace_back(p);
}
ExPolygons merged = union_ex(offset(pp, float(scale_(offs))), true);
ExPolygons merged = union_ex(Slic3r::offset(pp, float(scale_(offs))), true);
for(auto& expoly : merged) {
auto lines = expoly.lines();

View file

@ -65,11 +65,6 @@ PrinterTechnology BackgroundSlicingProcess::current_printer_technology() const
return m_print->technology();
}
static bool isspace(int ch)
{
return std::isspace(ch) != 0;
}
// This function may one day be merged into the Print, but historically the print was separated
// from the G-code generator.
void BackgroundSlicingProcess::process_fff()
@ -88,6 +83,27 @@ void BackgroundSlicingProcess::process_fff()
m_print->set_status(95, "Running post-processing scripts");
run_post_process_scripts(export_path, m_fff_print->config());
m_print->set_status(100, "G-code file exported to " + export_path);
} else if (! m_upload_job.empty()) {
// A print host upload job has been scheduled
// XXX: is fs::path::string() right?
// Generate a unique temp path to which the gcode is copied
boost::filesystem::path source_path = boost::filesystem::temp_directory_path()
/ boost::filesystem::unique_path(".printhost.%%%%-%%%%-%%%%-%%%%.gcode");
if (copy_file(m_temp_output_path, source_path.string()) != 0) {
throw std::runtime_error("Copying of the temporary G-code to the output G-code failed");
}
m_print->set_status(95, "Running post-processing scripts");
run_post_process_scripts(source_path.string(), m_fff_print->config());
m_print->set_status(100, (boost::format("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue") % m_upload_job.printhost->get_host()).str());
m_upload_job.upload_data.source_path = std::move(source_path);
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
GUI::wxGetApp().printhost_job_queue().enqueue(std::move(m_upload_job));
} else {
m_print->set_status(100, "Slicing complete");
}
@ -373,13 +389,10 @@ void BackgroundSlicingProcess::schedule_upload(Slic3r::PrintHostJob upload_job)
if (! m_export_path.empty())
return;
const boost::filesystem::path path = boost::filesystem::temp_directory_path()
/ boost::filesystem::unique_path(".upload.%%%%-%%%%-%%%%-%%%%.gcode");
// Guard against entering the export step before changing the export path.
tbb::mutex::scoped_lock lock(m_print->state_mutex());
this->invalidate_step(bspsGCodeFinalize);
m_export_path = path.string();
m_export_path = std::string();
m_upload_job = std::move(upload_job);
}

View file

@ -617,42 +617,71 @@ bool GLCanvas3D::Bed::_are_equal(const Pointfs& bed_1, const Pointfs& bed_2)
return true;
}
const double GLCanvas3D::Axes::Radius = 0.5;
const double GLCanvas3D::Axes::ArrowBaseRadius = 2.5 * GLCanvas3D::Axes::Radius;
const double GLCanvas3D::Axes::ArrowLength = 5.0;
GLCanvas3D::Axes::Axes()
: origin(Vec3d::Zero())
, length(0.0f)
, length(Vec3d::Zero())
{
m_quadric = ::gluNewQuadric();
if (m_quadric != nullptr)
::gluQuadricDrawStyle(m_quadric, GLU_FILL);
}
void GLCanvas3D::Axes::render(bool depth_test) const
GLCanvas3D::Axes::~Axes()
{
if (depth_test)
::glEnable(GL_DEPTH_TEST);
else
::glDisable(GL_DEPTH_TEST);
if (m_quadric != nullptr)
::gluDeleteQuadric(m_quadric);
}
::glLineWidth(2.0f);
::glBegin(GL_LINES);
// draw line for x axis
void GLCanvas3D::Axes::render() const
{
if (m_quadric == nullptr)
return;
::glEnable(GL_DEPTH_TEST);
::glEnable(GL_LIGHTING);
// x axis
::glColor3f(1.0f, 0.0f, 0.0f);
::glVertex3dv(origin.data());
::glVertex3f((GLfloat)origin(0) + length, (GLfloat)origin(1), (GLfloat)origin(2));
// draw line for y axis
::glColor3f(0.0f, 1.0f, 0.0f);
::glVertex3dv(origin.data());
::glVertex3f((GLfloat)origin(0), (GLfloat)origin(1) + length, (GLfloat)origin(2));
::glEnd();
// draw line for Z axis
// (re-enable depth test so that axis is correctly shown when objects are behind it)
if (!depth_test)
::glEnable(GL_DEPTH_TEST);
::glPushMatrix();
::glTranslated(origin(0), origin(1), origin(2));
::glRotated(90.0, 0.0, 1.0, 0.0);
render_axis(length(0));
::glPopMatrix();
::glBegin(GL_LINES);
// y axis
::glColor3f(0.0f, 1.0f, 0.0f);
::glPushMatrix();
::glTranslated(origin(0), origin(1), origin(2));
::glRotated(-90.0, 1.0, 0.0, 0.0);
render_axis(length(1));
::glPopMatrix();
// z axis
::glColor3f(0.0f, 0.0f, 1.0f);
::glVertex3dv(origin.data());
::glVertex3f((GLfloat)origin(0), (GLfloat)origin(1), (GLfloat)origin(2) + length);
::glEnd();
::glPushMatrix();
::glTranslated(origin(0), origin(1), origin(2));
render_axis(length(2));
::glPopMatrix();
::glDisable(GL_LIGHTING);
}
void GLCanvas3D::Axes::render_axis(double length) const
{
::gluQuadricOrientation(m_quadric, GLU_OUTSIDE);
::gluCylinder(m_quadric, Radius, Radius, length, 32, 1);
::gluQuadricOrientation(m_quadric, GLU_INSIDE);
::gluDisk(m_quadric, 0.0, Radius, 32, 1);
::glTranslated(0.0, 0.0, length);
::gluQuadricOrientation(m_quadric, GLU_OUTSIDE);
::gluCylinder(m_quadric, ArrowBaseRadius, 0.0, ArrowLength, 32, 1);
::gluQuadricOrientation(m_quadric, GLU_INSIDE);
::gluDisk(m_quadric, 0.0, ArrowBaseRadius, 32, 1);
}
GLCanvas3D::Shader::Shader()
: m_shader(nullptr)
@ -2501,9 +2530,9 @@ void GLCanvas3D::Selection::_ensure_on_bed()
}
#endif // ENABLE_ENSURE_ON_BED_WHILE_SCALING
const float GLCanvas3D::Gizmos::OverlayTexturesScale = 1.0f;
const float GLCanvas3D::Gizmos::OverlayOffsetX = 10.0f * OverlayTexturesScale;
const float GLCanvas3D::Gizmos::OverlayGapY = 5.0f * OverlayTexturesScale;
const float GLCanvas3D::Gizmos::OverlayIconsScale = 1.0f;
const float GLCanvas3D::Gizmos::OverlayBorder = 5.0f;
const float GLCanvas3D::Gizmos::OverlayGapY = 5.0f * OverlayIconsScale;
GLCanvas3D::Gizmos::Gizmos()
: m_enabled(false)
@ -2584,6 +2613,23 @@ bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent)
m_gizmos.insert(GizmosMap::value_type(SlaSupports, gizmo));
#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE
m_background_texture.metadata.filename = "toolbar_background.png";
m_background_texture.metadata.left = 16;
m_background_texture.metadata.top = 16;
m_background_texture.metadata.right = 16;
m_background_texture.metadata.bottom = 16;
if (!m_background_texture.metadata.filename.empty())
{
if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false))
{
_reset();
return false;
}
}
#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE
return true;
}
@ -2606,24 +2652,22 @@ std::string GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, con
float cnv_h = (float)canvas.get_canvas_size().get_height();
float height = _get_total_overlay_height();
float top_y = 0.5f * (cnv_h - height);
float top_y = 0.5f * (cnv_h - height) + OverlayBorder;
for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
{
if ((it->second == nullptr) || !it->second->is_selectable())
continue;
float tex_size = (float)it->second->get_textures_size() * OverlayTexturesScale;
float half_tex_size = 0.5f * tex_size;
float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale;
// we currently use circular icons for gizmo, so we check the radius
if (it->second->is_activable(selection) && (it->second->get_state() != GLGizmoBase::On))
{
bool inside = (mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size;
bool inside = (OverlayBorder <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= OverlayBorder + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size);
it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off);
if (inside)
name = it->second->get_name();
}
top_y += (tex_size + OverlayGapY);
top_y += (icon_size + OverlayGapY);
}
return name;
@ -2636,17 +2680,16 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec
float cnv_h = (float)canvas.get_canvas_size().get_height();
float height = _get_total_overlay_height();
float top_y = 0.5f * (cnv_h - height);
float top_y = 0.5f * (cnv_h - height) + OverlayBorder;
for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
{
if ((it->second == nullptr) || !it->second->is_selectable())
continue;
float tex_size = (float)it->second->get_textures_size() * OverlayTexturesScale;
float half_tex_size = 0.5f * tex_size;
float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale;
// we currently use circular icons for gizmo, so we check the radius
if (it->second->is_activable(selection) && ((mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size))
bool inside = (OverlayBorder <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= OverlayBorder + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size);
if (it->second->is_activable(selection) && inside)
{
if ((it->second->get_state() == GLGizmoBase::On))
{
@ -2662,7 +2705,7 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec
else
it->second->set_state(GLGizmoBase::Off);
top_y += (tex_size + OverlayGapY);
top_y += (icon_size + OverlayGapY);
}
GizmosMap::iterator it = m_gizmos.find(m_current);
@ -2734,20 +2777,18 @@ bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const
float cnv_h = (float)canvas.get_canvas_size().get_height();
float height = _get_total_overlay_height();
float top_y = 0.5f * (cnv_h - height);
float top_y = 0.5f * (cnv_h - height) + OverlayBorder;
for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
{
if ((it->second == nullptr) || !it->second->is_selectable())
continue;
float tex_size = (float)it->second->get_textures_size() * OverlayTexturesScale;
float half_tex_size = 0.5f * tex_size;
float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale;
// we currently use circular icons for gizmo, so we check the radius
if ((mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size)
if ((OverlayBorder <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= OverlayBorder + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size))
return true;
top_y += (tex_size + OverlayGapY);
top_y += (icon_size + OverlayGapY);
}
return false;
@ -3020,21 +3061,102 @@ void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas, const GLCanva
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
float height = _get_total_overlay_height();
float top_x = (OverlayOffsetX - 0.5f * cnv_w) * inv_zoom;
float top_y = 0.5f * height * inv_zoom;
#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE
float scaled_border = OverlayBorder * inv_zoom;
float top_x = (-0.5f * cnv_w) * inv_zoom;
float top_y = (0.5f * height) * inv_zoom;
float left = top_x;
float top = top_y;
float right = left + _get_total_overlay_width() * inv_zoom;
float bottom = top - height * inv_zoom;
// renders background
unsigned int bg_tex_id = m_background_texture.texture.get_id();
float bg_tex_width = (float)m_background_texture.texture.get_width();
float bg_tex_height = (float)m_background_texture.texture.get_height();
if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0))
{
float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f;
float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f;
float bg_uv_left = 0.0f;
float bg_uv_right = 1.0f;
float bg_uv_top = 1.0f;
float bg_uv_bottom = 0.0f;
float bg_left = left;
float bg_right = right;
float bg_top = top;
float bg_bottom = bottom;
float bg_width = right - left;
float bg_height = top - bottom;
float bg_min_size = std::min(bg_width, bg_height);
float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width;
float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width;
float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height;
float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height;
float bg_i_left = bg_left + scaled_border;
float bg_i_right = bg_right - scaled_border;
float bg_i_top = bg_top - scaled_border;
float bg_i_bottom = bg_bottom + scaled_border;
bg_uv_left = bg_uv_i_left;
bg_i_left = bg_left;
if ((OverlayBorder > 0) && (bg_uv_top != bg_uv_i_top))
{
if (bg_uv_left != bg_uv_i_left)
GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } });
GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } });
if (bg_uv_right != bg_uv_i_right)
GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } });
}
if ((OverlayBorder > 0) && (bg_uv_left != bg_uv_i_left))
GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } });
GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } });
if ((OverlayBorder > 0) && (bg_uv_right != bg_uv_i_right))
GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } });
if ((OverlayBorder > 0) && (bg_uv_bottom != bg_uv_i_bottom))
{
if (bg_uv_left != bg_uv_i_left)
GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } });
GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } });
if (bg_uv_right != bg_uv_i_right)
GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } });
}
}
top_x += OverlayBorder * inv_zoom;
top_y -= OverlayBorder * inv_zoom;
#else
float top_x = (OverlayBorder - 0.5f * cnv_w) * inv_zoom;
float top_y = (0.5f * height - OverlayBorder) * inv_zoom;
#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE
float scaled_gap_y = OverlayGapY * inv_zoom;
for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
{
if ((it->second == nullptr) || !it->second->is_selectable())
continue;
float tex_size = (float)it->second->get_textures_size() * OverlayTexturesScale * inv_zoom;
GLTexture::render_texture(it->second->get_texture_id(), top_x, top_x + tex_size, top_y - tex_size, top_y);
float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale * inv_zoom;
GLTexture::render_texture(it->second->get_texture_id(), top_x, top_x + icon_size, top_y - icon_size, top_y);
#if ENABLE_IMGUI
if (it->second->get_state() == GLGizmoBase::On)
it->second->render_input_window(2.0f * OverlayOffsetX + tex_size * zoom, 0.5f * cnv_h - top_y * zoom, selection);
it->second->render_input_window(2.0f * OverlayBorder + icon_size * zoom, 0.5f * cnv_h - top_y * zoom, selection);
#endif // ENABLE_IMGUI
top_y -= (tex_size + scaled_gap_y);
top_y -= (icon_size + scaled_gap_y);
}
}
@ -3047,19 +3169,35 @@ void GLCanvas3D::Gizmos::_render_current_gizmo(const GLCanvas3D::Selection& sele
float GLCanvas3D::Gizmos::_get_total_overlay_height() const
{
float height = 0.0f;
float height = 2.0f * OverlayBorder;
for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
{
if (it->first == SlaSupports && wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA)
if ((it->second == nullptr) || !it->second->is_selectable())
continue;
height += (float)it->second->get_textures_size() * OverlayTexturesScale + OverlayGapY;
height += (float)it->second->get_textures_size() * OverlayIconsScale + OverlayGapY;
}
return height - OverlayGapY;
}
#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE
float GLCanvas3D::Gizmos::_get_total_overlay_width() const
{
float max_icon_width = 0.0f;
for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
{
if ((it->second == nullptr) || !it->second->is_selectable())
continue;
max_icon_width = std::max(max_icon_width, (float)it->second->get_textures_size() * OverlayIconsScale);
}
return max_icon_width + 2.0f * OverlayBorder;
}
#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE
GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const
{
GizmosMap::const_iterator it = m_gizmos.find(m_current);
@ -3672,7 +3810,7 @@ void GLCanvas3D::set_bed_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);
set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size());
set_bed_axes_length(0.1 * m_bed.get_bounding_box().max_size());
if (new_shape)
zoom_to_bed();
@ -3680,9 +3818,9 @@ void GLCanvas3D::set_bed_shape(const Pointfs& shape)
m_dirty = true;
}
void GLCanvas3D::set_axes_length(float length)
void GLCanvas3D::set_bed_axes_length(double length)
{
m_axes.length = length;
m_axes.length = length * Vec3d::Ones();
}
void GLCanvas3D::set_color_by(const std::string& value)
@ -3942,21 +4080,16 @@ void GLCanvas3D::render()
_render_background();
if (is_custom_bed) // untextured bed needs to be rendered before objects
{
_render_bed(theta);
// disable depth testing so that axes are not covered by ground
_render_axes(false);
}
_render_objects();
_render_sla_slices();
_render_selection();
_render_axes();
if (!is_custom_bed) // textured bed needs to be rendered after objects
{
_render_axes(true);
_render_bed(theta);
}
// we need to set the mouse's scene position here because the depth buffer
// could be invalidated by the following gizmo render methods
@ -5910,9 +6043,9 @@ void GLCanvas3D::_render_bed(float theta) const
m_bed.render(theta);
}
void GLCanvas3D::_render_axes(bool depth_test) const
void GLCanvas3D::_render_axes() const
{
m_axes.render(depth_test);
m_axes.render();
}
void GLCanvas3D::_render_objects() const

View file

@ -20,6 +20,8 @@ class wxTimerEvent;
class wxPaintEvent;
class wxGLCanvas;
class GLUquadric;
typedef class GLUquadric GLUquadricObj;
namespace Slic3r {
@ -231,12 +233,20 @@ class GLCanvas3D
struct Axes
{
static const double Radius;
static const double ArrowBaseRadius;
static const double ArrowLength;
Vec3d origin;
float length;
Vec3d length;
GLUquadricObj* m_quadric;
Axes();
~Axes();
void render(bool depth_test) const;
void render() const;
private:
void render_axis(double length) const;
};
class Shader
@ -607,8 +617,8 @@ public:
private:
class Gizmos
{
static const float OverlayTexturesScale;
static const float OverlayOffsetX;
static const float OverlayIconsScale;
static const float OverlayBorder;
static const float OverlayGapY;
public:
@ -628,6 +638,9 @@ private:
bool m_enabled;
typedef std::map<EType, GLGizmoBase*> GizmosMap;
GizmosMap m_gizmos;
#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE
BackgroundTexture m_background_texture;
#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE
EType m_current;
public:
@ -696,6 +709,9 @@ private:
void _render_current_gizmo(const Selection& selection) const;
float _get_total_overlay_height() const;
#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE
float _get_total_overlay_width() const;
#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE
GLGizmoBase* _get_current() const;
};
@ -864,8 +880,7 @@ public:
// fills the m_bed.m_grid_lines and sets m_bed.m_origin.
// Sets m_bed.m_polygon to limit the object placement.
void set_bed_shape(const Pointfs& shape);
void set_axes_length(float length);
void set_bed_axes_length(double length);
void set_clipping_plane(unsigned int id, const ClippingPlane& plane)
{
@ -1005,7 +1020,7 @@ private:
void _picking_pass() const;
void _render_background() const;
void _render_bed(float theta) const;
void _render_axes(bool depth_test) const;
void _render_axes() const;
void _render_objects() const;
void _render_selection() const;
void _render_warning_texture() const;

View file

@ -73,7 +73,6 @@ GUI_App::GUI_App()
: wxApp()
#if ENABLE_IMGUI
, m_imgui(new ImGuiWrapper())
, m_printhost_queue(new PrintHostJobQueue())
#endif // ENABLE_IMGUI
{}
@ -142,6 +141,8 @@ bool GUI_App::OnInit()
update_mode();
SetTopWindow(mainframe);
m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
CallAfter([this]() {
// temporary workaround for the correct behavior of the Scrolled sidebar panel
auto& panel = sidebar();

View file

@ -92,7 +92,7 @@ class GUI_App : public wxApp
std::unique_ptr<ImGuiWrapper> m_imgui;
#endif // ENABLE_IMGUI
std::unique_ptr<PrintHostJobQueue> m_printhost_queue;
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
public:
bool OnInit() override;
@ -164,7 +164,7 @@ public:
ImGuiWrapper* imgui() { return m_imgui.get(); }
#endif // ENABLE_IMGUI
PrintHostJobQueue& printhost_queue() { return *m_printhost_queue.get(); }
PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); }
};
DECLARE_APP(GUI_App)

View file

@ -1460,12 +1460,9 @@ void ObjectList::update_selections()
select_items(sels);
if (GetSelection()) {
const wxRect& sel_rc = GetItemRect(GetSelection());
if (!sel_rc.IsEmpty()) {
const int rc_h = sel_rc.height;
const int displ = GetMainWindow()->GetClientRect().GetHeight()/(2*rc_h)+1;
ScrollLines(int(sel_rc.y / rc_h - displ));
}
const int sel_item_row = m_objects_model->GetRowByItem(GetSelection());
ScrollLines(sel_item_row - m_selected_row);
m_selected_row = sel_item_row;
}
}

View file

@ -108,6 +108,8 @@ class ObjectList : public wxDataViewCtrl
bool m_parts_changed = false;
bool m_part_settings_changed = false;
int m_selected_row = 0;
public:
ObjectList(wxWindow* parent);
~ObjectList();

View file

@ -18,6 +18,7 @@
#include "ProgressStatusBar.hpp"
#include "3DScene.hpp"
#include "AppConfig.hpp"
#include "PrintHostDialogs.hpp"
#include "wxExtensions.hpp"
#include "I18N.hpp"
@ -30,7 +31,8 @@ namespace GUI {
MainFrame::MainFrame(const bool no_plater, const bool loaded) :
wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE),
m_no_plater(no_plater),
m_loaded(loaded)
m_loaded(loaded),
m_printhost_queue_dlg(new PrintHostQueueDialog(this))
{
// Load the icon either from the exe, or from the ico file.
#if _WIN32
@ -326,7 +328,7 @@ void MainFrame::init_menubar()
size_t tab_offset = 0;
if (m_plater) {
#if ENABLE_REMOVE_TABS_FROM_PLATER
append_menu_item(windowMenu, wxID_ANY, L("Plater Tab\tCtrl+1"), L("Show the plater"),
append_menu_item(windowMenu, wxID_HIGHEST + 1, L("Plater Tab\tCtrl+1"), L("Show the plater"),
[this](wxCommandEvent&) { select_tab(0); }, "application_view_tile.png");
#else
append_menu_item(windowMenu, wxID_ANY, L("Select Plater Tab\tCtrl+1"), L("Show the plater"),
@ -338,22 +340,35 @@ void MainFrame::init_menubar()
windowMenu->AppendSeparator();
}
#if ENABLE_REMOVE_TABS_FROM_PLATER
append_menu_item(windowMenu, wxID_ANY, L("Print Settings Tab\tCtrl+2"), L("Show the print settings"),
append_menu_item(windowMenu, wxID_HIGHEST + 2, L("Print Settings Tab\tCtrl+2"), L("Show the print settings"),
[this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 0); }, "cog.png");
append_menu_item(windowMenu, wxID_ANY, L("Filament Settings Tab\tCtrl+3"), L("Show the filament settings"),
append_menu_item(windowMenu, wxID_HIGHEST + 3, L("Filament Settings Tab\tCtrl+3"), L("Show the filament settings"),
[this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 1); }, "spool.png");
append_menu_item(windowMenu, wxID_ANY, L("Printer Settings Tab\tCtrl+4"), L("Show the printer settings"),
append_menu_item(windowMenu, wxID_HIGHEST + 4, L("Printer Settings Tab\tCtrl+4"), L("Show the printer settings"),
[this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 2); }, "printer_empty.png");
if (m_plater) {
windowMenu->AppendSeparator();
wxMenuItem* item_3d = append_menu_item(windowMenu, wxID_ANY, L("3D\tCtrl+5"), L("Show the 3D editing view"),
[this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, "");
wxMenuItem* item_preview = append_menu_item(windowMenu, wxID_ANY, L("Preview\tCtrl+6"), L("Show the 3D slices preview"),
[this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, "");
wxMenuItem* item_3d = append_menu_item(windowMenu, wxID_HIGHEST + 5, L("3D\tCtrl+5"), L("Show the 3D editing view"),
[this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, "");
wxMenuItem* item_preview = append_menu_item(windowMenu, wxID_HIGHEST + 6, L("Preview\tCtrl+6"), L("Show the 3D slices preview"),
[this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, "");
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_3d->GetId());
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_preview->GetId());
}
#if _WIN32
// This is needed on Windows to fake the CTRL+# of the window menu when using the numpad
wxAcceleratorEntry entries[6];
entries[0].Set(wxACCEL_CTRL, WXK_NUMPAD1, wxID_HIGHEST + 1);
entries[1].Set(wxACCEL_CTRL, WXK_NUMPAD2, wxID_HIGHEST + 2);
entries[2].Set(wxACCEL_CTRL, WXK_NUMPAD3, wxID_HIGHEST + 3);
entries[3].Set(wxACCEL_CTRL, WXK_NUMPAD4, wxID_HIGHEST + 4);
entries[4].Set(wxACCEL_CTRL, WXK_NUMPAD5, wxID_HIGHEST + 5);
entries[5].Set(wxACCEL_CTRL, WXK_NUMPAD6, wxID_HIGHEST + 6);
wxAcceleratorTable accel(6, entries);
SetAcceleratorTable(accel);
#endif // _WIN32
#else
append_menu_item(windowMenu, wxID_ANY, L("Select Print Settings Tab\tCtrl+2"), L("Show the print settings"),
[this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 0); }, "cog.png");
@ -362,6 +377,10 @@ void MainFrame::init_menubar()
append_menu_item(windowMenu, wxID_ANY, L("Select Printer Settings Tab\tCtrl+4"), L("Show the printer settings"),
[this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 2); }, "printer_empty.png");
#endif // ENABLE_REMOVE_TABS_FROM_PLATER
windowMenu->AppendSeparator();
append_menu_item(windowMenu, wxID_ANY, L("Print Host Upload Queue"), L("Display the Print Host Upload Queue window"),
[this](wxCommandEvent&) { m_printhost_queue_dlg->ShowModal(); }, "arrow_up.png");
}
// View menu

View file

@ -21,7 +21,9 @@ class ProgressStatusBar;
namespace GUI
{
class Tab;
class PrintHostQueueDialog;
enum QuickSlice
{
@ -52,6 +54,8 @@ class MainFrame : public wxFrame
wxMenuItem* m_menu_item_repeat { nullptr };
wxMenuItem* m_menu_item_reslice_now { nullptr };
PrintHostQueueDialog *m_printhost_queue_dlg;
std::string get_base_name(const wxString full_name) const ;
std::string get_dir_name(const wxString full_name) const ;
@ -93,6 +97,8 @@ public:
void select_tab(size_t tab) const;
void select_view(const std::string& direction);
PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; }
Plater* m_plater { nullptr };
wxNotebook* m_tabpanel { nullptr };
wxProgressDialog* m_progress_dialog { nullptr };

View file

@ -732,8 +732,7 @@ void Sidebar::show_info_sizer()
p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast<int>(model_object->materials_count())));
auto& stats = model_object->volumes.front()->mesh.stl.stats;
auto sf = model_instance->get_scaling_factor();
p->object_info->info_volume->SetLabel(wxString::Format("%.2f", size(0) * size(1) * size(2) * sf(0) * sf(1) * sf(2)));
p->object_info->info_volume->SetLabel(wxString::Format("%.2f", size(0) * size(1) * size(2)));
p->object_info->info_facets->SetLabel(wxString::Format(_(L("%d (%d shells)")), static_cast<int>(model_object->facets_count()), stats.number_of_parts));
int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
@ -3145,7 +3144,7 @@ void Plater::send_gcode()
}
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
Slic3r::PrintHostSendDialog dlg(default_output_file);
PrintHostSendDialog dlg(default_output_file);
if (dlg.ShowModal() == wxID_OK) {
upload_job.upload_data.upload_path = dlg.filename();
upload_job.upload_data.start_print = dlg.start_print();

View file

@ -1,20 +1,28 @@
#include "PrintHostDialogs.hpp"
#include <algorithm>
#include <wx/frame.h>
#include <wx/event.h>
#include <wx/progdlg.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/checkbox.h>
#include <wx/button.h>
#include <wx/dataview.h>
#include <wx/wupdlock.h>
#include <wx/debug.h>
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "GUI.hpp"
#include "MsgDialog.hpp"
#include "I18N.hpp"
#include "../Utils/PrintHost.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path)
: MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE)
@ -45,5 +53,95 @@ fs::path PrintHostSendDialog::filename() const
bool PrintHostSendDialog::start_print() const
{
return box_print->GetValue(); }
return box_print->GetValue();
}
wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
wxDEFINE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id)
: wxEvent(winid, eventType)
, job_id(job_id)
{}
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, int progress)
: wxEvent(winid, eventType)
, job_id(job_id)
, progress(progress)
{}
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, wxString error)
: wxEvent(winid, eventType)
, job_id(job_id)
, error(std::move(error))
{}
wxEvent *PrintHostQueueDialog::Event::Clone() const
{
return new Event(*this);
}
PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
: wxDialog(parent, wxID_ANY, _(L("Print host upload queue")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, on_progress_evt(this, EVT_PRINTHOST_PROGRESS, &PrintHostQueueDialog::on_progress, this)
, on_error_evt(this, EVT_PRINTHOST_ERROR, &PrintHostQueueDialog::on_error, this)
{
enum { HEIGHT = 800, WIDTH = 400, SPACING = 5 };
SetMinSize(wxSize(HEIGHT, WIDTH));
auto *topsizer = new wxBoxSizer(wxVERTICAL);
job_list = new wxDataViewListCtrl(this, wxID_ANY);
job_list->AppendTextColumn("ID", wxDATAVIEW_CELL_INERT);
job_list->AppendProgressColumn("Progress", wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn("Status", wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn("Host", wxDATAVIEW_CELL_INERT);
job_list->AppendTextColumn("Filename", wxDATAVIEW_CELL_INERT);
auto *btnsizer = new wxBoxSizer(wxHORIZONTAL);
auto *btn_cancel = new wxButton(this, wxID_DELETE, _(L("Cancel selected")));
auto *btn_close = new wxButton(this, wxID_CANCEL, _(L("Close")));
btnsizer->Add(btn_cancel, 0, wxRIGHT, SPACING);
btnsizer->AddStretchSpacer();
btnsizer->Add(btn_close);
topsizer->Add(job_list, 1, wxEXPAND | wxBOTTOM, SPACING);
topsizer->Add(btnsizer, 0, wxEXPAND);
SetSizer(topsizer);
}
void PrintHostQueueDialog::append_job(const PrintHostJob &job)
{
wxCHECK_RET(!job.empty(), "PrintHostQueueDialog: Attempt to append an empty job");
wxVector<wxVariant> fields;
fields.push_back(wxVariant(wxString::Format("%d", job_list->GetItemCount() + 1)));
fields.push_back(wxVariant(0));
fields.push_back(wxVariant(_(L("Enqueued"))));
fields.push_back(wxVariant(job.printhost->get_host()));
fields.push_back(wxVariant(job.upload_data.upload_path.string()));
job_list->AppendItem(fields);
}
void PrintHostQueueDialog::on_progress(Event &evt)
{
wxCHECK_RET(evt.job_id < job_list->GetItemCount(), "Out of bounds access to job list");
const wxVariant status(evt.progress < 100 ? _(L("Uploading")) : _(L("Complete")));
job_list->SetValue(wxVariant(evt.progress), evt.job_id, 1);
job_list->SetValue(status, evt.job_id, 2);
}
void PrintHostQueueDialog::on_error(Event &evt)
{
wxCHECK_RET(evt.job_id < job_list->GetItemCount(), "Out of bounds access to job list");
// TODO
}
}}

View file

@ -2,24 +2,27 @@
#define slic3r_PrintHostSendDialog_hpp_
#include <string>
#include <boost/filesystem/path.hpp>
#include <wx/string.h>
#include <wx/frame.h>
#include <wx/event.h>
#include <wx/progdlg.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/checkbox.h>
#include <wx/dialog.h>
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "GUI.hpp"
#include "GUI_Utils.hpp"
#include "MsgDialog.hpp"
#include "../Utils/PrintHost.hpp"
class wxTextCtrl;
class wxCheckBox;
class wxDataViewListCtrl;
namespace Slic3r {
struct PrintHostJob;
namespace GUI {
class PrintHostSendDialog : public GUI::MsgDialog
{
@ -38,12 +41,38 @@ private:
class PrintHostQueueDialog : public wxDialog
{
public:
PrintHostQueueDialog();
class Event : public wxEvent
{
public:
size_t job_id;
int progress = 0; // in percent
wxString error;
Event(wxEventType eventType, int winid, size_t job_id);
Event(wxEventType eventType, int winid, size_t job_id, int progress);
Event(wxEventType eventType, int winid, size_t job_id, wxString error);
virtual wxEvent *Clone() const;
};
PrintHostQueueDialog(wxWindow *parent);
void append_job(const PrintHostJob &job);
private:
wxDataViewListCtrl *job_list;
// Note: EventGuard prevents delivery of progress evts to a freed PrintHostQueueDialog
EventGuard on_progress_evt;
EventGuard on_error_evt;
void on_progress(Event&);
void on_error(Event&);
};
wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
wxDECLARE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
}
}}
#endif

View file

@ -953,6 +953,44 @@ void PrusaObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType&
type = itUndef;
}
int PrusaObjectDataViewModel::GetRowByItem(const wxDataViewItem& item) const
{
if (m_objects.empty())
return -1;
int row_num = 0;
for (int i = 0; i < m_objects.size(); i++)
{
row_num++;
if (item == wxDataViewItem(m_objects[i]))
return row_num;
for (int j = 0; j < m_objects[i]->GetChildCount(); j++)
{
row_num++;
PrusaObjectDataViewModelNode* cur_node = m_objects[i]->GetNthChild(j);
if (item == wxDataViewItem(cur_node))
return row_num;
if (cur_node->m_type == itVolume && cur_node->GetChildCount() == 1)
row_num++;
if (cur_node->m_type == itInstanceRoot)
{
row_num++;
for (int t = 0; t < cur_node->GetChildCount(); t++)
{
row_num++;
if (item == wxDataViewItem(cur_node->GetNthChild(t)))
return row_num;
}
}
}
}
return -1;
}
wxString PrusaObjectDataViewModel::GetName(const wxDataViewItem &item) const
{
PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID();

View file

@ -463,6 +463,7 @@ public:
int GetVolumeIdByItem(const wxDataViewItem& item) const;
int GetInstanceIdByItem(const wxDataViewItem& item) const;
void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx);
int GetRowByItem(const wxDataViewItem& item) const;
bool IsEmpty() { return m_objects.empty(); }
// helper method for wxLog
@ -525,8 +526,14 @@ class PrusaBitmapTextRenderer : public wxDataViewCustomRenderer
#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
{
public:
PrusaBitmapTextRenderer(wxDataViewCellMode mode = wxDATAVIEW_CELL_EDITABLE,
int align = wxDVR_DEFAULT_ALIGNMENT
PrusaBitmapTextRenderer(wxDataViewCellMode mode =
#ifdef __WXOSX__
wxDATAVIEW_CELL_INERT
#else
wxDATAVIEW_CELL_EDITABLE
#endif
,int align = wxDVR_DEFAULT_ALIGNMENT
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
);
#else
@ -542,7 +549,14 @@ public:
virtual bool Render(wxRect cell, wxDC *dc, int state);
virtual wxSize GetSize() const;
bool HasEditorCtrl() const override { return true; }
bool HasEditorCtrl() const override
{
#ifdef __WXOSX__
return false;
#else
return true;
#endif
}
wxWindow* CreateEditorCtrl(wxWindow* parent,
wxRect labelRect,
const wxVariant& value) override;

View file

@ -20,7 +20,6 @@
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/PrintHostDialogs.hpp" // XXX
#include "Http.hpp"
namespace fs = boost::filesystem;
@ -55,89 +54,90 @@ wxString Duet::get_test_failed_msg (wxString &msg) const
return wxString::Format("%s: %s", _(L("Could not connect to Duet")), msg);
}
bool Duet::send_gcode(const std::string &filename) const
{
enum { PROGRESS_RANGE = 1000 };
const auto errortitle = _(L("Error while uploading to the Duet"));
fs::path filepath(filename);
PrintHostSendDialog send_dialog(filepath.filename());
if (send_dialog.ShowModal() != wxID_OK) { return false; }
const bool print = send_dialog.start_print();
const auto upload_filepath = send_dialog.filename();
const auto upload_filename = upload_filepath.filename();
const auto upload_parent_path = upload_filepath.parent_path();
wxProgressDialog progress_dialog(
_(L("Duet upload")),
_(L("Sending G-code file to Duet...")),
PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
progress_dialog.Pulse();
wxString connect_msg;
if (!connect(connect_msg)) {
auto errormsg = wxString::Format("%s: %s", errortitle, connect_msg);
GUI::show_error(&progress_dialog, std::move(errormsg));
return false;
}
bool res = true;
auto upload_cmd = get_upload_url(upload_filepath.string());
BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filename: %2%, path: %3%, print: %4%, command: %5%")
% filepath.string()
% upload_filename.string()
% upload_parent_path.string()
% print
% upload_cmd;
auto http = Http::post(std::move(upload_cmd));
http.set_post_body(filename)
.on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
progress_dialog.Update(PROGRESS_RANGE);
int err_code = get_err_code_from_body(body);
if (err_code != 0) {
auto msg = format_error(body, L("Unknown error occured"), 0);
GUI::show_error(&progress_dialog, std::move(msg));
res = false;
} else if (print) {
wxString errormsg;
res = start_print(errormsg, upload_filepath.string());
if (!res) {
GUI::show_error(&progress_dialog, std::move(errormsg));
}
}
})
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
GUI::show_error(&progress_dialog, std::move(errormsg));
res = false;
})
.on_progress([&](Http::Progress progress, bool &cancel) {
if (cancel) {
// Upload was canceled
res = false;
} else if (progress.ultotal > 0) {
int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing
} else {
cancel = !progress_dialog.Pulse();
}
})
.perform_sync();
disconnect();
return res;
}
bool Duet::upload(PrintHostUpload upload_data) const
// bool Duet::send_gcode(const std::string &filename) const
// {
// enum { PROGRESS_RANGE = 1000 };
// const auto errortitle = _(L("Error while uploading to the Duet"));
// fs::path filepath(filename);
// GUI::PrintHostSendDialog send_dialog(filepath.filename());
// if (send_dialog.ShowModal() != wxID_OK) { return false; }
// const bool print = send_dialog.start_print();
// const auto upload_filepath = send_dialog.filename();
// const auto upload_filename = upload_filepath.filename();
// const auto upload_parent_path = upload_filepath.parent_path();
// wxProgressDialog progress_dialog(
// _(L("Duet upload")),
// _(L("Sending G-code file to Duet...")),
// PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
// progress_dialog.Pulse();
// wxString connect_msg;
// if (!connect(connect_msg)) {
// auto errormsg = wxString::Format("%s: %s", errortitle, connect_msg);
// GUI::show_error(&progress_dialog, std::move(errormsg));
// return false;
// }
// bool res = true;
// auto upload_cmd = get_upload_url(upload_filepath.string());
// BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filename: %2%, path: %3%, print: %4%, command: %5%")
// % filepath.string()
// % upload_filename.string()
// % upload_parent_path.string()
// % print
// % upload_cmd;
// auto http = Http::post(std::move(upload_cmd));
// http.set_post_body(filename)
// .on_complete([&](std::string body, unsigned status) {
// BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body;
// progress_dialog.Update(PROGRESS_RANGE);
// int err_code = get_err_code_from_body(body);
// if (err_code != 0) {
// auto msg = format_error(body, L("Unknown error occured"), 0);
// GUI::show_error(&progress_dialog, std::move(msg));
// res = false;
// } else if (print) {
// wxString errormsg;
// res = start_print(errormsg, upload_filepath.string());
// if (!res) {
// GUI::show_error(&progress_dialog, std::move(errormsg));
// }
// }
// })
// .on_error([&](std::string body, std::string error, unsigned status) {
// BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
// auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
// GUI::show_error(&progress_dialog, std::move(errormsg));
// res = false;
// })
// .on_progress([&](Http::Progress progress, bool &cancel) {
// if (cancel) {
// // Upload was canceled
// res = false;
// } else if (progress.ultotal > 0) {
// int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
// cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing
// } else {
// cancel = !progress_dialog.Pulse();
// }
// })
// .perform_sync();
// disconnect();
// return res;
// }
bool Duet::upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const
{
// XXX: TODO
throw "unimplemented";
}

View file

@ -22,11 +22,10 @@ public:
bool test(wxString &curl_msg) const;
wxString get_test_ok_msg () const;
wxString get_test_failed_msg (wxString &msg) const;
// Send gcode file to duet, filename is expected to be in UTF-8
bool send_gcode(const std::string &filename) const;
bool upload(PrintHostUpload upload_data) const;
bool upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const;
bool has_auto_discovery() const;
bool can_test() const;
virtual std::string get_host() const { return host; }
private:
std::string host;
std::string password;

View file

@ -29,7 +29,7 @@ public:
typedef std::shared_ptr<Http> Ptr;
typedef std::function<void(std::string /* body */, unsigned /* http_status */)> CompleteFn;
// A HTTP request may fail at various stages of completeness (URL parsing, DNS lookup, TCP connection, ...).
// If the HTTP request could not be made or failed before completion, the `error` arg contains a description
// of the error and `http_status` is zero.

View file

@ -4,9 +4,10 @@
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <wx/progdlg.h>
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/PrintHostDialogs.hpp" // XXX
#include "Http.hpp"
@ -59,32 +60,19 @@ wxString OctoPrint::get_test_failed_msg (wxString &msg) const
_(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required.")));
}
bool OctoPrint::send_gcode(const std::string &filename) const
bool OctoPrint::upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const
{
enum { PROGRESS_RANGE = 1000 };
const auto errortitle = _(L("Error while uploading to the OctoPrint server"));
fs::path filepath(filename);
PrintHostSendDialog send_dialog(filepath.filename());
if (send_dialog.ShowModal() != wxID_OK) { return false; }
const bool print = send_dialog.start_print();
const auto upload_filepath = send_dialog.filename();
const auto upload_filename = upload_filepath.filename();
const auto upload_parent_path = upload_filepath.parent_path();
wxProgressDialog progress_dialog(
_(L("OctoPrint upload")),
_(L("Sending G-code file to the OctoPrint server...")),
PROGRESS_RANGE, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
progress_dialog.Pulse();
const auto upload_filename = upload_data.upload_path.filename();
const auto upload_parent_path = upload_data.upload_path.parent_path();
wxString test_msg;
if (!test(test_msg)) {
auto errormsg = wxString::Format("%s: %s", errortitle, test_msg);
GUI::show_error(&progress_dialog, std::move(errormsg));
return false;
if (! test(test_msg)) {
// TODO:
// auto errormsg = wxString::Format("%s: %s", errortitle, test_msg);
// GUI::show_error(&progress_dialog, std::move(errormsg));
// return false;
}
bool res = true;
@ -92,36 +80,31 @@ bool OctoPrint::send_gcode(const std::string &filename) const
auto url = make_url("api/files/local");
BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Uploading file %1% at %2%, filename: %3%, path: %4%, print: %5%")
% filepath.string()
% upload_data.source_path.string()
% url
% upload_filename.string()
% upload_parent_path.string()
% print;
% upload_data.start_print;
auto http = Http::post(std::move(url));
set_auth(http);
http.form_add("print", print ? "true" : "false")
http.form_add("print", upload_data.start_print ? "true" : "false")
.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???
.form_add_file("file", filename, upload_filename.string())
.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
.on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: File uploaded: HTTP %1%: %2%") % status % body;
progress_dialog.Update(PROGRESS_RANGE);
})
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body;
auto errormsg = wxString::Format("%s: %s", errortitle, format_error(body, error, status));
GUI::show_error(&progress_dialog, std::move(errormsg));
error_fn(std::move(body), std::move(error), status);
res = false;
})
.on_progress([&](Http::Progress progress, bool &cancel) {
prorgess_fn(std::move(progress), cancel);
if (cancel) {
// Upload was canceled
BOOST_LOG_TRIVIAL(error) << "Octoprint: Upload canceled";
res = false;
} else if (progress.ultotal > 0) {
int value = PROGRESS_RANGE * progress.ulnow / progress.ultotal;
cancel = !progress_dialog.Update(std::min(value, PROGRESS_RANGE - 1)); // Cap the value to prevent premature dialog closing
} else {
cancel = !progress_dialog.Pulse();
}
})
.perform_sync();
@ -129,11 +112,6 @@ bool OctoPrint::send_gcode(const std::string &filename) const
return res;
}
bool OctoPrint::upload(PrintHostUpload upload_data) const
{
throw "unimplemented";
}
bool OctoPrint::has_auto_discovery() const
{
return true;

View file

@ -22,11 +22,10 @@ public:
bool test(wxString &curl_msg) const;
wxString get_test_ok_msg () const;
wxString get_test_failed_msg (wxString &msg) const;
// Send gcode file to octoprint, filename is expected to be in UTF-8
bool send_gcode(const std::string &filename) const;
bool upload(PrintHostUpload upload_data) const;
bool upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const;
bool has_auto_discovery() const;
bool can_test() const;
virtual std::string get_host() const { return host; }
private:
std::string host;
std::string apikey;

View file

@ -1,15 +1,21 @@
#include "OctoPrint.hpp"
#include "Duet.hpp"
#include "PrintHost.hpp"
#include <vector>
#include <thread>
#include <boost/optional.hpp>
#include <boost/filesystem.hpp>
#include <wx/app.h>
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Channel.hpp"
#include "OctoPrint.hpp"
#include "Duet.hpp"
#include "../GUI/PrintHostDialogs.hpp"
namespace fs = boost::filesystem;
using boost::optional;
using Slic3r::GUI::PrintHostQueueDialog;
namespace Slic3r {
@ -30,30 +36,130 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
struct PrintHostJobQueue::priv
{
std::vector<PrintHostJob> jobs;
Channel<unsigned> channel;
// XXX: comment on how bg thread works
PrintHostJobQueue *q;
Channel<PrintHostJob> channel_jobs;
Channel<size_t> channel_cancels;
size_t job_id = 0;
int prev_progress = -1;
std::thread bg_thread;
optional<PrintHostJob> bg_job;
bool bg_exit = false;
PrintHostQueueDialog *queue_dialog;
priv(PrintHostJobQueue *q) : q(q) {}
void start_bg_thread();
void bg_thread_main();
void progress_fn(Http::Progress progress, bool &cancel);
void error_fn(std::string body, std::string error, unsigned http_status);
void perform_job(PrintHostJob the_job);
};
PrintHostJobQueue::PrintHostJobQueue()
: p(new priv())
PrintHostJobQueue::PrintHostJobQueue(PrintHostQueueDialog *queue_dialog)
: p(new priv(this))
{
std::shared_ptr<priv> p2 = p;
p->bg_thread = std::thread([p2]() {
// Wait for commands on the channel:
auto cmd = p2->channel.pop();
// TODO
});
p->queue_dialog = queue_dialog;
}
PrintHostJobQueue::~PrintHostJobQueue()
{
// TODO: stop the thread
// if (p && p->bg_thread.joinable()) {
// p->bg_thread.detach();
// }
if (p && p->bg_thread.joinable()) {
p->bg_exit = true;
p->channel_jobs.push(PrintHostJob()); // Push an empty job to wake up bg_thread in case it's sleeping
p->bg_thread.detach(); // Let the background thread go, it should exit on its own
}
}
void PrintHostJobQueue::priv::start_bg_thread()
{
if (bg_thread.joinable()) { return; }
std::shared_ptr<priv> p2 = q->p;
bg_thread = std::thread([p2]() {
p2->bg_thread_main();
});
}
void PrintHostJobQueue::priv::bg_thread_main()
{
// bg thread entry point
try {
// Pick up jobs from the job channel:
while (! bg_exit) {
auto job = channel_jobs.pop(); // Sleeps in a cond var if there are no jobs
if (! job.cancelled) {
perform_job(std::move(job));
}
job_id++;
}
} catch (...) {
wxTheApp->OnUnhandledException();
}
}
void PrintHostJobQueue::priv::progress_fn(Http::Progress progress, bool &cancel)
{
if (bg_exit) {
cancel = true;
return;
}
if (channel_cancels.size_hint() > 0) {
// Lock both queues
auto cancels = channel_cancels.lock_rw();
auto jobs = channel_jobs.lock_rw();
for (size_t cancel_id : *cancels) {
if (cancel_id == job_id) {
cancel = true;
} else if (cancel_id > job_id) {
jobs->at(cancel_id - job_id).cancelled = true;
}
}
cancels->clear();
}
int gui_progress = progress.ultotal > 0 ? 100*progress.ulnow / progress.ultotal : 0;
if (gui_progress != prev_progress) {
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, gui_progress);
wxQueueEvent(queue_dialog, evt);
prev_progress = gui_progress;
}
}
void PrintHostJobQueue::priv::error_fn(std::string body, std::string error, unsigned http_status)
{
// TODO
}
void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
{
if (bg_exit || the_job.empty()) { return; }
const fs::path gcode_path = the_job.upload_data.source_path;
the_job.printhost->upload(std::move(the_job.upload_data),
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
[this](std::string body, std::string error, unsigned http_status) { this->error_fn(std::move(body), std::move(error), http_status); }
);
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, 100);
wxQueueEvent(queue_dialog, evt);
fs::remove(gcode_path); // XXX: error handling
}
void PrintHostJobQueue::enqueue(PrintHostJob job)
{
p->start_bg_thread();
p->queue_dialog->append_job(job);
p->channel_jobs.push(std::move(job));
}

View file

@ -7,6 +7,8 @@
#include <wx/string.h>
#include "Http.hpp"
namespace Slic3r {
@ -29,11 +31,10 @@ public:
virtual bool test(wxString &curl_msg) const = 0;
virtual wxString get_test_ok_msg () const = 0;
virtual wxString get_test_failed_msg (wxString &msg) const = 0;
// Send gcode file to print host, filename is expected to be in UTF-8
virtual bool send_gcode(const std::string &filename) const = 0; // XXX: remove in favor of upload()
virtual bool upload(PrintHostUpload upload_data) const = 0;
virtual bool upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const = 0;
virtual bool has_auto_discovery() const = 0;
virtual bool can_test() const = 0;
virtual std::string get_host() const = 0;
static PrintHost* get_print_host(DynamicPrintConfig *config);
};
@ -43,6 +44,7 @@ struct PrintHostJob
{
PrintHostUpload upload_data;
std::unique_ptr<PrintHost> printhost;
bool cancelled = false;
PrintHostJob() {}
PrintHostJob(const PrintHostJob&) = delete;
@ -68,10 +70,12 @@ struct PrintHostJob
};
namespace GUI { class PrintHostQueueDialog; }
class PrintHostJobQueue
{
public:
PrintHostJobQueue();
PrintHostJobQueue(GUI::PrintHostQueueDialog *queue_dialog);
PrintHostJobQueue(const PrintHostJobQueue &) = delete;
PrintHostJobQueue(PrintHostJobQueue &&other) = delete;
~PrintHostJobQueue();
@ -79,6 +83,9 @@ public:
PrintHostJobQueue& operator=(const PrintHostJobQueue &) = delete;
PrintHostJobQueue& operator=(PrintHostJobQueue &&other) = delete;
void enqueue(PrintHostJob job);
void cancel(size_t id);
private:
struct priv;
std::shared_ptr<priv> p;