diff --git a/src/eigen/unsupported/Eigen/SparseExtra b/src/eigen/unsupported/Eigen/SparseExtra new file mode 100644 index 000000000..819cffa27 --- /dev/null +++ b/src/eigen/unsupported/Eigen/SparseExtra @@ -0,0 +1,53 @@ +// This file is part of Eigen, a lightweight C++ template library +// for linear algebra. +// +// Copyright (C) 2008-2009 Gael Guennebaud +// +// 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 +#include +#include +#include +#include +#include +#include + +#ifdef EIGEN_GOOGLEHASH_SUPPORT + #include +#endif + +/** + * \defgroup SparseExtra_Module SparseExtra module + * + * This module contains some experimental features extending the sparse module. + * + * \code + * #include + * \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 +#include "src/SparseExtra/MatrixMarketIterator.h" +#endif + +#include "../../Eigen/src/Core/util/ReenableStupidWarnings.h" + +#endif // EIGEN_SPARSE_EXTRA_MODULE_H diff --git a/src/eigen/unsupported/Eigen/src/SparseExtra/BlockOfDynamicSparseMatrix.h b/src/eigen/unsupported/Eigen/src/SparseExtra/BlockOfDynamicSparseMatrix.h new file mode 100644 index 000000000..e9ec746e3 --- /dev/null +++ b/src/eigen/unsupported/Eigen/src/SparseExtra/BlockOfDynamicSparseMatrix.h @@ -0,0 +1,122 @@ +// This file is part of Eigen, a lightweight C++ template library +// for linear algebra. +// +// Copyright (C) 2008-2009 Gael Guennebaud +// +// 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 +class SparseInnerVectorSet, Size> + : public SparseMatrixBase, Size> > +{ + typedef DynamicSparseMatrix<_Scalar, _Options, _Index> MatrixType; + public: + + enum { IsRowMajor = internal::traits::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 + inline SparseInnerVectorSet& operator=(const SparseMatrixBase& other) + { + if (IsRowMajor != ((OtherDerived::Flags&RowMajorBit)==RowMajorBit)) + { + // need to transpose => perform a block evaluation followed by a big swap + DynamicSparseMatrix aux(other); + *this = aux.markAsRValue(); + } + else + { + // evaluate/copy vector per vector + for (Index j=0; j 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=(other); + } + + Index nonZeros() const + { + Index count = 0; + for (Index j=0; j0); + return m_matrix.data()[m_outerStart].vale(m_matrix.data()[m_outerStart].size()-1); + } + +// template +// inline SparseInnerVectorSet& operator=(const SparseMatrixBase& 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 m_outerSize; + +}; + +#endif + +} // end namespace Eigen + +#endif // EIGEN_SPARSE_BLOCKFORDYNAMICMATRIX_H diff --git a/src/eigen/unsupported/Eigen/src/SparseExtra/BlockSparseMatrix.h b/src/eigen/unsupported/Eigen/src/SparseExtra/BlockSparseMatrix.h new file mode 100644 index 000000000..0e8350a7d --- /dev/null +++ b/src/eigen/unsupported/Eigen/src/SparseExtra/BlockSparseMatrix.h @@ -0,0 +1,1079 @@ +// This file is part of Eigen, a lightweight C++ template library +// for linear algebra. +// +// Copyright (C) 2013 Desire Nuentsa +// Copyright (C) 2013 Gael Guennebaud +// +// 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_SPARSEBLOCKMATRIX_H +#define EIGEN_SPARSEBLOCKMATRIX_H + +namespace Eigen { +/** \ingroup SparseCore_Module + * + * \class BlockSparseMatrix + * + * \brief A versatile sparse matrix representation where each element is a block + * + * This class provides routines to manipulate block sparse matrices stored in a + * BSR-like representation. There are two main types : + * + * 1. All blocks have the same number of rows and columns, called block size + * in the following. In this case, if this block size is known at compile time, + * it can be given as a template parameter like + * \code + * BlockSparseMatrix bmat(b_rows, b_cols); + * \endcode + * Here, bmat is a b_rows x b_cols block sparse matrix + * where each coefficient is a 3x3 dense matrix. + * If the block size is fixed but will be given at runtime, + * \code + * BlockSparseMatrix bmat(b_rows, b_cols); + * bmat.setBlockSize(block_size); + * \endcode + * + * 2. The second case is for variable-block sparse matrices. + * Here each block has its own dimensions. The only restriction is that all the blocks + * in a row (resp. a column) should have the same number of rows (resp. of columns). + * It is thus required in this case to describe the layout of the matrix by calling + * setBlockLayout(rowBlocks, colBlocks). + * + * In any of the previous case, the matrix can be filled by calling setFromTriplets(). + * A regular sparse matrix can be converted to a block sparse matrix and vice versa. + * It is obviously required to describe the block layout beforehand by calling either + * setBlockSize() for fixed-size blocks or setBlockLayout for variable-size blocks. + * + * \tparam _Scalar The Scalar type + * \tparam _BlockAtCompileTime The block layout option. It takes the following values + * Dynamic : block size known at runtime + * a numeric number : fixed-size block known at compile time + */ +template class BlockSparseMatrix; + +template class BlockSparseMatrixView; + +namespace internal { +template +struct traits > +{ + typedef _Scalar Scalar; + typedef _Index Index; + typedef Sparse StorageKind; // FIXME Where is it used ?? + typedef MatrixXpr XprKind; + enum { + RowsAtCompileTime = Dynamic, + ColsAtCompileTime = Dynamic, + MaxRowsAtCompileTime = Dynamic, + MaxColsAtCompileTime = Dynamic, + BlockSize = _BlockAtCompileTime, + Flags = _Options | NestByRefBit | LvalueBit, + CoeffReadCost = NumTraits::ReadCost, + SupportedAccessPatterns = InnerRandomAccessPattern + }; +}; +template +struct traits > +{ + typedef Ref > Scalar; + typedef Ref > RealScalar; + +}; + +// Function object to sort a triplet list +template +struct TripletComp +{ + typedef typename Iterator::value_type Triplet; + bool operator()(const Triplet& a, const Triplet& b) + { if(IsColMajor) + return ((a.col() == b.col() && a.row() < b.row()) || (a.col() < b.col())); + else + return ((a.row() == b.row() && a.col() < b.col()) || (a.row() < b.row())); + } +}; +} // end namespace internal + + +/* Proxy to view the block sparse matrix as a regular sparse matrix */ +template +class BlockSparseMatrixView : public SparseMatrixBase +{ + public: + typedef Ref Scalar; + typedef Ref RealScalar; + typedef typename BlockSparseMatrixT::Index Index; + typedef BlockSparseMatrixT Nested; + enum { + Flags = BlockSparseMatrixT::Options, + Options = BlockSparseMatrixT::Options, + RowsAtCompileTime = BlockSparseMatrixT::RowsAtCompileTime, + ColsAtCompileTime = BlockSparseMatrixT::ColsAtCompileTime, + MaxColsAtCompileTime = BlockSparseMatrixT::MaxColsAtCompileTime, + MaxRowsAtCompileTime = BlockSparseMatrixT::MaxRowsAtCompileTime + }; + public: + BlockSparseMatrixView(const BlockSparseMatrixT& spblockmat) + : m_spblockmat(spblockmat) + {} + + Index outerSize() const + { + return (Flags&RowMajorBit) == 1 ? this->rows() : this->cols(); + } + Index cols() const + { + return m_spblockmat.blockCols(); + } + Index rows() const + { + return m_spblockmat.blockRows(); + } + Scalar coeff(Index row, Index col) + { + return m_spblockmat.coeff(row, col); + } + Scalar coeffRef(Index row, Index col) + { + return m_spblockmat.coeffRef(row, col); + } + // Wrapper to iterate over all blocks + class InnerIterator : public BlockSparseMatrixT::BlockInnerIterator + { + public: + InnerIterator(const BlockSparseMatrixView& mat, Index outer) + : BlockSparseMatrixT::BlockInnerIterator(mat.m_spblockmat, outer) + {} + + }; + + protected: + const BlockSparseMatrixT& m_spblockmat; +}; + +// Proxy to view a regular vector as a block vector +template +class BlockVectorView +{ + public: + enum { + BlockSize = BlockSparseMatrixT::BlockSize, + ColsAtCompileTime = VectorType::ColsAtCompileTime, + RowsAtCompileTime = VectorType::RowsAtCompileTime, + Flags = VectorType::Flags + }; + typedef Ref >Scalar; + typedef typename BlockSparseMatrixT::Index Index; + public: + BlockVectorView(const BlockSparseMatrixT& spblockmat, const VectorType& vec) + : m_spblockmat(spblockmat),m_vec(vec) + { } + inline Index cols() const + { + return m_vec.cols(); + } + inline Index size() const + { + return m_spblockmat.blockRows(); + } + inline Scalar coeff(Index bi) const + { + Index startRow = m_spblockmat.blockRowsIndex(bi); + Index rowSize = m_spblockmat.blockRowsIndex(bi+1) - startRow; + return m_vec.middleRows(startRow, rowSize); + } + inline Scalar coeff(Index bi, Index j) const + { + Index startRow = m_spblockmat.blockRowsIndex(bi); + Index rowSize = m_spblockmat.blockRowsIndex(bi+1) - startRow; + return m_vec.block(startRow, j, rowSize, 1); + } + protected: + const BlockSparseMatrixT& m_spblockmat; + const VectorType& m_vec; +}; + +template class BlockVectorReturn; + + +// Proxy to view a regular vector as a block vector +template +class BlockVectorReturn +{ + public: + enum { + ColsAtCompileTime = VectorType::ColsAtCompileTime, + RowsAtCompileTime = VectorType::RowsAtCompileTime, + Flags = VectorType::Flags + }; + typedef Ref > Scalar; + typedef typename BlockSparseMatrixT::Index Index; + public: + BlockVectorReturn(const BlockSparseMatrixT& spblockmat, VectorType& vec) + : m_spblockmat(spblockmat),m_vec(vec) + { } + inline Index size() const + { + return m_spblockmat.blockRows(); + } + inline Scalar coeffRef(Index bi) + { + Index startRow = m_spblockmat.blockRowsIndex(bi); + Index rowSize = m_spblockmat.blockRowsIndex(bi+1) - startRow; + return m_vec.middleRows(startRow, rowSize); + } + inline Scalar coeffRef(Index bi, Index j) + { + Index startRow = m_spblockmat.blockRowsIndex(bi); + Index rowSize = m_spblockmat.blockRowsIndex(bi+1) - startRow; + return m_vec.block(startRow, j, rowSize, 1); + } + + protected: + const BlockSparseMatrixT& m_spblockmat; + VectorType& m_vec; +}; + +// Block version of the sparse dense product +template +class BlockSparseTimeDenseProduct; + +namespace internal { + +template +struct traits > +{ + typedef Dense StorageKind; + typedef MatrixXpr XprKind; + typedef typename BlockSparseMatrixT::Scalar Scalar; + typedef typename BlockSparseMatrixT::Index Index; + enum { + RowsAtCompileTime = Dynamic, + ColsAtCompileTime = Dynamic, + MaxRowsAtCompileTime = Dynamic, + MaxColsAtCompileTime = Dynamic, + Flags = 0, + CoeffReadCost = internal::traits::CoeffReadCost + }; +}; +} // end namespace internal + +template +class BlockSparseTimeDenseProduct + : public ProductBase, Lhs, Rhs> +{ + public: + EIGEN_PRODUCT_PUBLIC_INTERFACE(BlockSparseTimeDenseProduct) + + BlockSparseTimeDenseProduct(const Lhs& lhs, const Rhs& rhs) : Base(lhs,rhs) + {} + + template void scaleAndAddTo(Dest& dest, const typename Rhs::Scalar& alpha) const + { + BlockVectorReturn tmpDest(m_lhs, dest); + internal::sparse_time_dense_product( BlockSparseMatrixView(m_lhs), BlockVectorView(m_lhs, m_rhs), tmpDest, alpha); + } + + private: + BlockSparseTimeDenseProduct& operator=(const BlockSparseTimeDenseProduct&); +}; + +template +class BlockSparseMatrix : public SparseMatrixBase > +{ + public: + typedef _Scalar Scalar; + typedef typename NumTraits::Real RealScalar; + typedef _StorageIndex StorageIndex; + typedef typename internal::ref_selector >::type Nested; + + enum { + Options = _Options, + Flags = Options, + BlockSize=_BlockAtCompileTime, + RowsAtCompileTime = Dynamic, + ColsAtCompileTime = Dynamic, + MaxRowsAtCompileTime = Dynamic, + MaxColsAtCompileTime = Dynamic, + IsVectorAtCompileTime = 0, + IsColMajor = Flags&RowMajorBit ? 0 : 1 + }; + typedef Matrix BlockScalar; + typedef Matrix BlockRealScalar; + typedef typename internal::conditional<_BlockAtCompileTime==Dynamic, Scalar, BlockScalar>::type BlockScalarReturnType; + typedef BlockSparseMatrix PlainObject; + public: + // Default constructor + BlockSparseMatrix() + : m_innerBSize(0),m_outerBSize(0),m_innerOffset(0),m_outerOffset(0), + m_nonzerosblocks(0),m_values(0),m_blockPtr(0),m_indices(0), + m_outerIndex(0),m_blockSize(BlockSize) + { } + + + /** + * \brief Construct and resize + * + */ + BlockSparseMatrix(Index brow, Index bcol) + : m_innerBSize(IsColMajor ? brow : bcol), + m_outerBSize(IsColMajor ? bcol : brow), + m_innerOffset(0),m_outerOffset(0),m_nonzerosblocks(0), + m_values(0),m_blockPtr(0),m_indices(0), + m_outerIndex(0),m_blockSize(BlockSize) + { } + + /** + * \brief Copy-constructor + */ + BlockSparseMatrix(const BlockSparseMatrix& other) + : m_innerBSize(other.m_innerBSize),m_outerBSize(other.m_outerBSize), + m_nonzerosblocks(other.m_nonzerosblocks),m_nonzeros(other.m_nonzeros), + m_blockPtr(0),m_blockSize(other.m_blockSize) + { + // should we allow copying between variable-size blocks and fixed-size blocks ?? + eigen_assert(m_blockSize == BlockSize && " CAN NOT COPY BETWEEN FIXED-SIZE AND VARIABLE-SIZE BLOCKS"); + + std::copy(other.m_innerOffset, other.m_innerOffset+m_innerBSize+1, m_innerOffset); + std::copy(other.m_outerOffset, other.m_outerOffset+m_outerBSize+1, m_outerOffset); + std::copy(other.m_values, other.m_values+m_nonzeros, m_values); + + if(m_blockSize != Dynamic) + std::copy(other.m_blockPtr, other.m_blockPtr+m_nonzerosblocks, m_blockPtr); + + std::copy(other.m_indices, other.m_indices+m_nonzerosblocks, m_indices); + std::copy(other.m_outerIndex, other.m_outerIndex+m_outerBSize, m_outerIndex); + } + + friend void swap(BlockSparseMatrix& first, BlockSparseMatrix& second) + { + std::swap(first.m_innerBSize, second.m_innerBSize); + std::swap(first.m_outerBSize, second.m_outerBSize); + std::swap(first.m_innerOffset, second.m_innerOffset); + std::swap(first.m_outerOffset, second.m_outerOffset); + std::swap(first.m_nonzerosblocks, second.m_nonzerosblocks); + std::swap(first.m_nonzeros, second.m_nonzeros); + std::swap(first.m_values, second.m_values); + std::swap(first.m_blockPtr, second.m_blockPtr); + std::swap(first.m_indices, second.m_indices); + std::swap(first.m_outerIndex, second.m_outerIndex); + std::swap(first.m_BlockSize, second.m_blockSize); + } + + BlockSparseMatrix& operator=(BlockSparseMatrix other) + { + //Copy-and-swap paradigm ... avoid leaked data if thrown + swap(*this, other); + return *this; + } + + // Destructor + ~BlockSparseMatrix() + { + delete[] m_outerIndex; + delete[] m_innerOffset; + delete[] m_outerOffset; + delete[] m_indices; + delete[] m_blockPtr; + delete[] m_values; + } + + + /** + * \brief Constructor from a sparse matrix + * + */ + template + inline BlockSparseMatrix(const MatrixType& spmat) : m_blockSize(BlockSize) + { + EIGEN_STATIC_ASSERT((m_blockSize != Dynamic), THIS_METHOD_IS_ONLY_FOR_FIXED_SIZE); + + *this = spmat; + } + + /** + * \brief Assignment from a sparse matrix with the same storage order + * + * Convert from a sparse matrix to block sparse matrix. + * \warning Before calling this function, tt is necessary to call + * either setBlockLayout() (matrices with variable-size blocks) + * or setBlockSize() (for fixed-size blocks). + */ + template + inline BlockSparseMatrix& operator=(const MatrixType& spmat) + { + eigen_assert((m_innerBSize != 0 && m_outerBSize != 0) + && "Trying to assign to a zero-size matrix, call resize() first"); + eigen_assert(((MatrixType::Options&RowMajorBit) != IsColMajor) && "Wrong storage order"); + typedef SparseMatrix MatrixPatternType; + MatrixPatternType blockPattern(blockRows(), blockCols()); + m_nonzeros = 0; + + // First, compute the number of nonzero blocks and their locations + for(StorageIndex bj = 0; bj < m_outerBSize; ++bj) + { + // Browse each outer block and compute the structure + std::vector nzblocksFlag(m_innerBSize,false); // Record the existing blocks + blockPattern.startVec(bj); + for(StorageIndex j = blockOuterIndex(bj); j < blockOuterIndex(bj+1); ++j) + { + typename MatrixType::InnerIterator it_spmat(spmat, j); + for(; it_spmat; ++it_spmat) + { + StorageIndex bi = innerToBlock(it_spmat.index()); // Index of the current nonzero block + if(!nzblocksFlag[bi]) + { + // Save the index of this nonzero block + nzblocksFlag[bi] = true; + blockPattern.insertBackByOuterInnerUnordered(bj, bi) = true; + // Compute the total number of nonzeros (including explicit zeros in blocks) + m_nonzeros += blockOuterSize(bj) * blockInnerSize(bi); + } + } + } // end current outer block + } + blockPattern.finalize(); + + // Allocate the internal arrays + setBlockStructure(blockPattern); + + for(StorageIndex nz = 0; nz < m_nonzeros; ++nz) m_values[nz] = Scalar(0); + for(StorageIndex bj = 0; bj < m_outerBSize; ++bj) + { + // Now copy the values + for(StorageIndex j = blockOuterIndex(bj); j < blockOuterIndex(bj+1); ++j) + { + // Browse the outer block column by column (for column-major matrices) + typename MatrixType::InnerIterator it_spmat(spmat, j); + for(; it_spmat; ++it_spmat) + { + StorageIndex idx = 0; // Position of this block in the column block + StorageIndex bi = innerToBlock(it_spmat.index()); // Index of the current nonzero block + // Go to the inner block where this element belongs to + while(bi > m_indices[m_outerIndex[bj]+idx]) ++idx; // Not expensive for ordered blocks + StorageIndex idxVal;// Get the right position in the array of values for this element + if(m_blockSize == Dynamic) + { + // Offset from all blocks before ... + idxVal = m_blockPtr[m_outerIndex[bj]+idx]; + // ... and offset inside the block + idxVal += (j - blockOuterIndex(bj)) * blockOuterSize(bj) + it_spmat.index() - m_innerOffset[bi]; + } + else + { + // All blocks before + idxVal = (m_outerIndex[bj] + idx) * m_blockSize * m_blockSize; + // inside the block + idxVal += (j - blockOuterIndex(bj)) * m_blockSize + (it_spmat.index()%m_blockSize); + } + // Insert the value + m_values[idxVal] = it_spmat.value(); + } // end of this column + } // end of this block + } // end of this outer block + + return *this; + } + + /** + * \brief Set the nonzero block pattern of the matrix + * + * Given a sparse matrix describing the nonzero block pattern, + * this function prepares the internal pointers for values. + * After calling this function, any *nonzero* block (bi, bj) can be set + * with a simple call to coeffRef(bi,bj). + * + * + * \warning Before calling this function, tt is necessary to call + * either setBlockLayout() (matrices with variable-size blocks) + * or setBlockSize() (for fixed-size blocks). + * + * \param blockPattern Sparse matrix of boolean elements describing the block structure + * + * \sa setBlockLayout() \sa setBlockSize() + */ + template + void setBlockStructure(const MatrixType& blockPattern) + { + resize(blockPattern.rows(), blockPattern.cols()); + reserve(blockPattern.nonZeros()); + + // Browse the block pattern and set up the various pointers + m_outerIndex[0] = 0; + if(m_blockSize == Dynamic) m_blockPtr[0] = 0; + for(StorageIndex nz = 0; nz < m_nonzeros; ++nz) m_values[nz] = Scalar(0); + for(StorageIndex bj = 0; bj < m_outerBSize; ++bj) + { + //Browse each outer block + + //First, copy and save the indices of nonzero blocks + //FIXME : find a way to avoid this ... + std::vector nzBlockIdx; + typename MatrixType::InnerIterator it(blockPattern, bj); + for(; it; ++it) + { + nzBlockIdx.push_back(it.index()); + } + std::sort(nzBlockIdx.begin(), nzBlockIdx.end()); + + // Now, fill block indices and (eventually) pointers to blocks + for(StorageIndex idx = 0; idx < nzBlockIdx.size(); ++idx) + { + StorageIndex offset = m_outerIndex[bj]+idx; // offset in m_indices + m_indices[offset] = nzBlockIdx[idx]; + if(m_blockSize == Dynamic) + m_blockPtr[offset] = m_blockPtr[offset-1] + blockInnerSize(nzBlockIdx[idx]) * blockOuterSize(bj); + // There is no blockPtr for fixed-size blocks... not needed !??? + } + // Save the pointer to the next outer block + m_outerIndex[bj+1] = m_outerIndex[bj] + nzBlockIdx.size(); + } + } + + /** + * \brief Set the number of rows and columns blocks + */ + inline void resize(Index brow, Index bcol) + { + m_innerBSize = IsColMajor ? brow : bcol; + m_outerBSize = IsColMajor ? bcol : brow; + } + + /** + * \brief set the block size at runtime for fixed-size block layout + * + * Call this only for fixed-size blocks + */ + inline void setBlockSize(Index blockSize) + { + m_blockSize = blockSize; + } + + /** + * \brief Set the row and column block layouts, + * + * This function set the size of each row and column block. + * So this function should be used only for blocks with variable size. + * \param rowBlocks : Number of rows per row block + * \param colBlocks : Number of columns per column block + * \sa resize(), setBlockSize() + */ + inline void setBlockLayout(const VectorXi& rowBlocks, const VectorXi& colBlocks) + { + const VectorXi& innerBlocks = IsColMajor ? rowBlocks : colBlocks; + const VectorXi& outerBlocks = IsColMajor ? colBlocks : rowBlocks; + eigen_assert(m_innerBSize == innerBlocks.size() && "CHECK THE NUMBER OF ROW OR COLUMN BLOCKS"); + eigen_assert(m_outerBSize == outerBlocks.size() && "CHECK THE NUMBER OF ROW OR COLUMN BLOCKS"); + m_outerBSize = outerBlocks.size(); + // starting index of blocks... cumulative sums + m_innerOffset = new StorageIndex[m_innerBSize+1]; + m_outerOffset = new StorageIndex[m_outerBSize+1]; + m_innerOffset[0] = 0; + m_outerOffset[0] = 0; + std::partial_sum(&innerBlocks[0], &innerBlocks[m_innerBSize-1]+1, &m_innerOffset[1]); + std::partial_sum(&outerBlocks[0], &outerBlocks[m_outerBSize-1]+1, &m_outerOffset[1]); + + // Compute the total number of nonzeros + m_nonzeros = 0; + for(StorageIndex bj = 0; bj < m_outerBSize; ++bj) + for(StorageIndex bi = 0; bi < m_innerBSize; ++bi) + m_nonzeros += outerBlocks[bj] * innerBlocks[bi]; + + } + + /** + * \brief Allocate the internal array of pointers to blocks and their inner indices + * + * \note For fixed-size blocks, call setBlockSize() to set the block. + * And For variable-size blocks, call setBlockLayout() before using this function + * + * \param nonzerosblocks Number of nonzero blocks. The total number of nonzeros is + * is computed in setBlockLayout() for variable-size blocks + * \sa setBlockSize() + */ + inline void reserve(const Index nonzerosblocks) + { + eigen_assert((m_innerBSize != 0 && m_outerBSize != 0) && + "TRYING TO RESERVE ZERO-SIZE MATRICES, CALL resize() first"); + + //FIXME Should free if already allocated + m_outerIndex = new StorageIndex[m_outerBSize+1]; + + m_nonzerosblocks = nonzerosblocks; + if(m_blockSize != Dynamic) + { + m_nonzeros = nonzerosblocks * (m_blockSize * m_blockSize); + m_blockPtr = 0; + } + else + { + // m_nonzeros is already computed in setBlockLayout() + m_blockPtr = new StorageIndex[m_nonzerosblocks+1]; + } + m_indices = new StorageIndex[m_nonzerosblocks+1]; + m_values = new Scalar[m_nonzeros]; + } + + + /** + * \brief Fill values in a matrix from a triplet list. + * + * Each triplet item has a block stored in an Eigen dense matrix. + * The InputIterator class should provide the functions row(), col() and value() + * + * \note For fixed-size blocks, call setBlockSize() before this function. + * + * FIXME Do not accept duplicates + */ + template + void setFromTriplets(const InputIterator& begin, const InputIterator& end) + { + eigen_assert((m_innerBSize!=0 && m_outerBSize !=0) && "ZERO BLOCKS, PLEASE CALL resize() before"); + + /* First, sort the triplet list + * FIXME This can be unnecessarily expensive since only the inner indices have to be sorted + * The best approach is like in SparseMatrix::setFromTriplets() + */ + internal::TripletComp tripletcomp; + std::sort(begin, end, tripletcomp); + + /* Count the number of rows and column blocks, + * and the number of nonzero blocks per outer dimension + */ + VectorXi rowBlocks(m_innerBSize); // Size of each block row + VectorXi colBlocks(m_outerBSize); // Size of each block column + rowBlocks.setZero(); colBlocks.setZero(); + VectorXi nzblock_outer(m_outerBSize); // Number of nz blocks per outer vector + VectorXi nz_outer(m_outerBSize); // Number of nz per outer vector...for variable-size blocks + nzblock_outer.setZero(); + nz_outer.setZero(); + for(InputIterator it(begin); it !=end; ++it) + { + eigen_assert(it->row() >= 0 && it->row() < this->blockRows() && it->col() >= 0 && it->col() < this->blockCols()); + eigen_assert((it->value().rows() == it->value().cols() && (it->value().rows() == m_blockSize)) + || (m_blockSize == Dynamic)); + + if(m_blockSize == Dynamic) + { + eigen_assert((rowBlocks[it->row()] == 0 || rowBlocks[it->row()] == it->value().rows()) && + "NON CORRESPONDING SIZES FOR ROW BLOCKS"); + eigen_assert((colBlocks[it->col()] == 0 || colBlocks[it->col()] == it->value().cols()) && + "NON CORRESPONDING SIZES FOR COLUMN BLOCKS"); + rowBlocks[it->row()] =it->value().rows(); + colBlocks[it->col()] = it->value().cols(); + } + nz_outer(IsColMajor ? it->col() : it->row()) += it->value().rows() * it->value().cols(); + nzblock_outer(IsColMajor ? it->col() : it->row())++; + } + // Allocate member arrays + if(m_blockSize == Dynamic) setBlockLayout(rowBlocks, colBlocks); + StorageIndex nzblocks = nzblock_outer.sum(); + reserve(nzblocks); + + // Temporary markers + VectorXi block_id(m_outerBSize); // To be used as a block marker during insertion + + // Setup outer index pointers and markers + m_outerIndex[0] = 0; + if (m_blockSize == Dynamic) m_blockPtr[0] = 0; + for(StorageIndex bj = 0; bj < m_outerBSize; ++bj) + { + m_outerIndex[bj+1] = m_outerIndex[bj] + nzblock_outer(bj); + block_id(bj) = m_outerIndex[bj]; + if(m_blockSize==Dynamic) + { + m_blockPtr[m_outerIndex[bj+1]] = m_blockPtr[m_outerIndex[bj]] + nz_outer(bj); + } + } + + // Fill the matrix + for(InputIterator it(begin); it!=end; ++it) + { + StorageIndex outer = IsColMajor ? it->col() : it->row(); + StorageIndex inner = IsColMajor ? it->row() : it->col(); + m_indices[block_id(outer)] = inner; + StorageIndex block_size = it->value().rows()*it->value().cols(); + StorageIndex nz_marker = blockPtr(block_id[outer]); + memcpy(&(m_values[nz_marker]), it->value().data(), block_size * sizeof(Scalar)); + if(m_blockSize == Dynamic) + { + m_blockPtr[block_id(outer)+1] = m_blockPtr[block_id(outer)] + block_size; + } + block_id(outer)++; + } + + // An alternative when the outer indices are sorted...no need to use an array of markers +// for(Index bcol = 0; bcol < m_outerBSize; ++bcol) +// { +// Index id = 0, id_nz = 0, id_nzblock = 0; +// for(InputIterator it(begin); it!=end; ++it) +// { +// while (idvalue().rows()*it->value().cols(); +// m_blockPtr[id_nzblock+1] = m_blockPtr[id_nzblock] + block_size; +// id_nzblock++; +// memcpy(&(m_values[id_nz]),it->value().data(), block_size*sizeof(Scalar)); +// id_nz += block_size; +// } +// while(id < m_outerBSize-1) // Empty columns at the end +// { +// id++; +// m_outerIndex[id+1]=m_outerIndex[id]; +// } +// } + } + + + /** + * \returns the number of rows + */ + inline Index rows() const + { +// return blockRows(); + return (IsColMajor ? innerSize() : outerSize()); + } + + /** + * \returns the number of cols + */ + inline Index cols() const + { +// return blockCols(); + return (IsColMajor ? outerSize() : innerSize()); + } + + inline Index innerSize() const + { + if(m_blockSize == Dynamic) return m_innerOffset[m_innerBSize]; + else return (m_innerBSize * m_blockSize) ; + } + + inline Index outerSize() const + { + if(m_blockSize == Dynamic) return m_outerOffset[m_outerBSize]; + else return (m_outerBSize * m_blockSize) ; + } + /** \returns the number of rows grouped by blocks */ + inline Index blockRows() const + { + return (IsColMajor ? m_innerBSize : m_outerBSize); + } + /** \returns the number of columns grouped by blocks */ + inline Index blockCols() const + { + return (IsColMajor ? m_outerBSize : m_innerBSize); + } + + inline Index outerBlocks() const { return m_outerBSize; } + inline Index innerBlocks() const { return m_innerBSize; } + + /** \returns the block index where outer belongs to */ + inline Index outerToBlock(Index outer) const + { + eigen_assert(outer < outerSize() && "OUTER INDEX OUT OF BOUNDS"); + + if(m_blockSize != Dynamic) + return (outer / m_blockSize); // Integer division + + StorageIndex b_outer = 0; + while(m_outerOffset[b_outer] <= outer) ++b_outer; + return b_outer - 1; + } + /** \returns the block index where inner belongs to */ + inline Index innerToBlock(Index inner) const + { + eigen_assert(inner < innerSize() && "OUTER INDEX OUT OF BOUNDS"); + + if(m_blockSize != Dynamic) + return (inner / m_blockSize); // Integer division + + StorageIndex b_inner = 0; + while(m_innerOffset[b_inner] <= inner) ++b_inner; + return b_inner - 1; + } + + /** + *\returns a reference to the (i,j) block as an Eigen Dense Matrix + */ + Ref coeffRef(Index brow, Index bcol) + { + eigen_assert(brow < blockRows() && "BLOCK ROW INDEX OUT OF BOUNDS"); + eigen_assert(bcol < blockCols() && "BLOCK nzblocksFlagCOLUMN OUT OF BOUNDS"); + + StorageIndex rsize = IsColMajor ? blockInnerSize(brow): blockOuterSize(bcol); + StorageIndex csize = IsColMajor ? blockOuterSize(bcol) : blockInnerSize(brow); + StorageIndex inner = IsColMajor ? brow : bcol; + StorageIndex outer = IsColMajor ? bcol : brow; + StorageIndex offset = m_outerIndex[outer]; + while(offset < m_outerIndex[outer+1] && m_indices[offset] != inner) + offset++; + if(m_indices[offset] == inner) + { + return Map(&(m_values[blockPtr(offset)]), rsize, csize); + } + else + { + //FIXME the block does not exist, Insert it !!!!!!!!! + eigen_assert("DYNAMIC INSERTION IS NOT YET SUPPORTED"); + } + } + + /** + * \returns the value of the (i,j) block as an Eigen Dense Matrix + */ + Map coeff(Index brow, Index bcol) const + { + eigen_assert(brow < blockRows() && "BLOCK ROW INDEX OUT OF BOUNDS"); + eigen_assert(bcol < blockCols() && "BLOCK COLUMN OUT OF BOUNDS"); + + StorageIndex rsize = IsColMajor ? blockInnerSize(brow): blockOuterSize(bcol); + StorageIndex csize = IsColMajor ? blockOuterSize(bcol) : blockInnerSize(brow); + StorageIndex inner = IsColMajor ? brow : bcol; + StorageIndex outer = IsColMajor ? bcol : brow; + StorageIndex offset = m_outerIndex[outer]; + while(offset < m_outerIndex[outer+1] && m_indices[offset] != inner) offset++; + if(m_indices[offset] == inner) + { + return Map (&(m_values[blockPtr(offset)]), rsize, csize); + } + else +// return BlockScalar::Zero(rsize, csize); + eigen_assert("NOT YET SUPPORTED"); + } + + // Block Matrix times vector product + template + BlockSparseTimeDenseProduct operator*(const VecType& lhs) const + { + return BlockSparseTimeDenseProduct(*this, lhs); + } + + /** \returns the number of nonzero blocks */ + inline Index nonZerosBlocks() const { return m_nonzerosblocks; } + /** \returns the total number of nonzero elements, including eventual explicit zeros in blocks */ + inline Index nonZeros() const { return m_nonzeros; } + + inline BlockScalarReturnType *valuePtr() {return static_cast(m_values);} +// inline Scalar *valuePtr(){ return m_values; } + inline StorageIndex *innerIndexPtr() {return m_indices; } + inline const StorageIndex *innerIndexPtr() const {return m_indices; } + inline StorageIndex *outerIndexPtr() {return m_outerIndex; } + inline const StorageIndex* outerIndexPtr() const {return m_outerIndex; } + + /** \brief for compatibility purposes with the SparseMatrix class */ + inline bool isCompressed() const {return true;} + /** + * \returns the starting index of the bi row block + */ + inline Index blockRowsIndex(Index bi) const + { + return IsColMajor ? blockInnerIndex(bi) : blockOuterIndex(bi); + } + + /** + * \returns the starting index of the bj col block + */ + inline Index blockColsIndex(Index bj) const + { + return IsColMajor ? blockOuterIndex(bj) : blockInnerIndex(bj); + } + + inline Index blockOuterIndex(Index bj) const + { + return (m_blockSize == Dynamic) ? m_outerOffset[bj] : (bj * m_blockSize); + } + inline Index blockInnerIndex(Index bi) const + { + return (m_blockSize == Dynamic) ? m_innerOffset[bi] : (bi * m_blockSize); + } + + // Not needed ??? + inline Index blockInnerSize(Index bi) const + { + return (m_blockSize == Dynamic) ? (m_innerOffset[bi+1] - m_innerOffset[bi]) : m_blockSize; + } + inline Index blockOuterSize(Index bj) const + { + return (m_blockSize == Dynamic) ? (m_outerOffset[bj+1]- m_outerOffset[bj]) : m_blockSize; + } + + /** + * \brief Browse the matrix by outer index + */ + class InnerIterator; // Browse column by column + + /** + * \brief Browse the matrix by block outer index + */ + class BlockInnerIterator; // Browse block by block + + friend std::ostream & operator << (std::ostream & s, const BlockSparseMatrix& m) + { + for (StorageIndex j = 0; j < m.outerBlocks(); ++j) + { + BlockInnerIterator itb(m, j); + for(; itb; ++itb) + { + s << "("< in the array of values + */ + Index blockPtr(Index id) const + { + if(m_blockSize == Dynamic) return m_blockPtr[id]; + else return id * m_blockSize * m_blockSize; + //return blockDynIdx(id, typename internal::conditional<(BlockSize==Dynamic), internal::true_type, internal::false_type>::type()); + } + + + protected: +// inline Index blockDynIdx(Index id, internal::true_type) const +// { +// return m_blockPtr[id]; +// } +// inline Index blockDynIdx(Index id, internal::false_type) const +// { +// return id * BlockSize * BlockSize; +// } + + // To be implemented + // Insert a block at a particular location... need to make a room for that + Map insert(Index brow, Index bcol); + + Index m_innerBSize; // Number of block rows + Index m_outerBSize; // Number of block columns + StorageIndex *m_innerOffset; // Starting index of each inner block (size m_innerBSize+1) + StorageIndex *m_outerOffset; // Starting index of each outer block (size m_outerBSize+1) + Index m_nonzerosblocks; // Total nonzeros blocks (lower than m_innerBSize x m_outerBSize) + Index m_nonzeros; // Total nonzeros elements + Scalar *m_values; //Values stored block column after block column (size m_nonzeros) + StorageIndex *m_blockPtr; // Pointer to the beginning of each block in m_values, size m_nonzeroblocks ... null for fixed-size blocks + StorageIndex *m_indices; //Inner block indices, size m_nonzerosblocks ... OK + StorageIndex *m_outerIndex; // Starting pointer of each block column in m_indices (size m_outerBSize)... OK + Index m_blockSize; // Size of a block for fixed-size blocks, otherwise -1 +}; + +template +class BlockSparseMatrix<_Scalar, _BlockAtCompileTime, _Options, _StorageIndex>::BlockInnerIterator +{ + public: + + enum{ + Flags = _Options + }; + + BlockInnerIterator(const BlockSparseMatrix& mat, const Index outer) + : m_mat(mat),m_outer(outer), + m_id(mat.m_outerIndex[outer]), + m_end(mat.m_outerIndex[outer+1]) + { + } + + inline BlockInnerIterator& operator++() {m_id++; return *this; } + + inline const Map value() const + { + return Map(&(m_mat.m_values[m_mat.blockPtr(m_id)]), + rows(),cols()); + } + inline Map valueRef() + { + return Map(&(m_mat.m_values[m_mat.blockPtr(m_id)]), + rows(),cols()); + } + // Block inner index + inline Index index() const {return m_mat.m_indices[m_id]; } + inline Index outer() const { return m_outer; } + // block row index + inline Index row() const {return index(); } + // block column index + inline Index col() const {return outer(); } + // FIXME Number of rows in the current block + inline Index rows() const { return (m_mat.m_blockSize==Dynamic) ? (m_mat.m_innerOffset[index()+1] - m_mat.m_innerOffset[index()]) : m_mat.m_blockSize; } + // Number of columns in the current block ... + inline Index cols() const { return (m_mat.m_blockSize==Dynamic) ? (m_mat.m_outerOffset[m_outer+1]-m_mat.m_outerOffset[m_outer]) : m_mat.m_blockSize;} + inline operator bool() const { return (m_id < m_end); } + + protected: + const BlockSparseMatrix<_Scalar, _BlockAtCompileTime, _Options, StorageIndex>& m_mat; + const Index m_outer; + Index m_id; + Index m_end; +}; + +template +class BlockSparseMatrix<_Scalar, _BlockAtCompileTime, _Options, _StorageIndex>::InnerIterator +{ + public: + InnerIterator(const BlockSparseMatrix& mat, Index outer) + : m_mat(mat),m_outerB(mat.outerToBlock(outer)),m_outer(outer), + itb(mat, mat.outerToBlock(outer)), + m_offset(outer - mat.blockOuterIndex(m_outerB)) + { + if (itb) + { + m_id = m_mat.blockInnerIndex(itb.index()); + m_start = m_id; + m_end = m_mat.blockInnerIndex(itb.index()+1); + } + } + inline InnerIterator& operator++() + { + m_id++; + if (m_id >= m_end) + { + ++itb; + if (itb) + { + m_id = m_mat.blockInnerIndex(itb.index()); + m_start = m_id; + m_end = m_mat.blockInnerIndex(itb.index()+1); + } + } + return *this; + } + inline const Scalar& value() const + { + return itb.value().coeff(m_id - m_start, m_offset); + } + inline Scalar& valueRef() + { + return itb.valueRef().coeff(m_id - m_start, m_offset); + } + inline Index index() const { return m_id; } + inline Index outer() const {return m_outer; } + inline Index col() const {return outer(); } + inline Index row() const { return index();} + inline operator bool() const + { + return itb; + } + protected: + const BlockSparseMatrix& m_mat; + const Index m_outer; + const Index m_outerB; + BlockInnerIterator itb; // Iterator through the blocks + const Index m_offset; // Position of this column in the block + Index m_start; // starting inner index of this block + Index m_id; // current inner index in the block + Index m_end; // starting inner index of the next block + +}; +} // end namespace Eigen + +#endif // EIGEN_SPARSEBLOCKMATRIX_H diff --git a/src/eigen/unsupported/Eigen/src/SparseExtra/DynamicSparseMatrix.h b/src/eigen/unsupported/Eigen/src/SparseExtra/DynamicSparseMatrix.h new file mode 100644 index 000000000..037a13f86 --- /dev/null +++ b/src/eigen/unsupported/Eigen/src/SparseExtra/DynamicSparseMatrix.h @@ -0,0 +1,392 @@ +// This file is part of Eigen, a lightweight C++ template library +// for linear algebra. +// +// Copyright (C) 2008-2009 Gael Guennebaud +// +// 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 +struct traits > +{ + 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::ReadCost, + SupportedAccessPatterns = OuterRandomAccessPattern + }; +}; +} + +template + class DynamicSparseMatrix + : public SparseMatrixBase > +{ + typedef SparseMatrixBase 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 Map; + using Base::IsRowMajor; + using Base::operator=; + enum { + Options = _Options + }; + + protected: + + typedef DynamicSparseMatrix TransposedSparseMatrix; + + Index m_innerSize; + std::vector > 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 >& _data() { return m_data; } + const std::vector >& _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; j0) + { + Index reserveSizePerVector = (std::max)(reserveSize/outerSize(),Index(4)); + for (Index j=0; j(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::dummy_precision()) + { + for (Index j=0; jinnerSize) + { + // 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 + EIGEN_DEPRECATED explicit inline DynamicSparseMatrix(const SparseMatrixBase& 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 +class DynamicSparseMatrix::InnerIterator : public SparseVector::InnerIterator +{ + typedef typename SparseVector::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 +class DynamicSparseMatrix::ReverseInnerIterator : public SparseVector::ReverseInnerIterator +{ + typedef typename SparseVector::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 +struct evaluator > + : evaluator_base > +{ + 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 diff --git a/src/eigen/unsupported/Eigen/src/SparseExtra/MarketIO.h b/src/eigen/unsupported/Eigen/src/SparseExtra/MarketIO.h new file mode 100644 index 000000000..41e4af4a4 --- /dev/null +++ b/src/eigen/unsupported/Eigen/src/SparseExtra/MarketIO.h @@ -0,0 +1,275 @@ +// This file is part of Eigen, a lightweight C++ template library +// for linear algebra. +// +// Copyright (C) 2011 Gael Guennebaud +// Copyright (C) 2012 Desire NUENTSA WAKAM +// +// 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 + +namespace Eigen { + +namespace internal +{ + template + 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 + inline bool GetMarketLine (std::stringstream& line, Index& M, Index& N, Index& i, Index& j, std::complex& value) + { + Scalar valR, valI; + line >> i >> j >> valR >> valI; + i--; + j--; + if(i>=0 && j>=0 && i(valR, valI); + return true; + } + else + return false; + } + + template + inline void GetVectorElt (const std::string& line, RealScalar& val) + { + std::istringstream newline(line); + newline >> val; + } + + template + inline void GetVectorElt (const std::string& line, std::complex& val) + { + RealScalar valR, valI; + std::istringstream newline(line); + newline >> valR >> valI; + val = std::complex(valR, valI); + } + + template + inline void putMarketHeader(std::string& header,int sym) + { + header= "%%MatrixMarket matrix coordinate "; + if(internal::is_same >::value || internal::is_same >::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 + inline void PutMatrixElt(Scalar value, int row, int col, std::ofstream& out) + { + out << row << " "<< col << " " << value << "\n"; + } + template + inline void PutMatrixElt(std::complex value, int row, int col, std::ofstream& out) + { + out << row << " " << col << " " << value.real() << " " << value.imag() << "\n"; + } + + + template + inline void putVectorElt(Scalar value, std::ofstream& out) + { + out << value << "\n"; + } + template + inline void putVectorElt(std::complex 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 +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 T; + std::vector 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 +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 +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(header, sym); + out << header << std::endl; + out << mat.rows() << " " << mat.cols() << " " << mat.nonZeros() << "\n"; + int count = 0; + for(int j=0; j +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 >::value || internal::is_same >::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 diff --git a/src/eigen/unsupported/Eigen/src/SparseExtra/MatrixMarketIterator.h b/src/eigen/unsupported/Eigen/src/SparseExtra/MatrixMarketIterator.h new file mode 100644 index 000000000..02916ea6f --- /dev/null +++ b/src/eigen/unsupported/Eigen/src/SparseExtra/MatrixMarketIterator.h @@ -0,0 +1,247 @@ + +// This file is part of Eigen, a lightweight C++ template library +// for linear algebra. +// +// Copyright (C) 2012 Desire NUENTSA WAKAM +// +// 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 +class MatrixMarketIterator +{ + typedef typename NumTraits::Real RealScalar; + public: + typedef Matrix VectorType; + typedef SparseMatrix 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().norm(); + RealScalar upper_norm = m_mat.template triangularView().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(); + } + 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(); + } + } + 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 >::value || internal::is_same >::value) + continue; + } + if (iscomplex) + { + if(internal::is_same::value || internal::is_same::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 diff --git a/src/eigen/unsupported/Eigen/src/SparseExtra/RandomSetter.h b/src/eigen/unsupported/Eigen/src/SparseExtra/RandomSetter.h new file mode 100644 index 000000000..ee97299af --- /dev/null +++ b/src/eigen/unsupported/Eigen/src/SparseExtra/RandomSetter.h @@ -0,0 +1,327 @@ +// This file is part of Eigen, a lightweight C++ template library +// for linear algebra. +// +// Copyright (C) 2008 Gael Guennebaud +// +// 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 struct StdMapTraits +{ + typedef int KeyType; + typedef std::map 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 + * #define EIGEN_UNORDERED_MAP_SUPPORT + * namespace std { + * using std::tr1::unordered_map; + * } + * \endcode + * + * \see RandomSetter + */ +template struct StdUnorderedMapTraits +{ + typedef int KeyType; + typedef std::unordered_map 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 struct GoogleDenseHashMapTraits +{ + typedef int KeyType; + typedef google::dense_hash_map 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 struct GoogleSparseHashMapTraits +{ + typedef int KeyType; + typedef google::sparse_hash_map 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 m(rows,cols); + * { + * RandomSetter > 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 yourself \b before Eigen/Sparse header + * - define EIGEN_GOOGLEHASH_SUPPORT + * In the later case the inclusion of is made for you. + * + * \see http://code.google.com/p/google-sparsehash/ + */ +template 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::KeyType KeyType; + typedef typename MapTraits::Type HashMapType; + static const int OuterPacketMask = (1 << OuterPacketBits) - 1; + enum { + SwapStorage = 1 - MapTraits::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::setInvalidKey(m_hashmaps[k],ik); + + // insert current coeffs + for (Index j=0; jouterSize(); ++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<setZero(); + mp_target->makeCompressed(); + mp_target->reserve(nonZeros()); + Index prevOuter = -1; + for (Index k=0; kfirst >> 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; kfirst & keyBitsMask; + ++positions[outer]; + } + } + // prefix sum + Index count = 0; + for (Index j=0; jouterSize(); ++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; kfirst >> 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((outerMinor<(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 diff --git a/src/libslic3r/Channel.hpp b/src/libslic3r/Channel.hpp index 8d1a07d35..9cf025f2c 100644 --- a/src/libslic3r/Channel.hpp +++ b/src/libslic3r/Channel.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_Channel_hpp_ #define slic3r_Channel_hpp_ +#include #include #include #include @@ -13,32 +14,26 @@ namespace Slic3r { template class Channel { -private: - using UniqueLock = std::unique_lock; - using Queue = std::deque; public: - class Guard + using UniqueLock = std::unique_lock; + + template 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; + using LockedConstPtr = std::unique_ptr>; + using LockedPtr = std::unique_ptr>; Channel() {} ~Channel() {} @@ -56,7 +51,7 @@ public: { { UniqueLock lock(m_mutex); - m_queue.push_back(std::forward(item)); + m_queue.push_back(std::forward(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(UniqueLock(m_mutex))); } + LockedPtr lock_rw() + { + return LockedPtr(&m_queue, Unlocker(UniqueLock(m_mutex))); + } private: Queue m_queue; - std::mutex m_mutex; + mutable std::mutex m_mutex; std::condition_variable m_condition; }; diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp index 6c0b055d6..80aae75cf 100644 --- a/src/libslic3r/Format/PRUS.cpp +++ b/src/libslic3r/Format/PRUS.cpp @@ -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 = ""; 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 = ""; @@ -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; diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 8615ab91c..37b0c0ffc 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -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 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); diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index 50d7775a2..5d40bb514 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -1,3 +1,4 @@ +#include #include "SLA/SLASupportTree.hpp" #include "SLA/SLABoilerPlate.hpp" #include "SLA/SLASpatIndex.hpp" @@ -9,15 +10,8 @@ #include "boost/geometry/index/rtree.hpp" #include - -//#if !defined(_MSC_VER) || defined(_WIN64) -#if 1 -#define IGL_COMPATIBLE -#endif - -#ifdef IGL_COMPATIBLE #include -#endif +#include #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; + + auto line = Line3D::Through(e1, e2); + double d = line.distance(p); + return std::abs(d) < eps; +} + +template 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 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 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(); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 891e5f0dc..d748919c9 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -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); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8b1d636e5..eecefd381 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -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 diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index eecc3261b..e07c60ed2 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -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 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; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index f4047ae3e..e4db9b6e1 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -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(); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 875a92456..3c2b4a21f 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -92,7 +92,7 @@ class GUI_App : public wxApp std::unique_ptr m_imgui; #endif // ENABLE_IMGUI - std::unique_ptr m_printhost_queue; + std::unique_ptr 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) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a7f1ca608..cba42470a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -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; } } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 3664e6fda..7631782df 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -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(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index a74d5f72a..4d4ee17ae 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -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 diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 2559b5ed1..fab6aea90 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -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 }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8db804c12..a8a75fc3f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -732,8 +732,7 @@ void Sidebar::show_info_sizer() p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast(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(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(); diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index a5de7c3c6..8ac8615a8 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -1,20 +1,28 @@ #include "PrintHostDialogs.hpp" +#include + #include -#include #include #include #include #include #include +#include +#include +#include +#include -#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 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 +} + + +}} diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp index d27fbe576..e38acee32 100644 --- a/src/slic3r/GUI/PrintHostDialogs.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -2,24 +2,27 @@ #define slic3r_PrintHostSendDialog_hpp_ #include - #include #include -#include #include -#include -#include -#include -#include -#include +#include -#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 diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 98e758bc8..2daba5df4 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -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(); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 637f1bcff..e8fba1ea2 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -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; diff --git a/src/slic3r/Utils/Duet.cpp b/src/slic3r/Utils/Duet.cpp index 4eda7bd46..1772ae8ef 100644 --- a/src/slic3r/Utils/Duet.cpp +++ b/src/slic3r/Utils/Duet.cpp @@ -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"; } diff --git a/src/slic3r/Utils/Duet.hpp b/src/slic3r/Utils/Duet.hpp index db21fd0a1..0608f85a5 100644 --- a/src/slic3r/Utils/Duet.hpp +++ b/src/slic3r/Utils/Duet.hpp @@ -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; diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index 44580b7ea..fd3f8830d 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -29,7 +29,7 @@ public: typedef std::shared_ptr Ptr; typedef std::function 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. diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index b2e2d4d45..67c58a972 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -4,9 +4,10 @@ #include #include +#include + #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; diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index 314e4cfae..9267b4c83 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -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; diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index 570d72f68..48f504884 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -1,15 +1,21 @@ -#include "OctoPrint.hpp" -#include "Duet.hpp" +#include "PrintHost.hpp" #include #include #include +#include + +#include #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 jobs; - Channel channel; + // XXX: comment on how bg thread works + + PrintHostJobQueue *q; + + Channel channel_jobs; + Channel channel_cancels; + size_t job_id = 0; + int prev_progress = -1; std::thread bg_thread; - optional 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 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 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)); } diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp index 53f7c43d3..52ef38058 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -7,6 +7,8 @@ #include +#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; + 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 p;