From b613334b818011f332e6bc7a5b2f9f472c34f893 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 17 Dec 2018 15:58:15 +0100 Subject: [PATCH 1/6] Considering multiple neighboring triangles for support point normals --- src/eigen/unsupported/Eigen/SparseExtra | 53 + .../SparseExtra/BlockOfDynamicSparseMatrix.h | 122 ++ .../Eigen/src/SparseExtra/BlockSparseMatrix.h | 1079 +++++++++++++++++ .../src/SparseExtra/DynamicSparseMatrix.h | 392 ++++++ .../Eigen/src/SparseExtra/MarketIO.h | 275 +++++ .../src/SparseExtra/MatrixMarketIterator.h | 247 ++++ .../Eigen/src/SparseExtra/RandomSetter.h | 327 +++++ src/libslic3r/SLA/SLASupportTree.cpp | 6 +- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 127 +- 9 files changed, 2605 insertions(+), 23 deletions(-) create mode 100644 src/eigen/unsupported/Eigen/SparseExtra create mode 100644 src/eigen/unsupported/Eigen/src/SparseExtra/BlockOfDynamicSparseMatrix.h create mode 100644 src/eigen/unsupported/Eigen/src/SparseExtra/BlockSparseMatrix.h create mode 100644 src/eigen/unsupported/Eigen/src/SparseExtra/DynamicSparseMatrix.h create mode 100644 src/eigen/unsupported/Eigen/src/SparseExtra/MarketIO.h create mode 100644 src/eigen/unsupported/Eigen/src/SparseExtra/MatrixMarketIterator.h create mode 100644 src/eigen/unsupported/Eigen/src/SparseExtra/RandomSetter.h 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/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(); From 862217a6b3739fd66df277bc08f19cf4d4dbb45f Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 14 Dec 2018 15:27:34 +0100 Subject: [PATCH 2/6] OctoPrint basics working, niceties to-do --- src/libslic3r/Channel.hpp | 52 +++--- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 31 ++-- src/slic3r/GUI/GUI_App.cpp | 3 +- src/slic3r/GUI/GUI_App.hpp | 4 +- src/slic3r/GUI/MainFrame.cpp | 8 +- src/slic3r/GUI/MainFrame.hpp | 6 + src/slic3r/GUI/Plater.cpp | 2 +- src/slic3r/GUI/PrintHostDialogs.cpp | 108 ++++++++++++- src/slic3r/GUI/PrintHostDialogs.hpp | 51 ++++-- src/slic3r/Utils/Duet.cpp | 166 ++++++++++---------- src/slic3r/Utils/Duet.hpp | 5 +- src/slic3r/Utils/Http.hpp | 2 +- src/slic3r/Utils/OctoPrint.cpp | 60 +++---- src/slic3r/Utils/OctoPrint.hpp | 5 +- src/slic3r/Utils/PrintHost.cpp | 142 ++++++++++++++--- src/slic3r/Utils/PrintHost.hpp | 15 +- 16 files changed, 450 insertions(+), 210 deletions(-) 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/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/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/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index aa5634fec..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 @@ -375,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 aeda2e8c2..a8a75fc3f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3144,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/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; From 6411ab5b63f3ba88a5d59f8d792b4565cfe5b602 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 17 Dec 2018 18:04:23 +0100 Subject: [PATCH 3/6] Fix of SPE-607 Part changed to Support enforcer is not sliced with one another part --- src/libslic3r/Print.cpp | 17 ++++++++++------- src/libslic3r/Print.hpp | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index e3382573c..df50012b9 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1052,10 +1052,12 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co goto print_object_end; } else { this_region_config = region_config_from_model_volume(m_default_region_config, volume, num_extruders); - for (size_t i = 0; i < region_id; ++ i) - if (m_regions[i]->config().equals(this_region_config)) - // Regions were merged. Reset this print_object. - goto print_object_end; + for (size_t i = 0; i < region_id; ++i) { + const PrintRegion ®ion_other = *m_regions[i]; + if (region_other.m_refcnt != 0 && region_other.config().equals(this_region_config)) + // Regions were merged. Reset this print_object. + goto print_object_end; + } this_region_config_set = true; } } @@ -1102,9 +1104,10 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co // Find an existing print region with the same config. int idx_empty_slot = -1; for (int i = 0; i < (int)m_regions.size(); ++ i) { - if (m_regions[i]->m_refcnt == 0) - idx_empty_slot = i; - else if (config.equals(m_regions[i]->config())) { + if (m_regions[i]->m_refcnt == 0) { + if (idx_empty_slot == -1) + idx_empty_slot = i; + } else if (config.equals(m_regions[i]->config())) { region_id = i; break; } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 1b79ef295..ad32f42ba 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -107,8 +107,8 @@ public: // adds region_id, too, if necessary void add_region_volume(unsigned int region_id, int volume_id) { if (region_id >= region_volumes.size()) - region_volumes.resize(region_id + 1); - region_volumes[region_id].push_back(volume_id); + region_volumes.assign(region_id + 1, std::vector()); + region_volumes[region_id].emplace_back(volume_id); } // This is the *total* layer count (including support layers) // this value is not supposed to be compared with Layer::id From 01edb23ffc6a526d59f011ebf1e29a4ddd85c185 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 17 Dec 2018 15:44:30 +0100 Subject: [PATCH 4/6] Fixed the enabling of the "split to objects/parts" buttons --- src/slic3r/GUI/Plater.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a8a75fc3f..6544da789 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1033,6 +1033,7 @@ private: bool can_decrease_instances() const; bool can_split_to_objects() const; bool can_split_to_volumes() const; + bool can_split() const; bool layers_height_allowed() const; bool can_delete_all() const; bool can_arrange() const; @@ -1652,8 +1653,8 @@ void Plater::priv::selection_changed() view3D->enable_toolbar_item("delete", can_delete_object()); view3D->enable_toolbar_item("more", can_increase_instances()); view3D->enable_toolbar_item("fewer", can_decrease_instances()); - view3D->enable_toolbar_item("splitobjects", can_split_to_objects()); - view3D->enable_toolbar_item("splitvolumes", can_split_to_volumes()); + view3D->enable_toolbar_item("splitobjects", can_split/*_to_objects*/()); + view3D->enable_toolbar_item("splitvolumes", can_split/*_to_volumes*/()); view3D->enable_toolbar_item("layersediting", layers_height_allowed()); // forces a frame render to update the view (to avoid a missed update if, for example, the context menu appears) view3D->render(); @@ -1661,8 +1662,8 @@ void Plater::priv::selection_changed() this->canvas3D->enable_toolbar_item("delete", can_delete_object()); this->canvas3D->enable_toolbar_item("more", can_increase_instances()); this->canvas3D->enable_toolbar_item("fewer", can_decrease_instances()); - this->canvas3D->enable_toolbar_item("splitobjects", can_split_to_objects()); - this->canvas3D->enable_toolbar_item("splitvolumes", can_split_to_volumes()); + this->canvas3D->enable_toolbar_item("splitobjects", can_split/*_to_objects*/()); + this->canvas3D->enable_toolbar_item("splitvolumes", can_split/*_to_volumes*/()); this->canvas3D->enable_toolbar_item("layersediting", layers_height_allowed()); // forces a frame render to update the view (to avoid a missed update if, for example, the context menu appears) this->canvas3D->render(); @@ -2454,8 +2455,8 @@ void Plater::priv::on_action_layersediting(SimpleEvent&) void Plater::priv::on_object_select(SimpleEvent& evt) { - selection_changed(); wxGetApp().obj_list()->update_selections(); + selection_changed(); } void Plater::priv::on_viewport_changed(SimpleEvent& evt) @@ -2605,9 +2606,9 @@ bool Plater::priv::complit_init_object_menu() // ui updates needs to be binded to the parent panel if (q != nullptr) { - q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split_to_objects() || can_split_to_volumes()); }, item_split->GetId()); - q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split_to_objects()); }, item_split_objects->GetId()); - q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split_to_volumes()); }, item_split_volumes->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split/*_to_objects() || can_split_to_volumes*/()); }, item_split->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split/*_to_objects*/()); }, item_split_objects->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split/*_to_volumes*/()); }, item_split_volumes->GetId()); } return true; } @@ -2626,7 +2627,7 @@ bool Plater::priv::complit_init_sla_object_menu() // ui updates needs to be binded to the parent panel if (q != nullptr) { - q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split_to_objects()); }, item_split->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split/*_to_objects*/()); }, item_split->GetId()); } return true; @@ -2645,7 +2646,7 @@ bool Plater::priv::complit_init_part_menu() // ui updates needs to be binded to the parent panel if (q != nullptr) { - q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split_to_volumes()); }, item_split->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split/*_to_volumes*/()); }, item_split->GetId()); } return true; @@ -2763,6 +2764,13 @@ bool Plater::priv::can_split_to_volumes() const return sidebar->obj_list()->is_splittable(); } +bool Plater::priv::can_split() const +{ + if (printer_technology == ptSLA) + return false; + return sidebar->obj_list()->is_splittable(); +} + bool Plater::priv::layers_height_allowed() const { int obj_idx = get_selected_object_idx(); From 17164ee33312cd01f374df004b3bb5f23e184c55 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 17 Dec 2018 18:07:31 +0100 Subject: [PATCH 5/6] Fixed #1225 (Added "Parameter validation" for "mm or %" values) + fixed correct updating of the "Contact Z distance" parameter --- src/slic3r/GUI/Field.cpp | 43 +++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index d4126d933..fc428ac0d 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -163,9 +163,31 @@ void Field::get_value_by_opt_type(wxString& str) break; } case coString: case coStrings: - case coFloatOrPercent: - m_value = str.ToStdString(); - break; + case coFloatOrPercent: { + if (m_opt.type == coFloatOrPercent && !str.IsEmpty() && str.Last() != '%') + { + double val; + if (!str.ToCDouble(&val)) + { + show_error(m_parent, _(L("Input value contains incorrect symbol(s).\nUse, please, only digits"))); + set_value(double_to_string(val), true); + } + else if (val > 1) + { + const int nVal = int(val); + wxString msg_text = wxString::Format(_(L("Do you mean %d%% instead of %dmm?\n" + "Select YES if you want to change this value to %d%%, \n" + "or NO if you are sure that %dmm is a correct value.")), nVal, nVal, nVal, nVal); + auto dialog = new wxMessageDialog(m_parent, msg_text, _(L("Parameter validation")), wxICON_WARNING | wxYES | wxNO); + if (dialog->ShowModal() == wxID_YES) { + set_value(wxString::Format("%s%%", str), true); + str += "%%"; + } + } + } + + m_value = str.ToStdString(); + break; } default: break; } @@ -611,9 +633,7 @@ boost::any& Choice::get_value() if (m_opt_id == rp_option) return m_value = boost::any(ret_str); - if (m_opt.type != coEnum) - /*m_value = */get_value_by_opt_type(ret_str); - else + if (m_opt.type == coEnum) { int ret_enum = static_cast(window)->GetSelection(); if (m_opt_id.compare("external_fill_pattern") == 0) @@ -640,7 +660,16 @@ boost::any& Choice::get_value() m_value = static_cast(ret_enum); else if (m_opt_id.compare("display_orientation") == 0) m_value = static_cast(ret_enum); - } + } + else if (m_opt.gui_type == "f_enum_open") { + const int ret_enum = static_cast(window)->GetSelection(); + if (ret_enum < 0 || m_opt.enum_values.empty()) + get_value_by_opt_type(ret_str); + else + m_value = m_opt.enum_values[ret_enum]; + } + else + get_value_by_opt_type(ret_str); return m_value; } From 2c63af5dd9e21dd6465bffa0475929697809f6e1 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 17 Dec 2018 19:46:36 +0100 Subject: [PATCH 6/6] Fix of SPE-607 --- src/libslic3r/Print.cpp | 6 ++++-- src/libslic3r/Print.hpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index df50012b9..0a1bac4e4 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1095,8 +1095,10 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co bool fresh = print_object.region_volumes.empty(); unsigned int volume_id = 0; for (const ModelVolume *volume : model_object.volumes) { - if (! volume->is_model_part() && ! volume->is_modifier()) - continue; + if (! volume->is_model_part() && ! volume->is_modifier()) { + ++ volume_id; + continue; + } int region_id = -1; if (&print_object == &print_object0) { // Get the config applied to this volume. diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index ad32f42ba..e5060cb76 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -107,7 +107,7 @@ public: // adds region_id, too, if necessary void add_region_volume(unsigned int region_id, int volume_id) { if (region_id >= region_volumes.size()) - region_volumes.assign(region_id + 1, std::vector()); + region_volumes.resize(region_id + 1); region_volumes[region_id].emplace_back(volume_id); } // This is the *total* layer count (including support layers)