diff --git a/resources/icons/toolbar_background.png b/resources/icons/toolbar_background.png new file mode 100644 index 0000000000..2b5ea013be Binary files /dev/null and b/resources/icons/toolbar_background.png differ diff --git a/resources/icons/view_toolbar.png b/resources/icons/view_toolbar.png index 26202a2a42..dd1f5aca49 100644 Binary files a/resources/icons/view_toolbar.png and b/resources/icons/view_toolbar.png differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d714eb91a7..31c8013796 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -46,7 +46,18 @@ endif() add_subdirectory(libslic3r) if (SLIC3R_GUI) - message(STATUS "WXWIN environment set to: $ENV{WXWIN}") + if(WIN32) + message(STATUS "WXWIN environment set to: $ENV{WXWIN}") + elseif(UNIX) + message(STATUS "wx-config path: ${wxWidgets_CONFIG_EXECUTABLE}") + set(wxWidgets_USE_UNICODE ON) + if(SLIC3R_STATIC) + set(wxWidgets_USE_STATIC ON) + else() + set(wxWidgets_USE_STATIC OFF) + endif() + endif() + find_package(wxWidgets REQUIRED COMPONENTS base core adv html gl) include(${wxWidgets_USE_FILE}) endif() diff --git a/src/eigen/unsupported/Eigen/SparseExtra b/src/eigen/unsupported/Eigen/SparseExtra new file mode 100644 index 0000000000..819cffa275 --- /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 0000000000..e9ec746e3b --- /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 0000000000..0e8350a7db --- /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 0000000000..037a13f86a --- /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 0000000000..41e4af4a4e --- /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 0000000000..02916ea6f0 --- /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 0000000000..ee97299af9 --- /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/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt index 163143bcbf..1faf542ddc 100644 --- a/src/libnest2d/CMakeLists.txt +++ b/src/libnest2d/CMakeLists.txt @@ -68,7 +68,7 @@ if(TBB_FOUND) target_compile_definitions(libnest2d INTERFACE -D__TBB_NO_IMPLICIT_LINKAGE) endif() # The Intel TBB library will use the std::exception_ptr feature of C++11. - target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=1) + target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0) target_link_libraries(libnest2d INTERFACE tbb) else() diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index df3ddb6de0..7870bd1fb5 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -194,6 +194,10 @@ target_link_libraries(libslic3r tbb ) +if(WIN32) + target_link_libraries(libslic3r Psapi.lib) +endif() + if(SLIC3R_PROFILE) target_link_libraries(slic3r Shiny) endif() diff --git a/src/libslic3r/Channel.hpp b/src/libslic3r/Channel.hpp index 8d1a07d358..9cf025f2c8 100644 --- a/src/libslic3r/Channel.hpp +++ b/src/libslic3r/Channel.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_Channel_hpp_ #define slic3r_Channel_hpp_ +#include #include #include #include @@ -13,32 +14,26 @@ namespace Slic3r { template class Channel { -private: - using UniqueLock = std::unique_lock; - using Queue = std::deque; public: - class Guard + using UniqueLock = std::unique_lock; + + template class Unlocker { public: - Guard(UniqueLock lock, const Queue &queue) : m_lock(std::move(lock)), m_queue(queue) {} - Guard(const Guard &other) = delete; - Guard(Guard &&other) = delete; - ~Guard() {} + Unlocker(UniqueLock lock) : m_lock(std::move(lock)) {} + Unlocker(const Unlocker &other) noexcept : m_lock(std::move(other.m_lock)) {} // XXX: done beacuse of MSVC 2013 not supporting init of deleter by move + Unlocker(Unlocker &&other) noexcept : m_lock(std::move(other.m_lock)) {} + Unlocker& operator=(const Unlocker &other) = delete; + Unlocker& operator=(Unlocker &&other) { m_lock = std::move(other.m_lock); } - // Access trampolines - size_t size() const noexcept { return m_queue.size(); } - bool empty() const noexcept { return m_queue.empty(); } - typename Queue::const_iterator begin() const noexcept { return m_queue.begin(); } - typename Queue::const_iterator end() const noexcept { return m_queue.end(); } - typename Queue::const_reference operator[](size_t i) const { return m_queue[i]; } - - Guard& operator=(const Guard &other) = delete; - Guard& operator=(Guard &&other) = delete; + void operator()(Ptr*) { m_lock.unlock(); } private: - UniqueLock m_lock; - const Queue &m_queue; + mutable UniqueLock m_lock; // XXX: mutable: see above }; + using Queue = std::deque; + using LockedConstPtr = std::unique_ptr>; + using LockedPtr = std::unique_ptr>; Channel() {} ~Channel() {} @@ -56,7 +51,7 @@ public: { { UniqueLock lock(m_mutex); - m_queue.push_back(std::forward(item)); + m_queue.push_back(std::forward(item)); } if (! silent) { m_condition.notify_one(); } } @@ -82,19 +77,22 @@ public: } } - // Unlocked observers - // Thread unsafe! Keep in mind you need to re-verify the result after acquiring lock! - size_t size() const noexcept { return m_queue.size(); } - bool empty() const noexcept { return m_queue.empty(); } + // Unlocked observers/hints + // Thread unsafe! Keep in mind you need to re-verify the result after locking! + size_t size_hint() const noexcept { return m_queue.size(); } - Guard read() const + LockedConstPtr lock_read() const { - return Guard(UniqueLock(m_mutex), m_queue); + return LockedConstPtr(&m_queue, Unlocker(UniqueLock(m_mutex))); } + LockedPtr lock_rw() + { + return LockedPtr(&m_queue, Unlocker(UniqueLock(m_mutex))); + } private: Queue m_queue; - std::mutex m_mutex; + mutable std::mutex m_mutex; std::condition_variable m_condition; }; diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp index f79721be71..80aae75cf6 100644 --- a/src/libslic3r/Format/PRUS.cpp +++ b/src/libslic3r/Format/PRUS.cpp @@ -96,7 +96,6 @@ static void extract_model_from_archive( const char *model_xml = strstr(scene_xml_data.data(), model_name_tag); const char *zero_tag = ""; const char *zero_xml = strstr(scene_xml_data.data(), zero_tag); - float trafo[3][4] = { 0 }; Vec3d instance_rotation = Vec3d::Zero(); Vec3d instance_scaling_factor = Vec3d::Ones(); Vec3d instance_offset = Vec3d::Zero(); @@ -124,19 +123,7 @@ static void extract_model_from_archive( "[%f, %f, %f]", zero, zero+1, zero+2) == 3) { instance_scaling_factor = Vec3d((double)scale[0], (double)scale[1], (double)scale[2]); instance_rotation = Vec3d(-(double)rotation[0], -(double)rotation[1], -(double)rotation[2]); - Eigen::Matrix3f mat_rot, mat_scale, mat_trafo; - mat_rot = Eigen::AngleAxisf(-rotation[2], Eigen::Vector3f::UnitZ()) * - Eigen::AngleAxisf(-rotation[1], Eigen::Vector3f::UnitY()) * - Eigen::AngleAxisf(-rotation[0], Eigen::Vector3f::UnitX()); - mat_scale = Eigen::Scaling(scale[0], scale[1], scale[2]); - mat_trafo = mat_rot * mat_scale; - for (size_t r = 0; r < 3; ++ r) { - for (size_t c = 0; c < 3; ++ c) - trafo[r][c] += mat_trafo(r, c); - } instance_offset = Vec3d((double)(position[0] - zero[0]), (double)(position[1] - zero[1]), (double)(position[2] - zero[2])); - // CHECK_ME -> Is the following correct ? - trafo[2][3] = position[2] / (float)instance_scaling_factor(2); trafo_set = true; } const char *group_tag = ""; @@ -189,8 +176,6 @@ static void extract_model_from_archive( // All the faces have been read. stl_get_size(&stl); mesh.repair(); - // Transform the model. - stl_transform(&stl, &trafo[0][0]); if (std::abs(stl.stats.min(2)) < EPSILON) stl.stats.min(2) = 0.; // Add a mesh to a model. @@ -274,8 +259,6 @@ static void extract_model_from_archive( memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50); stl_get_size(&stl); mesh.repair(); - // Transform the model. - stl_transform(&stl, &trafo[0][0]); // Add a mesh to a model. if (mesh.facets_count() > 0) mesh_valid = true; @@ -329,7 +312,7 @@ bool load_prus(const char *path, Model *model) if (! mz_zip_reader_file_stat(&archive, i, &stat)) continue; std::vector buffer; - buffer.assign((size_t)stat.m_uncomp_size + 1, 0); + buffer.assign((size_t)stat.m_uncomp_size, 0); res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (char*)buffer.data(), (size_t)stat.m_uncomp_size, 0); if (res == MZ_FALSE) std::runtime_error(std::string("Error while extracting a file from ") + path); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7f56b5f99b..b13c387425 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -423,7 +423,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ print->set_started(psGCodeExport); - BOOST_LOG_TRIVIAL(info) << "Exporting G-code..."; + BOOST_LOG_TRIVIAL(info) << "Exporting G-code..." << log_memory_info(); // Remove the old g-code if it exists. boost::nowide::remove(path); @@ -435,9 +435,11 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ if (file == nullptr) throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); + m_enable_analyzer = preview_data != nullptr; + try { m_placeholder_parser_failed_templates.clear(); - this->_do_export(*print, file, preview_data); + this->_do_export(*print, file); fflush(file); if (ferror(file)) { fclose(file); @@ -453,15 +455,6 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ } fclose(file); - if (print->config().remaining_times.value) { - BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for normal mode"; - m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f); - if (m_silent_time_estimator_enabled) { - BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for silent mode"; - m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f); - } - } - if (! m_placeholder_parser_failed_templates.empty()) { // G-code export proceeded, but some of the PlaceholderParser substitutions failed. std::string msg = std::string("G-code export to ") + path + " failed due to invalid custom G-code sections:\n\n"; @@ -475,12 +468,30 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ throw std::runtime_error(msg); } + if (print->config().remaining_times.value) { + BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for normal mode"; + m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f); + m_normal_time_estimator.reset(); + if (m_silent_time_estimator_enabled) { + BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for silent mode"; + m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f); + m_silent_time_estimator.reset(); + } + } + + // starts analyzer calculations + if (m_enable_analyzer) { + BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data"; + m_analyzer.calc_gcode_preview_data(*preview_data); + m_analyzer.reset(); + } + if (rename_file(path_tmp, path) != 0) throw std::runtime_error( std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "Is " + path_tmp + " locked?" + '\n'); - BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished"; + BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info(); print->set_done(psGCodeExport); // Write the profiler measurements to file @@ -488,7 +499,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str()); } -void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) +void GCode::_do_export(Print &print, FILE *file) { PROFILE_FUNC(); @@ -558,7 +569,6 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) // resets analyzer m_analyzer.reset(); - m_enable_analyzer = preview_data != nullptr; // resets analyzer's tracking data m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; @@ -1034,12 +1044,6 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) _write(file, full_config); } print.throw_if_canceled(); - - // starts analyzer calculations - if (preview_data != nullptr) { - BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data"; - m_analyzer.calc_gcode_preview_data(*preview_data); - } } std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) @@ -1231,7 +1235,7 @@ void GCode::process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, - const LayerTools &layer_tools, + const LayerTools &layer_tools, // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. const size_t single_object_idx) @@ -1644,6 +1648,11 @@ void GCode::process_layer( // printf("G-code after filter:\n%s\n", out.c_str()); _write(file, gcode); + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << + ", time estimator memory: " << + format_memsize_MB(m_normal_time_estimator.memory_used() + m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0) << + ", analyzer memory: " << + format_memsize_MB(m_analyzer.memory_used()); } void GCode::apply_print_config(const PrintConfig &print_config) diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index bf65311db4..32a7057512 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -180,7 +180,7 @@ public: static void append_full_config(const Print& print, std::string& str); protected: - void _do_export(Print &print, FILE *file, GCodePreviewData *preview_data); + void _do_export(Print &print, FILE *file); // Object and support extrusions of the same PrintObject at the same print_z. struct LayerToPrint diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index c56f02753a..8212b1703d 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -4,6 +4,7 @@ #include "../libslic3r.h" #include "../PrintConfig.hpp" +#include "../Utils.hpp" #include "Print.hpp" #include "Analyzer.hpp" @@ -852,6 +853,16 @@ void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_ } } +// Return an estimate of the memory consumed by the time estimator. +size_t GCodeAnalyzer::memory_used() const +{ + size_t out = sizeof(*this); + for (const std::pair &kvp : m_moves_map) + out += sizeof(kvp) + SLIC3R_STDVEC_MEMSIZE(kvp.second, GCodeMove); + out += m_process_output.size(); + return out; +} + GCodePreviewData::Color operator + (const GCodePreviewData::Color& c1, const GCodePreviewData::Color& c2) { return GCodePreviewData::Color(clamp(0.0f, 1.0f, c1.rgba[0] + c2.rgba[0]), diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index f50138b56d..389c11cec4 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -120,6 +120,9 @@ public: // Calculates all data needed for gcode visualization void calc_gcode_preview_data(GCodePreviewData& preview_data); + // Return an estimate of the memory consumed by the time estimator. + size_t memory_used() const; + static bool is_valid_extrusion_role(ExtrusionRole role); private: diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp index 3f2df95324..d4aa9bc021 100644 --- a/src/libslic3r/GCode/PreviewData.cpp +++ b/src/libslic3r/GCode/PreviewData.cpp @@ -2,6 +2,7 @@ #include "PreviewData.hpp" #include #include +#include "Utils.hpp" #include @@ -205,6 +206,18 @@ bool GCodePreviewData::Extrusion::is_role_flag_set(unsigned int flags, Extrusion return GCodeAnalyzer::is_valid_extrusion_role(role) && (flags & (1 << (role - erPerimeter))) != 0; } +size_t GCodePreviewData::Extrusion::memory_used() const +{ + size_t out = sizeof(*this); + out += SLIC3R_STDVEC_MEMSIZE(this->layers, Layer); + for (const Layer &layer : this->layers) { + out += SLIC3R_STDVEC_MEMSIZE(layer.paths, ExtrusionPath); + for (const ExtrusionPath &path : layer.paths) + out += SLIC3R_STDVEC_MEMSIZE(path.polyline.points, Point); + } + return out; +} + const float GCodePreviewData::Travel::Default_Width = 0.075f; const float GCodePreviewData::Travel::Default_Height = 0.075f; const GCodePreviewData::Color GCodePreviewData::Travel::Default_Type_Colors[Num_Types] = @@ -224,6 +237,15 @@ void GCodePreviewData::Travel::set_default() is_visible = false; } +size_t GCodePreviewData::Travel::memory_used() const +{ + size_t out = sizeof(*this); + out += SLIC3R_STDVEC_MEMSIZE(this->polylines, Polyline); + for (const Polyline &polyline : this->polylines) + out += SLIC3R_STDVEC_MEMSIZE(polyline.polyline.points, Vec3crd); + return out; +} + const GCodePreviewData::Color GCodePreviewData::Retraction::Default_Color = GCodePreviewData::Color(1.0f, 1.0f, 1.0f, 1.0f); GCodePreviewData::Retraction::Position::Position(const Vec3crd& position, float width, float height) @@ -239,6 +261,11 @@ void GCodePreviewData::Retraction::set_default() is_visible = false; } +size_t GCodePreviewData::Retraction::memory_used() const +{ + return sizeof(*this) + SLIC3R_STDVEC_MEMSIZE(this->positions, Position); +} + void GCodePreviewData::Shell::set_default() { is_visible = false; @@ -483,4 +510,15 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: return items; } +// Return an estimate of the memory consumed by the time estimator. +size_t GCodePreviewData::memory_used() const +{ + return + this->extrusion.memory_used() + + this->travel.memory_used() + + this->retraction.memory_used() + + this->unretraction.memory_used() + + sizeof(shell) + sizeof(ranges); +} + } // namespace Slic3r diff --git a/src/libslic3r/GCode/PreviewData.hpp b/src/libslic3r/GCode/PreviewData.hpp index 9f882788d0..8ed5e91c73 100644 --- a/src/libslic3r/GCode/PreviewData.hpp +++ b/src/libslic3r/GCode/PreviewData.hpp @@ -99,6 +99,9 @@ public: void set_default(); bool is_role_flag_set(ExtrusionRole role) const; + // Return an estimate of the memory consumed by the time estimator. + size_t memory_used() const; + static bool is_role_flag_set(unsigned int flags, ExtrusionRole role); }; @@ -144,6 +147,9 @@ public: size_t color_print_idx; void set_default(); + + // Return an estimate of the memory consumed by the time estimator. + size_t memory_used() const; }; struct Retraction @@ -166,6 +172,9 @@ public: bool is_visible; void set_default(); + + // Return an estimate of the memory consumed by the time estimator. + size_t memory_used() const; }; struct Shell @@ -199,6 +208,9 @@ public: std::string get_legend_title() const; LegendItemsList get_legend_items(const std::vector& tool_colors, const std::vector>& cp_values) const; + + // Return an estimate of the memory consumed by the time estimator. + size_t memory_used() const; }; GCodePreviewData::Color operator + (const GCodePreviewData::Color& c1, const GCodePreviewData::Color& c2); diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 5c24cd71cd..f87969505e 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -315,6 +315,20 @@ public: return *this; }; + // Let the firmware back up the active speed override value. + Writer& speed_override_backup() + { + m_gcode += "M220 B\n"; + return *this; + }; + + // Let the firmware restore the active speed override value. + Writer& speed_override_restore() + { + m_gcode += "M220 R\n"; + return *this; + }; + // Set digital trimpot motor Writer& set_extruder_trimpot(int current) { @@ -473,7 +487,6 @@ WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *nam return INVALID; } - // Returns gcode to prime the nozzles at the front edge of the print bed. WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // print_z of the first layer. @@ -501,12 +514,15 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( .set_initial_tool(m_current_tool) .append(";--------------------\n" "; CP PRIMING START\n") - .append(";--------------------\n") - .speed_override(100); + .append(";--------------------\n"); + if (m_retain_speed_override) + writer.speed_override_backup(); + writer.speed_override(100); writer.set_initial_position(xy(0.f, 0.f)) // Always move to the starting position - .travel(cleaning_box.ld, 7200) - .set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. + .travel(cleaning_box.ld, 7200); + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { unsigned int tool = tools[idx_tool]; @@ -533,8 +549,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // in the output gcode - we should not remember emitting them (we will output them twice in the worst case) // Reset the extruder current to a normal value. - writer.set_extruder_trimpot(550) - .feedrate(6000) + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); + if (m_retain_speed_override) + writer.speed_override_restore(); + writer.feedrate(6000) .flush_planner_queue() .reset_extruder() .append("; CP PRIMING END\n" @@ -600,14 +619,17 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo "; CP TOOLCHANGE START\n") .comment_with_value(" toolchange #", m_num_tool_changes + 1) // the number is zero-based .comment_material(m_filpar[m_current_tool].material) - .append(";--------------------\n") - .speed_override(100); + .append(";--------------------\n"); + if (m_retain_speed_override) + writer.speed_override_backup(); + writer.speed_override(100); xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); // Increase the extruder driver current to allow fast ramming. - writer.set_extruder_trimpot(750); + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. if (tool != (unsigned int)-1){ // This is not the last change. @@ -635,8 +657,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo } } - writer.set_extruder_trimpot(550) // Reset the extruder current to a normal value. - .feedrate(6000) + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); // Reset the extruder current to a normal value. + if (m_retain_speed_override) + writer.speed_override_restore(); + writer.feedrate(6000) .flush_planner_queue() .reset_extruder() .append("; CP TOOLCHANGE END\n" @@ -881,14 +906,15 @@ void WipeTowerPrusaMM::toolchange_Change( case FLEX: speed_override = 35; break; default: speed_override = 100; } - writer.set_tool(new_tool) - .speed_override(speed_override) - .flush_planner_queue(); + writer.set_tool(new_tool); + if (m_retain_speed_override) + assert(speed_override == 100); + else + writer.speed_override(speed_override); + writer.flush_planner_queue(); m_current_tool = new_tool; } - - void WipeTowerPrusaMM::toolchange_Load( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box) @@ -916,12 +942,10 @@ void WipeTowerPrusaMM::toolchange_Load( .resume_preview(); // Reset the extruder current to the normal value. - writer.set_extruder_trimpot(550); + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); } - - - // Wipe the newly loaded filament until the end of the assigned wipe area. void WipeTowerPrusaMM::toolchange_Wipe( PrusaMultiMaterial::Writer &writer, diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index 06625d189f..70c9526e60 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -44,7 +44,8 @@ public: // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) // wipe_area -- space available for one toolchange in mm WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction, - float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging, + float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, + float bridging, bool set_extruder_trimpot, const std::vector>& wiping_matrix, unsigned int initial_tool) : m_wipe_tower_pos(x, y), m_wipe_tower_width(width), @@ -57,6 +58,7 @@ public: m_parking_pos_retraction(parking_pos_retraction), m_extra_loading_move(extra_loading_move), m_bridging(bridging), + m_set_extruder_trimpot(set_extruder_trimpot), m_current_tool(initial_tool), wipe_volumes(wiping_matrix) {} @@ -73,6 +75,11 @@ public: m_filpar.push_back(FilamentParameters()); m_filpar[idx].material = material; + if (material == FLEX || material == SCAFF || material == PVA) { + // MMU2 lowers the print speed using the speed override (M220) for printing of soluble PVA/BVOH and flex materials. + // Therefore it does not make sense to use the new M220 B and M220 R (backup / restore). + m_retain_speed_override = false; + } m_filpar[idx].temperature = temp; m_filpar[idx].first_layer_temperature = first_layer_temp; m_filpar[idx].loading_speed = loading_speed; @@ -212,6 +219,8 @@ private: float m_parking_pos_retraction = 0.f; float m_extra_loading_move = 0.f; float m_bridging = 0.f; + bool m_set_extruder_trimpot = false; + bool m_retain_speed_override = true; bool m_adhesion = true; float m_perimeter_width = 0.4 * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill. diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index 4bfd5d63fe..461b4cd356 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -290,7 +290,8 @@ namespace Slic3r { // buffer line to export only when greater than 64K to reduce writing calls std::string export_line; char time_line[64]; - while (std::getline(in, gcode_line)) + G1LineIdToBlockIdMap::const_iterator it_line_id = _g1_line_ids.begin(); + while (std::getline(in, gcode_line)) { if (!in.good()) { @@ -310,29 +311,29 @@ namespace Slic3r { // add remaining time lines where needed _parser.parse_line(gcode_line, - [this, &g1_lines_count, &last_recorded_time, &time_line, &gcode_line, time_mask, interval](GCodeReader& reader, const GCodeReader::GCodeLine& line) + [this, &it_line_id, &g1_lines_count, &last_recorded_time, &time_line, &gcode_line, time_mask, interval](GCodeReader& reader, const GCodeReader::GCodeLine& line) { if (line.cmd_is("G1")) { ++g1_lines_count; - if (!line.has_e()) - return; + assert(it_line_id == _g1_line_ids.end() || it_line_id->first >= g1_lines_count); - G1LineIdToBlockIdMap::const_iterator it = _g1_line_ids.find(g1_lines_count); - if ((it != _g1_line_ids.end()) && (it->second < (unsigned int)_blocks.size())) - { - const Block& block = _blocks[it->second]; - if (block.elapsed_time != -1.0f) + const Block *block = nullptr; + if (it_line_id != _g1_line_ids.end() && it_line_id->first == g1_lines_count) { + if (line.has_e() && it_line_id->second < (unsigned int)_blocks.size()) + block = &_blocks[it_line_id->second]; + ++it_line_id; + } + + if (block != nullptr && block->elapsed_time != -1.0f) { + float block_remaining_time = _time - block->elapsed_time; + if (std::abs(last_recorded_time - block_remaining_time) > interval) { - float block_remaining_time = _time - block.elapsed_time; - if (std::abs(last_recorded_time - block_remaining_time) > interval) - { - sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block.elapsed_time / _time)).c_str(), _get_time_minutes(block_remaining_time).c_str()); - gcode_line += time_line; + sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block->elapsed_time / _time)).c_str(), _get_time_minutes(block_remaining_time).c_str()); + gcode_line += time_line; - last_recorded_time = block_remaining_time; - } + last_recorded_time = block_remaining_time; } } } @@ -667,6 +668,15 @@ namespace Slic3r { return _get_time_minutes(get_time()); } + // Return an estimate of the memory consumed by the time estimator. + size_t GCodeTimeEstimator::memory_used() const + { + size_t out = sizeof(*this); + out += SLIC3R_STDVEC_MEMSIZE(this->_blocks, Block); + out += SLIC3R_STDVEC_MEMSIZE(this->_g1_line_ids, G1LineIdToBlockId); + return out; + } + void GCodeTimeEstimator::_reset() { _curr.reset(); @@ -1072,7 +1082,7 @@ namespace Slic3r { // adds block to blocks list _blocks.emplace_back(block); - _g1_line_ids.insert(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)_blocks.size() - 1)); + _g1_line_ids.emplace_back(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)_blocks.size() - 1)); } void GCodeTimeEstimator::_processG4(const GCodeReader::GCodeLine& line) @@ -1223,7 +1233,8 @@ namespace Slic3r { return; // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate - float factor = (dialect == gcfMarlin) ? 1.0f : MMMIN_TO_MMSEC; + // http://smoothieware.org/supported-g-codes + float factor = (dialect == gcfMarlin || dialect == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; if (line.has_x()) set_axis_max_feedrate(X, line.x() * factor); diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp index e9da584c31..ef91d5ff1d 100644 --- a/src/libslic3r/GCodeTimeEstimator.hpp +++ b/src/libslic3r/GCodeTimeEstimator.hpp @@ -209,7 +209,8 @@ namespace Slic3r { typedef std::map MovesStatsMap; #endif // ENABLE_MOVE_STATS - typedef std::map G1LineIdToBlockIdMap; + typedef std::pair G1LineIdToBlockId; + typedef std::vector G1LineIdToBlockIdMap; private: EMode _mode; @@ -338,6 +339,9 @@ namespace Slic3r { // Returns the estimated time, in minutes (integer) std::string get_time_minutes() const; + // Return an estimate of the memory consumed by the time estimator. + size_t memory_used() const; + private: void _reset(); void _reset_time(); diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 7878bffabb..fa5d296924 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -17,6 +17,16 @@ Layer::~Layer() m_regions.clear(); } +// Test whether whether there are any slices assigned to this layer. +bool Layer::empty() const +{ + for (const LayerRegion *layerm : m_regions) + if (layerm != nullptr && ! layerm->slices.empty()) + // Non empty layer. + return false; + return true; +} + LayerRegion* Layer::add_region(PrintRegion* print_region) { m_regions.emplace_back(new LayerRegion(this, print_region)); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 8dbe850ccc..78897a2dbb 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -114,7 +114,8 @@ public: LayerRegion* get_region(int idx) { return m_regions[idx]; } LayerRegion* add_region(PrintRegion* print_region); const LayerRegionPtrs& regions() const { return m_regions; } - + // Test whether whether there are any slices assigned to this layer. + bool empty() const; void make_slices(); void merge_slices(); template bool any_internal_region_slice_contains(const T &item) const { diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 35cfa2b760..02f1cb7c2b 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -34,23 +34,22 @@ bool Line::intersection_infinite(const Line &other, Point* point) const return true; } -/* distance to the closest point of line */ -double Line::distance_to(const Point &point) const +// Distance to the closest point of line. +double Line::distance_to_squared(const Point &point, const Point &a, const Point &b) { - const Line &line = *this; - const Vec2d v = (line.b - line.a).cast(); - const Vec2d va = (point - line.a).cast(); + const Vec2d v = (b - a).cast(); + const Vec2d va = (point - a).cast(); const double l2 = v.squaredNorm(); // avoid a sqrt if (l2 == 0.0) - // line.a == line.b case - return va.norm(); - // Consider the line extending the segment, parameterized as line.a + t (line.b - line.a). + // a == b case + return va.squaredNorm(); + // Consider the line extending the segment, parameterized as a + t (b - a). // We find projection of this point onto the line. - // It falls where t = [(this-line.a) . (line.b-line.a)] / |line.b-line.a|^2 + // It falls where t = [(this-a) . (b-a)] / |b-a|^2 const double t = va.dot(v) / l2; - if (t < 0.0) return va.norm(); // beyond the 'a' end of the segment - else if (t > 1.0) return (point - line.b).cast().norm(); // beyond the 'b' end of the segment - return (t * v - va).norm(); + if (t < 0.0) return va.squaredNorm(); // beyond the 'a' end of the segment + else if (t > 1.0) return (point - b).cast().squaredNorm(); // beyond the 'b' end of the segment + return (t * v - va).squaredNorm(); } double Line::perp_distance_to(const Point &point) const diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 36e02247ce..559ca946a8 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -31,7 +31,8 @@ public: Point midpoint() const { return (this->a + this->b) / 2; } bool intersection_infinite(const Line &other, Point* point) const; bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; } - double distance_to(const Point &point) const; + double distance_to_squared(const Point &point) const { return distance_to_squared(point, this->a, this->b); } + double distance_to(const Point &point) const { return distance_to(point, this->a, this->b); } double perp_distance_to(const Point &point) const; bool parallel_to(double angle) const; bool parallel_to(const Line &line) const { return this->parallel_to(line.direction()); } @@ -43,6 +44,9 @@ public: bool intersection(const Line& line, Point* intersection) const; double ccw(const Point& point) const { return point.ccw(*this); } + static double distance_to_squared(const Point &point, const Point &a, const Point &b); + static double distance_to(const Point &point, const Point &a, const Point &b) { return sqrt(distance_to_squared(point, a, b)); } + Point a; Point b; }; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 07f1b98c10..e56a4b6e10 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -809,6 +809,25 @@ TriangleMesh ModelObject::raw_mesh() const return mesh; } +// Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. +TriangleMesh ModelObject::full_raw_mesh() const +{ + TriangleMesh mesh; + for (const ModelVolume *v : this->volumes) +#if ENABLE_MODELVOLUME_TRANSFORM + { + TriangleMesh vol_mesh(v->mesh); + vol_mesh.transform(v->get_matrix()); + mesh.merge(vol_mesh); + } +#else + { + mesh.merge(v->mesh); + } +#endif // ENABLE_MODELVOLUME_TRANSFORM + return mesh; +} + // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. // This bounding box is only used for the actual slicing. BoundingBoxf3 ModelObject::raw_bounding_box() const @@ -964,6 +983,16 @@ void ModelObject::mirror(Axis axis) this->invalidate_bounding_box(); } +void ModelObject::scale_mesh(const Vec3d &versor) +{ + for (ModelVolume *v : this->volumes) + { + v->scale_geometry(versor); + v->set_offset(versor.cwiseProduct(v->get_offset())); + } + this->invalidate_bounding_box(); +} + size_t ModelObject::materials_count() const { std::set material_ids; @@ -1495,6 +1524,12 @@ void ModelVolume::mirror(Axis axis) #endif // ENABLE_MODELVOLUME_TRANSFORM } +void ModelVolume::scale_geometry(const Vec3d& versor) +{ + mesh.scale(versor); + m_convex_hull.scale(versor); +} + #if !ENABLE_MODELVOLUME_TRANSFORM void ModelInstance::set_rotation(const Vec3d& rotation) { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index b02862203d..c26eb0f7a7 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -218,6 +218,8 @@ public: // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. // Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D platter. TriangleMesh raw_mesh() const; + // Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. + TriangleMesh full_raw_mesh() const; // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. // This bounding box is only used for the actual slicing. BoundingBoxf3 raw_bounding_box() const; @@ -235,6 +237,9 @@ public: void rotate(double angle, Axis axis); void rotate(double angle, const Vec3d& axis); void mirror(Axis axis); + + void scale_mesh(const Vec3d& versor); + size_t materials_count() const; size_t facets_count() const; bool needed_repair() const; @@ -329,6 +334,8 @@ public: void rotate(double angle, const Vec3d& axis); void mirror(Axis axis); + void scale_geometry(const Vec3d& versor); + #if ENABLE_MODELVOLUME_TRANSFORM // translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box void center_geometry(); diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index 93c63c4cf6..795ee38c29 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -162,45 +162,51 @@ bool MultiPoint::first_intersection(const Line& line, Point* intersection) const return found; } -//FIXME This is very inefficient in term of memory use. -// The recursive algorithm shall run in place, not allocating temporary data in each recursion. -Points -MultiPoint::_douglas_peucker(const Points &points, const double tolerance) +std::vector MultiPoint::_douglas_peucker(const std::vector& pts, const double tolerance) { - assert(points.size() >= 2); - Points results; - double dmax = 0; - size_t index = 0; - Line full(points.front(), points.back()); - for (Points::const_iterator it = points.begin() + 1; it != points.end(); ++it) { - // we use shortest distance, not perpendicular distance - double d = full.distance_to(*it); - if (d > dmax) { - index = it - points.begin(); - dmax = d; + std::vector result_pts; + if (! pts.empty()) { + const Point *anchor = &pts.front(); + size_t anchor_idx = 0; + const Point *floater = &pts.back(); + size_t floater_idx = pts.size() - 1; + result_pts.reserve(pts.size()); + result_pts.emplace_back(*anchor); + if (anchor_idx != floater_idx) { + assert(pts.size() > 1); + std::vector dpStack; + dpStack.reserve(pts.size()); + dpStack.emplace_back(floater_idx); + for (;;) { + double max_distSq = 0.0; + size_t furthest_idx = anchor_idx; + // find point furthest from line seg created by (anchor, floater) and note it + for (size_t i = anchor_idx + 1; i < floater_idx; ++ i) { + double dist = Line::distance_to_squared(pts[i], *anchor, *floater); + if (dist > max_distSq) { + max_distSq = dist; + furthest_idx = i; + } + } + // remove point if less than tolerance + if (max_distSq <= tolerance) { + result_pts.emplace_back(*floater); + anchor_idx = floater_idx; + anchor = floater; + assert(dpStack.back() == floater_idx); + dpStack.pop_back(); + if (dpStack.empty()) + break; + floater_idx = dpStack.back(); + } else { + floater_idx = furthest_idx; + dpStack.emplace_back(floater_idx); + } + floater = &pts[floater_idx]; + } } } - if (dmax >= tolerance) { - Points dp0; - dp0.reserve(index + 1); - dp0.insert(dp0.end(), points.begin(), points.begin() + index + 1); - // Recursive call. - Points dp1 = MultiPoint::_douglas_peucker(dp0, tolerance); - results.reserve(results.size() + dp1.size() - 1); - results.insert(results.end(), dp1.begin(), dp1.end() - 1); - - dp0.clear(); - dp0.reserve(points.size() - index); - dp0.insert(dp0.end(), points.begin() + index, points.end()); - // Recursive call. - dp1 = MultiPoint::_douglas_peucker(dp0, tolerance); - results.reserve(results.size() + dp1.size()); - results.insert(results.end(), dp1.begin(), dp1.end()); - } else { - results.push_back(points.front()); - results.push_back(points.back()); - } - return results; + return result_pts; } // Visivalingam simplification algorithm https://github.com/slic3r/Slic3r/pull/3825 diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 77cf9c0f6e..97eb03662e 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -8,13 +8,14 @@ #include "SupportMaterial.hpp" #include "GCode.hpp" #include "GCode/WipeTowerPrusaMM.hpp" -#include -#include -#include +#include "Utils.hpp" #include "PrintExport.hpp" +#include +#include #include +#include //! macro used to mark string used at localization, //! return same string @@ -213,6 +214,7 @@ bool Print::invalidate_state_by_config_options(const std::vectorconfig().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; } } @@ -1092,8 +1096,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. @@ -1101,9 +1107,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; } @@ -1469,7 +1476,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const // Slicing process, running at a background thread. void Print::process() { - BOOST_LOG_TRIVIAL(info) << "Staring the slicing process."; + BOOST_LOG_TRIVIAL(info) << "Staring the slicing process." << log_memory_info(); for (PrintObject *obj : m_objects) obj->make_perimeters(); this->set_status(70, "Infilling layers"); @@ -1501,7 +1508,7 @@ void Print::process() } this->set_done(psWipeTower); } - BOOST_LOG_TRIVIAL(info) << "Slicing process finished."; + BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info(); } // G-code export process, running at a background thread. @@ -1768,7 +1775,8 @@ void Print::_make_wipe_tower() float(m_config.wipe_tower_width.value), float(m_config.wipe_tower_rotation_angle.value), float(m_config.cooling_tube_retraction.value), float(m_config.cooling_tube_length.value), float(m_config.parking_pos_retraction.value), - float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging), wipe_volumes, + float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging), + m_config.high_current_on_filament_swap.value, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder()); //wipe_tower.set_retract(); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 1b79ef295c..e5060cb766 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.resize(region_id + 1); + 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 diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ab26f5d544..f24d40fed8 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -925,6 +925,15 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->default_value = new ConfigOptionEnum(gcfRepRap); + def = this->add("high_current_on_filament_swap", coBool); + def->label = L("High extruder current on filament swap"); + def->tooltip = L("It may be beneficial to increase the extruder motor current during the filament exchange" + " sequence to allow for rapid ramming feed rates and to overcome resistance when loading" + " a filament with an ugly shaped tip."); + def->cli = "high-current-on-filament-swap!"; + def->mode = comExpert; + def->default_value = new ConfigOptionBool(0); + def = this->add("infill_acceleration", coFloat); def->label = L("Infill"); def->tooltip = L("This is the acceleration your printer will use for infill. Set zero to disable " @@ -2394,8 +2403,10 @@ void PrintConfigDef::init_sla_params() def->tooltip = L("Display orientation"); def->cli = "display-orientation=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); - def->enum_values.push_back("Landscape"); - def->enum_values.push_back("Portrait"); + def->enum_values.push_back("landscape"); + def->enum_values.push_back("portrait"); + def->enum_labels.push_back(L("Landscape")); + def->enum_labels.push_back(L("Portrait")); def->default_value = new ConfigOptionEnum(sladoPortrait); def = this->add("printer_correction", coFloats); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f639cad764..08f42f39ba 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -36,7 +36,7 @@ enum GCodeFlavor { }; enum PrintHostType { - htOctoPrint, htDuet, + htOctoPrint, htDuet, htSL1, }; enum InfillPattern { @@ -155,8 +155,8 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::ge template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static const t_config_enum_values keys_map = { - { "Landscape", sladoLandscape}, - { "Portrait", sladoPortrait} + { "landscape", sladoLandscape}, + { "portrait", sladoPortrait} }; return keys_map; @@ -627,6 +627,7 @@ public: ConfigOptionBool variable_layer_height; ConfigOptionFloat cooling_tube_retraction; ConfigOptionFloat cooling_tube_length; + ConfigOptionBool high_current_on_filament_swap; ConfigOptionFloat parking_pos_retraction; ConfigOptionBool remaining_times; ConfigOptionBool silent_mode; @@ -695,6 +696,7 @@ protected: OPT_PTR(variable_layer_height); OPT_PTR(cooling_tube_retraction); OPT_PTR(cooling_tube_length); + OPT_PTR(high_current_on_filament_swap); OPT_PTR(parking_pos_retraction); OPT_PTR(remaining_times); OPT_PTR(silent_mode); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 4b7a8906f0..877c77b0c7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -5,6 +5,7 @@ #include "SupportMaterial.hpp" #include "Surface.hpp" #include "Slicing.hpp" +#include "Utils.hpp" #include #include @@ -132,7 +133,7 @@ void PrintObject::make_perimeters() return; m_print->set_status(20, "Generating perimeters"); - BOOST_LOG_TRIVIAL(info) << "Generating perimeters..."; + BOOST_LOG_TRIVIAL(info) << "Generating perimeters..." << log_memory_info(); // merge slices if they were split into types if (this->typed_slices) { @@ -253,7 +254,7 @@ void PrintObject::prepare_infill() // Decide what surfaces are to be filled. // Here the S_TYPE_TOP / S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is turned to just S_TYPE_INTERNAL if zero top / bottom infill layers are configured. // Also tiny S_TYPE_INTERNAL surfaces are turned to S_TYPE_INTERNAL_SOLID. - BOOST_LOG_TRIVIAL(info) << "Preparing fill surfaces..."; + BOOST_LOG_TRIVIAL(info) << "Preparing fill surfaces..." << log_memory_info(); for (auto *layer : m_layers) for (auto *region : layer->m_regions) { region->prepare_fill_surfaces(); @@ -384,6 +385,14 @@ void PrintObject::generate_support_material() m_print->set_status(85, "Generating support material"); this->_generate_support_material(); m_print->throw_if_canceled(); + } else { +#if 0 + // Printing without supports. Empty layer means some objects or object parts are levitating, + // therefore they cannot be printed without supports. + for (const Layer *layer : m_layers) + if (layer->empty()) + throw std::runtime_error("Levitating objects cannot be printed without supports."); +#endif } this->set_done(posSupportMaterial); } @@ -522,11 +531,13 @@ bool PrintObject::invalidate_state_by_config_options(const std::vectorinvalidate_step(psGCodeExport); + } else if ( + opt_key == "wipe_into_infill" + || opt_key == "wipe_into_objects") { + invalidated |= m_print->invalidate_step(psWipeTower); + invalidated |= m_print->invalidate_step(psGCodeExport); } else { // for legacy, if we can't handle this option let's invalidate all steps this->invalidate_all_steps(); @@ -547,15 +558,15 @@ bool PrintObject::invalidate_step(PrintObjectStep step) // propagate to dependent steps if (step == posPerimeters) { - invalidated |= this->invalidate_step(posPrepareInfill); + invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill }); invalidated |= m_print->invalidate_steps({ psSkirt, psBrim }); } else if (step == posPrepareInfill) { invalidated |= this->invalidate_step(posInfill); } else if (step == posInfill) { invalidated |= m_print->invalidate_steps({ psSkirt, psBrim }); } else if (step == posSlice) { - invalidated |= this->invalidate_steps({ posPerimeters, posSupportMaterial }); - invalidated |= m_print->invalidate_step(psWipeTower); + invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posSupportMaterial }); + invalidated |= m_print->invalidate_steps({ psSkirt, psBrim }); } else if (step == posSupportMaterial) invalidated |= m_print->invalidate_steps({ psSkirt, psBrim }); @@ -591,7 +602,7 @@ bool PrintObject::has_support_material() const // If a part of a region is of stBottom and stTop, the stBottom wins. void PrintObject::detect_surfaces_type() { - BOOST_LOG_TRIVIAL(info) << "Detecting solid surfaces..."; + BOOST_LOG_TRIVIAL(info) << "Detecting solid surfaces..." << log_memory_info(); // Interface shells: the intersecting parts are treated as self standing objects supporting each other. // Each of the objects will have a full number of top / bottom layers, even if these top / bottom layers @@ -783,7 +794,7 @@ void PrintObject::detect_surfaces_type() void PrintObject::process_external_surfaces() { - BOOST_LOG_TRIVIAL(info) << "Processing external surfaces..."; + BOOST_LOG_TRIVIAL(info) << "Processing external surfaces..." << log_memory_info(); for (size_t region_id = 0; region_id < this->region_volumes.size(); ++region_id) { const PrintRegion ®ion = *m_print->regions()[region_id]; @@ -808,7 +819,7 @@ void PrintObject::discover_vertical_shells() { PROFILE_FUNC(); - BOOST_LOG_TRIVIAL(info) << "Discovering vertical shells..."; + BOOST_LOG_TRIVIAL(info) << "Discovering vertical shells..." << log_memory_info(); struct DiscoverVerticalShellsCacheEntry { @@ -1192,7 +1203,7 @@ void PrintObject::discover_vertical_shells() sparse infill */ void PrintObject::bridge_over_infill() { - BOOST_LOG_TRIVIAL(info) << "Bridge over infill..."; + BOOST_LOG_TRIVIAL(info) << "Bridge over infill..." << log_memory_info(); for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { const PrintRegion ®ion = *m_print->regions()[region_id]; @@ -1377,7 +1388,7 @@ bool PrintObject::update_layer_height_profile() // this should be idempotent void PrintObject::_slice() { - BOOST_LOG_TRIVIAL(info) << "Slicing objects..."; + BOOST_LOG_TRIVIAL(info) << "Slicing objects..." << log_memory_info(); this->typed_slices = false; @@ -1463,10 +1474,8 @@ void PrintObject::_slice() BOOST_LOG_TRIVIAL(debug) << "Slicing objects - removing top empty layers"; while (! m_layers.empty()) { const Layer *layer = m_layers.back(); - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) - if (layer->m_regions[region_id] != nullptr && ! layer->m_regions[region_id]->slices.empty()) - // Non empty layer. - goto end; + if (! layer->empty()) + goto end; delete layer; m_layers.pop_back(); if (! m_layers.empty()) @@ -1701,7 +1710,7 @@ void PrintObject::_make_perimeters() if (! this->set_started(posPerimeters)) return; - BOOST_LOG_TRIVIAL(info) << "Generating perimeters..."; + BOOST_LOG_TRIVIAL(info) << "Generating perimeters..." << log_memory_info(); // merge slices if they were split into types if (this->typed_slices) { diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 8615ab91c5..37b0c0ffce 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 50d7775a21..5d40bb5142 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -1,3 +1,4 @@ +#include #include "SLA/SLASupportTree.hpp" #include "SLA/SLABoilerPlate.hpp" #include "SLA/SLASpatIndex.hpp" @@ -9,15 +10,8 @@ #include "boost/geometry/index/rtree.hpp" #include - -//#if !defined(_MSC_VER) || defined(_WIN64) -#if 1 -#define IGL_COMPATIBLE -#endif - -#ifdef IGL_COMPATIBLE #include -#endif +#include #include "SLASpatIndex.hpp" #include "ClipperUtils.hpp" @@ -84,33 +78,124 @@ size_t SpatIndex::size() const return m_impl->m_store.size(); } -PointSet normals(const PointSet& points, const EigenMesh3D& mesh) { - if(points.rows() == 0 || mesh.V.rows() == 0 || mesh.F.rows() == 0) return {}; -#ifdef IGL_COMPATIBLE +bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, + double eps = 0.05) +{ + using Line3D = Eigen::ParametrizedLine; + + auto line = Line3D::Through(e1, e2); + double d = line.distance(p); + return std::abs(d) < eps; +} + +template double distance(const Vec& pp1, const Vec& pp2) { + auto p = pp2 - pp1; + return std::sqrt(p.transpose() * p); +} + +PointSet normals(const PointSet& points, const EigenMesh3D& emesh, + double eps, + std::function throw_on_cancel) { + if(points.rows() == 0 || emesh.V.rows() == 0 || emesh.F.rows() == 0) + return {}; + Eigen::VectorXd dists; Eigen::VectorXi I; PointSet C; + // We need to remove duplicate vertices and have a true index triangle + // structure + EigenMesh3D mesh; + Eigen::VectorXi SVI, SVJ; + igl::remove_duplicate_vertices(emesh.V, emesh.F, 1e-6, + mesh.V, SVI, SVJ, mesh.F); + igl::point_mesh_squared_distance( points, mesh.V, mesh.F, dists, I, C); PointSet ret(I.rows(), 3); for(int i = 0; i < I.rows(); i++) { + throw_on_cancel(); auto idx = I(i); auto trindex = mesh.F.row(idx); - auto& p1 = mesh.V.row(trindex(0)); - auto& p2 = mesh.V.row(trindex(1)); - auto& p3 = mesh.V.row(trindex(2)); + const Vec3d& p1 = mesh.V.row(trindex(0)); + const Vec3d& p2 = mesh.V.row(trindex(1)); + const Vec3d& p3 = mesh.V.row(trindex(2)); - Eigen::Vector3d U = p2 - p1; - Eigen::Vector3d V = p3 - p1; - ret.row(i) = U.cross(V).normalized(); + // We should check if the point lies on an edge of the hosting triangle. + // If it does than all the other triangles using the same two points + // have to be searched and the final normal should be some kind of + // aggregation of the participating triangle normals. We should also + // consider the cases where the support point lies right on a vertex + // of its triangle. The procedure is the same, get the neighbor + // triangles and calculate an average normal. + + const Vec3d& p = C.row(i); + + // mark the vertex indices of the edge. ia and ib marks and edge ic + // will mark a single vertex. + int ia = -1, ib = -1, ic = -1; + + if(std::abs(distance(p, p1)) < eps) { + ic = trindex(0); + } + else if(std::abs(distance(p, p2)) < eps) { + ic = trindex(1); + } + else if(std::abs(distance(p, p3)) < eps) { + ic = trindex(2); + } + else if(point_on_edge(p, p1, p2, eps)) { + ia = trindex(0); ib = trindex(1); + } + else if(point_on_edge(p, p2, p3, eps)) { + ia = trindex(1); ib = trindex(2); + } + else if(point_on_edge(p, p1, p3, eps)) { + ia = trindex(0); ib = trindex(2); + } + + std::vector neigh; + if(ic >= 0) { // The point is right on a vertex of the triangle + for(int n = 0; n < mesh.F.rows(); ++n) { + throw_on_cancel(); + Vec3i ni = mesh.F.row(n); + if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic)) + neigh.emplace_back(ni); + } + } + else if(ia >= 0 && ib >= 0) { // the point is on and edge + // now get all the neigboring triangles + for(int n = 0; n < mesh.F.rows(); ++n) { + throw_on_cancel(); + Vec3i ni = mesh.F.row(n); + if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) && + (ni(X) == ib || ni(Y) == ib || ni(Z) == ib)) + neigh.emplace_back(ni); + } + } + + if(!neigh.empty()) { // there were neighbors to count with + Vec3d sumnorm(0, 0, 0); + for(const Vec3i& tri : neigh) { + const Vec3d& pt1 = mesh.V.row(tri(0)); + const Vec3d& pt2 = mesh.V.row(tri(1)); + const Vec3d& pt3 = mesh.V.row(tri(2)); + Eigen::Vector3d U = pt2 - pt1; + Eigen::Vector3d V = pt3 - pt1; + sumnorm += U.cross(V).normalized(); + } + sumnorm /= neigh.size(); + ret.row(i) = sumnorm; + } + else { // point lies safely within its triangle + Eigen::Vector3d U = p2 - p1; + Eigen::Vector3d V = p3 - p1; + ret.row(i) = U.cross(V).normalized(); + } } return ret; -#else // TODO: do something on 32 bit windows - return {}; -#endif } double ray_mesh_intersect(const Vec3d& s, @@ -223,7 +308,7 @@ Segments model_boundary(const EigenMesh3D& emesh, double offs) pp.emplace_back(p); } - ExPolygons merged = union_ex(offset(pp, float(scale_(offs))), true); + ExPolygons merged = union_ex(Slic3r::offset(pp, float(scale_(offs))), true); for(auto& expoly : merged) { auto lines = expoly.lines(); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 062c759de5..3dc854dc65 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -436,10 +436,17 @@ std::vector SLAPrint::calculate_heights(const BoundingBoxf3& bb3d, float return heights; } +template +void report_status(SLAPrint& p, int st, const std::string& msg, Args&&...args) { + BOOST_LOG_TRIVIAL(info) << st << "% " << msg; + p.set_status(st, msg, std::forward(args)...); +} + void SLAPrint::process() { using namespace sla; + using ExPolygon = Slic3r::ExPolygon; // Assumption: at this point the print objects should be populated only with // the model objects we have to process and the instances are also filtered @@ -556,9 +563,11 @@ void SLAPrint::process() // scaling for the sub operations double d = *stthis / (objcount * 100.0); - ctl.statuscb = [this, init, d](unsigned st, const std::string& msg){ - set_status(int(init + st*d), msg); + ctl.statuscb = [this, init, d](unsigned st, const std::string& msg) + { + report_status(*this, int(init + st*d), msg); }; + ctl.stopcondition = [this](){ return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; @@ -568,9 +577,9 @@ void SLAPrint::process() // Create the unified mesh auto rc = SlicingStatus::RELOAD_SCENE; - set_status(-1, L("Visualizing supports")); + report_status(*this, -1, L("Visualizing supports")); po.m_supportdata->support_tree_ptr->merged_mesh(); - set_status(-1, L("Visualizing supports"), rc); + report_status(*this, -1, L("Visualizing supports"), rc); } catch(sla::SLASupportsStoppedException&) { // no need to rethrow // throw_if_canceled(); @@ -613,7 +622,7 @@ void SLAPrint::process() po.throw_if_canceled(); auto rc = SlicingStatus::RELOAD_SCENE; - set_status(-1, L("Visualizing supports"), rc); + report_status(*this, -1, L("Visualizing supports"), rc); }; // Slicing the support geometries similarly to the model slicing procedure. @@ -772,8 +781,12 @@ void SLAPrint::process() auto lvlcnt = unsigned(m_printer_input.size()); printer.layers(lvlcnt); + // slot is the portion of 100% that is realted to rasterization unsigned slot = PRINT_STEP_LEVELS[slapsRasterize]; + // ist: initial state; pst: previous state unsigned ist = max_objstatus, pst = ist; + // coefficient to map the rasterization state (0-99) to the allocated + // portion (slot) of the process state double sd = (100 - ist) / 100.0; SpinMutex slck; @@ -810,11 +823,11 @@ void SLAPrint::process() // Finish the layer for later saving it. printer.finish_layer(level_id); - // Status indication + // Status indication guarded with the spinlock auto st = ist + unsigned(sd*level_id*slot/m_printer_input.size()); { std::lock_guard lck(slck); if( st > pst) { - set_status(int(st), PRINT_STEP_LABELS[slapsRasterize]); + report_status(*this, int(st), PRINT_STEP_LABELS[slapsRasterize]); pst = st; } } @@ -862,9 +875,14 @@ void SLAPrint::process() unsigned st = min_objstatus; unsigned incr = 0; + BOOST_LOG_TRIVIAL(info) << "Start slicing process."; + // TODO: this loop could run in parallel but should not exhaust all the CPU // power available for(SLAPrintObject * po : m_objects) { + + BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name; + for(size_t s = 0; s < objectsteps.size(); ++s) { auto currentstep = objectsteps[s]; @@ -876,8 +894,7 @@ void SLAPrint::process() st += unsigned(incr * ostepd); if(po->m_stepmask[currentstep] && po->set_started(currentstep)) { - - set_status(int(st), OBJ_STEP_LABELS[currentstep]); + report_status(*this, int(st), OBJ_STEP_LABELS[currentstep]); pobj_program[currentstep](*po); po->set_done(currentstep); } @@ -902,7 +919,7 @@ void SLAPrint::process() if(m_stepmask[currentstep] && set_started(currentstep)) { - set_status(int(st), PRINT_STEP_LABELS[currentstep]); + report_status(*this, int(st), PRINT_STEP_LABELS[currentstep]); print_program[currentstep](); set_done(currentstep); } @@ -911,7 +928,7 @@ void SLAPrint::process() } // If everything vent well - set_status(100, L("Slicing done")); + report_status(*this, 100, L("Slicing done")); } bool SLAPrint::invalidate_state_by_config_options(const std::vector &opt_keys) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index cef735db0c..0f0ae974b5 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -36,6 +36,12 @@ #define ENABLE_REMOVE_TABS_FROM_PLATER (1 && ENABLE_1_42_0) // Constrains the camera target into the scene bounding box #define ENABLE_CONSTRAINED_CAMERA_TARGET (1 && ENABLE_1_42_0) +// Use wxDataViewRender instead of wxDataViewCustomRenderer +#define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0) +// Adds background texture to toolbars +#define ENABLE_TOOLBAR_BACKGROUND_TEXTURE (1 && ENABLE_1_42_0) +// Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active +#define ENABLE_RENDER_SELECTION_CENTER (0 && ENABLE_1_42_0) #endif // _technologies_h_ diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 7f2c94f034..cfae9edd1a 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -11,7 +11,13 @@ namespace Slic3r { extern void set_logging_level(unsigned int level); +extern unsigned get_logging_level(); extern void trace(unsigned int level, const char *message); +// Format memory allocated, separate thousands by comma. +extern std::string format_memsize_MB(size_t n); +// Return string to be added to the boost::log output to inform about the current process memory allocation. +// The string is non-empty only if the loglevel >= info (3). +extern std::string log_memory_info(); extern void disable_multi_threading(); // Set a path with GUI resource files. @@ -182,7 +188,12 @@ public: void reset() { closure = Closure(); } }; - } // namespace Slic3r +#if WIN32 + #define SLIC3R_STDVEC_MEMSIZE(NAME, TYPE) NAME.capacity() * ((sizeof(TYPE) + __alignof(TYPE) - 1) / __alignof(TYPE)) * __alignof(TYPE) +#else + #define SLIC3R_STDVEC_MEMSIZE(NAME, TYPE) NAME.capacity() * ((sizeof(TYPE) + alignof(TYPE) - 1) / alignof(TYPE)) * alignof(TYPE) +#endif + #endif // slic3r_Utils_hpp_ diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 7a51a61046..f48abfd89e 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -8,6 +8,7 @@ #ifdef WIN32 #include +#include #else #include #endif @@ -58,6 +59,18 @@ void set_logging_level(unsigned int level) ); } +unsigned get_logging_level() +{ + switch (logSeverity) { + case boost::log::trivial::fatal : return 0; + case boost::log::trivial::error : return 1; + case boost::log::trivial::warning : return 2; + case boost::log::trivial::info : return 3; + case boost::log::trivial::debug : return 4; + default: return 1; + } +} + // Force set_logging_level(<=error) after loading of the DLL. // Switch boost::filesystem to utf8. static struct RunOnInit { @@ -365,4 +378,71 @@ std::string xml_escape(std::string text) return text; } +std::string format_memsize_MB(size_t n) +{ + std::string out; + size_t n2 = 0; + size_t scale = 1; + // Round to MB + n += 500000; + n /= 1000000; + while (n >= 1000) { + n2 = n2 + scale * (n % 1000); + n /= 1000; + scale *= 1000; + } + char buf[8]; + sprintf(buf, "%d", n); + out = buf; + while (scale != 1) { + scale /= 1000; + n = n2 / scale; + n2 = n2 % scale; + sprintf(buf, ",%03d", n); + out += buf; + } + return out + "MB"; +} + +#ifdef WIN32 + +#ifndef PROCESS_MEMORY_COUNTERS_EX + // MingW32 doesn't have this struct in psapi.h + typedef struct _PROCESS_MEMORY_COUNTERS_EX { + DWORD cb; + DWORD PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + SIZE_T PrivateUsage; + } PROCESS_MEMORY_COUNTERS_EX, *PPROCESS_MEMORY_COUNTERS_EX; +#endif /* PROCESS_MEMORY_COUNTERS_EX */ + +std::string log_memory_info() +{ + std::string out; + if (logSeverity <= boost::log::trivial::info) { + HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ::GetCurrentProcessId()); + if (hProcess != nullptr) { + PROCESS_MEMORY_COUNTERS_EX pmc; + if (GetProcessMemoryInfo(hProcess, (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) + out = " WorkingSet: " + format_memsize_MB(pmc.WorkingSetSize) + " PrivateBytes: " + format_memsize_MB(pmc.PrivateUsage) + " Pagefile(peak): " + format_memsize_MB(pmc.PagefileUsage) + "(" + format_memsize_MB(pmc.PeakPagefileUsage) + ")"; + CloseHandle(hProcess); + } + } + return out; +} + +#else +std::string log_memory_info() +{ + return std::string(); +} +#endif + }; // namespace Slic3r diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 891e5f0dcc..d748919c95 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/Field.cpp b/src/slic3r/GUI/Field.cpp index ebc7f3665c..0283b7c292 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -163,17 +163,40 @@ 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; } } -bool TextCtrl::is_defined_input_value() const +template +bool is_defined_input_value(wxWindow* win, const ConfigOptionType& type) { - if (static_cast(window)->GetValue().empty() && m_opt.type != coString && m_opt.type != coStrings) + if (static_cast(win)->GetValue().empty() && type != coString && type != coStrings) return false; return true; } @@ -252,7 +275,7 @@ void TextCtrl::BUILD() { temp->GetToolTip()->Enable(true); #endif // __WXGTK__ // if (!is_defined_input_value()) - if (is_defined_input_value()) + if (is_defined_input_value(window, m_opt.type)) on_change_field(); else on_kill_focus(e); @@ -377,6 +400,9 @@ void SpinCtrl::BUILD() { 0, min_val, max_val, default_value); // temp->Bind(wxEVT_SPINCTRL, ([this](wxCommandEvent e) { tmp_value = undef_spin_val; on_change_field(); }), temp->GetId()); + + // #ys_FIXME_KILL_FOCUS + // wxEVT_KILL_FOCUS doesn't handled on OSX now temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { if (tmp_value < 0) @@ -386,6 +412,7 @@ void SpinCtrl::BUILD() { on_change_field(); } }), temp->GetId()); + temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { // # On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value @@ -398,9 +425,15 @@ void SpinCtrl::BUILD() { tmp_value = std::stoi(value); else tmp_value = -9999; // on_change_field(); -// # We don't reset tmp_value here because _on_change might put callbacks -// # in the CallAfter queue, and we want the tmp value to be available from -// # them as well. +#ifdef __WXOSX__ + // #ys_FIXME_KILL_FOCUS so call on_change_field() inside wxEVT_TEXT + if (tmp_value < 0) { + if (m_on_kill_focus != nullptr) + m_on_kill_focus(m_opt_id); + } + else + on_change_field(); +#endif }), temp->GetId()); temp->SetToolTip(get_tooltip_text(text_value)); @@ -432,9 +465,24 @@ void Choice::BUILD() { } set_selection(); } - temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId()); +// temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId()); temp->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId()); + if (temp->GetWindowStyle() != wxCB_READONLY) { + temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { + e.Skip(); + double old_val = !m_value.empty() ? boost::any_cast(m_value) : -99999; + if (is_defined_input_value(window, m_opt.type)) { + if (fabs(old_val - boost::any_cast(get_value())) <= 0.0001) + return; + else + on_change_field(); + } + else + on_kill_focus(e); + }), temp->GetId()); + } + temp->SetToolTip(get_tooltip_text(temp->GetValue())); } @@ -611,9 +659,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) @@ -638,7 +684,18 @@ boost::any& Choice::get_value() m_value = static_cast(ret_enum); else if (m_opt_id.compare("host_type") == 0) 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 = atof(m_opt.enum_values[ret_enum].c_str()); + } + else + get_value_by_opt_type(ret_str); return m_value; } @@ -702,8 +759,11 @@ void PointCtrl::BUILD() temp->Add(new wxStaticText(m_parent, wxID_ANY, " y : "), 0, wxALIGN_CENTER_VERTICAL, 0); temp->Add(y_textctrl); - x_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), x_textctrl->GetId()); - y_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), y_textctrl->GetId()); +// x_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), x_textctrl->GetId()); +// y_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), y_textctrl->GetId()); + + x_textctrl->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { OnKillFocus(e, x_textctrl); }), x_textctrl->GetId()); + y_textctrl->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { OnKillFocus(e, x_textctrl); }), y_textctrl->GetId()); // // recast as a wxWindow to fit the calling convention sizer = dynamic_cast(temp); @@ -712,6 +772,16 @@ void PointCtrl::BUILD() y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y)); } +void PointCtrl::OnKillFocus(wxEvent& e, wxTextCtrl* win) +{ + e.Skip(); + if (!win->GetValue().empty()) { + on_change_field(); + } + else + on_kill_focus(e); +} + void PointCtrl::set_value(const Vec2d& value, bool change_event) { m_disable_change_event = !change_event; diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 0097d3ec00..4a19b61033 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -266,7 +266,6 @@ public: } boost::any& get_value() override; - bool is_defined_input_value() const ; virtual void enable(); virtual void disable(); @@ -395,6 +394,7 @@ public: void BUILD() override; + void OnKillFocus(wxEvent& e, wxTextCtrl* win); void set_value(const Vec2d& value, bool change_event = false); void set_value(const boost::any& value, bool change_event = false); boost::any& get_value() override; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 352ea2f497..e01e1dbbe1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -617,42 +617,71 @@ bool GLCanvas3D::Bed::_are_equal(const Pointfs& bed_1, const Pointfs& bed_2) return true; } +const double GLCanvas3D::Axes::Radius = 0.5; +const double GLCanvas3D::Axes::ArrowBaseRadius = 2.5 * GLCanvas3D::Axes::Radius; +const double GLCanvas3D::Axes::ArrowLength = 5.0; + GLCanvas3D::Axes::Axes() : origin(Vec3d::Zero()) - , length(0.0f) + , length(Vec3d::Zero()) { + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); } -void GLCanvas3D::Axes::render(bool depth_test) const +GLCanvas3D::Axes::~Axes() { - if (depth_test) - ::glEnable(GL_DEPTH_TEST); - else - ::glDisable(GL_DEPTH_TEST); + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); +} - ::glLineWidth(2.0f); - ::glBegin(GL_LINES); - // draw line for x axis +void GLCanvas3D::Axes::render() const +{ + if (m_quadric == nullptr) + return; + + ::glEnable(GL_DEPTH_TEST); + ::glEnable(GL_LIGHTING); + + // x axis ::glColor3f(1.0f, 0.0f, 0.0f); - ::glVertex3dv(origin.data()); - ::glVertex3f((GLfloat)origin(0) + length, (GLfloat)origin(1), (GLfloat)origin(2)); - // draw line for y axis - ::glColor3f(0.0f, 1.0f, 0.0f); - ::glVertex3dv(origin.data()); - ::glVertex3f((GLfloat)origin(0), (GLfloat)origin(1) + length, (GLfloat)origin(2)); - ::glEnd(); - // draw line for Z axis - // (re-enable depth test so that axis is correctly shown when objects are behind it) - if (!depth_test) - ::glEnable(GL_DEPTH_TEST); + ::glPushMatrix(); + ::glTranslated(origin(0), origin(1), origin(2)); + ::glRotated(90.0, 0.0, 1.0, 0.0); + render_axis(length(0)); + ::glPopMatrix(); - ::glBegin(GL_LINES); + // y axis + ::glColor3f(0.0f, 1.0f, 0.0f); + ::glPushMatrix(); + ::glTranslated(origin(0), origin(1), origin(2)); + ::glRotated(-90.0, 1.0, 0.0, 0.0); + render_axis(length(1)); + ::glPopMatrix(); + + // z axis ::glColor3f(0.0f, 0.0f, 1.0f); - ::glVertex3dv(origin.data()); - ::glVertex3f((GLfloat)origin(0), (GLfloat)origin(1), (GLfloat)origin(2) + length); - ::glEnd(); + ::glPushMatrix(); + ::glTranslated(origin(0), origin(1), origin(2)); + render_axis(length(2)); + ::glPopMatrix(); + + ::glDisable(GL_LIGHTING); } +void GLCanvas3D::Axes::render_axis(double length) const +{ + ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); + ::gluCylinder(m_quadric, Radius, Radius, length, 32, 1); + ::gluQuadricOrientation(m_quadric, GLU_INSIDE); + ::gluDisk(m_quadric, 0.0, Radius, 32, 1); + ::glTranslated(0.0, 0.0, length); + ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); + ::gluCylinder(m_quadric, ArrowBaseRadius, 0.0, ArrowLength, 32, 1); + ::gluQuadricOrientation(m_quadric, GLU_INSIDE); + ::gluDisk(m_quadric, 0.0, ArrowBaseRadius, 32, 1); +} GLCanvas3D::Shader::Shader() : m_shader(nullptr) @@ -1133,8 +1162,21 @@ GLCanvas3D::Selection::Selection() , m_valid(false) , m_bounding_box_dirty(true) { +#if ENABLE_RENDER_SELECTION_CENTER + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +#endif // ENABLE_RENDER_SELECTION_CENTER } +#if ENABLE_RENDER_SELECTION_CENTER +GLCanvas3D::Selection::~Selection() +{ + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); +} +#endif // ENABLE_RENDER_SELECTION_CENTER + void GLCanvas3D::Selection::set_volumes(GLVolumePtrs* volumes) { m_volumes = volumes; @@ -1436,6 +1478,14 @@ bool GLCanvas3D::Selection::is_from_single_object() const return (0 <= idx) && (idx < 1000); } +bool GLCanvas3D::Selection::requires_uniform_scale() const +{ + if (is_single_full_instance() || is_single_modifier() || is_single_volume()) + return false; + + return true; +} + int GLCanvas3D::Selection::get_object_idx() const { return (m_cache.content.size() == 1) ? m_cache.content.begin()->first : -1; @@ -1490,8 +1540,13 @@ void GLCanvas3D::Selection::translate(const Vec3d& displacement) #if ENABLE_MODELVOLUME_TRANSFORM if ((m_mode == Volume) || (*m_volumes)[i]->is_wipe_tower) { - Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; - (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); + if (_requires_local_axes()) + (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); + else + { + Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; + (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); + } } else if (m_mode == Instance) (*m_volumes)[i]->set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); @@ -1520,9 +1575,14 @@ void GLCanvas3D::Selection::rotate(const Vec3d& rotation, bool local) if (is_single_full_instance()) #if ENABLE_WORLD_ROTATIONS { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()); - (*m_volumes)[i]->set_instance_rotation(new_rotation); + if (local) + (*m_volumes)[i]->set_instance_rotation(rotation); + else + { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()); + (*m_volumes)[i]->set_instance_rotation(new_rotation); + } } #else #if ENABLE_MODELVOLUME_TRANSFORM @@ -1535,11 +1595,16 @@ void GLCanvas3D::Selection::rotate(const Vec3d& rotation, bool local) else if (is_single_volume() || is_single_modifier()) #if ENABLE_WORLD_ROTATIONS { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - const Transform3d& inst_m = m_cache.volumes_data[i].get_instance_rotation_matrix(); - Vec3d new_rotation = Geometry::extract_euler_angles(inst_m.inverse() * m * inst_m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - (*m_volumes)[i]->set_volume_rotation(new_rotation); - } + if (_requires_local_axes()) + (*m_volumes)[i]->set_volume_rotation(rotation); + else + { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + const Transform3d& inst_m = m_cache.volumes_data[i].get_instance_rotation_matrix(); + Vec3d new_rotation = Geometry::extract_euler_angles(inst_m.inverse() * m * inst_m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + (*m_volumes)[i]->set_volume_rotation(new_rotation); + } + } #else (*m_volumes)[i]->set_volume_rotation(rotation); #endif // ENABLE_WORLD_ROTATIONS @@ -1960,7 +2025,7 @@ void GLCanvas3D::Selection::erase() void GLCanvas3D::Selection::render() const { - if (is_empty()) + if (!m_valid || is_empty()) return; // render cumulative bounding box of selected volumes @@ -1968,6 +2033,28 @@ void GLCanvas3D::Selection::render() const _render_synchronized_volumes(); } +#if ENABLE_RENDER_SELECTION_CENTER +void GLCanvas3D::Selection::render_center() const +{ + if (!m_valid || is_empty() || (m_quadric == nullptr)) + return; + + const Vec3d& center = get_bounding_box().center(); + + ::glDisable(GL_DEPTH_TEST); + + ::glEnable(GL_LIGHTING); + + ::glColor3f(1.0f, 1.0f, 1.0f); + ::glPushMatrix(); + ::glTranslated(center(0), center(1), center(2)); + ::gluSphere(m_quadric, 0.75, 32, 32); + ::glPopMatrix(); + + ::glDisable(GL_LIGHTING); +} +#endif // ENABLE_RENDER_SELECTION_CENTER + void GLCanvas3D::Selection::_update_valid() { m_valid = (m_volumes != nullptr) && (m_model != nullptr); @@ -2501,9 +2588,14 @@ void GLCanvas3D::Selection::_ensure_on_bed() } #endif // ENABLE_ENSURE_ON_BED_WHILE_SCALING -const float GLCanvas3D::Gizmos::OverlayTexturesScale = 1.0f; -const float GLCanvas3D::Gizmos::OverlayOffsetX = 10.0f * OverlayTexturesScale; -const float GLCanvas3D::Gizmos::OverlayGapY = 5.0f * OverlayTexturesScale; +bool GLCanvas3D::Selection::_requires_local_axes() const +{ + return (m_mode == Volume) && is_from_single_instance(); +} + +const float GLCanvas3D::Gizmos::OverlayIconsScale = 1.0f; +const float GLCanvas3D::Gizmos::OverlayBorder = 5.0f; +const float GLCanvas3D::Gizmos::OverlayGapY = 5.0f * OverlayIconsScale; GLCanvas3D::Gizmos::Gizmos() : m_enabled(false) @@ -2584,6 +2676,23 @@ bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(SlaSupports, gizmo)); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_background_texture.metadata.filename = "toolbar_background.png"; + m_background_texture.metadata.left = 16; + m_background_texture.metadata.top = 16; + m_background_texture.metadata.right = 16; + m_background_texture.metadata.bottom = 16; + + if (!m_background_texture.metadata.filename.empty()) + { + if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false)) + { + _reset(); + return false; + } + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + return true; } @@ -2606,24 +2715,22 @@ std::string GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, con float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = _get_total_overlay_height(); - float top_y = 0.5f * (cnv_h - height); + float top_y = 0.5f * (cnv_h - height) + OverlayBorder; for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float tex_size = (float)it->second->get_textures_size() * OverlayTexturesScale; - float half_tex_size = 0.5f * tex_size; + float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale; - // we currently use circular icons for gizmo, so we check the radius if (it->second->is_activable(selection) && (it->second->get_state() != GLGizmoBase::On)) { - bool inside = (mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size; + bool inside = (OverlayBorder <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= OverlayBorder + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size); it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off); if (inside) name = it->second->get_name(); } - top_y += (tex_size + OverlayGapY); + top_y += (icon_size + OverlayGapY); } return name; @@ -2636,17 +2743,16 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = _get_total_overlay_height(); - float top_y = 0.5f * (cnv_h - height); + float top_y = 0.5f * (cnv_h - height) + OverlayBorder; for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float tex_size = (float)it->second->get_textures_size() * OverlayTexturesScale; - float half_tex_size = 0.5f * tex_size; + float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale; - // we currently use circular icons for gizmo, so we check the radius - if (it->second->is_activable(selection) && ((mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size)) + bool inside = (OverlayBorder <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= OverlayBorder + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size); + if (it->second->is_activable(selection) && inside) { if ((it->second->get_state() == GLGizmoBase::On)) { @@ -2662,7 +2768,7 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec else it->second->set_state(GLGizmoBase::Off); - top_y += (tex_size + OverlayGapY); + top_y += (icon_size + OverlayGapY); } GizmosMap::iterator it = m_gizmos.find(m_current); @@ -2734,20 +2840,18 @@ bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = _get_total_overlay_height(); - float top_y = 0.5f * (cnv_h - height); + float top_y = 0.5f * (cnv_h - height) + OverlayBorder; for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float tex_size = (float)it->second->get_textures_size() * OverlayTexturesScale; - float half_tex_size = 0.5f * tex_size; + float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale; - // we currently use circular icons for gizmo, so we check the radius - if ((mouse_pos - Vec2d(OverlayOffsetX + half_tex_size, top_y + half_tex_size)).norm() < half_tex_size) + if ((OverlayBorder <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= OverlayBorder + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size)) return true; - top_y += (tex_size + OverlayGapY); + top_y += (icon_size + OverlayGapY); } return false; @@ -3020,21 +3124,102 @@ void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas, const GLCanva float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float height = _get_total_overlay_height(); - float top_x = (OverlayOffsetX - 0.5f * cnv_w) * inv_zoom; - float top_y = 0.5f * height * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = OverlayBorder * inv_zoom; + + float top_x = (-0.5f * cnv_w) * inv_zoom; + float top_y = (0.5f * height) * inv_zoom; + + float left = top_x; + float top = top_y; + float right = left + _get_total_overlay_width() * inv_zoom; + float bottom = top - height * inv_zoom; + + // renders background + unsigned int bg_tex_id = m_background_texture.texture.get_id(); + float bg_tex_width = (float)m_background_texture.texture.get_width(); + float bg_tex_height = (float)m_background_texture.texture.get_height(); + if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) + { + float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; + float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; + + float bg_uv_left = 0.0f; + float bg_uv_right = 1.0f; + float bg_uv_top = 1.0f; + float bg_uv_bottom = 0.0f; + + float bg_left = left; + float bg_right = right; + float bg_top = top; + float bg_bottom = bottom; + float bg_width = right - left; + float bg_height = top - bottom; + float bg_min_size = std::min(bg_width, bg_height); + + float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; + float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; + float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; + float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; + + float bg_i_left = bg_left + scaled_border; + float bg_i_right = bg_right - scaled_border; + float bg_i_top = bg_top - scaled_border; + float bg_i_bottom = bg_bottom + scaled_border; + + bg_uv_left = bg_uv_i_left; + bg_i_left = bg_left; + + if ((OverlayBorder > 0) && (bg_uv_top != bg_uv_i_top)) + { + if (bg_uv_left != bg_uv_i_left) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); + + if (bg_uv_right != bg_uv_i_right) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); + } + + if ((OverlayBorder > 0) && (bg_uv_left != bg_uv_i_left)) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); + + if ((OverlayBorder > 0) && (bg_uv_right != bg_uv_i_right)) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); + + if ((OverlayBorder > 0) && (bg_uv_bottom != bg_uv_i_bottom)) + { + if (bg_uv_left != bg_uv_i_left) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); + + if (bg_uv_right != bg_uv_i_right) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); + } + } + + top_x += OverlayBorder * inv_zoom; + top_y -= OverlayBorder * inv_zoom; +#else + float top_x = (OverlayBorder - 0.5f * cnv_w) * inv_zoom; + float top_y = (0.5f * height - OverlayBorder) * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_gap_y = OverlayGapY * inv_zoom; for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float tex_size = (float)it->second->get_textures_size() * OverlayTexturesScale * inv_zoom; - GLTexture::render_texture(it->second->get_texture_id(), top_x, top_x + tex_size, top_y - tex_size, top_y); + float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale * inv_zoom; + GLTexture::render_texture(it->second->get_texture_id(), top_x, top_x + icon_size, top_y - icon_size, top_y); #if ENABLE_IMGUI if (it->second->get_state() == GLGizmoBase::On) - it->second->render_input_window(2.0f * OverlayOffsetX + tex_size * zoom, 0.5f * cnv_h - top_y * zoom, selection); + it->second->render_input_window(2.0f * OverlayBorder + icon_size * zoom, 0.5f * cnv_h - top_y * zoom, selection); #endif // ENABLE_IMGUI - top_y -= (tex_size + scaled_gap_y); + top_y -= (icon_size + scaled_gap_y); } } @@ -3047,19 +3232,35 @@ void GLCanvas3D::Gizmos::_render_current_gizmo(const GLCanvas3D::Selection& sele float GLCanvas3D::Gizmos::_get_total_overlay_height() const { - float height = 0.0f; + float height = 2.0f * OverlayBorder; for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { - if (it->first == SlaSupports && wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) + if ((it->second == nullptr) || !it->second->is_selectable()) continue; - height += (float)it->second->get_textures_size() * OverlayTexturesScale + OverlayGapY; + height += (float)it->second->get_textures_size() * OverlayIconsScale + OverlayGapY; } return height - OverlayGapY; } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +float GLCanvas3D::Gizmos::_get_total_overlay_width() const +{ + float max_icon_width = 0.0f; + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if ((it->second == nullptr) || !it->second->is_selectable()) + continue; + + max_icon_width = std::max(max_icon_width, (float)it->second->get_textures_size() * OverlayIconsScale); + } + + return max_icon_width + 2.0f * OverlayBorder; +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const { GizmosMap::const_iterator it = m_gizmos.find(m_current); @@ -3434,11 +3635,16 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) : m_canvas(canvas) , m_context(nullptr) , m_in_render(false) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + , m_toolbar(GLToolbar::Normal) +#else , m_toolbar(*this) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #if ENABLE_REMOVE_TABS_FROM_PLATER , m_view_toolbar(nullptr) #endif // ENABLE_REMOVE_TABS_FROM_PLATER , m_use_clipping_planes(false) + , m_sidebar_field("") , m_config(nullptr) , m_process(nullptr) , m_model(nullptr) @@ -3668,7 +3874,7 @@ void GLCanvas3D::set_bed_shape(const Pointfs& shape) // Set the origin and size for painting of the coordinate system axes. m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z); - set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size()); + set_bed_axes_length(0.1 * m_bed.get_bounding_box().max_size()); if (new_shape) zoom_to_bed(); @@ -3676,9 +3882,9 @@ void GLCanvas3D::set_bed_shape(const Pointfs& shape) m_dirty = true; } -void GLCanvas3D::set_axes_length(float length) +void GLCanvas3D::set_bed_axes_length(double length) { - m_axes.length = length; + m_axes.length = length * Vec3d::Ones(); } void GLCanvas3D::set_color_by(const std::string& value) @@ -3938,21 +4144,20 @@ void GLCanvas3D::render() _render_background(); if (is_custom_bed) // untextured bed needs to be rendered before objects - { _render_bed(theta); - // disable depth testing so that axes are not covered by ground - _render_axes(false); - } _render_objects(); _render_sla_slices(); _render_selection(); + _render_axes(); + if (!is_custom_bed) // textured bed needs to be rendered after objects - { - _render_axes(true); _render_bed(theta); - } + +#if ENABLE_RENDER_SELECTION_CENTER + _render_selection_center(); +#endif // ENABLE_RENDER_SELECTION_CENTER // we need to set the mouse's scene position here because the depth buffer // could be invalidated by the following gizmo render methods @@ -4675,7 +4880,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1; m_layers_editing.last_object_id = layer_editing_object_idx; bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + int toolbar_contains_mouse = m_toolbar.contains_mouse(m_mouse.position, *this); +#else int toolbar_contains_mouse = m_toolbar.contains_mouse(m_mouse.position); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #if ENABLE_REMOVE_TABS_FROM_PLATER int view_toolbar_contains_mouse = (m_view_toolbar != nullptr) ? m_view_toolbar->contains_mouse(m_mouse.position, *this) : -1; #endif // ENABLE_REMOVE_TABS_FROM_PLATER @@ -4699,7 +4908,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if (evt.LeftDClick() && (toolbar_contains_mouse != -1)) { m_toolbar_action_running = true; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_toolbar.do_action((unsigned int)toolbar_contains_mouse, *this); +#else m_toolbar.do_action((unsigned int)toolbar_contains_mouse); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } else if (evt.LeftDClick() && (m_gizmos.get_current_type() != Gizmos::Undefined)) { @@ -4778,7 +4991,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if (toolbar_contains_mouse != -1) { m_toolbar_action_running = true; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_toolbar.do_action((unsigned int)toolbar_contains_mouse, *this); +#else m_toolbar.do_action((unsigned int)toolbar_contains_mouse); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE m_mouse.left_down = false; } else @@ -4983,7 +5200,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() && !is_layers_editing_enabled()) { // deselect and propagate event through callback - if (m_picking_enabled && !m_toolbar_action_running && !m_mouse.ignore_up_event) + if (!evt.ShiftDown() && m_picking_enabled && !m_toolbar_action_running && !m_mouse.ignore_up_event) { m_selection.clear(); m_selection.set_mode(Selection::Instance); @@ -5061,7 +5278,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // updates toolbar overlay if (tooltip.empty()) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + tooltip = m_toolbar.update_hover_state(m_mouse.position, *this); +#else tooltip = m_toolbar.update_hover_state(m_mouse.position); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE // updates view toolbar overlay if (tooltip.empty() && (m_view_toolbar != nullptr)) @@ -5413,6 +5634,17 @@ void GLCanvas3D::update_gizmos_on_off_state() m_gizmos.update_on_off_state(get_selection()); } +void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on) +{ + m_sidebar_field = focus_on ? opt_key : ""; + + if (!m_sidebar_field.empty()) + { + m_gizmos.reset_all_states(); + m_dirty = true; + } +} + bool GLCanvas3D::_is_shown_on_screen() const { return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; @@ -5429,7 +5661,24 @@ bool GLCanvas3D::_init_toolbar() if (!m_toolbar.is_enabled()) return true; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + ItemsIconsTexture::Metadata icons_data; + icons_data.filename = "toolbar.png"; + icons_data.icon_size = 36; + icons_data.icon_border_size = 1; + icons_data.icon_gap_size = 1; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!m_toolbar.init(icons_data, background_data)) +#else if (!m_toolbar.init("toolbar.png", 36, 1, 1)) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { // unable to init the toolbar texture, disable it m_toolbar.set_enabled(false); @@ -5438,6 +5687,10 @@ bool GLCanvas3D::_init_toolbar() // m_toolbar.set_layout_type(GLToolbar::Layout::Vertical); m_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_toolbar.set_layout_orientation(GLToolbar::Layout::Top); + m_toolbar.set_border(5.0f); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE m_toolbar.set_separator_size(5); m_toolbar.set_gap_size(2); @@ -5524,9 +5777,6 @@ bool GLCanvas3D::_init_toolbar() if (!m_toolbar.add_item(item)) return false; - if (!m_toolbar.add_separator()) - return false; - enable_toolbar_item("add", true); return true; @@ -5872,9 +6122,9 @@ void GLCanvas3D::_render_bed(float theta) const m_bed.render(theta); } -void GLCanvas3D::_render_axes(bool depth_test) const +void GLCanvas3D::_render_axes() const { - m_axes.render(depth_test); + m_axes.render(); } void GLCanvas3D::_render_objects() const @@ -5950,6 +6200,14 @@ void GLCanvas3D::_render_selection() const m_selection.render(); } +#if ENABLE_RENDER_SELECTION_CENTER +void GLCanvas3D::_render_selection_center() const +{ + if (!m_gizmos.is_running()) + m_selection.render_center(); +} +#endif // ENABLE_RENDER_SELECTION_CENTER + void GLCanvas3D::_render_warning_texture() const { if (!m_warning_texture_enabled) @@ -6063,7 +6321,11 @@ void GLCanvas3D::_render_toolbar() const #if !ENABLE_REMOVE_TABS_FROM_PLATER _resize_toolbar(); #endif // !ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_toolbar.render(*this); +#else m_toolbar.render(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } #if ENABLE_REMOVE_TABS_FROM_PLATER @@ -6093,9 +6355,7 @@ void GLCanvas3D::_render_camera_target() const ::glColor3f(0.0f, 1.0f, 0.0f); ::glVertex3d(target(0), target(1) - half_length, target(2)); ::glVertex3d(target(0), target(1) + half_length, target(2)); - ::glEnd(); - - ::glBegin(GL_LINES); + // draw line for z axis ::glColor3f(0.0f, 0.0f, 1.0f); ::glVertex3d(target(0), target(1), target(2) - half_length); ::glVertex3d(target(0), target(1), target(2) + half_length); @@ -7721,25 +7981,54 @@ void GLCanvas3D::_resize_toolbar() const float zoom = get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + switch (m_toolbar.get_layout_type()) { default: case GLToolbar::Layout::Horizontal: { // centers the toolbar on the top edge of the 3d scene - unsigned int toolbar_width = m_toolbar.get_width(); - float top = (0.5f * (float)cnv_size.get_height() - 2.0f) * inv_zoom; - float left = -0.5f * (float)toolbar_width * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float top, left; + if (orientation == GLToolbar::Layout::Top) + { + top = 0.5f * (float)cnv_size.get_height() * inv_zoom; + left = -0.5f * m_toolbar.get_width() * inv_zoom; + } + else + { + top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar->get_height()) * inv_zoom; + left = -0.5f * m_toolbar.get_width() * inv_zoom; + } +#else + float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; + float left = -0.5f * m_toolbar.get_width() * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE m_toolbar.set_position(top, left); break; } case GLToolbar::Layout::Vertical: { // centers the toolbar on the right edge of the 3d scene - unsigned int toolbar_width = m_toolbar.get_width(); - unsigned int toolbar_height = m_toolbar.get_height(); - float top = 0.5f * (float)toolbar_height * inv_zoom; - float left = (0.5f * (float)cnv_size.get_width() - toolbar_width - 2.0f) * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float top, left; + if (orientation == GLToolbar::Layout::Left) + { + top = 0.5f * m_toolbar.get_height() * inv_zoom; + left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; + } + else + { + top = 0.5f * m_toolbar.get_height() * inv_zoom; + left = (0.5f * (float)cnv_size.get_width() - m_toolbar.get_width()) * inv_zoom; + } +#else + float top = 0.5f * m_toolbar.get_height() * inv_zoom; + float left = (0.5f * (float)cnv_size.get_width() - m_toolbar.get_width()) * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE m_toolbar.set_position(top, left); break; } @@ -7748,6 +8037,7 @@ void GLCanvas3D::_resize_toolbar() const #if ENABLE_REMOVE_TABS_FROM_PLATER if (m_view_toolbar != nullptr) { + // places the toolbar on the bottom-left corner of the 3d scene float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar->get_height()) * inv_zoom; float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; m_view_toolbar->set_position(top, left); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 12a83eebd4..08368a1316 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -20,6 +20,8 @@ class wxTimerEvent; class wxPaintEvent; class wxGLCanvas; +class GLUquadric; +typedef class GLUquadric GLUquadricObj; namespace Slic3r { @@ -155,7 +157,7 @@ class GLCanvas3D // float distance; #if !ENABLE_CONSTRAINED_CAMERA_TARGET Vec3d target; -#endif !// ENABLE_CONSTRAINED_CAMERA_TARGET +#endif // ENABLE_CONSTRAINED_CAMERA_TARGET private: #if ENABLE_CONSTRAINED_CAMERA_TARGET @@ -231,12 +233,20 @@ class GLCanvas3D struct Axes { + static const double Radius; + static const double ArrowBaseRadius; + static const double ArrowLength; Vec3d origin; - float length; + Vec3d length; + GLUquadricObj* m_quadric; Axes(); + ~Axes(); - void render(bool depth_test) const; + void render() const; + + private: + void render_axis(double length) const; }; class Shader @@ -481,8 +491,15 @@ public: mutable BoundingBoxf3 m_bounding_box; mutable bool m_bounding_box_dirty; +#if ENABLE_RENDER_SELECTION_CENTER + GLUquadricObj* m_quadric; +#endif // ENABLE_RENDER_SELECTION_CENTER + public: Selection(); +#if ENABLE_RENDER_SELECTION_CENTER + ~Selection(); +#endif // ENABLE_RENDER_SELECTION_CENTER void set_volumes(GLVolumePtrs* volumes); @@ -515,6 +532,7 @@ public: bool is_wipe_tower() const { return m_type == WipeTower; } bool is_modifier() const { return (m_type == SingleModifier) || (m_type == MultipleModifier); } bool is_single_modifier() const { return m_type == SingleModifier; } + bool is_multiple_modifier() const { return m_type == MultipleModifier; } bool is_single_full_instance() const; bool is_multiple_full_instance() const { return m_type == MultipleFullInstance; } bool is_single_full_object() const { return m_type == SingleFullObject; } @@ -526,6 +544,7 @@ public: bool is_from_single_object() const; bool contains_volume(unsigned int volume_idx) const { return std::find(m_list.begin(), m_list.end(), volume_idx) != m_list.end(); } + bool requires_uniform_scale() const; // Returns the the object id if the selection is from a single object, otherwise is -1 int get_object_idx() const; @@ -557,6 +576,9 @@ public: void erase(); void render() const; +#if ENABLE_RENDER_SELECTION_CENTER + void render_center() const; +#endif // ENABLE_RENDER_SELECTION_CENTER private: void _update_valid(); @@ -577,6 +599,7 @@ public: #if ENABLE_ENSURE_ON_BED_WHILE_SCALING void _ensure_on_bed(); #endif // ENABLE_ENSURE_ON_BED_WHILE_SCALING + bool _requires_local_axes() const; }; class ClippingPlane @@ -607,8 +630,8 @@ public: private: class Gizmos { - static const float OverlayTexturesScale; - static const float OverlayOffsetX; + static const float OverlayIconsScale; + static const float OverlayBorder; static const float OverlayGapY; public: @@ -628,6 +651,9 @@ private: bool m_enabled; typedef std::map GizmosMap; GizmosMap m_gizmos; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + BackgroundTexture m_background_texture; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE EType m_current; public: @@ -696,6 +722,9 @@ private: void _render_current_gizmo(const Selection& selection) const; float _get_total_overlay_height() const; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float _get_total_overlay_width() const; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE GLGizmoBase* _get_current() const; }; @@ -769,11 +798,16 @@ private: mutable Gizmos m_gizmos; mutable GLToolbar m_toolbar; #if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + GLToolbar* m_view_toolbar; +#else GLRadioToolbar* m_view_toolbar; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #endif // ENABLE_REMOVE_TABS_FROM_PLATER ClippingPlane m_clipping_planes[2]; bool m_use_clipping_planes; mutable SlaCap m_sla_caps[2]; + std::string m_sidebar_field; mutable GLVolumeCollection m_volumes; Selection m_selection; @@ -824,7 +858,11 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas; } #if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void set_view_toolbar(GLToolbar* toolbar) { m_view_toolbar = toolbar; } +#else void set_view_toolbar(GLRadioToolbar* toolbar) { m_view_toolbar = toolbar; } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #endif // ENABLE_REMOVE_TABS_FROM_PLATER bool init(bool useVBOs, bool use_legacy_opengl); @@ -856,8 +894,7 @@ public: // fills the m_bed.m_grid_lines and sets m_bed.m_origin. // Sets m_bed.m_polygon to limit the object placement. void set_bed_shape(const Pointfs& shape); - - void set_axes_length(float length); + void set_bed_axes_length(double length); void set_clipping_plane(unsigned int id, const ClippingPlane& plane) { @@ -972,7 +1009,7 @@ public: void viewport_changed(); #endif // ENABLE_CONSTRAINED_CAMERA_TARGET - void handle_sidebar_focus_event(const std::string& opt_key) {} + void handle_sidebar_focus_event(const std::string& opt_key, bool focus_on); private: bool _is_shown_on_screen() const; @@ -997,9 +1034,12 @@ private: void _picking_pass() const; void _render_background() const; void _render_bed(float theta) const; - void _render_axes(bool depth_test) const; + void _render_axes() const; void _render_objects() const; void _render_selection() const; +#if ENABLE_RENDER_SELECTION_CENTER + void _render_selection_center() const; +#endif // ENABLE_RENDER_SELECTION_CENTER void _render_warning_texture() const; void _render_legend_texture() const; void _render_layer_editing_overlay() const; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 92b3e96afe..43da818091 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -75,6 +75,13 @@ bool GLToolbarItem::is_enabled() const return m_state != Disabled; } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +bool GLToolbarItem::is_disabled() const +{ + return m_state == Disabled; +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + bool GLToolbarItem::is_hovered() const { return (m_state == Hover) || (m_state == HoverPressed); @@ -124,24 +131,64 @@ GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int texture_size, unsigned i return uvs; } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +ItemsIconsTexture::Metadata::Metadata() + : filename("") + , icon_size(0) + , icon_border_size(0) + , icon_gap_size(0) +{ +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + +#if !ENABLE_TOOLBAR_BACKGROUND_TEXTURE ItemsIconsTexture::ItemsIconsTexture() : items_icon_size(0) , items_icon_border_size(0) , items_icon_gap_size(0) { } +#endif // !ENABLE_TOOLBAR_BACKGROUND_TEXTURE + +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +BackgroundTexture::Metadata::Metadata() + : filename("") + , left(0) + , right(0) + , top(0) + , bottom(0) +{ +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE GLToolbar::Layout::Layout() : type(Horizontal) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + , orientation(Center) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE , top(0.0f) , left(0.0f) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + , border(0.0f) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE , separator_size(0.0f) , gap_size(0.0f) + , icons_scale(1.0f) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + , width(0.0f) + , height(0.0f) + , dirty(true) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +GLToolbar::GLToolbar(GLToolbar::EType type) + : m_type(type) +#else GLToolbar::GLToolbar(GLCanvas3D& parent) : m_parent(parent) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE , m_enabled(false) { } @@ -154,6 +201,26 @@ GLToolbar::~GLToolbar() } } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture) +{ + if (m_icons_texture.texture.get_id() != 0) + return true; + + std::string path = resources_dir() + "/icons/"; + bool res = !icons_texture.filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture.filename, false); + if (res) + m_icons_texture.metadata = icons_texture; + + if (!background_texture.filename.empty()) + res = m_background_texture.texture.load_from_file(path + background_texture.filename, false); + + if (res) + m_background_texture.metadata = background_texture; + + return res; +} +#else bool GLToolbar::init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size) { std::string path = resources_dir() + "/icons/"; @@ -167,31 +234,69 @@ bool GLToolbar::init(const std::string& icons_texture_filename, unsigned int ite return res; } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE -GLToolbar::Layout::Type GLToolbar::get_layout_type() const +GLToolbar::Layout::EType GLToolbar::get_layout_type() const { return m_layout.type; } -void GLToolbar::set_layout_type(GLToolbar::Layout::Type type) +void GLToolbar::set_layout_type(GLToolbar::Layout::EType type) { m_layout.type = type; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_layout.dirty = true; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +GLToolbar::Layout::EOrientation GLToolbar::get_layout_orientation() const +{ + return m_layout.orientation; +} + +void GLToolbar::set_layout_orientation(GLToolbar::Layout::EOrientation orientation) +{ + m_layout.orientation = orientation; +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void GLToolbar::set_position(float top, float left) { m_layout.top = top; m_layout.left = left; } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::set_border(float border) +{ + m_layout.border = border; + m_layout.dirty = true; +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void GLToolbar::set_separator_size(float size) { m_layout.separator_size = size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_layout.dirty = true; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } void GLToolbar::set_gap_size(float size) { m_layout.gap_size = size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_layout.dirty = true; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +} + +void GLToolbar::set_icons_scale(float scale) +{ + m_layout.icons_scale = scale; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_layout.dirty = true; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } bool GLToolbar::is_enabled() const @@ -211,6 +316,9 @@ bool GLToolbar::add_item(const GLToolbarItem::Data& data) return false; m_items.push_back(item); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_layout.dirty = true; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE return true; } @@ -222,11 +330,20 @@ bool GLToolbar::add_separator() return false; m_items.push_back(item); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + m_layout.dirty = true; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE return true; } float GLToolbar::get_width() const { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + if (m_layout.dirty) + calc_layout(); + + return m_layout.width; +#else switch (m_layout.type) { default: @@ -239,10 +356,17 @@ float GLToolbar::get_width() const return get_width_vertical(); } } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } float GLToolbar::get_height() const { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + if (m_layout.dirty) + calc_layout(); + + return m_layout.height; +#else switch (m_layout.type) { default: @@ -255,6 +379,7 @@ float GLToolbar::get_height() const return get_height_vertical(); } } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } void GLToolbar::enable_item(const std::string& name) @@ -281,6 +406,23 @@ void GLToolbar::disable_item(const std::string& name) } } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::select_item(const std::string& name) +{ + if (is_item_disabled(name)) + return; + + for (GLToolbarItem* item : m_items) + { + if (!item->is_disabled()) + { + bool hover = item->is_hovered(); + item->set_state((item->get_name() == name) ? (hover ? GLToolbarItem::HoverPressed : GLToolbarItem::Pressed) : (hover ? GLToolbarItem::Hover : GLToolbarItem::Normal)); + } + } +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + bool GLToolbar::is_item_pressed(const std::string& name) const { for (GLToolbarItem* item : m_items) @@ -292,10 +434,31 @@ bool GLToolbar::is_item_pressed(const std::string& name) const return false; } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +bool GLToolbar::is_item_disabled(const std::string& name) const +{ + for (GLToolbarItem* item : m_items) + { + if (item->get_name() == name) + return item->is_disabled(); + } + + return false; +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + #if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +std::string GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) +#else std::string GLToolbar::update_hover_state(const Vec2d& mouse_pos) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) #else void GLToolbar::update_hover_state(const Vec2d& mouse_pos) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #endif // ENABLE_REMOVE_TABS_FROM_PLATER { #if ENABLE_REMOVE_TABS_FROM_PLATER @@ -310,24 +473,30 @@ void GLToolbar::update_hover_state(const Vec2d& mouse_pos) { default: #if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + case Layout::Horizontal: { return update_hover_state_horizontal(mouse_pos, parent); } + case Layout::Vertical: { return update_hover_state_vertical(mouse_pos, parent); } +#else case Layout::Horizontal: { return update_hover_state_horizontal(mouse_pos); } case Layout::Vertical: { return update_hover_state_vertical(mouse_pos); } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #else - case Layout::Horizontal: - { - update_hover_state_horizontal(mouse_pos); - break; - } - case Layout::Vertical: - { - update_hover_state_vertical(mouse_pos); - break; - } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + case Layout::Horizontal: { update_hover_state_horizontal(mouse_pos, parent); break; } + case Layout::Vertical: { update_hover_state_vertical(mouse_pos, parent); break; } +#else + case Layout::Horizontal: { update_hover_state_horizontal(mouse_pos); break; } + case Layout::Vertical: { update_hover_state_vertical(mouse_pos); break; } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #endif // ENABLE_REMOVE_TABS_FROM_PLATER } } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +int GLToolbar::contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const +#else int GLToolbar::contains_mouse(const Vec2d& mouse_pos) const +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { if (!m_enabled) return -1; @@ -335,18 +504,21 @@ int GLToolbar::contains_mouse(const Vec2d& mouse_pos) const switch (m_layout.type) { default: - case Layout::Horizontal: - { - return contains_mouse_horizontal(mouse_pos); - } - case Layout::Vertical: - { - return contains_mouse_vertical(mouse_pos); - } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + case Layout::Horizontal: { return contains_mouse_horizontal(mouse_pos, parent); } + case Layout::Vertical: { return contains_mouse_vertical(mouse_pos, parent); } +#else + case Layout::Horizontal: { return contains_mouse_horizontal(mouse_pos); } + case Layout::Vertical: { return contains_mouse_vertical(mouse_pos); } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) +#else void GLToolbar::do_action(unsigned int item_id) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { if (item_id < (unsigned int)m_items.size()) { @@ -361,26 +533,51 @@ void GLToolbar::do_action(unsigned int item_id) else if (state == GLToolbarItem::HoverPressed) item->set_state(GLToolbarItem::Hover); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.render(); + item->do_action(parent.get_wxglcanvas()); +#else m_parent.render(); item->do_action(m_parent.get_wxglcanvas()); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } else { - item->set_state(GLToolbarItem::HoverPressed); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + if (m_type == Radio) + select_item(item->get_name()); + else +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + item->set_state(GLToolbarItem::HoverPressed); + +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.render(); + item->do_action(parent.get_wxglcanvas()); + if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) +#else m_parent.render(); item->do_action(m_parent.get_wxglcanvas()); if (item->get_state() != GLToolbarItem::Disabled) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { // the item may get disabled during the action, if not, set it back to hover state item->set_state(GLToolbarItem::Hover); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.render(); +#else m_parent.render(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } } } } } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::render(const GLCanvas3D& parent) const +#else void GLToolbar::render() const +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { if (!m_enabled || m_items.empty()) return; @@ -393,21 +590,42 @@ void GLToolbar::render() const switch (m_layout.type) { default: - case Layout::Horizontal: - { - render_horizontal(); - break; - } - case Layout::Vertical: - { - render_vertical(); - break; - } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + case Layout::Horizontal: { render_horizontal(parent); break; } + case Layout::Vertical: { render_vertical(parent); break; } +#else + case Layout::Horizontal: { render_horizontal(); break; } + case Layout::Vertical: { render_vertical(); break; } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } ::glPopMatrix(); } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::calc_layout() const +{ + switch (m_layout.type) + { + default: + case Layout::Horizontal: + { + m_layout.width = get_width_horizontal(); + m_layout.height = get_height_horizontal(); + break; + } + case Layout::Vertical: + { + m_layout.width = get_width_vertical(); + m_layout.height = get_height_vertical(); + break; + } + } + + m_layout.dirty = false; +} +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float GLToolbar::get_width_horizontal() const { return get_main_size(); @@ -415,12 +633,20 @@ float GLToolbar::get_width_horizontal() const float GLToolbar::get_width_vertical() const { - return m_icons_texture.items_icon_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + return 2.0f * m_layout.border + m_icons_texture.metadata.icon_size * m_layout.icons_scale; +#else + return m_icons_texture.items_icon_size * m_layout.icons_scale; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } float GLToolbar::get_height_horizontal() const { - return m_icons_texture.items_icon_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + return 2.0f * m_layout.border + m_icons_texture.metadata.icon_size * m_layout.icons_scale; +#else + return m_icons_texture.items_icon_size * m_layout.icons_scale; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } float GLToolbar::get_height_vertical() const @@ -430,13 +656,21 @@ float GLToolbar::get_height_vertical() const float GLToolbar::get_main_size() const { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float size = 2.0f * m_layout.border; +#else float size = 0.0f; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) { if (m_items[i]->is_separator()) size += m_layout.separator_size; else - size += (float)m_icons_texture.items_icon_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + size += (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale; +#else + size += (float)m_icons_texture.items_icon_size * m_layout.icons_scale; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } if (m_items.size() > 1) @@ -446,26 +680,54 @@ float GLToolbar::get_main_size() const } #if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent) +#else std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent) #else void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #endif // ENABLE_REMOVE_TABS_FROM_PLATER { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float zoom = parent.get_camera_zoom(); +#else float zoom = m_parent.get_camera_zoom(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + Size cnv_size = parent.get_canvas_size(); +#else Size cnv_size = m_parent.get_canvas_size(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); - float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; +#else + float scaled_icons_size = (float)m_icons_texture.items_icon_size * m_layout.icons_scale * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_separator_size = m_layout.separator_size * inv_zoom; float scaled_gap_size = m_layout.gap_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = m_layout.border * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float left = m_layout.left + scaled_border; + float top = m_layout.top - scaled_border; +#else float left = m_layout.left; float top = m_layout.top; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE std::string tooltip = ""; @@ -486,7 +748,14 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) case GLToolbarItem::Normal: { if (inside) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::Hover); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE break; } @@ -495,15 +764,29 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) if (inside) tooltip = item->get_tooltip(); else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::Normal); - +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + break; } case GLToolbarItem::Pressed: { if (inside) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::HoverPressed); - +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + break; } case GLToolbarItem::HoverPressed: @@ -511,8 +794,15 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) if (inside) tooltip = item->get_tooltip(); else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::Pressed); - +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + break; } default: @@ -535,26 +825,54 @@ void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos) } #if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent) +#else std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent) #else void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #endif // ENABLE_REMOVE_TABS_FROM_PLATER { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float zoom = parent.get_camera_zoom(); +#else float zoom = m_parent.get_camera_zoom(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + Size cnv_size = parent.get_canvas_size(); +#else Size cnv_size = m_parent.get_canvas_size(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); - float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; +#else + float scaled_icons_size = (float)m_icons_texture.items_icon_size * m_layout.icons_scale * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_separator_size = m_layout.separator_size * inv_zoom; float scaled_gap_size = m_layout.gap_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = m_layout.border * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float left = m_layout.left + scaled_border; + float top = m_layout.top - scaled_border; +#else float left = m_layout.left; float top = m_layout.top; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE std::string tooltip = ""; @@ -575,7 +893,14 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) case GLToolbarItem::Normal: { if (inside) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::Hover); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE break; } @@ -584,14 +909,28 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) if (inside) tooltip = item->get_tooltip(); else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::Normal); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE break; } case GLToolbarItem::Pressed: { if (inside) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::HoverPressed); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE break; } @@ -600,7 +939,14 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) if (inside) tooltip = item->get_tooltip(); else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + { +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE item->set_state(GLToolbarItem::Pressed); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + parent.set_as_dirty(); + } +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE break; } @@ -622,23 +968,47 @@ void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos) #endif // ENABLE_REMOVE_TABS_FROM_PLATER } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const +#else int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos) const +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float zoom = parent.get_camera_zoom(); +#else float zoom = m_parent.get_camera_zoom(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + Size cnv_size = parent.get_canvas_size(); +#else Size cnv_size = m_parent.get_canvas_size(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); - float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; +#else + float scaled_icons_size = (float)m_icons_texture.items_icon_size * m_layout.icons_scale * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_separator_size = m_layout.separator_size * inv_zoom; float scaled_gap_size = m_layout.gap_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = m_layout.border * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float left = m_layout.left + scaled_border; + float top = m_layout.top - scaled_border; +#else float left = m_layout.left; float top = m_layout.top; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE int id = -1; @@ -663,23 +1033,47 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos) const return -1; } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const +#else int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos) const +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float zoom = parent.get_camera_zoom(); +#else float zoom = m_parent.get_camera_zoom(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + Size cnv_size = parent.get_canvas_size(); +#else Size cnv_size = m_parent.get_canvas_size(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); - float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; +#else + float scaled_icons_size = (float)m_icons_texture.items_icon_size * m_layout.icons_scale * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_separator_size = m_layout.separator_size * inv_zoom; float scaled_gap_size = m_layout.gap_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = m_layout.border * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float left = m_layout.left + scaled_border; + float top = m_layout.top - scaled_border; +#else float left = m_layout.left; float top = m_layout.top; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE int id = -1; @@ -704,7 +1098,11 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos) const return -1; } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::render_horizontal(const GLCanvas3D& parent) const +#else void GLToolbar::render_horizontal() const +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { unsigned int tex_id = m_icons_texture.texture.get_id(); int tex_size = m_icons_texture.texture.get_width(); @@ -712,18 +1110,124 @@ void GLToolbar::render_horizontal() const if ((tex_id == 0) || (tex_size <= 0)) return; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float zoom = parent.get_camera_zoom(); +#else float zoom = m_parent.get_camera_zoom(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; +#else + float scaled_icons_size = (float)m_icons_texture.items_icon_size * m_layout.icons_scale * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_separator_size = m_layout.separator_size * inv_zoom; float scaled_gap_size = m_layout.gap_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = m_layout.border * inv_zoom; + float scaled_width = get_width() * inv_zoom; + float scaled_height = get_height() * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE float left = m_layout.left; float top = m_layout.top; + float right = left + scaled_width; + float bottom = top - scaled_height; + + // renders background + unsigned int bg_tex_id = m_background_texture.texture.get_id(); + float bg_tex_width = (float)m_background_texture.texture.get_width(); + float bg_tex_height = (float)m_background_texture.texture.get_height(); + if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) + { + float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; + float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; + + float bg_uv_left = 0.0f; + float bg_uv_right = 1.0f; + float bg_uv_top = 1.0f; + float bg_uv_bottom = 0.0f; + + float bg_left = left; + float bg_right = right; + float bg_top = top; + float bg_bottom = bottom; + float bg_width = right - left; + float bg_height = top - bottom; + float bg_min_size = std::min(bg_width, bg_height); + + float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; + float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; + float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; + float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; + + float bg_i_left = bg_left + scaled_border; + float bg_i_right = bg_right - scaled_border; + float bg_i_top = bg_top - scaled_border; + float bg_i_bottom = bg_bottom + scaled_border; + + switch (m_layout.orientation) + { + case Layout::Top: + { + bg_uv_top = bg_uv_i_top; + bg_i_top = bg_top; + break; + } + case Layout::Bottom: + { + bg_uv_bottom = bg_uv_i_bottom; + bg_i_bottom = bg_bottom; + break; + } + case Layout::Center: + { + break; + } + }; + + if ((m_layout.border > 0) && (bg_uv_top != bg_uv_i_top)) + { + if (bg_uv_left != bg_uv_i_left) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); + + if (bg_uv_right != bg_uv_i_right) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); + } + + if ((m_layout.border > 0) && (bg_uv_left != bg_uv_i_left)) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); + + if ((m_layout.border > 0) && (bg_uv_right != bg_uv_i_right)) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); + + if ((m_layout.border > 0) && (bg_uv_bottom != bg_uv_i_bottom)) + { + if (bg_uv_left != bg_uv_i_left) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); + + if (bg_uv_right != bg_uv_i_right) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); + } + } + + left += scaled_border; + top -= scaled_border; +#else + float left = m_layout.left; + float top = m_layout.top; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE // renders icons for (const GLToolbarItem* item : m_items) @@ -732,13 +1236,21 @@ void GLToolbar::render_horizontal() const left += separator_stride; else { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.metadata.icon_border_size, m_icons_texture.metadata.icon_size, m_icons_texture.metadata.icon_gap_size); +#else item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE left += icon_stride; } } } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void GLToolbar::render_vertical(const GLCanvas3D& parent) const +#else void GLToolbar::render_vertical() const +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { unsigned int tex_id = m_icons_texture.texture.get_id(); int tex_size = m_icons_texture.texture.get_width(); @@ -746,18 +1258,124 @@ void GLToolbar::render_vertical() const if ((tex_id == 0) || (tex_size <= 0)) return; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float zoom = parent.get_camera_zoom(); +#else float zoom = m_parent.get_camera_zoom(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; +#else + float scaled_icons_size = (float)m_icons_texture.items_icon_size * m_layout.icons_scale * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float scaled_separator_size = m_layout.separator_size * inv_zoom; float scaled_gap_size = m_layout.gap_size * inv_zoom; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float scaled_border = m_layout.border * inv_zoom; + float scaled_width = get_width() * inv_zoom; + float scaled_height = get_height() * inv_zoom; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE float left = m_layout.left; float top = m_layout.top; + float right = left + scaled_width; + float bottom = top - scaled_height; + + // renders background + unsigned int bg_tex_id = m_background_texture.texture.get_id(); + float bg_tex_width = (float)m_background_texture.texture.get_width(); + float bg_tex_height = (float)m_background_texture.texture.get_height(); + if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) + { + float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; + float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; + + float bg_uv_left = 0.0f; + float bg_uv_right = 1.0f; + float bg_uv_top = 1.0f; + float bg_uv_bottom = 0.0f; + + float bg_left = left; + float bg_right = right; + float bg_top = top; + float bg_bottom = bottom; + float bg_width = right - left; + float bg_height = top - bottom; + float bg_min_size = std::min(bg_width, bg_height); + + float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; + float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; + float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; + float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; + + float bg_i_left = bg_left + scaled_border; + float bg_i_right = bg_right - scaled_border; + float bg_i_top = bg_top - scaled_border; + float bg_i_bottom = bg_bottom + scaled_border; + + switch (m_layout.orientation) + { + case Layout::Left: + { + bg_uv_left = bg_uv_i_left; + bg_i_left = bg_left; + break; + } + case Layout::Right: + { + bg_uv_right = bg_uv_i_right; + bg_i_right = bg_right; + break; + } + case Layout::Center: + { + break; + } + }; + + if ((m_layout.border > 0) && (bg_uv_top != bg_uv_i_top)) + { + if (bg_uv_left != bg_uv_i_left) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); + + if (bg_uv_right != bg_uv_i_right) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); + } + + if ((m_layout.border > 0) && (bg_uv_left != bg_uv_i_left)) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); + + if ((m_layout.border > 0) && (bg_uv_right != bg_uv_i_right)) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); + + if ((m_layout.border > 0) && (bg_uv_bottom != bg_uv_i_bottom)) + { + if (bg_uv_left != bg_uv_i_left) + GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); + + GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); + + if (bg_uv_right != bg_uv_i_right) + GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); + } + } + + left += scaled_border; + top -= scaled_border; +#else + float left = m_layout.left; + float top = m_layout.top; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE // renders icons for (const GLToolbarItem* item : m_items) @@ -766,12 +1384,17 @@ void GLToolbar::render_vertical() const top -= separator_stride; else { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.metadata.icon_border_size, m_icons_texture.metadata.icon_size, m_icons_texture.metadata.icon_gap_size); +#else item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE top -= icon_stride; } } } +#if !ENABLE_TOOLBAR_BACKGROUND_TEXTURE GLRadioToolbarItem::Data::Data() : name("") , tooltip("") @@ -1075,6 +1698,7 @@ void GLRadioToolbar::render(const GLCanvas3D& parent) const ::glPopMatrix(); } +#endif // !ENABLE_TOOLBAR_BACKGROUND_TEXTURE } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 7eab518f73..430d4a8442 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -77,6 +77,9 @@ public: void do_action(wxEvtHandler *target); bool is_enabled() const; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + bool is_disabled() const; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE bool is_hovered() const; bool is_pressed() const; @@ -94,7 +97,25 @@ private: // from left to right struct ItemsIconsTexture { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + struct Metadata + { + // path of the file containing the icons' texture + std::string filename; + // size of the square icons, in pixels + unsigned int icon_size; + // size of the border, in pixels + unsigned int icon_border_size; + // distance between two adjacent icons (to avoid filtering artifacts), in pixels + unsigned int icon_gap_size; + + Metadata(); + }; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE GLTexture texture; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + Metadata metadata; +#else // size of the square icons, in pixels unsigned int items_icon_size; // distance from the border, in pixels @@ -103,25 +124,82 @@ struct ItemsIconsTexture unsigned int items_icon_gap_size; ItemsIconsTexture(); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE }; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +struct BackgroundTexture +{ + struct Metadata + { + // path of the file containing the background texture + std::string filename; + // size of the left edge, in pixels + unsigned int left; + // size of the right edge, in pixels + unsigned int right; + // size of the top edge, in pixels + unsigned int top; + // size of the bottom edge, in pixels + unsigned int bottom; + + Metadata(); + }; + + GLTexture texture; + Metadata metadata; +}; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + class GLToolbar { public: +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + enum EType : unsigned char + { + Normal, + Radio, + Num_Types + }; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + struct Layout { - enum Type : unsigned char + enum EType : unsigned char { Horizontal, Vertical, Num_Types }; - Type type; + enum EOrientation : unsigned int + { + Top, + Bottom, + Left, + Right, + Center, + Num_Locations + }; + + EType type; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + EOrientation orientation; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float top; float left; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float border; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float separator_size; float gap_size; + float icons_scale; + +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + float width; + float height; + bool dirty; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE Layout(); }; @@ -129,25 +207,50 @@ public: private: typedef std::vector ItemsList; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + EType m_type; +#else GLCanvas3D& m_parent; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE bool m_enabled; ItemsIconsTexture m_icons_texture; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + BackgroundTexture m_background_texture; + mutable Layout m_layout; +#else Layout m_layout; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE ItemsList m_items; public: +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + explicit GLToolbar(EType type); +#else explicit GLToolbar(GLCanvas3D& parent); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE ~GLToolbar(); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + bool init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture); +#else bool init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size); - - Layout::Type get_layout_type() const; - void set_layout_type(Layout::Type type); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE + + Layout::EType get_layout_type() const; + void set_layout_type(Layout::EType type); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + Layout::EOrientation get_layout_orientation() const; + void set_layout_orientation(Layout::EOrientation orientation); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE void set_position(float top, float left); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void set_border(float border); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE void set_separator_size(float size); void set_gap_size(float size); + void set_icons_scale(float scale); bool is_enabled() const; void set_enabled(bool enable); @@ -160,42 +263,89 @@ public: void enable_item(const std::string& name); void disable_item(const std::string& name); +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void select_item(const std::string& name); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE bool is_item_pressed(const std::string& name) const; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + bool is_item_disabled(const std::string& name) const; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + std::string update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent); +#else std::string update_hover_state(const Vec2d& mouse_pos); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent); #else void update_hover_state(const Vec2d& mouse_pos); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #endif // ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + // returns the id of the item under the given mouse position or -1 if none + int contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; + + void do_action(unsigned int item_id, GLCanvas3D& parent); +#else // returns the id of the item under the given mouse position or -1 if none int contains_mouse(const Vec2d& mouse_pos) const; void do_action(unsigned int item_id); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void render(const GLCanvas3D& parent) const; +#else void render() const; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE private: +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void calc_layout() const; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE float get_width_horizontal() const; float get_width_vertical() const; float get_height_horizontal() const; float get_height_vertical() const; float get_main_size() const; #if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + std::string update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent); + std::string update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent); +#else std::string update_hover_state_horizontal(const Vec2d& mouse_pos); std::string update_hover_state_vertical(const Vec2d& mouse_pos); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#else +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent); + void update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent); #else void update_hover_state_horizontal(const Vec2d& mouse_pos); void update_hover_state_vertical(const Vec2d& mouse_pos); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #endif // ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + int contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; + int contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; + + void render_horizontal(const GLCanvas3D& parent) const; + void render_vertical(const GLCanvas3D& parent) const; +#else int contains_mouse_horizontal(const Vec2d& mouse_pos) const; int contains_mouse_vertical(const Vec2d& mouse_pos) const; void render_horizontal() const; void render_vertical() const; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE }; +#if !ENABLE_TOOLBAR_BACKGROUND_TEXTURE class GLRadioToolbarItem { public: @@ -274,6 +424,7 @@ public: void render(const GLCanvas3D& parent) const; }; +#endif // !ENABLE_TOOLBAR_BACKGROUND_TEXTURE } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index bc7ea98994..fb6676aed5 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -195,6 +195,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); else if (opt_key.compare("host_type") == 0) config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); + else if (opt_key.compare("display_orientation") == 0) + config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); } break; case coPoints:{ diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index f4047ae3e4..e4db9b6e1a 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 875a924562..3c2b4a21fa 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -92,7 +92,7 @@ class GUI_App : public wxApp std::unique_ptr m_imgui; #endif // ENABLE_IMGUI - std::unique_ptr m_printhost_queue; + std::unique_ptr m_printhost_job_queue; public: bool OnInit() override; @@ -164,7 +164,7 @@ public: ImGuiWrapper* imgui() { return m_imgui.get(); } #endif // ENABLE_IMGUI - PrintHostJobQueue& printhost_queue() { return *m_printhost_queue.get(); } + PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); } }; DECLARE_APP(GUI_App) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 575444c4bc..5e20ceaab5 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -982,6 +982,10 @@ void ObjectList::del_instances_from_object(const int obj_idx) bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type) { + if (obj_idx == 1000) + // Cannot delete a wipe tower. + return false; + if (type == itVolume) { const auto volume = (*m_objects)[obj_idx]->volumes[idx]; @@ -1399,6 +1403,20 @@ void ObjectList::update_selections() auto& selection = wxGetApp().plater()->canvas3D()->get_selection(); wxDataViewItemArray sels; + // We doesn't update selection if SettingsItem for the current object/part is selected + if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) + { + const auto item = GetSelection(); + if (selection.is_single_full_object() && + m_objects_model->GetIdByItem(m_objects_model->GetParent(item)) == selection.get_object_idx()) + return; + if (selection.is_single_volume() || selection.is_modifier()) { + const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin()); + if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx()) + return; + } + } + if (selection.is_single_full_object()) { sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); @@ -1459,13 +1477,11 @@ void ObjectList::update_selections() select_items(sels); -#ifdef __WXMSW__ if (GetSelection()) { - const int sel_item_row = GetRowByItem(GetSelection()); + const int sel_item_row = m_objects_model->GetRowByItem(GetSelection()); ScrollLines(sel_item_row - m_selected_row); m_selected_row = sel_item_row; } -#endif //__WXMSW__ } void ObjectList::update_selections_on_canvas() @@ -1656,7 +1672,9 @@ void ObjectList::change_part_type() void ObjectList::last_volume_is_deleted(const int obj_idx) { - if (obj_idx < 0 || (*m_objects).empty() || (*m_objects)[obj_idx]->volumes.empty()) + if (obj_idx < 0 || m_objects->empty() || + obj_idx <= m_objects->size() || + (*m_objects)[obj_idx]->volumes.empty()) return; auto volume = (*m_objects)[obj_idx]->volumes[0]; diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index d193a11a92..ddf699c2cd 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -25,18 +25,6 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_og->m_on_change = [this](const std::string& opt_key, const boost::any& value) { std::vector axes{ "_x", "_y", "_z" }; - if (opt_key == "scale_unit") { - const wxString& selection = boost::any_cast(value); - for (auto axis : axes) { - std::string key = "scale" + axis; - m_og->set_side_text(key, selection); - } - - m_is_percent_scale = selection == _("%"); - update_scale_values(); - return; - } - std::string param; std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param)); @@ -51,29 +39,51 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : change_rotation_value(new_value); else if (param == "scale") change_scale_value(new_value); + else if (param == "size") + change_size_value(new_value); + + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false); }; m_og->m_fill_empty_value = [this](const std::string& opt_key) { - if (opt_key == "scale_unit") - return; - std::string param; std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param)); + + double value = 0.0; + if (param == "position") { int axis = opt_key.back() == 'x' ? 0 : opt_key.back() == 'y' ? 1 : 2; - m_og->set_value(opt_key, double_to_string(cache_position(axis))); - return; + value = cache_position(axis); + } + else if (param == "rotation") { + int axis = opt_key.back() == 'x' ? 0 : + opt_key.back() == 'y' ? 1 : 2; + + value = cache_rotation(axis); + } + else if (param == "scale") { + int axis = opt_key.back() == 'x' ? 0 : + opt_key.back() == 'y' ? 1 : 2; + + value = cache_scale(axis); + } + else if (param == "size") { + int axis = opt_key.back() == 'x' ? 0 : + opt_key.back() == 'y' ? 1 : 2; + + value = cache_size(axis); } - m_og->set_value(opt_key, double_to_string(0.0)); + m_og->set_value(opt_key, double_to_string(value)); + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, false); }; m_og->m_set_focus = [this](const std::string& opt_key) { - wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key); + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, true); }; ConfigOptionDef def; @@ -106,59 +116,37 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : auto add_og_to_object_settings = [](const std::string& option_name, const std::string& sidetext) { Line line = { _(option_name), "" }; - if (option_name == "Scale") { - line.near_label_widget = [](wxWindow* parent) { - auto btn = new PrusaLockButton(parent, wxID_ANY); - btn->Bind(wxEVT_BUTTON, [btn](wxCommandEvent &event) { - event.Skip(); - wxTheApp->CallAfter([btn]() { - wxGetApp().obj_manipul()->set_uniform_scaling(btn->IsLocked()); - }); - }); - return btn; - }; - } - ConfigOptionDef def; def.type = coFloat; def.default_value = new ConfigOptionFloat(0.0); def.width = 50; if (option_name == "Rotation") + { def.min = -360; + def.max = 360; + } const std::string lower_name = boost::algorithm::to_lower_copy(option_name); std::vector axes{ "x", "y", "z" }; for (auto axis : axes) { - if (axis == "z" && option_name != "Scale") + if (axis == "z") def.sidetext = sidetext; Option option = Option(def, lower_name + "_" + axis); option.opt.full_width = true; line.append_option(option); } - if (option_name == "Scale") - { - def.width = 45; - def.type = coStrings; - def.gui_type = "select_open"; - def.enum_labels.push_back(L("%")); - def.enum_labels.push_back(L("mm")); - def.default_value = new ConfigOptionStrings{ "mm" }; - - const Option option = Option(def, lower_name + "_unit"); - line.append_option(option); - } - return line; }; // Settings table m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")), &m_move_Label); - m_og->append_line(add_og_to_object_settings(L("Rotation"), "°")); - m_og->append_line(add_og_to_object_settings(L("Scale"), "mm")); + m_og->append_line(add_og_to_object_settings(L("Rotation"), "°"), &m_rotate_Label); + m_og->append_line(add_og_to_object_settings(L("Scale"), "%"), &m_scale_Label); + m_og->append_line(add_og_to_object_settings(L("Size"), "mm")); /* Unused parameter at this time def.label = L("Place on bed"); @@ -204,9 +192,11 @@ int ObjectManipulation::ol_selection() void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& selection) { - wxString move_label = _(L("Position")); + wxString move_label = _(L("Position:")); + wxString rotate_label = _(L("Rotation:")); + wxString scale_label = _(L("Scale factors:")); #if ENABLE_MODELVOLUME_TRANSFORM - if (selection.is_single_full_instance() || selection.is_single_full_object()) + if (selection.is_single_full_instance()) #else if (selection.is_single_full_object()) { @@ -232,6 +222,7 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele update_position_value(volume->get_instance_offset()); update_rotation_value(volume->get_instance_rotation()); update_scale_value(volume->get_instance_scaling_factor()); + update_size_value(volume->get_instance_transformation().get_matrix(true, true) * volume->bounding_box.size()); #else update_position_value(volume->get_offset()); update_rotation_value(volume->get_rotation()); @@ -239,19 +230,15 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele #endif // ENABLE_MODELVOLUME_TRANSFORM m_og->enable(); } - else if (selection.is_wipe_tower()) + else if (selection.is_single_full_object()) { - // the selection contains a single volume - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); -#if ENABLE_MODELVOLUME_TRANSFORM - update_position_value(volume->get_volume_offset()); - update_rotation_value(volume->get_volume_rotation()); - update_scale_value(volume->get_volume_scaling_factor()); -#else - update_position_value(volume->get_offset()); - update_rotation_value(volume->get_rotation()); - update_scale_value(volume->get_scaling_factor()); -#endif // ENABLE_MODELVOLUME_TRANSFORM + const BoundingBoxf3& box = selection.get_bounding_box(); + update_position_value(box.center()); + reset_rotation_value(); + reset_scale_value(); + update_size_value(box.size()); + rotate_label = _(L("Rotate:")); + scale_label = _(L("Scale:")); m_og->enable(); } else if (selection.is_single_modifier() || selection.is_single_volume()) @@ -262,6 +249,7 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele update_position_value(volume->get_volume_offset()); update_rotation_value(volume->get_volume_rotation()); update_scale_value(volume->get_volume_scaling_factor()); + update_size_value(volume->bounding_box.size()); #else update_position_value(volume->get_offset()); update_rotation_value(volume->get_rotation()); @@ -272,14 +260,18 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele else if (wxGetApp().obj_list()->multiple_selection()) { reset_settings_value(); - move_label = _(L("Displacement")); + move_label = _(L("Translate:")); + rotate_label = _(L("Rotate:")); + scale_label = _(L("Scale:")); + update_size_value(selection.get_bounding_box().size()); m_og->enable(); } else reset_settings_value(); m_move_Label->SetLabel(move_label); - m_og->get_field("scale_unit")->disable();// temporary decision + m_rotate_Label->SetLabel(rotate_label); + m_scale_Label->SetLabel(scale_label); } void ObjectManipulation::reset_settings_value() @@ -299,7 +291,7 @@ void ObjectManipulation::reset_position_value() m_og->set_value("position_y", def_0); m_og->set_value("position_z", def_0); - cache_position = { 0., 0., 0. }; + cache_position = Vec3d::Zero(); } void ObjectManipulation::reset_rotation_value() @@ -307,68 +299,26 @@ void ObjectManipulation::reset_rotation_value() m_og->set_value("rotation_x", def_0); m_og->set_value("rotation_y", def_0); m_og->set_value("rotation_z", def_0); + + cache_rotation = Vec3d::Zero(); } void ObjectManipulation::reset_scale_value() { - m_is_percent_scale = true; - m_og->set_value("scale_unit", _("%")); m_og->set_value("scale_x", def_100); m_og->set_value("scale_y", def_100); m_og->set_value("scale_z", def_100); + + cache_scale = Vec3d(100.0, 100.0, 100.0); } -void ObjectManipulation::update_values() +void ObjectManipulation::reset_size_value() { - int selection = ol_selection(); - if (selection < 0 || wxGetApp().mainframe->m_plater->model().objects.size() <= selection) { - m_og->set_value("position_x", def_0); - m_og->set_value("position_y", def_0); - m_og->set_value("position_z", def_0); - m_og->set_value("scale_x" , def_0); - m_og->set_value("scale_y" , def_0); - m_og->set_value("scale_z" , def_0); - m_og->set_value("rotation_x", def_0); - m_og->set_value("rotation_y", def_0); - m_og->set_value("rotation_z", def_0); - m_og->disable(); - return; - } - m_is_percent_scale = boost::any_cast(m_og->get_value("scale_unit")) == _("%"); + m_og->set_value("size_x", def_0); + m_og->set_value("size_y", def_0); + m_og->set_value("size_z", def_0); - update_position_values(); - update_scale_values(); - update_rotation_values(); - m_og->enable(); -} - -void ObjectManipulation::update_scale_values() -{ - int selection = ol_selection(); - ModelObjectPtrs& objects = wxGetApp().mainframe->m_plater->model().objects; - - auto instance = objects[selection]->instances.front(); - auto size = objects[selection]->instance_bounding_box(0).size(); - - if (m_is_percent_scale) { - m_og->set_value("scale_x", double_to_string(instance->get_scaling_factor(X) * 100, 2)); - m_og->set_value("scale_y", double_to_string(instance->get_scaling_factor(Y) * 100, 2)); - m_og->set_value("scale_z", double_to_string(instance->get_scaling_factor(Z) * 100, 2)); - } - else { - m_og->set_value("scale_x", double_to_string(size(0), 2)); - m_og->set_value("scale_y", double_to_string(size(1), 2)); - m_og->set_value("scale_z", double_to_string(size(2), 2)); - } -} - -void ObjectManipulation::update_position_values() -{ - auto instance = wxGetApp().mainframe->m_plater->model().objects[ol_selection()]->instances.front(); - - m_og->set_value("position_x", double_to_string(instance->get_offset(X), 2)); - m_og->set_value("position_y", double_to_string(instance->get_offset(Y), 2)); - m_og->set_value("position_z", double_to_string(instance->get_offset(Z), 2)); + cache_size = Vec3d::Zero(); } void ObjectManipulation::update_position_value(const Vec3d& position) @@ -382,42 +332,21 @@ void ObjectManipulation::update_position_value(const Vec3d& position) void ObjectManipulation::update_scale_value(const Vec3d& scaling_factor) { - // this is temporary - // to be able to update the values as size - // we need to store somewhere the original size - // or have it passed as parameter - if (!m_is_percent_scale) { - m_is_percent_scale = true; - m_og->set_value("scale_unit", _("%")); - } - auto scale = scaling_factor * 100.0; m_og->set_value("scale_x", double_to_string(scale(0), 2)); m_og->set_value("scale_y", double_to_string(scale(1), 2)); m_og->set_value("scale_z", double_to_string(scale(2), 2)); + + cache_scale = scale; } -void ObjectManipulation::update_rotation_values() +void ObjectManipulation::update_size_value(const Vec3d& size) { - update_rotation_value(wxGetApp().mainframe->m_plater->model().objects[ol_selection()]->instances.front()->get_rotation()); -} + m_og->set_value("size_x", double_to_string(size(0), 2)); + m_og->set_value("size_y", double_to_string(size(1), 2)); + m_og->set_value("size_z", double_to_string(size(2), 2)); -void ObjectManipulation::update_rotation_value(double angle, Axis axis) -{ - std::string axis_str; - switch (axis) { - case X: { - axis_str = "rotation_x"; - break; } - case Y: { - axis_str = "rotation_y"; - break; } - case Z: { - axis_str = "rotation_z"; - break; } - } - - m_og->set_value(axis_str, round_nearest(int(Geometry::rad2deg(angle)), 0)); + cache_size = size; } void ObjectManipulation::update_rotation_value(const Vec3d& rotation) @@ -425,16 +354,15 @@ void ObjectManipulation::update_rotation_value(const Vec3d& rotation) m_og->set_value("rotation_x", double_to_string(round_nearest(Geometry::rad2deg(rotation(0)), 0), 2)); m_og->set_value("rotation_y", double_to_string(round_nearest(Geometry::rad2deg(rotation(1)), 0), 2)); m_og->set_value("rotation_z", double_to_string(round_nearest(Geometry::rad2deg(rotation(2)), 0), 2)); -} + cache_rotation = rotation; +} void ObjectManipulation::change_position_value(const Vec3d& position) { - Vec3d displacement(position - cache_position); - auto canvas = wxGetApp().plater()->canvas3D(); canvas->get_selection().start_dragging(); - canvas->get_selection().translate(displacement); + canvas->get_selection().translate(position - cache_position); canvas->do_move(); cache_position = position; @@ -442,38 +370,62 @@ void ObjectManipulation::change_position_value(const Vec3d& position) void ObjectManipulation::change_rotation_value(const Vec3d& rotation) { + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + const GLCanvas3D::Selection& selection = canvas->get_selection(); + Vec3d rad_rotation; for (size_t i = 0; i < 3; ++i) + { rad_rotation(i) = Geometry::deg2rad(rotation(i)); - auto canvas = wxGetApp().plater()->canvas3D(); + } + canvas->get_selection().start_dragging(); - canvas->get_selection().rotate(rad_rotation, false); + canvas->get_selection().rotate(rad_rotation, selection.is_single_full_instance()); canvas->do_rotate(); } void ObjectManipulation::change_scale_value(const Vec3d& scale) { - Vec3d scaling_factor; - if (m_is_percent_scale) - scaling_factor = scale*0.01; - else { - int selection = ol_selection(); - ModelObjectPtrs& objects = *wxGetApp().model_objects(); - - auto size = objects[selection]->instance_bounding_box(0).size(); - for (size_t i = 0; i < 3; ++i) - scaling_factor(i) = scale(i) / size(i); + Vec3d scaling_factor = scale; + const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + if (selection.requires_uniform_scale()) + { + Vec3d abs_scale_diff = (scale - cache_scale).cwiseAbs(); + double max_diff = abs_scale_diff(X); + Axis max_diff_axis = X; + if (max_diff < abs_scale_diff(Y)) + { + max_diff = abs_scale_diff(Y); + max_diff_axis = Y; + } + if (max_diff < abs_scale_diff(Z)) + { + max_diff = abs_scale_diff(Z); + max_diff_axis = Z; + } + scaling_factor = Vec3d(scale(max_diff_axis), scale(max_diff_axis), scale(max_diff_axis)); } + scaling_factor *= 0.01; + auto canvas = wxGetApp().plater()->canvas3D(); canvas->get_selection().start_dragging(); canvas->get_selection().scale(scaling_factor, false); canvas->do_scale(); } -void ObjectManipulation::print_cashe_value(const std::string& label, const Vec3d& v) +void ObjectManipulation::change_size_value(const Vec3d& size) { - std::cout << label << " => " << " X:" << v(0) << " Y:" << v(1) << " Z:" << v(2) << std::endl; + const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + + Vec3d ref_size = cache_size; + if (selection.is_single_full_instance()) + { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + ref_size = volume->bounding_box.size(); + } + + change_scale_value(100.0 * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2))); } } //namespace GUI diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 3a8df4111f..0ada37b968 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -14,12 +14,14 @@ namespace GUI { class ObjectManipulation : public OG_Settings { - bool m_is_percent_scale = false; // true -> percentage scale unit - // false -> uniform scale unit - bool m_is_uniform_scale = false; // It indicates if scale is uniform - Vec3d cache_position { 0., 0., 0. }; + Vec3d cache_rotation { 0., 0., 0. }; + Vec3d cache_scale { 100., 100., 100. }; + Vec3d cache_size { 0., 0., 0. }; + wxStaticText* m_move_Label = nullptr; + wxStaticText* m_scale_Label = nullptr; + wxStaticText* m_rotate_Label = nullptr; public: ObjectManipulation(wxWindow* parent); @@ -36,31 +38,22 @@ public: void reset_position_value(); void reset_rotation_value(); void reset_scale_value(); + void reset_size_value(); - void update_values(); // update position values displacements or "gizmos" - void update_position_values(); void update_position_value(const Vec3d& position); // update scale values after scale unit changing or "gizmos" - void update_scale_values(); void update_scale_value(const Vec3d& scaling_factor); - // update rotation values object selection changing - void update_rotation_values(); + // update size values after scale unit changing or "gizmos" + void update_size_value(const Vec3d& size); // update rotation value after "gizmos" - void update_rotation_value(double angle, Axis axis); void update_rotation_value(const Vec3d& rotation); - void set_uniform_scaling(const bool uniform_scale) { m_is_uniform_scale = uniform_scale; } - - // change values void change_position_value(const Vec3d& position); void change_rotation_value(const Vec3d& rotation); void change_scale_value(const Vec3d& scale); - - -private: - void print_cashe_value(const std::string& label, const Vec3d& value); + void change_size_value(const Vec3d& size); }; }} diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 724815076e..71c92f2d1d 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -92,7 +92,11 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba return true; } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void View3D::set_view_toolbar(GLToolbar* toolbar) +#else void View3D::set_view_toolbar(GLRadioToolbar* toolbar) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { if (m_canvas != nullptr) m_canvas->set_view_toolbar(toolbar); @@ -365,7 +369,11 @@ Preview::~Preview() } #if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +void Preview::set_view_toolbar(GLToolbar* toolbar) +#else void Preview::set_view_toolbar(GLRadioToolbar* toolbar) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { if (m_canvas != nullptr) m_canvas->set_view_toolbar(toolbar); @@ -377,17 +385,13 @@ void Preview::set_number_extruders(unsigned int number_extruders) if (m_number_extruders != number_extruders) { m_number_extruders = number_extruders; - int type = 0; // color by a feature type - if (number_extruders > 1) - { - int tool_idx = m_choice_view_type->FindString(_(L("Tool"))); - int type = (number_extruders > 1) ? tool_idx /* color by a tool number */ : 0; // color by a feature type - m_choice_view_type->SetSelection(type); - if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types)) - m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; + int tool_idx = m_choice_view_type->FindString(_(L("Tool"))); + int type = (number_extruders > 1) ? tool_idx /* color by a tool number */ : 0; // color by a feature type + m_choice_view_type->SetSelection(type); + if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types)) + m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; - m_preferred_color_mode = (type == tool_idx) ? "tool_or_feature" : "feature"; - } + m_preferred_color_mode = (type == tool_idx) ? "tool_or_feature" : "feature"; } } diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 2aebdccd5a..23e6a682f6 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -28,9 +28,15 @@ class Model; namespace GUI { class GLCanvas3D; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE +#if ENABLE_REMOVE_TABS_FROM_PLATER +class GLToolbar; +#endif // ENABLE_REMOVE_TABS_FROM_PLATER +#else #if ENABLE_REMOVE_TABS_FROM_PLATER class GLRadioToolbar; #endif // ENABLE_REMOVE_TABS_FROM_PLATER +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #if ENABLE_REMOVE_TABS_FROM_PLATER class View3D : public wxPanel @@ -53,7 +59,11 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } GLCanvas3D* get_canvas3d() { return m_canvas; } +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void set_view_toolbar(GLToolbar* toolbar); +#else void set_view_toolbar(GLRadioToolbar* toolbar); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE void set_as_dirty(); void set_bed_shape(const Pointfs& shape); @@ -122,7 +132,11 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } #if ENABLE_REMOVE_TABS_FROM_PLATER +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + void set_view_toolbar(GLToolbar* toolbar); +#else void set_view_toolbar(GLRadioToolbar* toolbar); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #endif // ENABLE_REMOVE_TABS_FROM_PLATER void set_number_extruders(unsigned int number_extruders); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index a74d5f72a8..4d4ee17aeb 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -18,6 +18,7 @@ #include "ProgressStatusBar.hpp" #include "3DScene.hpp" #include "AppConfig.hpp" +#include "PrintHostDialogs.hpp" #include "wxExtensions.hpp" #include "I18N.hpp" @@ -30,7 +31,8 @@ namespace GUI { MainFrame::MainFrame(const bool no_plater, const bool loaded) : wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE), m_no_plater(no_plater), - m_loaded(loaded) + m_loaded(loaded), + m_printhost_queue_dlg(new PrintHostQueueDialog(this)) { // Load the icon either from the exe, or from the ico file. #if _WIN32 @@ -326,7 +328,7 @@ void MainFrame::init_menubar() size_t tab_offset = 0; if (m_plater) { #if ENABLE_REMOVE_TABS_FROM_PLATER - append_menu_item(windowMenu, wxID_ANY, L("Plater Tab\tCtrl+1"), L("Show the plater"), + append_menu_item(windowMenu, wxID_HIGHEST + 1, L("Plater Tab\tCtrl+1"), L("Show the plater"), [this](wxCommandEvent&) { select_tab(0); }, "application_view_tile.png"); #else append_menu_item(windowMenu, wxID_ANY, L("Select Plater Tab\tCtrl+1"), L("Show the plater"), @@ -338,22 +340,35 @@ void MainFrame::init_menubar() windowMenu->AppendSeparator(); } #if ENABLE_REMOVE_TABS_FROM_PLATER - append_menu_item(windowMenu, wxID_ANY, L("Print Settings Tab\tCtrl+2"), L("Show the print settings"), + append_menu_item(windowMenu, wxID_HIGHEST + 2, L("Print Settings Tab\tCtrl+2"), L("Show the print settings"), [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 0); }, "cog.png"); - append_menu_item(windowMenu, wxID_ANY, L("Filament Settings Tab\tCtrl+3"), L("Show the filament settings"), + append_menu_item(windowMenu, wxID_HIGHEST + 3, L("Filament Settings Tab\tCtrl+3"), L("Show the filament settings"), [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 1); }, "spool.png"); - append_menu_item(windowMenu, wxID_ANY, L("Printer Settings Tab\tCtrl+4"), L("Show the printer settings"), + append_menu_item(windowMenu, wxID_HIGHEST + 4, L("Printer Settings Tab\tCtrl+4"), L("Show the printer settings"), [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 2); }, "printer_empty.png"); if (m_plater) { windowMenu->AppendSeparator(); - wxMenuItem* item_3d = append_menu_item(windowMenu, wxID_ANY, L("3D\tCtrl+5"), L("Show the 3D editing view"), - [this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, ""); - wxMenuItem* item_preview = append_menu_item(windowMenu, wxID_ANY, L("Preview\tCtrl+6"), L("Show the 3D slices preview"), - [this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, ""); + wxMenuItem* item_3d = append_menu_item(windowMenu, wxID_HIGHEST + 5, L("3D\tCtrl+5"), L("Show the 3D editing view"), + [this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, ""); + wxMenuItem* item_preview = append_menu_item(windowMenu, wxID_HIGHEST + 6, L("Preview\tCtrl+6"), L("Show the 3D slices preview"), + [this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, ""); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_3d->GetId()); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_preview->GetId()); } + +#if _WIN32 + // This is needed on Windows to fake the CTRL+# of the window menu when using the numpad + wxAcceleratorEntry entries[6]; + entries[0].Set(wxACCEL_CTRL, WXK_NUMPAD1, wxID_HIGHEST + 1); + entries[1].Set(wxACCEL_CTRL, WXK_NUMPAD2, wxID_HIGHEST + 2); + entries[2].Set(wxACCEL_CTRL, WXK_NUMPAD3, wxID_HIGHEST + 3); + entries[3].Set(wxACCEL_CTRL, WXK_NUMPAD4, wxID_HIGHEST + 4); + entries[4].Set(wxACCEL_CTRL, WXK_NUMPAD5, wxID_HIGHEST + 5); + entries[5].Set(wxACCEL_CTRL, WXK_NUMPAD6, wxID_HIGHEST + 6); + wxAcceleratorTable accel(6, entries); + SetAcceleratorTable(accel); +#endif // _WIN32 #else append_menu_item(windowMenu, wxID_ANY, L("Select Print Settings Tab\tCtrl+2"), L("Show the print settings"), [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 0); }, "cog.png"); @@ -362,6 +377,10 @@ void MainFrame::init_menubar() append_menu_item(windowMenu, wxID_ANY, L("Select Printer Settings Tab\tCtrl+4"), L("Show the printer settings"), [this, tab_offset](wxCommandEvent&) { select_tab(tab_offset + 2); }, "printer_empty.png"); #endif // ENABLE_REMOVE_TABS_FROM_PLATER + + windowMenu->AppendSeparator(); + append_menu_item(windowMenu, wxID_ANY, L("Print Host Upload Queue"), L("Display the Print Host Upload Queue window"), + [this](wxCommandEvent&) { m_printhost_queue_dlg->ShowModal(); }, "arrow_up.png"); } // View menu diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 2559b5ed1e..fab6aea908 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/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index ae7b404848..d6b8b438e9 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" @@ -61,8 +62,11 @@ MsgDialog::~MsgDialog() {} // ErrorDialog -ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) : - MsgDialog(parent, _(L("Slic3r error")), _(L("Slic3r has encountered an error")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG)) +ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) + : MsgDialog(parent, _(L("Slic3r error")), _(L("Slic3r has encountered an error")), + wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG), + wxID_NONE) + , msg(msg) { auto *panel = new wxScrolledWindow(this); auto *p_sizer = new wxBoxSizer(wxVERTICAL); @@ -77,6 +81,20 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) : content_sizer->Add(panel, 1, wxEXPAND); + auto *btn_copy = new wxButton(this, wxID_ANY, _(L("Copy to clipboard"))); + btn_copy->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { + if (wxTheClipboard->Open()) { + wxTheClipboard->SetData(new wxTextDataObject(this->msg)); // Note: the clipboard takes ownership of the pointer + wxTheClipboard->Close(); + } + }); + + auto *btn_ok = new wxButton(this, wxID_OK); + btn_ok->SetFocus(); + + btn_sizer->Add(btn_copy, 0, wxRIGHT, HORIZ_SPACING); + btn_sizer->Add(btn_ok); + SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT)); Fit(); } diff --git a/src/slic3r/GUI/MsgDialog.hpp b/src/slic3r/GUI/MsgDialog.hpp index ca349eb5c8..6064d2a9f5 100644 --- a/src/slic3r/GUI/MsgDialog.hpp +++ b/src/slic3r/GUI/MsgDialog.hpp @@ -50,14 +50,18 @@ protected: // Generic error dialog, used for displaying exceptions -struct ErrorDialog : MsgDialog +class ErrorDialog : public MsgDialog { +public: ErrorDialog(wxWindow *parent, const wxString &msg); ErrorDialog(ErrorDialog &&) = delete; ErrorDialog(const ErrorDialog &) = delete; ErrorDialog &operator=(ErrorDialog &&) = delete; ErrorDialog &operator=(const ErrorDialog &) = delete; virtual ~ErrorDialog(); + +private: + wxString msg; }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b1526fdb49..ea8a83871d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -213,7 +213,7 @@ void SlicedInfo::SetTextAndShow(SlisedInfoIdx idx, const wxString& text, const w } PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) : - wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY), + wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200,-1), 0, nullptr, wxCB_READONLY), preset_type(preset_type), last_selected(wxNOT_FOUND) { @@ -484,7 +484,7 @@ Sidebar::Sidebar(Plater *parent) : wxPanel(parent), p(new priv(parent)) { p->scrolled = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(400, -1)); - p->scrolled->SetScrollbars(0, 1, 1, 1); + p->scrolled->SetScrollbars(0, 20, 1, 2); // Sizer in the scrolled area auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL); @@ -732,8 +732,7 @@ void Sidebar::show_info_sizer() p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast(model_object->materials_count()))); auto& stats = model_object->volumes.front()->mesh.stl.stats; - auto sf = model_instance->get_scaling_factor(); - p->object_info->info_volume->SetLabel(wxString::Format("%.2f", size(0) * size(1) * size(2) * sf(0) * sf(1) * sf(2))); + p->object_info->info_volume->SetLabel(wxString::Format("%.2f", size(0) * size(1) * size(2))); p->object_info->info_facets->SetLabel(wxString::Format(_(L("%d (%d shells)")), static_cast(model_object->facets_count()), stats.number_of_parts)); int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + @@ -913,7 +912,11 @@ struct Plater::priv Sidebar *sidebar; #if ENABLE_REMOVE_TABS_FROM_PLATER View3D* view3D; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + GLToolbar view_toolbar; +#else GLRadioToolbar view_toolbar; +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE #else #if !ENABLE_IMGUI wxPanel *panel3d; @@ -1030,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; @@ -1068,6 +1072,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) #endif // !ENABLE_REMOVE_TABS_FROM_PLATER , delayed_scene_refresh(false) , project_filename(wxEmptyString) +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + , view_toolbar(GLToolbar::Radio) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE { arranging.store(false); rotoptimizing.store(false); @@ -1179,7 +1186,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_MODEL_UPDATE, [this](SimpleEvent&) { this->schedule_background_process(); }); view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); }); - view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [q](Event &evt) { evt.data == 1 ? q->increase_instances() : q->decrease_instances(); }); + view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event &evt) + { if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); }); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event &evt) { this->sidebar->enable_buttons(evt.data); }); @@ -1205,7 +1213,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) canvas3Dwidget->Bind(EVT_GLCANVAS_MODEL_UPDATE, [this](SimpleEvent&) { this->schedule_background_process(); }); canvas3Dwidget->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); canvas3Dwidget->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); }); - canvas3Dwidget->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [q](Event &evt) { evt.data == 1 ? q->increase_instances() : q->decrease_instances(); }); + canvas3Dwidget->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event &evt) + { if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); }); canvas3Dwidget->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); canvas3Dwidget->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); canvas3Dwidget->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event &evt) { this->sidebar->enable_buttons(evt.data); }); @@ -1286,7 +1295,9 @@ void Plater::priv::select_view_3D(const std::string& name) else if (name == "Preview") set_current_panel(preview); +#if !ENABLE_TOOLBAR_BACKGROUND_TEXTURE view_toolbar.set_selection(name); +#endif // !ENABLE_TOOLBAR_BACKGROUND_TEXTURE } #else void Plater::priv::select_view(const std::string& direction) @@ -1485,7 +1496,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode #if !ENABLE_MODELVOLUME_TRANSFORM const Vec3d bed_center = Slic3r::to_3d(bed_shape.center().cast(), 0.0); #endif // !ENABLE_MODELVOLUME_TRANSFORM - const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast(), 1.0); + const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast(), 1.0) - 2.0 * Vec3d::Ones(); bool need_arrange = false; bool scaled_down = false; @@ -1517,9 +1528,10 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode if (max_ratio > 10000) { // the size of the object is too big -> this could lead to overflow when moving to clipper coordinates, // so scale down the mesh - // const Vec3d inverse = ratio.cwiseInverse(); - // object->scale(inverse); - object->scale(ratio.cwiseInverse()); + double inv = 1. / max_ratio; + object->scale_mesh(Vec3d(inv, inv, inv)); + object->origin_translation = Vec3d::Zero(); + object->center_around_origin(); scaled_down = true; } else if (max_ratio > 5) { const Vec3d inverse = ratio.cwiseInverse(); @@ -1644,8 +1656,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(); @@ -1653,8 +1665,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(); @@ -2446,8 +2458,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) @@ -2597,9 +2609,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; } @@ -2618,7 +2630,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; @@ -2637,7 +2649,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; @@ -2646,9 +2658,58 @@ bool Plater::priv::complit_init_part_menu() #if ENABLE_REMOVE_TABS_FROM_PLATER void Plater::priv::init_view_toolbar() { +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + ItemsIconsTexture::Metadata icons_data; + icons_data.filename = "view_toolbar.png"; + icons_data.icon_size = 64; + icons_data.icon_border_size = 0; + icons_data.icon_gap_size = 0; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!view_toolbar.init(icons_data, background_data)) +#else if (!view_toolbar.init("view_toolbar.png", 64, 0, 0)) +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE return; +#if ENABLE_TOOLBAR_BACKGROUND_TEXTURE + view_toolbar.set_layout_orientation(GLToolbar::Layout::Bottom); + view_toolbar.set_border(5.0f); + view_toolbar.set_gap_size(1.0f); + + GLToolbarItem::Data item; + + item.name = "3D"; + item.tooltip = GUI::L_str("3D editor view"); + item.sprite_id = 0; + item.action_event = EVT_GLVIEWTOOLBAR_3D; + item.is_toggable = false; + if (!view_toolbar.add_item(item)) + return; + + item.name = "Preview"; + item.tooltip = GUI::L_str("Preview"); + item.sprite_id = 1; + item.action_event = EVT_GLVIEWTOOLBAR_PREVIEW; + item.is_toggable = false; + if (!view_toolbar.add_item(item)) + return; + + view_toolbar.enable_item("3D"); + view_toolbar.enable_item("Preview"); + + view_toolbar.select_item("3D"); + view_toolbar.set_enabled(true); + + view3D->set_view_toolbar(&view_toolbar); + preview->set_view_toolbar(&view_toolbar); +#else GLRadioToolbarItem::Data item; item.name = "3D"; @@ -2669,6 +2730,7 @@ void Plater::priv::init_view_toolbar() preview->set_view_toolbar(&view_toolbar); view_toolbar.set_selection("3D"); +#endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE } #endif // ENABLE_REMOVE_TABS_FROM_PLATER @@ -2705,6 +2767,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(); @@ -3086,7 +3155,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(); @@ -3178,6 +3247,8 @@ void Plater::on_config_change(const DynamicPrintConfig &config) #endif // ENABLE_REMOVE_TABS_FROM_PLATER if (p->preview) p->preview->set_bed_shape(p->config->option("bed_shape")->values); update_scheduled = true; + } else if (opt_key == "host_type" && this->p->printer_technology == ptSLA) { + p->config->option>(opt_key)->value = htSL1; } } diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 639f70cf7e..bfda0a2f38 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -366,7 +366,8 @@ const std::vector& Preset::printer_options() "host_type", "print_host", "printhost_apikey", "printhost_cafile", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction", - "cooling_tube_length", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits", + "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height", + "default_print_profile", "inherits", "remaining_times", "silent_mode", "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", "machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e", @@ -455,6 +456,7 @@ const std::vector& Preset::sla_printer_options() "display_width", "display_height", "display_pixels_x", "display_pixels_y", "display_orientation", "printer_correction", + "print_host", "printhost_apikey", "printhost_cafile", "printer_notes", "inherits" }; diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index a5de7c3c69..8ac8615a84 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 d27fbe5766..e38acee325 100644 --- a/src/slic3r/GUI/PrintHostDialogs.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -2,24 +2,27 @@ #define slic3r_PrintHostSendDialog_hpp_ #include - #include #include -#include #include -#include -#include -#include -#include -#include +#include -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/MsgDialog.hpp" +#include "GUI.hpp" +#include "GUI_Utils.hpp" +#include "MsgDialog.hpp" +#include "../Utils/PrintHost.hpp" +class wxTextCtrl; +class wxCheckBox; +class wxDataViewListCtrl; namespace Slic3r { +struct PrintHostJob; + +namespace GUI { + class PrintHostSendDialog : public GUI::MsgDialog { @@ -38,12 +41,38 @@ private: class PrintHostQueueDialog : public wxDialog { public: - PrintHostQueueDialog(); + class Event : public wxEvent + { + public: + size_t job_id; + int progress = 0; // in percent + wxString error; + Event(wxEventType eventType, int winid, size_t job_id); + Event(wxEventType eventType, int winid, size_t job_id, int progress); + Event(wxEventType eventType, int winid, size_t job_id, wxString error); + + virtual wxEvent *Clone() const; + }; + + + PrintHostQueueDialog(wxWindow *parent); + + void append_job(const PrintHostJob &job); private: + wxDataViewListCtrl *job_list; + // Note: EventGuard prevents delivery of progress evts to a freed PrintHostQueueDialog + EventGuard on_progress_evt; + EventGuard on_error_evt; + + void on_progress(Event&); + void on_error(Event&); }; +wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event); +wxDECLARE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event); -} + +}} #endif diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6372f73215..0b59a21aba 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -270,7 +270,7 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str auto panel = this; #endif PageShp page(new Page(panel, title, icon_idx)); - page->SetScrollbars(1, 1, 1, 2); + page->SetScrollbars(1, 20, 1, 2); page->Hide(); m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); @@ -1538,6 +1538,108 @@ bool Tab::current_preset_is_dirty() return m_presets->current_is_dirty(); } +void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) +{ + const bool sla = m_presets->get_selected_preset().printer_technology() == ptSLA; + + // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) + if (! sla) { + optgroup->append_single_option_line("host_type"); + } else { + m_config->option>("host_type", true)->value = htSL1; + } + + auto printhost_browse = [this, optgroup] (wxWindow* parent) { + + // TODO: SLA + + auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG)); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); + + btn->Bind(wxEVT_BUTTON, [this, parent, optgroup](wxCommandEvent e) { + BonjourDialog dialog(parent); + if (dialog.show_and_lookup()) { + optgroup->set_value("print_host", std::move(dialog.get_selected()), true); + } + }); + + return sizer; + }; + + auto print_host_test = [this](wxWindow* parent) { + auto btn = m_print_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), + wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); + btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG)); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); + + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) { + std::unique_ptr host(PrintHost::get_print_host(m_config)); + if (! host) { + const auto text = wxString::Format("%s", + _(L("Could not get a valid Printer Host reference"))); + show_error(this, text); + return; + } + wxString msg; + if (host->test(msg)) { + show_info(this, host->get_test_ok_msg(), _(L("Success!"))); + } else { + show_error(this, host->get_test_failed_msg(msg)); + } + }); + + return sizer; + }; + + Line host_line = optgroup->create_single_option_line("print_host"); + host_line.append_widget(printhost_browse); + host_line.append_widget(print_host_test); + optgroup->append_line(host_line); + optgroup->append_single_option_line("printhost_apikey"); + + if (Http::ca_file_supported()) { + + Line cafile_line = optgroup->create_single_option_line("printhost_cafile"); + + auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { + auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG)); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); + + btn->Bind(wxEVT_BUTTON, [this, optgroup] (wxCommandEvent e) { + static const auto filemasks = _(L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*")); + wxFileDialog openFileDialog(this, _(L("Open CA certificate file")), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_CANCEL) { + optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true); + } + }); + + return sizer; + }; + + cafile_line.append_widget(printhost_cafile_browse); + optgroup->append_line(cafile_line); + + auto printhost_cafile_hint = [this, optgroup] (wxWindow* parent) { + auto txt = new wxStaticText(parent, wxID_ANY, + _(L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate."))); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt); + return sizer; + }; + + Line cafile_hint { "", "" }; + cafile_hint.full_width = 1; + cafile_hint.widget = std::move(printhost_cafile_hint); + optgroup->append_line(cafile_hint); + + } +} + void TabPrinter::build() { m_presets = &m_preset_bundle->printers; @@ -1665,96 +1767,8 @@ void TabPrinter::build_fff() } #endif - optgroup = page->new_optgroup(_(L("Printer Host upload"))); - - optgroup->append_single_option_line("host_type"); - - auto printhost_browse = [this, optgroup] (wxWindow* parent) { - auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG)); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); - - btn->Bind(wxEVT_BUTTON, [this, parent, optgroup](wxCommandEvent e) { - BonjourDialog dialog(parent); - if (dialog.show_and_lookup()) { - optgroup->set_value("print_host", std::move(dialog.get_selected()), true); - } - }); - - return sizer; - }; - - auto print_host_test = [this](wxWindow* parent) { - auto btn = m_print_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), - wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); - btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG)); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); - - btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) { - std::unique_ptr host(PrintHost::get_print_host(m_config)); - if (! host) { - const auto text = wxString::Format("%s", - _(L("Could not get a valid Printer Host reference"))); - show_error(this, text); - return; - } - wxString msg; - if (host->test(msg)) { - show_info(this, host->get_test_ok_msg(), _(L("Success!"))); - } else { - show_error(this, host->get_test_failed_msg(msg)); - } - }); - - return sizer; - }; - - Line host_line = optgroup->create_single_option_line("print_host"); - host_line.append_widget(printhost_browse); - host_line.append_widget(print_host_test); - optgroup->append_line(host_line); - optgroup->append_single_option_line("printhost_apikey"); - - if (Http::ca_file_supported()) { - - Line cafile_line = optgroup->create_single_option_line("printhost_cafile"); - - auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { - auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG)); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); - - btn->Bind(wxEVT_BUTTON, [this, optgroup] (wxCommandEvent e) { - static const auto filemasks = _(L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*")); - wxFileDialog openFileDialog(this, _(L("Open CA certificate file")), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (openFileDialog.ShowModal() != wxID_CANCEL) { - optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true); - } - }); - - return sizer; - }; - - cafile_line.append_widget(printhost_cafile_browse); - optgroup->append_line(cafile_line); - - auto printhost_cafile_hint = [this, optgroup] (wxWindow* parent) { - auto txt = new wxStaticText(parent, wxID_ANY, - _(L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate."))); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(txt); - return sizer; - }; - - Line cafile_hint { "", "" }; - cafile_hint.full_width = 1; - cafile_hint.widget = std::move(printhost_cafile_hint); - optgroup->append_line(cafile_hint); - - } + optgroup = page->new_optgroup(_(L("Print Host upload"))); + build_printhost(optgroup.get()); optgroup = page->new_optgroup(_(L("Firmware"))); optgroup->append_single_option_line("gcode_flavor"); @@ -1897,6 +1911,9 @@ void TabPrinter::build_sla() } optgroup->append_line(line); + optgroup = page->new_optgroup(_(L("Print Host upload"))); + build_printhost(optgroup.get()); + page = add_options_page(_(L("Notes")), "note.png"); optgroup = page->new_optgroup(_(L("Notes")), 0); option = optgroup->get_option("printer_notes"); @@ -2041,6 +2058,7 @@ void TabPrinter::build_extruder_pages() optgroup->append_single_option_line("cooling_tube_length"); optgroup->append_single_option_line("parking_pos_retraction"); optgroup->append_single_option_line("extra_loading_move"); + optgroup->append_single_option_line("high_current_on_filament_swap"); m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page); m_has_single_extruder_MM_page = true; } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 337fbf0067..93db1383ab 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -325,6 +325,8 @@ class TabPrinter : public Tab std::vector m_pages_fff; std::vector m_pages_sla; + + void build_printhost(ConfigOptionsGroup *optgroup); public: wxButton* m_serial_test_btn = nullptr; wxButton* m_print_host_test_btn = nullptr; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 2e5a37040b..2daba5df41 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -953,6 +953,44 @@ void PrusaObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type = itUndef; } +int PrusaObjectDataViewModel::GetRowByItem(const wxDataViewItem& item) const +{ + if (m_objects.empty()) + return -1; + + int row_num = 0; + + for (int i = 0; i < m_objects.size(); i++) + { + row_num++; + if (item == wxDataViewItem(m_objects[i])) + return row_num; + + for (int j = 0; j < m_objects[i]->GetChildCount(); j++) + { + row_num++; + PrusaObjectDataViewModelNode* cur_node = m_objects[i]->GetNthChild(j); + if (item == wxDataViewItem(cur_node)) + return row_num; + + if (cur_node->m_type == itVolume && cur_node->GetChildCount() == 1) + row_num++; + if (cur_node->m_type == itInstanceRoot) + { + row_num++; + for (int t = 0; t < cur_node->GetChildCount(); t++) + { + row_num++; + if (item == wxDataViewItem(cur_node->GetNthChild(t))) + return row_num; + } + } + } + } + + return -1; +} + wxString PrusaObjectDataViewModel::GetName(const wxDataViewItem &item) const { PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); @@ -1240,6 +1278,16 @@ IMPLEMENT_VARIANT_OBJECT(PrusaDataViewBitmapText) // PrusaIconTextRenderer // --------------------------------------------------------- +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +PrusaBitmapTextRenderer::PrusaBitmapTextRenderer(wxDataViewCellMode mode /*= wxDATAVIEW_CELL_EDITABLE*/, + int align /*= wxDVR_DEFAULT_ALIGNMENT*/): +wxDataViewRenderer(wxT("PrusaDataViewBitmapText"), mode, align) +{ + SetMode(mode); + SetAlignment(align); +} +#endif // ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + bool PrusaBitmapTextRenderer::SetValue(const wxVariant &value) { m_value << value; @@ -1251,6 +1299,13 @@ bool PrusaBitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const return false; } +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY +wxString PrusaBitmapTextRenderer::GetAccessibleDescription() const +{ + return m_value.GetText(); +} +#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + bool PrusaBitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) { int xoffset = 0; @@ -1291,12 +1346,12 @@ wxWindow* PrusaBitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect lab PrusaDataViewBitmapText data; data << value; - m_bmp_from_editing_item = data.GetBitmap(); + m_was_unusable_symbol = false; wxPoint position = labelRect.GetPosition(); - if (m_bmp_from_editing_item.IsOk()) { - const int bmp_width = m_bmp_from_editing_item.GetWidth(); + if (data.GetBitmap().IsOk()) { + const int bmp_width = data.GetBitmap().GetWidth(); position.x += bmp_width; labelRect.SetWidth(labelRect.GetWidth() - bmp_width); } @@ -1304,6 +1359,7 @@ wxWindow* PrusaBitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect lab wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(), position, labelRect.GetSize(), wxTE_PROCESS_ENTER); text_editor->SetInsertionPointEnd(); + text_editor->SelectAll(); return text_editor; } @@ -1323,7 +1379,17 @@ bool PrusaBitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& } } - value << PrusaDataViewBitmapText(text_editor->GetValue(), m_bmp_from_editing_item); + // The icon can't be edited so get its old value and reuse it. + wxVariant valueOld; + GetView()->GetModel()->GetValue(valueOld, m_item, 0); + + PrusaDataViewBitmapText bmpText; + bmpText << valueOld; + + // But replace the text with the value entered by user. + bmpText.SetText(text_editor->GetValue()); + + value << bmpText; return true; } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 3fbf9a0839..e8fba1ea24 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -463,6 +463,7 @@ public: int GetVolumeIdByItem(const wxDataViewItem& item) const; int GetInstanceIdByItem(const wxDataViewItem& item) const; void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx); + int GetRowByItem(const wxDataViewItem& item) const; bool IsEmpty() { return m_objects.empty(); } // helper method for wxLog @@ -518,21 +519,44 @@ public: // ---------------------------------------------------------------------------- // PrusaBitmapTextRenderer // ---------------------------------------------------------------------------- - +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +class PrusaBitmapTextRenderer : public wxDataViewRenderer +#else class PrusaBitmapTextRenderer : public wxDataViewCustomRenderer +#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING { public: - PrusaBitmapTextRenderer( wxDataViewCellMode mode = wxDATAVIEW_CELL_EDITABLE, - int align = wxDVR_DEFAULT_ALIGNMENT): - wxDataViewCustomRenderer(wxT("PrusaDataViewBitmapText"), mode, align) {} + PrusaBitmapTextRenderer(wxDataViewCellMode mode = +#ifdef __WXOSX__ + wxDATAVIEW_CELL_INERT +#else + wxDATAVIEW_CELL_EDITABLE +#endif + + ,int align = wxDVR_DEFAULT_ALIGNMENT +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + ); +#else + ) : wxDataViewCustomRenderer(wxT("PrusaDataViewBitmapText"), mode, align) {} +#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING bool SetValue(const wxVariant &value); bool GetValue(wxVariant &value) const; +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY + virtual wxString GetAccessibleDescription() const override; +#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING virtual bool Render(wxRect cell, wxDC *dc, int state); virtual wxSize GetSize() const; - bool HasEditorCtrl() const override { return true; } + bool HasEditorCtrl() const override + { +#ifdef __WXOSX__ + return false; +#else + return true; +#endif + } wxWindow* CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) override; @@ -542,8 +566,7 @@ public: private: PrusaDataViewBitmapText m_value; - wxBitmap m_bmp_from_editing_item; - bool m_was_unusable_symbol; + bool m_was_unusable_symbol {false}; }; diff --git a/src/slic3r/Utils/Duet.cpp b/src/slic3r/Utils/Duet.cpp index 4eda7bd46a..1772ae8ef4 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 db21fd0a18..0608f85a58 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.cpp b/src/slic3r/Utils/Http.cpp index 67c24f3f4a..6e6c9ed44b 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -11,6 +11,7 @@ #include #include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" namespace fs = boost::filesystem; @@ -44,6 +45,7 @@ struct Http::priv // Using a deque here because unlike vector it doesn't ivalidate pointers on insertion std::deque form_files; std::string postfields; + std::string error_buffer; // Used for CURLOPT_ERRORBUFFER size_t limit; bool cancel; @@ -69,13 +71,14 @@ struct Http::priv void http_perform(); }; -Http::priv::priv(const std::string &url) : - curl(::curl_easy_init()), - form(nullptr), - form_end(nullptr), - headerlist(nullptr), - limit(0), - cancel(false) +Http::priv::priv(const std::string &url) + : curl(::curl_easy_init()) + , form(nullptr) + , form_end(nullptr) + , headerlist(nullptr) + , error_buffer(CURL_ERROR_SIZE + 1, '\0') + , limit(0) + , cancel(false) { if (curl == nullptr) { throw std::runtime_error(std::string("Could not construct Curl object")); @@ -83,6 +86,7 @@ Http::priv::priv(const std::string &url) : ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally ::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_FORK_NAME "/" SLIC3R_VERSION); + ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer.front()); } Http::priv::~priv() @@ -199,9 +203,10 @@ void Http::priv::set_post_body(const fs::path &path) std::string Http::priv::curl_error(CURLcode curlcode) { - return (boost::format("%1% (%2%)") + return (boost::format("%1% (%2%): %3%") % ::curl_easy_strerror(curlcode) % curlcode + % error_buffer ).str(); } @@ -227,9 +232,7 @@ void Http::priv::http_perform() ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, static_cast(this)); #endif -#ifndef NDEBUG - ::curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); -#endif + ::curl_easy_setopt(curl, CURLOPT_VERBOSE, get_logging_level() >= 4); if (headerlist != nullptr) { ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index 44580b7eaf..fd3f8830dd 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 b2e2d4d455..cbb81c54f8 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" @@ -16,164 +17,161 @@ namespace fs = boost::filesystem; namespace Slic3r { OctoPrint::OctoPrint(DynamicPrintConfig *config) : - host(config->opt_string("print_host")), - apikey(config->opt_string("printhost_apikey")), - cafile(config->opt_string("printhost_cafile")) + host(config->opt_string("print_host")), + apikey(config->opt_string("printhost_apikey")), + cafile(config->opt_string("printhost_cafile")) {} OctoPrint::~OctoPrint() {} bool OctoPrint::test(wxString &msg) const { - // Since the request is performed synchronously here, - // it is ok to refer to `msg` from within the closure + // Since the request is performed synchronously here, + // it is ok to refer to `msg` from within the closure - bool res = true; - auto url = make_url("api/version"); + bool res = true; + auto url = make_url("api/version"); - BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Get version at: %1%") % url; + BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Get version at: %1%") % url; - auto http = Http::get(std::move(url)); - set_auth(http); - http.on_error([&](std::string body, std::string error, unsigned status) { - BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error getting version: %1%, HTTP %2%, body: `%3%`") % error % status % body; - res = false; - msg = format_error(body, error, status); - }) - .on_complete([&](std::string body, unsigned) { - BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: Got version: %1%") % body; - }) - .perform_sync(); + auto http = Http::get(std::move(url)); + set_auth(http); + http.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error getting version: %1%, HTTP %2%, body: `%3%`") % error % status % body; + res = false; + msg = format_error(body, error, status); + }) + .on_complete([&](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: Got version: %1%") % body; - return res; + // TODO: parse body, call validate_version_text + + }) + .perform_sync(); + + return res; } wxString OctoPrint::get_test_ok_msg () const { - return wxString::Format("%s", _(L("Connection to OctoPrint works correctly."))); + return wxString::Format("%s", _(L("Connection to OctoPrint works correctly."))); } wxString OctoPrint::get_test_failed_msg (wxString &msg) const { - return wxString::Format("%s: %s\n\n%s", - _(L("Could not connect to OctoPrint")), msg, _(L("Note: OctoPrint version at least 1.1.0 is required."))); + return wxString::Format("%s: %s\n\n%s", + _(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 upload_filename = upload_data.upload_path.filename(); + const auto upload_parent_path = upload_data.upload_path.parent_path(); - const auto errortitle = _(L("Error while uploading to the OctoPrint server")); - fs::path filepath(filename); + wxString test_msg; + if (! test(test_msg)) { - PrintHostSendDialog send_dialog(filepath.filename()); - if (send_dialog.ShowModal() != wxID_OK) { return false; } + // TODO: - 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(); + // auto errormsg = wxString::Format("%s: %s", errortitle, test_msg); + // GUI::show_error(&progress_dialog, std::move(errormsg)); + // return false; + } - 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(); + bool res = true; - 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; - } + auto url = make_url("api/files/local"); - bool res = true; + BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Uploading file %1% at %2%, filename: %3%, path: %4%, print: %5%") + % upload_data.source_path.string() + % url + % upload_filename.string() + % upload_parent_path.string() + % upload_data.start_print; - auto url = make_url("api/files/local"); + auto http = Http::post(std::move(url)); + set_auth(http); + 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", 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; + }) + .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; + 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; + } + }) + .perform_sync(); - BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Uploading file %1% at %2%, filename: %3%, path: %4%, print: %5%") - % filepath.string() - % url - % upload_filename.string() - % upload_parent_path.string() - % print; - - auto http = Http::post(std::move(url)); - set_auth(http); - http.form_add("print", print ? "true" : "false") - .form_add("path", upload_parent_path.string()) // XXX: slashes on windows ??? - .form_add_file("file", filename, 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)); - 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(); - - return res; -} - -bool OctoPrint::upload(PrintHostUpload upload_data) const -{ - throw "unimplemented"; + return res; } bool OctoPrint::has_auto_discovery() const { - return true; + return true; } bool OctoPrint::can_test() const { - return true; + return true; +} + +bool OctoPrint::validate_version_text(const std::string &version_text) +{ + // FIXME + return true; } void OctoPrint::set_auth(Http &http) const { - http.header("X-Api-Key", apikey); + http.header("X-Api-Key", apikey); - if (! cafile.empty()) { - http.ca_file(cafile); - } + if (! cafile.empty()) { + http.ca_file(cafile); + } } std::string OctoPrint::make_url(const std::string &path) const { - if (host.find("http://") == 0 || host.find("https://") == 0) { - if (host.back() == '/') { - return (boost::format("%1%%2%") % host % path).str(); - } else { - return (boost::format("%1%/%2%") % host % path).str(); - } - } else { - return (boost::format("http://%1%/%2%") % host % path).str(); - } + if (host.find("http://") == 0 || host.find("https://") == 0) { + if (host.back() == '/') { + return (boost::format("%1%%2%") % host % path).str(); + } else { + return (boost::format("%1%/%2%") % host % path).str(); + } + } else { + return (boost::format("http://%1%/%2%") % host % path).str(); + } } wxString OctoPrint::format_error(const std::string &body, const std::string &error, unsigned status) { - if (status != 0) { - auto wxbody = wxString::FromUTF8(body.data()); - return wxString::Format("HTTP %u: %s", status, wxbody); - } else { - return wxString::FromUTF8(error.data()); - } + if (status != 0) { + auto wxbody = wxString::FromUTF8(body.data()); + return wxString::Format("HTTP %u: %s", status, wxbody); + } else { + return wxString::FromUTF8(error.data()); + } +} + + +// SL1 + +SL1Host::~SL1Host() {} + +bool SL1Host::validate_version_text(const std::string &version_text) +{ + // FIXME + return true; } diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index 314e4cfae0..4d6555e13f 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -16,25 +16,39 @@ class Http; class OctoPrint : public PrintHost { public: - OctoPrint(DynamicPrintConfig *config); - virtual ~OctoPrint(); + OctoPrint(DynamicPrintConfig *config); + virtual ~OctoPrint(); + + bool test(wxString &curl_msg) const; + wxString get_test_ok_msg () const; + wxString get_test_failed_msg (wxString &msg) 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; } + +protected: + virtual bool validate_version_text(const std::string &version_text); - 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 has_auto_discovery() const; - bool can_test() const; private: - std::string host; - std::string apikey; - std::string cafile; + std::string host; + std::string apikey; + std::string cafile; - void set_auth(Http &http) const; - std::string make_url(const std::string &path) const; - static wxString format_error(const std::string &body, const std::string &error, unsigned status); + void set_auth(Http &http) const; + std::string make_url(const std::string &path) const; + static wxString format_error(const std::string &body, const std::string &error, unsigned status); +}; + + +class SL1Host: public OctoPrint +{ +public: + SL1Host(DynamicPrintConfig *config) : OctoPrint(config) {} + virtual ~SL1Host(); + +protected: + virtual bool validate_version_text(const std::string &version_text); }; diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index 570d72f687..cdd0c107ef 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -1,15 +1,22 @@ -#include "OctoPrint.hpp" -#include "Duet.hpp" +#include "PrintHost.hpp" #include #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 { @@ -18,42 +25,152 @@ PrintHost::~PrintHost() {} PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) { - PrintHostType kind = config->option>("host_type")->value; - if (kind == htOctoPrint) { - return new OctoPrint(config); - } else if (kind == htDuet) { - return new Duet(config); + const auto opt = config->option>("host_type"); + if (opt == nullptr) { return nullptr; } + + switch (opt->value) { + case htOctoPrint: return new OctoPrint(config); + case htSL1: return new SL1Host(config); + case htDuet: return new Duet(config); + default: return nullptr; } - return nullptr; } 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; } + + BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue/bg_thread: Got job: `%1%` -> `%1%`") + % the_job.upload_data.upload_path + % the_job.printhost->get_host(); + + 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); + + boost::system::error_code ec; + fs::remove(gcode_path, ec); + if (ec) { + BOOST_LOG_TRIVIAL(error) << boost::format("PrintHostJobQueue: Error removing file `%1%`: %2%") % gcode_path % ec; + } +} + +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 53f7c43d31..52ef380589 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;