mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge branch 'master' into lm_sla_supports_auto
This commit is contained in:
		
						commit
						75ef3431b3
					
				
					 77 changed files with 5581 additions and 1010 deletions
				
			
		|  | @ -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() | ||||
|  |  | |||
							
								
								
									
										53
									
								
								src/eigen/unsupported/Eigen/SparseExtra
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/eigen/unsupported/Eigen/SparseExtra
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | |||
| // This file is part of Eigen, a lightweight C++ template library | ||||
| // for linear algebra. | ||||
| // | ||||
| // Copyright (C) 2008-2009 Gael Guennebaud <g.gael@free.fr> | ||||
| // | ||||
| // This Source Code Form is subject to the terms of the Mozilla | ||||
| // Public License v. 2.0. If a copy of the MPL was not distributed | ||||
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||||
| 
 | ||||
| #ifndef EIGEN_SPARSE_EXTRA_MODULE_H | ||||
| #define EIGEN_SPARSE_EXTRA_MODULE_H | ||||
| 
 | ||||
| #include "../../Eigen/Sparse" | ||||
| 
 | ||||
| #include "../../Eigen/src/Core/util/DisableStupidWarnings.h" | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <map> | ||||
| #include <cstdlib> | ||||
| #include <cstring> | ||||
| #include <algorithm> | ||||
| #include <fstream> | ||||
| #include <sstream> | ||||
| 
 | ||||
| #ifdef EIGEN_GOOGLEHASH_SUPPORT | ||||
|   #include <google/dense_hash_map> | ||||
| #endif | ||||
| 
 | ||||
| /** | ||||
|   * \defgroup SparseExtra_Module SparseExtra module | ||||
|   * | ||||
|   * This module contains some experimental features extending the sparse module. | ||||
|   * | ||||
|   * \code | ||||
|   * #include <Eigen/SparseExtra> | ||||
|   * \endcode | ||||
|   */ | ||||
| 
 | ||||
| 
 | ||||
| #include "src/SparseExtra/DynamicSparseMatrix.h" | ||||
| #include "src/SparseExtra/BlockOfDynamicSparseMatrix.h" | ||||
| #include "src/SparseExtra/RandomSetter.h" | ||||
| 
 | ||||
| #include "src/SparseExtra/MarketIO.h" | ||||
| 
 | ||||
| #if !defined(_WIN32) | ||||
| #include <dirent.h> | ||||
| #include "src/SparseExtra/MatrixMarketIterator.h" | ||||
| #endif | ||||
| 
 | ||||
| #include "../../Eigen/src/Core/util/ReenableStupidWarnings.h" | ||||
| 
 | ||||
| #endif // EIGEN_SPARSE_EXTRA_MODULE_H | ||||
|  | @ -0,0 +1,122 @@ | |||
| // This file is part of Eigen, a lightweight C++ template library
 | ||||
| // for linear algebra.
 | ||||
| //
 | ||||
| // Copyright (C) 2008-2009 Gael Guennebaud <gael.guennebaud@inria.fr>
 | ||||
| //
 | ||||
| // This Source Code Form is subject to the terms of the Mozilla
 | ||||
| // Public License v. 2.0. If a copy of the MPL was not distributed
 | ||||
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | ||||
| 
 | ||||
| #ifndef EIGEN_SPARSE_BLOCKFORDYNAMICMATRIX_H | ||||
| #define EIGEN_SPARSE_BLOCKFORDYNAMICMATRIX_H | ||||
| 
 | ||||
| namespace Eigen {  | ||||
| 
 | ||||
| #if 0 | ||||
| 
 | ||||
| // NOTE Have to be reimplemented as a specialization of BlockImpl< DynamicSparseMatrix<_Scalar, _Options, _Index>, ... >
 | ||||
| // See SparseBlock.h for an example
 | ||||
| 
 | ||||
| 
 | ||||
| /***************************************************************************
 | ||||
| * specialisation for DynamicSparseMatrix | ||||
| ***************************************************************************/ | ||||
| 
 | ||||
| template<typename _Scalar, int _Options, typename _Index, int Size> | ||||
| class SparseInnerVectorSet<DynamicSparseMatrix<_Scalar, _Options, _Index>, Size> | ||||
|   : public SparseMatrixBase<SparseInnerVectorSet<DynamicSparseMatrix<_Scalar, _Options, _Index>, Size> > | ||||
| { | ||||
|     typedef DynamicSparseMatrix<_Scalar, _Options, _Index> MatrixType; | ||||
|   public: | ||||
| 
 | ||||
|     enum { IsRowMajor = internal::traits<SparseInnerVectorSet>::IsRowMajor }; | ||||
| 
 | ||||
|     EIGEN_SPARSE_PUBLIC_INTERFACE(SparseInnerVectorSet) | ||||
|     class InnerIterator: public MatrixType::InnerIterator | ||||
|     { | ||||
|       public: | ||||
|         inline InnerIterator(const SparseInnerVectorSet& xpr, Index outer) | ||||
|           : MatrixType::InnerIterator(xpr.m_matrix, xpr.m_outerStart + outer), m_outer(outer) | ||||
|         {} | ||||
|         inline Index row() const { return IsRowMajor ? m_outer : this->index(); } | ||||
|         inline Index col() const { return IsRowMajor ? this->index() : m_outer; } | ||||
|       protected: | ||||
|         Index m_outer; | ||||
|     }; | ||||
| 
 | ||||
|     inline SparseInnerVectorSet(const MatrixType& matrix, Index outerStart, Index outerSize) | ||||
|       : m_matrix(matrix), m_outerStart(outerStart), m_outerSize(outerSize) | ||||
|     { | ||||
|       eigen_assert( (outerStart>=0) && ((outerStart+outerSize)<=matrix.outerSize()) ); | ||||
|     } | ||||
| 
 | ||||
|     inline SparseInnerVectorSet(const MatrixType& matrix, Index outer) | ||||
|       : m_matrix(matrix), m_outerStart(outer), m_outerSize(Size) | ||||
|     { | ||||
|       eigen_assert(Size!=Dynamic); | ||||
|       eigen_assert( (outer>=0) && (outer<matrix.outerSize()) ); | ||||
|     } | ||||
| 
 | ||||
|     template<typename OtherDerived> | ||||
|     inline SparseInnerVectorSet& operator=(const SparseMatrixBase<OtherDerived>& other) | ||||
|     { | ||||
|       if (IsRowMajor != ((OtherDerived::Flags&RowMajorBit)==RowMajorBit)) | ||||
|       { | ||||
|         // need to transpose => perform a block evaluation followed by a big swap
 | ||||
|         DynamicSparseMatrix<Scalar,IsRowMajor?RowMajorBit:0> aux(other); | ||||
|         *this = aux.markAsRValue(); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         // evaluate/copy vector per vector
 | ||||
|         for (Index j=0; j<m_outerSize.value(); ++j) | ||||
|         { | ||||
|           SparseVector<Scalar,IsRowMajor ? RowMajorBit : 0> aux(other.innerVector(j)); | ||||
|           m_matrix.const_cast_derived()._data()[m_outerStart+j].swap(aux._data()); | ||||
|         } | ||||
|       } | ||||
|       return *this; | ||||
|     } | ||||
| 
 | ||||
|     inline SparseInnerVectorSet& operator=(const SparseInnerVectorSet& other) | ||||
|     { | ||||
|       return operator=<SparseInnerVectorSet>(other); | ||||
|     } | ||||
| 
 | ||||
|     Index nonZeros() const | ||||
|     { | ||||
|       Index count = 0; | ||||
|       for (Index j=0; j<m_outerSize.value(); ++j) | ||||
|         count += m_matrix._data()[m_outerStart+j].size(); | ||||
|       return count; | ||||
|     } | ||||
| 
 | ||||
|     const Scalar& lastCoeff() const | ||||
|     { | ||||
|       EIGEN_STATIC_ASSERT_VECTOR_ONLY(SparseInnerVectorSet); | ||||
|       eigen_assert(m_matrix.data()[m_outerStart].size()>0); | ||||
|       return m_matrix.data()[m_outerStart].vale(m_matrix.data()[m_outerStart].size()-1); | ||||
|     } | ||||
| 
 | ||||
| //     template<typename Sparse>
 | ||||
| //     inline SparseInnerVectorSet& operator=(const SparseMatrixBase<OtherDerived>& other)
 | ||||
| //     {
 | ||||
| //       return *this;
 | ||||
| //     }
 | ||||
| 
 | ||||
|     EIGEN_STRONG_INLINE Index rows() const { return IsRowMajor ? m_outerSize.value() : m_matrix.rows(); } | ||||
|     EIGEN_STRONG_INLINE Index cols() const { return IsRowMajor ? m_matrix.cols() : m_outerSize.value(); } | ||||
| 
 | ||||
|   protected: | ||||
| 
 | ||||
|     const typename MatrixType::Nested m_matrix; | ||||
|     Index m_outerStart; | ||||
|     const internal::variable_if_dynamic<Index, Size> m_outerSize; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| } // end namespace Eigen
 | ||||
| 
 | ||||
| #endif // EIGEN_SPARSE_BLOCKFORDYNAMICMATRIX_H
 | ||||
							
								
								
									
										1079
									
								
								src/eigen/unsupported/Eigen/src/SparseExtra/BlockSparseMatrix.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1079
									
								
								src/eigen/unsupported/Eigen/src/SparseExtra/BlockSparseMatrix.h
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -0,0 +1,392 @@ | |||
| // This file is part of Eigen, a lightweight C++ template library
 | ||||
| // for linear algebra.
 | ||||
| //
 | ||||
| // Copyright (C) 2008-2009 Gael Guennebaud <gael.guennebaud@inria.fr>
 | ||||
| //
 | ||||
| // This Source Code Form is subject to the terms of the Mozilla
 | ||||
| // Public License v. 2.0. If a copy of the MPL was not distributed
 | ||||
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | ||||
| 
 | ||||
| #ifndef EIGEN_DYNAMIC_SPARSEMATRIX_H | ||||
| #define EIGEN_DYNAMIC_SPARSEMATRIX_H | ||||
| 
 | ||||
| namespace Eigen {  | ||||
| 
 | ||||
| /** \deprecated use a SparseMatrix in an uncompressed mode
 | ||||
|   * | ||||
|   * \class DynamicSparseMatrix | ||||
|   * | ||||
|   * \brief A sparse matrix class designed for matrix assembly purpose | ||||
|   * | ||||
|   * \param _Scalar the scalar type, i.e. the type of the coefficients | ||||
|   * | ||||
|   * Unlike SparseMatrix, this class provides a much higher degree of flexibility. In particular, it allows | ||||
|   * random read/write accesses in log(rho*outer_size) where \c rho is the probability that a coefficient is | ||||
|   * nonzero and outer_size is the number of columns if the matrix is column-major and the number of rows | ||||
|   * otherwise. | ||||
|   * | ||||
|   * Internally, the data are stored as a std::vector of compressed vector. The performances of random writes might | ||||
|   * decrease as the number of nonzeros per inner-vector increase. In practice, we observed very good performance | ||||
|   * till about 100 nonzeros/vector, and the performance remains relatively good till 500 nonzeros/vectors. | ||||
|   * | ||||
|   * \see SparseMatrix | ||||
|   */ | ||||
| 
 | ||||
| namespace internal { | ||||
| template<typename _Scalar, int _Options, typename _StorageIndex> | ||||
| struct traits<DynamicSparseMatrix<_Scalar, _Options, _StorageIndex> > | ||||
| { | ||||
|   typedef _Scalar Scalar; | ||||
|   typedef _StorageIndex StorageIndex; | ||||
|   typedef Sparse StorageKind; | ||||
|   typedef MatrixXpr XprKind; | ||||
|   enum { | ||||
|     RowsAtCompileTime = Dynamic, | ||||
|     ColsAtCompileTime = Dynamic, | ||||
|     MaxRowsAtCompileTime = Dynamic, | ||||
|     MaxColsAtCompileTime = Dynamic, | ||||
|     Flags = _Options | NestByRefBit | LvalueBit, | ||||
|     CoeffReadCost = NumTraits<Scalar>::ReadCost, | ||||
|     SupportedAccessPatterns = OuterRandomAccessPattern | ||||
|   }; | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| template<typename _Scalar, int _Options, typename _StorageIndex> | ||||
|  class  DynamicSparseMatrix | ||||
|   : public SparseMatrixBase<DynamicSparseMatrix<_Scalar, _Options, _StorageIndex> > | ||||
| { | ||||
|     typedef SparseMatrixBase<DynamicSparseMatrix> Base; | ||||
|     using Base::convert_index; | ||||
|   public: | ||||
|     EIGEN_SPARSE_PUBLIC_INTERFACE(DynamicSparseMatrix) | ||||
|     // FIXME: why are these operator already alvailable ???
 | ||||
|     // EIGEN_SPARSE_INHERIT_ASSIGNMENT_OPERATOR(DynamicSparseMatrix, +=)
 | ||||
|     // EIGEN_SPARSE_INHERIT_ASSIGNMENT_OPERATOR(DynamicSparseMatrix, -=)
 | ||||
|     typedef MappedSparseMatrix<Scalar,Flags> Map; | ||||
|     using Base::IsRowMajor; | ||||
|     using Base::operator=; | ||||
|     enum { | ||||
|       Options = _Options | ||||
|     }; | ||||
| 
 | ||||
|   protected: | ||||
| 
 | ||||
|     typedef DynamicSparseMatrix<Scalar,(Flags&~RowMajorBit)|(IsRowMajor?RowMajorBit:0), StorageIndex> TransposedSparseMatrix; | ||||
| 
 | ||||
|     Index m_innerSize; | ||||
|     std::vector<internal::CompressedStorage<Scalar,StorageIndex> > m_data; | ||||
| 
 | ||||
|   public: | ||||
| 
 | ||||
|     inline Index rows() const { return IsRowMajor ? outerSize() : m_innerSize; } | ||||
|     inline Index cols() const { return IsRowMajor ? m_innerSize : outerSize(); } | ||||
|     inline Index innerSize() const { return m_innerSize; } | ||||
|     inline Index outerSize() const { return convert_index(m_data.size()); } | ||||
|     inline Index innerNonZeros(Index j) const { return m_data[j].size(); } | ||||
| 
 | ||||
|     std::vector<internal::CompressedStorage<Scalar,StorageIndex> >& _data() { return m_data; } | ||||
|     const std::vector<internal::CompressedStorage<Scalar,StorageIndex> >& _data() const { return m_data; } | ||||
| 
 | ||||
|     /** \returns the coefficient value at given position \a row, \a col
 | ||||
|       * This operation involes a log(rho*outer_size) binary search. | ||||
|       */ | ||||
|     inline Scalar coeff(Index row, Index col) const | ||||
|     { | ||||
|       const Index outer = IsRowMajor ? row : col; | ||||
|       const Index inner = IsRowMajor ? col : row; | ||||
|       return m_data[outer].at(inner); | ||||
|     } | ||||
| 
 | ||||
|     /** \returns a reference to the coefficient value at given position \a row, \a col
 | ||||
|       * This operation involes a log(rho*outer_size) binary search. If the coefficient does not | ||||
|       * exist yet, then a sorted insertion into a sequential buffer is performed. | ||||
|       */ | ||||
|     inline Scalar& coeffRef(Index row, Index col) | ||||
|     { | ||||
|       const Index outer = IsRowMajor ? row : col; | ||||
|       const Index inner = IsRowMajor ? col : row; | ||||
|       return m_data[outer].atWithInsertion(inner); | ||||
|     } | ||||
| 
 | ||||
|     class InnerIterator; | ||||
|     class ReverseInnerIterator; | ||||
| 
 | ||||
|     void setZero() | ||||
|     { | ||||
|       for (Index j=0; j<outerSize(); ++j) | ||||
|         m_data[j].clear(); | ||||
|     } | ||||
| 
 | ||||
|     /** \returns the number of non zero coefficients */ | ||||
|     Index nonZeros() const | ||||
|     { | ||||
|       Index res = 0; | ||||
|       for (Index j=0; j<outerSize(); ++j) | ||||
|         res += m_data[j].size(); | ||||
|       return res; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     void reserve(Index reserveSize = 1000) | ||||
|     { | ||||
|       if (outerSize()>0) | ||||
|       { | ||||
|         Index reserveSizePerVector = (std::max)(reserveSize/outerSize(),Index(4)); | ||||
|         for (Index j=0; j<outerSize(); ++j) | ||||
|         { | ||||
|           m_data[j].reserve(reserveSizePerVector); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     /** Does nothing: provided for compatibility with SparseMatrix */ | ||||
|     inline void startVec(Index /*outer*/) {} | ||||
| 
 | ||||
|     /** \returns a reference to the non zero coefficient at position \a row, \a col assuming that:
 | ||||
|       * - the nonzero does not already exist | ||||
|       * - the new coefficient is the last one of the given inner vector. | ||||
|       * | ||||
|       * \sa insert, insertBackByOuterInner */ | ||||
|     inline Scalar& insertBack(Index row, Index col) | ||||
|     { | ||||
|       return insertBackByOuterInner(IsRowMajor?row:col, IsRowMajor?col:row); | ||||
|     } | ||||
| 
 | ||||
|     /** \sa insertBack */ | ||||
|     inline Scalar& insertBackByOuterInner(Index outer, Index inner) | ||||
|     { | ||||
|       eigen_assert(outer<Index(m_data.size()) && inner<m_innerSize && "out of range"); | ||||
|       eigen_assert(((m_data[outer].size()==0) || (m_data[outer].index(m_data[outer].size()-1)<inner)) | ||||
|                 && "wrong sorted insertion"); | ||||
|       m_data[outer].append(0, inner); | ||||
|       return m_data[outer].value(m_data[outer].size()-1); | ||||
|     } | ||||
| 
 | ||||
|     inline Scalar& insert(Index row, Index col) | ||||
|     { | ||||
|       const Index outer = IsRowMajor ? row : col; | ||||
|       const Index inner = IsRowMajor ? col : row; | ||||
| 
 | ||||
|       Index startId = 0; | ||||
|       Index id = static_cast<Index>(m_data[outer].size()) - 1; | ||||
|       m_data[outer].resize(id+2,1); | ||||
| 
 | ||||
|       while ( (id >= startId) && (m_data[outer].index(id) > inner) ) | ||||
|       { | ||||
|         m_data[outer].index(id+1) = m_data[outer].index(id); | ||||
|         m_data[outer].value(id+1) = m_data[outer].value(id); | ||||
|         --id; | ||||
|       } | ||||
|       m_data[outer].index(id+1) = inner; | ||||
|       m_data[outer].value(id+1) = 0; | ||||
|       return m_data[outer].value(id+1); | ||||
|     } | ||||
| 
 | ||||
|     /** Does nothing: provided for compatibility with SparseMatrix */ | ||||
|     inline void finalize() {} | ||||
| 
 | ||||
|     /** Suppress all nonzeros which are smaller than \a reference under the tolerence \a epsilon */ | ||||
|     void prune(Scalar reference, RealScalar epsilon = NumTraits<RealScalar>::dummy_precision()) | ||||
|     { | ||||
|       for (Index j=0; j<outerSize(); ++j) | ||||
|         m_data[j].prune(reference,epsilon); | ||||
|     } | ||||
| 
 | ||||
|     /** Resize the matrix without preserving the data (the matrix is set to zero)
 | ||||
|       */ | ||||
|     void resize(Index rows, Index cols) | ||||
|     { | ||||
|       const Index outerSize = IsRowMajor ? rows : cols; | ||||
|       m_innerSize = convert_index(IsRowMajor ? cols : rows); | ||||
|       setZero(); | ||||
|       if (Index(m_data.size()) != outerSize) | ||||
|       { | ||||
|         m_data.resize(outerSize); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     void resizeAndKeepData(Index rows, Index cols) | ||||
|     { | ||||
|       const Index outerSize = IsRowMajor ? rows : cols; | ||||
|       const Index innerSize = IsRowMajor ? cols : rows; | ||||
|       if (m_innerSize>innerSize) | ||||
|       { | ||||
|         // remove all coefficients with innerCoord>=innerSize
 | ||||
|         // TODO
 | ||||
|         //std::cerr << "not implemented yet\n";
 | ||||
|         exit(2); | ||||
|       } | ||||
|       if (m_data.size() != outerSize) | ||||
|       { | ||||
|         m_data.resize(outerSize); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     /** The class DynamicSparseMatrix is deprectaed */ | ||||
|     EIGEN_DEPRECATED inline DynamicSparseMatrix() | ||||
|       : m_innerSize(0), m_data(0) | ||||
|     { | ||||
|       eigen_assert(innerSize()==0 && outerSize()==0); | ||||
|     } | ||||
| 
 | ||||
|     /** The class DynamicSparseMatrix is deprectaed */ | ||||
|     EIGEN_DEPRECATED inline DynamicSparseMatrix(Index rows, Index cols) | ||||
|       : m_innerSize(0) | ||||
|     { | ||||
|       resize(rows, cols); | ||||
|     } | ||||
| 
 | ||||
|     /** The class DynamicSparseMatrix is deprectaed */ | ||||
|     template<typename OtherDerived> | ||||
|     EIGEN_DEPRECATED explicit inline DynamicSparseMatrix(const SparseMatrixBase<OtherDerived>& other) | ||||
|       : m_innerSize(0) | ||||
|     { | ||||
|     Base::operator=(other.derived()); | ||||
|     } | ||||
| 
 | ||||
|     inline DynamicSparseMatrix(const DynamicSparseMatrix& other) | ||||
|       : Base(), m_innerSize(0) | ||||
|     { | ||||
|       *this = other.derived(); | ||||
|     } | ||||
| 
 | ||||
|     inline void swap(DynamicSparseMatrix& other) | ||||
|     { | ||||
|       //EIGEN_DBG_SPARSE(std::cout << "SparseMatrix:: swap\n");
 | ||||
|       std::swap(m_innerSize, other.m_innerSize); | ||||
|       //std::swap(m_outerSize, other.m_outerSize);
 | ||||
|       m_data.swap(other.m_data); | ||||
|     } | ||||
| 
 | ||||
|     inline DynamicSparseMatrix& operator=(const DynamicSparseMatrix& other) | ||||
|     { | ||||
|       if (other.isRValue()) | ||||
|       { | ||||
|         swap(other.const_cast_derived()); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         resize(other.rows(), other.cols()); | ||||
|         m_data = other.m_data; | ||||
|       } | ||||
|       return *this; | ||||
|     } | ||||
| 
 | ||||
|     /** Destructor */ | ||||
|     inline ~DynamicSparseMatrix() {} | ||||
| 
 | ||||
|   public: | ||||
| 
 | ||||
|     /** \deprecated
 | ||||
|       * Set the matrix to zero and reserve the memory for \a reserveSize nonzero coefficients. */ | ||||
|     EIGEN_DEPRECATED void startFill(Index reserveSize = 1000) | ||||
|     { | ||||
|       setZero(); | ||||
|       reserve(reserveSize); | ||||
|     } | ||||
| 
 | ||||
|     /** \deprecated use insert()
 | ||||
|       * inserts a nonzero coefficient at given coordinates \a row, \a col and returns its reference assuming that: | ||||
|       *  1 - the coefficient does not exist yet | ||||
|       *  2 - this the coefficient with greater inner coordinate for the given outer coordinate. | ||||
|       * In other words, assuming \c *this is column-major, then there must not exists any nonzero coefficient of coordinates | ||||
|       * \c i \c x \a col such that \c i >= \a row. Otherwise the matrix is invalid. | ||||
|       * | ||||
|       * \see fillrand(), coeffRef() | ||||
|       */ | ||||
|     EIGEN_DEPRECATED Scalar& fill(Index row, Index col) | ||||
|     { | ||||
|       const Index outer = IsRowMajor ? row : col; | ||||
|       const Index inner = IsRowMajor ? col : row; | ||||
|       return insertBack(outer,inner); | ||||
|     } | ||||
| 
 | ||||
|     /** \deprecated use insert()
 | ||||
|       * Like fill() but with random inner coordinates. | ||||
|       * Compared to the generic coeffRef(), the unique limitation is that we assume | ||||
|       * the coefficient does not exist yet. | ||||
|       */ | ||||
|     EIGEN_DEPRECATED Scalar& fillrand(Index row, Index col) | ||||
|     { | ||||
|       return insert(row,col); | ||||
|     } | ||||
| 
 | ||||
|     /** \deprecated use finalize()
 | ||||
|       * Does nothing. Provided for compatibility with SparseMatrix. */ | ||||
|     EIGEN_DEPRECATED void endFill() {} | ||||
|      | ||||
| #   ifdef EIGEN_DYNAMICSPARSEMATRIX_PLUGIN | ||||
| #     include EIGEN_DYNAMICSPARSEMATRIX_PLUGIN | ||||
| #   endif | ||||
|  }; | ||||
| 
 | ||||
| template<typename Scalar, int _Options, typename _StorageIndex> | ||||
| class DynamicSparseMatrix<Scalar,_Options,_StorageIndex>::InnerIterator : public SparseVector<Scalar,_Options,_StorageIndex>::InnerIterator | ||||
| { | ||||
|     typedef typename SparseVector<Scalar,_Options,_StorageIndex>::InnerIterator Base; | ||||
|   public: | ||||
|     InnerIterator(const DynamicSparseMatrix& mat, Index outer) | ||||
|       : Base(mat.m_data[outer]), m_outer(outer) | ||||
|     {} | ||||
| 
 | ||||
|     inline Index row() const { return IsRowMajor ? m_outer : Base::index(); } | ||||
|     inline Index col() const { return IsRowMajor ? Base::index() : m_outer; } | ||||
|     inline Index outer() const { return m_outer; } | ||||
| 
 | ||||
|   protected: | ||||
|     const Index m_outer; | ||||
| }; | ||||
| 
 | ||||
| template<typename Scalar, int _Options, typename _StorageIndex> | ||||
| class DynamicSparseMatrix<Scalar,_Options,_StorageIndex>::ReverseInnerIterator : public SparseVector<Scalar,_Options,_StorageIndex>::ReverseInnerIterator | ||||
| { | ||||
|     typedef typename SparseVector<Scalar,_Options,_StorageIndex>::ReverseInnerIterator Base; | ||||
|   public: | ||||
|     ReverseInnerIterator(const DynamicSparseMatrix& mat, Index outer) | ||||
|       : Base(mat.m_data[outer]), m_outer(outer) | ||||
|     {} | ||||
| 
 | ||||
|     inline Index row() const { return IsRowMajor ? m_outer : Base::index(); } | ||||
|     inline Index col() const { return IsRowMajor ? Base::index() : m_outer; } | ||||
|     inline Index outer() const { return m_outer; } | ||||
| 
 | ||||
|   protected: | ||||
|     const Index m_outer; | ||||
| }; | ||||
| 
 | ||||
| namespace internal { | ||||
| 
 | ||||
| template<typename _Scalar, int _Options, typename _StorageIndex> | ||||
| struct evaluator<DynamicSparseMatrix<_Scalar,_Options,_StorageIndex> > | ||||
|   : evaluator_base<DynamicSparseMatrix<_Scalar,_Options,_StorageIndex> > | ||||
| { | ||||
|   typedef _Scalar Scalar; | ||||
|   typedef DynamicSparseMatrix<_Scalar,_Options,_StorageIndex> SparseMatrixType; | ||||
|   typedef typename SparseMatrixType::InnerIterator InnerIterator; | ||||
|   typedef typename SparseMatrixType::ReverseInnerIterator ReverseInnerIterator; | ||||
|    | ||||
|   enum { | ||||
|     CoeffReadCost = NumTraits<_Scalar>::ReadCost, | ||||
|     Flags = SparseMatrixType::Flags | ||||
|   }; | ||||
|    | ||||
|   evaluator() : m_matrix(0) {} | ||||
|   evaluator(const SparseMatrixType &mat) : m_matrix(&mat) {} | ||||
|    | ||||
|   operator SparseMatrixType&() { return m_matrix->const_cast_derived(); } | ||||
|   operator const SparseMatrixType&() const { return *m_matrix; } | ||||
|    | ||||
|   Scalar coeff(Index row, Index col) const { return m_matrix->coeff(row,col); } | ||||
|    | ||||
|   Index nonZerosEstimate() const { return m_matrix->nonZeros(); } | ||||
| 
 | ||||
|   const SparseMatrixType *m_matrix; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| } // end namespace Eigen
 | ||||
| 
 | ||||
| #endif // EIGEN_DYNAMIC_SPARSEMATRIX_H
 | ||||
							
								
								
									
										275
									
								
								src/eigen/unsupported/Eigen/src/SparseExtra/MarketIO.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								src/eigen/unsupported/Eigen/src/SparseExtra/MarketIO.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,275 @@ | |||
| // This file is part of Eigen, a lightweight C++ template library
 | ||||
| // for linear algebra.
 | ||||
| //
 | ||||
| // Copyright (C) 2011 Gael Guennebaud <gael.guennebaud@inria.fr>
 | ||||
| // Copyright (C) 2012 Desire NUENTSA WAKAM <desire.nuentsa_wakam@inria.fr>
 | ||||
| //
 | ||||
| // This Source Code Form is subject to the terms of the Mozilla
 | ||||
| // Public License v. 2.0. If a copy of the MPL was not distributed
 | ||||
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | ||||
| 
 | ||||
| #ifndef EIGEN_SPARSE_MARKET_IO_H | ||||
| #define EIGEN_SPARSE_MARKET_IO_H | ||||
| 
 | ||||
| #include <iostream> | ||||
| 
 | ||||
| namespace Eigen {  | ||||
| 
 | ||||
| namespace internal  | ||||
| { | ||||
|   template <typename Scalar> | ||||
|   inline bool GetMarketLine (std::stringstream& line, Index& M, Index& N, Index& i, Index& j, Scalar& value) | ||||
|   { | ||||
|     line >> i >> j >> value; | ||||
|     i--; | ||||
|     j--; | ||||
|     if(i>=0 && j>=0 && i<M && j<N) | ||||
|     { | ||||
|       return true;  | ||||
|     } | ||||
|     else | ||||
|       return false; | ||||
|   } | ||||
|   template <typename Scalar> | ||||
|   inline bool GetMarketLine (std::stringstream& line, Index& M, Index& N, Index& i, Index& j, std::complex<Scalar>& value) | ||||
|   { | ||||
|     Scalar valR, valI; | ||||
|     line >> i >> j >> valR >> valI; | ||||
|     i--; | ||||
|     j--; | ||||
|     if(i>=0 && j>=0 && i<M && j<N) | ||||
|     { | ||||
|       value = std::complex<Scalar>(valR, valI); | ||||
|       return true;  | ||||
|     } | ||||
|     else | ||||
|       return false; | ||||
|   } | ||||
| 
 | ||||
|   template <typename RealScalar> | ||||
|   inline void  GetVectorElt (const std::string& line, RealScalar& val) | ||||
|   { | ||||
|     std::istringstream newline(line); | ||||
|     newline >> val;   | ||||
|   } | ||||
| 
 | ||||
|   template <typename RealScalar> | ||||
|   inline void GetVectorElt (const std::string& line, std::complex<RealScalar>& val) | ||||
|   { | ||||
|     RealScalar valR, valI;  | ||||
|     std::istringstream newline(line); | ||||
|     newline >> valR >> valI;  | ||||
|     val = std::complex<RealScalar>(valR, valI); | ||||
|   } | ||||
|    | ||||
|   template<typename Scalar> | ||||
|   inline void putMarketHeader(std::string& header,int sym) | ||||
|   { | ||||
|     header= "%%MatrixMarket matrix coordinate "; | ||||
|     if(internal::is_same<Scalar, std::complex<float> >::value || internal::is_same<Scalar, std::complex<double> >::value) | ||||
|     { | ||||
|       header += " complex";  | ||||
|       if(sym == Symmetric) header += " symmetric"; | ||||
|       else if (sym == SelfAdjoint) header += " Hermitian"; | ||||
|       else header += " general"; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|       header += " real";  | ||||
|       if(sym == Symmetric) header += " symmetric"; | ||||
|       else header += " general"; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   template<typename Scalar> | ||||
|   inline void PutMatrixElt(Scalar value, int row, int col, std::ofstream& out) | ||||
|   { | ||||
|     out << row << " "<< col << " " << value << "\n"; | ||||
|   } | ||||
|   template<typename Scalar> | ||||
|   inline void PutMatrixElt(std::complex<Scalar> value, int row, int col, std::ofstream& out) | ||||
|   { | ||||
|     out << row << " " << col << " " << value.real() << " " << value.imag() << "\n"; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   template<typename Scalar> | ||||
|   inline void putVectorElt(Scalar value, std::ofstream& out) | ||||
|   { | ||||
|     out << value << "\n";  | ||||
|   } | ||||
|   template<typename Scalar> | ||||
|   inline void putVectorElt(std::complex<Scalar> value, std::ofstream& out) | ||||
|   { | ||||
|     out << value.real << " " << value.imag()<< "\n";  | ||||
|   } | ||||
| 
 | ||||
| } // end namepsace internal
 | ||||
| 
 | ||||
| inline bool getMarketHeader(const std::string& filename, int& sym, bool& iscomplex, bool& isvector) | ||||
| { | ||||
|   sym = 0;  | ||||
|   iscomplex = false; | ||||
|   isvector = false; | ||||
|   std::ifstream in(filename.c_str(),std::ios::in); | ||||
|   if(!in) | ||||
|     return false; | ||||
|    | ||||
|   std::string line;  | ||||
|   // The matrix header is always the first line in the file 
 | ||||
|   std::getline(in, line); eigen_assert(in.good()); | ||||
|    | ||||
|   std::stringstream fmtline(line);  | ||||
|   std::string substr[5]; | ||||
|   fmtline>> substr[0] >> substr[1] >> substr[2] >> substr[3] >> substr[4]; | ||||
|   if(substr[2].compare("array") == 0) isvector = true; | ||||
|   if(substr[3].compare("complex") == 0) iscomplex = true; | ||||
|   if(substr[4].compare("symmetric") == 0) sym = Symmetric; | ||||
|   else if (substr[4].compare("Hermitian") == 0) sym = SelfAdjoint; | ||||
|    | ||||
|   return true; | ||||
| } | ||||
|    | ||||
| template<typename SparseMatrixType> | ||||
| bool loadMarket(SparseMatrixType& mat, const std::string& filename) | ||||
| { | ||||
|   typedef typename SparseMatrixType::Scalar Scalar; | ||||
|   typedef typename SparseMatrixType::Index Index; | ||||
|   std::ifstream input(filename.c_str(),std::ios::in); | ||||
|   if(!input) | ||||
|     return false; | ||||
|    | ||||
|   const int maxBuffersize = 2048; | ||||
|   char buffer[maxBuffersize]; | ||||
|    | ||||
|   bool readsizes = false; | ||||
| 
 | ||||
|   typedef Triplet<Scalar,Index> T; | ||||
|   std::vector<T> elements; | ||||
|    | ||||
|   Index M(-1), N(-1), NNZ(-1); | ||||
|   Index count = 0; | ||||
|   while(input.getline(buffer, maxBuffersize)) | ||||
|   { | ||||
|     // skip comments   
 | ||||
|     //NOTE An appropriate test should be done on the header to get the  symmetry
 | ||||
|     if(buffer[0]=='%') | ||||
|       continue; | ||||
|      | ||||
|     std::stringstream line(buffer); | ||||
|      | ||||
|     if(!readsizes) | ||||
|     { | ||||
|       line >> M >> N >> NNZ; | ||||
|       if(M > 0 && N > 0 && NNZ > 0)  | ||||
|       { | ||||
|         readsizes = true; | ||||
|         //std::cout << "sizes: " << M << "," << N << "," << NNZ << "\n";
 | ||||
|         mat.resize(M,N); | ||||
|         mat.reserve(NNZ); | ||||
|       } | ||||
|     } | ||||
|     else | ||||
|     {  | ||||
|       Index i(-1), j(-1); | ||||
|       Scalar value;  | ||||
|       if( internal::GetMarketLine(line, M, N, i, j, value) )  | ||||
|       { | ||||
|         ++ count; | ||||
|         elements.push_back(T(i,j,value)); | ||||
|       } | ||||
|       else  | ||||
|         std::cerr << "Invalid read: " << i << "," << j << "\n";         | ||||
|     } | ||||
|   } | ||||
|   mat.setFromTriplets(elements.begin(), elements.end()); | ||||
|   if(count!=NNZ) | ||||
|     std::cerr << count << "!=" << NNZ << "\n"; | ||||
|    | ||||
|   input.close(); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| template<typename VectorType> | ||||
| bool loadMarketVector(VectorType& vec, const std::string& filename) | ||||
| { | ||||
|    typedef typename VectorType::Scalar Scalar; | ||||
|   std::ifstream in(filename.c_str(), std::ios::in); | ||||
|   if(!in) | ||||
|     return false; | ||||
|    | ||||
|   std::string line;  | ||||
|   int n(0), col(0);  | ||||
|   do  | ||||
|   { // Skip comments
 | ||||
|     std::getline(in, line); eigen_assert(in.good()); | ||||
|   } while (line[0] == '%'); | ||||
|   std::istringstream newline(line); | ||||
|   newline  >> n >> col;  | ||||
|   eigen_assert(n>0 && col>0); | ||||
|   vec.resize(n); | ||||
|   int i = 0;  | ||||
|   Scalar value;  | ||||
|   while ( std::getline(in, line) && (i < n) ){ | ||||
|     internal::GetVectorElt(line, value);  | ||||
|     vec(i++) = value;  | ||||
|   } | ||||
|   in.close(); | ||||
|   if (i!=n){ | ||||
|     std::cerr<< "Unable to read all elements from file " << filename << "\n"; | ||||
|     return false; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| template<typename SparseMatrixType> | ||||
| bool saveMarket(const SparseMatrixType& mat, const std::string& filename, int sym = 0) | ||||
| { | ||||
|   typedef typename SparseMatrixType::Scalar Scalar; | ||||
|   std::ofstream out(filename.c_str(),std::ios::out); | ||||
|   if(!out) | ||||
|     return false; | ||||
|    | ||||
|   out.flags(std::ios_base::scientific); | ||||
|   out.precision(64); | ||||
|   std::string header;  | ||||
|   internal::putMarketHeader<Scalar>(header, sym);  | ||||
|   out << header << std::endl;  | ||||
|   out << mat.rows() << " " << mat.cols() << " " << mat.nonZeros() << "\n"; | ||||
|   int count = 0; | ||||
|   for(int j=0; j<mat.outerSize(); ++j) | ||||
|     for(typename SparseMatrixType::InnerIterator it(mat,j); it; ++it) | ||||
|     { | ||||
|       ++ count; | ||||
|       internal::PutMatrixElt(it.value(), it.row()+1, it.col()+1, out); | ||||
|       // out << it.row()+1 << " " << it.col()+1 << " " << it.value() << "\n";
 | ||||
|     } | ||||
|   out.close(); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| template<typename VectorType> | ||||
| bool saveMarketVector (const VectorType& vec, const std::string& filename) | ||||
| { | ||||
|  typedef typename VectorType::Scalar Scalar;  | ||||
|  std::ofstream out(filename.c_str(),std::ios::out); | ||||
|   if(!out) | ||||
|     return false; | ||||
|    | ||||
|   out.flags(std::ios_base::scientific); | ||||
|   out.precision(64); | ||||
|   if(internal::is_same<Scalar, std::complex<float> >::value || internal::is_same<Scalar, std::complex<double> >::value) | ||||
|       out << "%%MatrixMarket matrix array complex general\n";  | ||||
|   else | ||||
|     out << "%%MatrixMarket matrix array real general\n";  | ||||
|   out << vec.size() << " "<< 1 << "\n"; | ||||
|   for (int i=0; i < vec.size(); i++){ | ||||
|     internal::putVectorElt(vec(i), out);  | ||||
|   } | ||||
|   out.close(); | ||||
|   return true;  | ||||
| } | ||||
| 
 | ||||
| } // end namespace Eigen
 | ||||
| 
 | ||||
| #endif // EIGEN_SPARSE_MARKET_IO_H
 | ||||
|  | @ -0,0 +1,247 @@ | |||
| 
 | ||||
| // This file is part of Eigen, a lightweight C++ template library
 | ||||
| // for linear algebra.
 | ||||
| //
 | ||||
| // Copyright (C) 2012 Desire NUENTSA WAKAM <desire.nuentsa_wakam@inria.fr>
 | ||||
| //
 | ||||
| // This Source Code Form is subject to the terms of the Mozilla
 | ||||
| // Public License v. 2.0. If a copy of the MPL was not distributed
 | ||||
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | ||||
| 
 | ||||
| #ifndef EIGEN_BROWSE_MATRICES_H | ||||
| #define EIGEN_BROWSE_MATRICES_H | ||||
| 
 | ||||
| namespace Eigen { | ||||
| 
 | ||||
| enum { | ||||
|   SPD = 0x100, | ||||
|   NonSymmetric = 0x0 | ||||
| };  | ||||
| 
 | ||||
| /** 
 | ||||
|  * @brief Iterator to browse matrices from a specified folder | ||||
|  *  | ||||
|  * This is used to load all the matrices from a folder.  | ||||
|  * The matrices should be in Matrix Market format | ||||
|  * It is assumed that the matrices are named as matname.mtx | ||||
|  * and matname_SPD.mtx if the matrix is Symmetric and positive definite (or Hermitian) | ||||
|  * The right hand side vectors are loaded as well, if they exist. | ||||
|  * They should be named as matname_b.mtx.  | ||||
|  * Note that the right hand side for a SPD matrix is named as matname_SPD_b.mtx | ||||
|  *  | ||||
|  * Sometimes a reference solution is available. In this case, it should be named as matname_x.mtx | ||||
|  *  | ||||
|  * Sample code | ||||
|  * \code | ||||
|  *  | ||||
|  * \endcode | ||||
|  *  | ||||
|  * \tparam Scalar The scalar type  | ||||
|  */ | ||||
| template <typename Scalar> | ||||
| class MatrixMarketIterator  | ||||
| { | ||||
|     typedef typename NumTraits<Scalar>::Real RealScalar; | ||||
|   public: | ||||
|     typedef Matrix<Scalar,Dynamic,1> VectorType;  | ||||
|     typedef SparseMatrix<Scalar,ColMajor> MatrixType;  | ||||
|    | ||||
|   public: | ||||
|     MatrixMarketIterator(const std::string &folder) | ||||
|       : m_sym(0), m_isvalid(false), m_matIsLoaded(false), m_hasRhs(false), m_hasrefX(false), m_folder(folder) | ||||
|     { | ||||
|       m_folder_id = opendir(folder.c_str()); | ||||
|       if(m_folder_id) | ||||
|         Getnextvalidmatrix(); | ||||
|     } | ||||
|      | ||||
|     ~MatrixMarketIterator() | ||||
|     { | ||||
|       if (m_folder_id) closedir(m_folder_id);  | ||||
|     } | ||||
|      | ||||
|     inline MatrixMarketIterator& operator++() | ||||
|     { | ||||
|       m_matIsLoaded = false; | ||||
|       m_hasrefX = false; | ||||
|       m_hasRhs = false; | ||||
|       Getnextvalidmatrix(); | ||||
|       return *this; | ||||
|     } | ||||
|     inline operator bool() const { return m_isvalid;} | ||||
|      | ||||
|     /** Return the sparse matrix corresponding to the current file */ | ||||
|     inline MatrixType& matrix()  | ||||
|     {  | ||||
|       // Read the matrix
 | ||||
|       if (m_matIsLoaded) return m_mat; | ||||
|        | ||||
|       std::string matrix_file = m_folder + "/" + m_matname + ".mtx"; | ||||
|       if ( !loadMarket(m_mat, matrix_file))  | ||||
|       { | ||||
|         std::cerr << "Warning loadMarket failed when loading \"" << matrix_file << "\"" << std::endl; | ||||
|         m_matIsLoaded = false; | ||||
|         return m_mat; | ||||
|       } | ||||
|       m_matIsLoaded = true;  | ||||
| 
 | ||||
|       if (m_sym != NonSymmetric)  | ||||
|       { | ||||
|         // Check whether we need to restore a full matrix:
 | ||||
|         RealScalar diag_norm  = m_mat.diagonal().norm(); | ||||
|         RealScalar lower_norm = m_mat.template triangularView<Lower>().norm(); | ||||
|         RealScalar upper_norm = m_mat.template triangularView<Upper>().norm(); | ||||
|         if(lower_norm>diag_norm && upper_norm==diag_norm) | ||||
|         { | ||||
|           // only the lower part is stored
 | ||||
|           MatrixType tmp(m_mat); | ||||
|           m_mat = tmp.template selfadjointView<Lower>(); | ||||
|         } | ||||
|         else if(upper_norm>diag_norm && lower_norm==diag_norm) | ||||
|         { | ||||
|           // only the upper part is stored
 | ||||
|           MatrixType tmp(m_mat); | ||||
|           m_mat = tmp.template selfadjointView<Upper>(); | ||||
|         } | ||||
|       } | ||||
|       return m_mat;  | ||||
|     } | ||||
|      | ||||
|     /** Return the right hand side corresponding to the current matrix. 
 | ||||
|      * If the rhs file is not provided, a random rhs is generated | ||||
|      */ | ||||
|     inline VectorType& rhs()  | ||||
|     {  | ||||
|        // Get the right hand side
 | ||||
|       if (m_hasRhs) return m_rhs; | ||||
|        | ||||
|       std::string rhs_file; | ||||
|       rhs_file = m_folder + "/" + m_matname + "_b.mtx"; // The pattern is matname_b.mtx
 | ||||
|       m_hasRhs = Fileexists(rhs_file); | ||||
|       if (m_hasRhs) | ||||
|       { | ||||
|         m_rhs.resize(m_mat.cols()); | ||||
|         m_hasRhs = loadMarketVector(m_rhs, rhs_file); | ||||
|       } | ||||
|       if (!m_hasRhs) | ||||
|       { | ||||
|         // Generate a random right hand side
 | ||||
|         if (!m_matIsLoaded) this->matrix();  | ||||
|         m_refX.resize(m_mat.cols()); | ||||
|         m_refX.setRandom(); | ||||
|         m_rhs = m_mat * m_refX; | ||||
|         m_hasrefX = true; | ||||
|         m_hasRhs = true; | ||||
|       } | ||||
|       return m_rhs;  | ||||
|     } | ||||
|      | ||||
|     /** Return a reference solution
 | ||||
|      * If it is not provided and if the right hand side is not available | ||||
|      * then refX is randomly generated such that A*refX = b  | ||||
|      * where A and b are the matrix and the rhs.  | ||||
|      * Note that when a rhs is provided, refX is not available  | ||||
|      */ | ||||
|     inline VectorType& refX()  | ||||
|     {  | ||||
|       // Check if a reference solution is provided
 | ||||
|       if (m_hasrefX) return m_refX; | ||||
|        | ||||
|       std::string lhs_file; | ||||
|       lhs_file = m_folder + "/" + m_matname + "_x.mtx";  | ||||
|       m_hasrefX = Fileexists(lhs_file); | ||||
|       if (m_hasrefX) | ||||
|       { | ||||
|         m_refX.resize(m_mat.cols()); | ||||
|         m_hasrefX = loadMarketVector(m_refX, lhs_file); | ||||
|       } | ||||
|       else | ||||
|         m_refX.resize(0); | ||||
|       return m_refX;  | ||||
|     } | ||||
|      | ||||
|     inline std::string& matname() { return m_matname; } | ||||
|      | ||||
|     inline int sym() { return m_sym; } | ||||
|      | ||||
|     bool hasRhs() {return m_hasRhs; } | ||||
|     bool hasrefX() {return m_hasrefX; } | ||||
|     bool isFolderValid() { return bool(m_folder_id); } | ||||
|      | ||||
|   protected: | ||||
|      | ||||
|     inline bool Fileexists(std::string file) | ||||
|     { | ||||
|       std::ifstream file_id(file.c_str()); | ||||
|       if (!file_id.good() )  | ||||
|       { | ||||
|         return false; | ||||
|       } | ||||
|       else  | ||||
|       { | ||||
|         file_id.close(); | ||||
|         return true; | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     void Getnextvalidmatrix( ) | ||||
|     { | ||||
|       m_isvalid = false; | ||||
|       // Here, we return with the next valid matrix in the folder
 | ||||
|       while ( (m_curs_id = readdir(m_folder_id)) != NULL) { | ||||
|         m_isvalid = false; | ||||
|         std::string curfile; | ||||
|         curfile = m_folder + "/" + m_curs_id->d_name; | ||||
|         // Discard if it is a folder
 | ||||
|         if (m_curs_id->d_type == DT_DIR) continue; //FIXME This may not be available on non BSD systems
 | ||||
| //         struct stat st_buf; 
 | ||||
| //         stat (curfile.c_str(), &st_buf);
 | ||||
| //         if (S_ISDIR(st_buf.st_mode)) continue;
 | ||||
|          | ||||
|         // Determine from the header if it is a matrix or a right hand side 
 | ||||
|         bool isvector,iscomplex=false; | ||||
|         if(!getMarketHeader(curfile,m_sym,iscomplex,isvector)) continue; | ||||
|         if(isvector) continue; | ||||
|         if (!iscomplex) | ||||
|         { | ||||
|           if(internal::is_same<Scalar, std::complex<float> >::value || internal::is_same<Scalar, std::complex<double> >::value) | ||||
|             continue;  | ||||
|         } | ||||
|         if (iscomplex) | ||||
|         { | ||||
|           if(internal::is_same<Scalar, float>::value || internal::is_same<Scalar, double>::value) | ||||
|             continue;  | ||||
|         } | ||||
|          | ||||
|          | ||||
|         // Get the matrix name
 | ||||
|         std::string filename = m_curs_id->d_name; | ||||
|         m_matname = filename.substr(0, filename.length()-4);  | ||||
|          | ||||
|         // Find if the matrix is SPD 
 | ||||
|         size_t found = m_matname.find("SPD"); | ||||
|         if( (found!=std::string::npos) && (m_sym != NonSymmetric) ) | ||||
|           m_sym = SPD; | ||||
|         | ||||
|         m_isvalid = true; | ||||
|         break;  | ||||
|       } | ||||
|     } | ||||
|     int m_sym; // Symmetry of the matrix
 | ||||
|     MatrixType m_mat; // Current matrix  
 | ||||
|     VectorType m_rhs;  // Current vector
 | ||||
|     VectorType m_refX; // The reference solution, if exists
 | ||||
|     std::string m_matname; // Matrix Name
 | ||||
|     bool m_isvalid;  | ||||
|     bool m_matIsLoaded; // Determine if the matrix has already been loaded from the file
 | ||||
|     bool m_hasRhs; // The right hand side exists
 | ||||
|     bool m_hasrefX; // A reference solution is provided
 | ||||
|     std::string m_folder; | ||||
|     DIR * m_folder_id; | ||||
|     struct dirent *m_curs_id;  | ||||
|      | ||||
| }; | ||||
| 
 | ||||
| } // end namespace Eigen
 | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										327
									
								
								src/eigen/unsupported/Eigen/src/SparseExtra/RandomSetter.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										327
									
								
								src/eigen/unsupported/Eigen/src/SparseExtra/RandomSetter.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,327 @@ | |||
| // This file is part of Eigen, a lightweight C++ template library
 | ||||
| // for linear algebra.
 | ||||
| //
 | ||||
| // Copyright (C) 2008 Gael Guennebaud <gael.guennebaud@inria.fr>
 | ||||
| //
 | ||||
| // This Source Code Form is subject to the terms of the Mozilla
 | ||||
| // Public License v. 2.0. If a copy of the MPL was not distributed
 | ||||
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | ||||
| 
 | ||||
| #ifndef EIGEN_RANDOMSETTER_H | ||||
| #define EIGEN_RANDOMSETTER_H | ||||
| 
 | ||||
| namespace Eigen {  | ||||
| 
 | ||||
| /** Represents a std::map
 | ||||
|   * | ||||
|   * \see RandomSetter | ||||
|   */ | ||||
| template<typename Scalar> struct StdMapTraits | ||||
| { | ||||
|   typedef int KeyType; | ||||
|   typedef std::map<KeyType,Scalar> Type; | ||||
|   enum { | ||||
|     IsSorted = 1 | ||||
|   }; | ||||
| 
 | ||||
|   static void setInvalidKey(Type&, const KeyType&) {} | ||||
| }; | ||||
| 
 | ||||
| #ifdef EIGEN_UNORDERED_MAP_SUPPORT | ||||
| /** Represents a std::unordered_map
 | ||||
|   * | ||||
|   * To use it you need to both define EIGEN_UNORDERED_MAP_SUPPORT and include the unordered_map header file | ||||
|   * yourself making sure that unordered_map is defined in the std namespace. | ||||
|   * | ||||
|   * For instance, with current version of gcc you can either enable C++0x standard (-std=c++0x) or do: | ||||
|   * \code | ||||
|   * #include <tr1/unordered_map> | ||||
|   * #define EIGEN_UNORDERED_MAP_SUPPORT | ||||
|   * namespace std { | ||||
|   *   using std::tr1::unordered_map; | ||||
|   * } | ||||
|   * \endcode | ||||
|   * | ||||
|   * \see RandomSetter | ||||
|   */ | ||||
| template<typename Scalar> struct StdUnorderedMapTraits | ||||
| { | ||||
|   typedef int KeyType; | ||||
|   typedef std::unordered_map<KeyType,Scalar> Type; | ||||
|   enum { | ||||
|     IsSorted = 0 | ||||
|   }; | ||||
| 
 | ||||
|   static void setInvalidKey(Type&, const KeyType&) {} | ||||
| }; | ||||
| #endif // EIGEN_UNORDERED_MAP_SUPPORT
 | ||||
| 
 | ||||
| #ifdef _DENSE_HASH_MAP_H_ | ||||
| /** Represents a google::dense_hash_map
 | ||||
|   * | ||||
|   * \see RandomSetter | ||||
|   */ | ||||
| template<typename Scalar> struct GoogleDenseHashMapTraits | ||||
| { | ||||
|   typedef int KeyType; | ||||
|   typedef google::dense_hash_map<KeyType,Scalar> Type; | ||||
|   enum { | ||||
|     IsSorted = 0 | ||||
|   }; | ||||
| 
 | ||||
|   static void setInvalidKey(Type& map, const KeyType& k) | ||||
|   { map.set_empty_key(k); } | ||||
| }; | ||||
| #endif | ||||
| 
 | ||||
| #ifdef _SPARSE_HASH_MAP_H_ | ||||
| /** Represents a google::sparse_hash_map
 | ||||
|   * | ||||
|   * \see RandomSetter | ||||
|   */ | ||||
| template<typename Scalar> struct GoogleSparseHashMapTraits | ||||
| { | ||||
|   typedef int KeyType; | ||||
|   typedef google::sparse_hash_map<KeyType,Scalar> Type; | ||||
|   enum { | ||||
|     IsSorted = 0 | ||||
|   }; | ||||
| 
 | ||||
|   static void setInvalidKey(Type&, const KeyType&) {} | ||||
| }; | ||||
| #endif | ||||
| 
 | ||||
| /** \class RandomSetter
 | ||||
|   * | ||||
|   * \brief The RandomSetter is a wrapper object allowing to set/update a sparse matrix with random access | ||||
|   * | ||||
|   * \tparam SparseMatrixType the type of the sparse matrix we are updating | ||||
|   * \tparam MapTraits a traits class representing the map implementation used for the temporary sparse storage. | ||||
|   *                  Its default value depends on the system. | ||||
|   * \tparam OuterPacketBits defines the number of rows (or columns) manage by a single map object | ||||
|   *                        as a power of two exponent. | ||||
|   * | ||||
|   * This class temporarily represents a sparse matrix object using a generic map implementation allowing for | ||||
|   * efficient random access. The conversion from the compressed representation to a hash_map object is performed | ||||
|   * in the RandomSetter constructor, while the sparse matrix is updated back at destruction time. This strategy | ||||
|   * suggest the use of nested blocks as in this example: | ||||
|   * | ||||
|   * \code | ||||
|   * SparseMatrix<double> m(rows,cols); | ||||
|   * { | ||||
|   *   RandomSetter<SparseMatrix<double> > w(m); | ||||
|   *   // don't use m but w instead with read/write random access to the coefficients:
 | ||||
|   *   for(;;) | ||||
|   *     w(rand(),rand()) = rand; | ||||
|   * } | ||||
|   * // when w is deleted, the data are copied back to m
 | ||||
|   * // and m is ready to use.
 | ||||
|   * \endcode | ||||
|   * | ||||
|   * Since hash_map objects are not fully sorted, representing a full matrix as a single hash_map would | ||||
|   * involve a big and costly sort to update the compressed matrix back. To overcome this issue, a RandomSetter | ||||
|   * use multiple hash_map, each representing 2^OuterPacketBits columns or rows according to the storage order. | ||||
|   * To reach optimal performance, this value should be adjusted according to the average number of nonzeros | ||||
|   * per rows/columns. | ||||
|   * | ||||
|   * The possible values for the template parameter MapTraits are: | ||||
|   *  - \b StdMapTraits: corresponds to std::map. (does not perform very well) | ||||
|   *  - \b GnuHashMapTraits: corresponds to __gnu_cxx::hash_map (available only with GCC) | ||||
|   *  - \b GoogleDenseHashMapTraits: corresponds to google::dense_hash_map (best efficiency, reasonable memory consumption) | ||||
|   *  - \b GoogleSparseHashMapTraits: corresponds to google::sparse_hash_map (best memory consumption, relatively good performance) | ||||
|   * | ||||
|   * The default map implementation depends on the availability, and the preferred order is: | ||||
|   * GoogleSparseHashMapTraits, GnuHashMapTraits, and finally StdMapTraits. | ||||
|   * | ||||
|   * For performance and memory consumption reasons it is highly recommended to use one of | ||||
|   * the Google's hash_map implementation. To enable the support for them, you have two options: | ||||
|   *  - \#include <google/dense_hash_map> yourself \b before Eigen/Sparse header | ||||
|   *  - define EIGEN_GOOGLEHASH_SUPPORT | ||||
|   * In the later case the inclusion of <google/dense_hash_map> is made for you. | ||||
|   * | ||||
|   * \see http://code.google.com/p/google-sparsehash/
 | ||||
|   */ | ||||
| template<typename SparseMatrixType, | ||||
|          template <typename T> class MapTraits = | ||||
| #if defined _DENSE_HASH_MAP_H_ | ||||
|           GoogleDenseHashMapTraits | ||||
| #elif defined _HASH_MAP | ||||
|           GnuHashMapTraits | ||||
| #else | ||||
|           StdMapTraits | ||||
| #endif | ||||
|          ,int OuterPacketBits = 6> | ||||
| class RandomSetter | ||||
| { | ||||
|     typedef typename SparseMatrixType::Scalar Scalar; | ||||
|     typedef typename SparseMatrixType::StorageIndex StorageIndex; | ||||
| 
 | ||||
|     struct ScalarWrapper | ||||
|     { | ||||
|       ScalarWrapper() : value(0) {} | ||||
|       Scalar value; | ||||
|     }; | ||||
|     typedef typename MapTraits<ScalarWrapper>::KeyType KeyType; | ||||
|     typedef typename MapTraits<ScalarWrapper>::Type HashMapType; | ||||
|     static const int OuterPacketMask = (1 << OuterPacketBits) - 1; | ||||
|     enum { | ||||
|       SwapStorage = 1 - MapTraits<ScalarWrapper>::IsSorted, | ||||
|       TargetRowMajor = (SparseMatrixType::Flags & RowMajorBit) ? 1 : 0, | ||||
|       SetterRowMajor = SwapStorage ? 1-TargetRowMajor : TargetRowMajor | ||||
|     }; | ||||
| 
 | ||||
|   public: | ||||
| 
 | ||||
|     /** Constructs a random setter object from the sparse matrix \a target
 | ||||
|       * | ||||
|       * Note that the initial value of \a target are imported. If you want to re-set | ||||
|       * a sparse matrix from scratch, then you must set it to zero first using the | ||||
|       * setZero() function. | ||||
|       */ | ||||
|     inline RandomSetter(SparseMatrixType& target) | ||||
|       : mp_target(&target) | ||||
|     { | ||||
|       const Index outerSize = SwapStorage ? target.innerSize() : target.outerSize(); | ||||
|       const Index innerSize = SwapStorage ? target.outerSize() : target.innerSize(); | ||||
|       m_outerPackets = outerSize >> OuterPacketBits; | ||||
|       if (outerSize&OuterPacketMask) | ||||
|         m_outerPackets += 1; | ||||
|       m_hashmaps = new HashMapType[m_outerPackets]; | ||||
|       // compute number of bits needed to store inner indices
 | ||||
|       Index aux = innerSize - 1; | ||||
|       m_keyBitsOffset = 0; | ||||
|       while (aux) | ||||
|       { | ||||
|         ++m_keyBitsOffset; | ||||
|         aux = aux >> 1; | ||||
|       } | ||||
|       KeyType ik = (1<<(OuterPacketBits+m_keyBitsOffset)); | ||||
|       for (Index k=0; k<m_outerPackets; ++k) | ||||
|         MapTraits<ScalarWrapper>::setInvalidKey(m_hashmaps[k],ik); | ||||
| 
 | ||||
|       // insert current coeffs
 | ||||
|       for (Index j=0; j<mp_target->outerSize(); ++j) | ||||
|         for (typename SparseMatrixType::InnerIterator it(*mp_target,j); it; ++it) | ||||
|           (*this)(TargetRowMajor?j:it.index(), TargetRowMajor?it.index():j) = it.value(); | ||||
|     } | ||||
| 
 | ||||
|     /** Destructor updating back the sparse matrix target */ | ||||
|     ~RandomSetter() | ||||
|     { | ||||
|       KeyType keyBitsMask = (1<<m_keyBitsOffset)-1; | ||||
|       if (!SwapStorage) // also means the map is sorted
 | ||||
|       { | ||||
|         mp_target->setZero(); | ||||
|         mp_target->makeCompressed(); | ||||
|         mp_target->reserve(nonZeros()); | ||||
|         Index prevOuter = -1; | ||||
|         for (Index k=0; k<m_outerPackets; ++k) | ||||
|         { | ||||
|           const Index outerOffset = (1<<OuterPacketBits) * k; | ||||
|           typename HashMapType::iterator end = m_hashmaps[k].end(); | ||||
|           for (typename HashMapType::iterator it = m_hashmaps[k].begin(); it!=end; ++it) | ||||
|           { | ||||
|             const Index outer = (it->first >> m_keyBitsOffset) + outerOffset; | ||||
|             const Index inner = it->first & keyBitsMask; | ||||
|             if (prevOuter!=outer) | ||||
|             { | ||||
|               for (Index j=prevOuter+1;j<=outer;++j) | ||||
|                 mp_target->startVec(j); | ||||
|               prevOuter = outer; | ||||
|             } | ||||
|             mp_target->insertBackByOuterInner(outer, inner) = it->second.value; | ||||
|           } | ||||
|         } | ||||
|         mp_target->finalize(); | ||||
|       } | ||||
|       else | ||||
|       { | ||||
|         VectorXi positions(mp_target->outerSize()); | ||||
|         positions.setZero(); | ||||
|         // pass 1
 | ||||
|         for (Index k=0; k<m_outerPackets; ++k) | ||||
|         { | ||||
|           typename HashMapType::iterator end = m_hashmaps[k].end(); | ||||
|           for (typename HashMapType::iterator it = m_hashmaps[k].begin(); it!=end; ++it) | ||||
|           { | ||||
|             const Index outer = it->first & keyBitsMask; | ||||
|             ++positions[outer]; | ||||
|           } | ||||
|         } | ||||
|         // prefix sum
 | ||||
|         Index count = 0; | ||||
|         for (Index j=0; j<mp_target->outerSize(); ++j) | ||||
|         { | ||||
|           Index tmp = positions[j]; | ||||
|           mp_target->outerIndexPtr()[j] = count; | ||||
|           positions[j] = count; | ||||
|           count += tmp; | ||||
|         } | ||||
|         mp_target->makeCompressed(); | ||||
|         mp_target->outerIndexPtr()[mp_target->outerSize()] = count; | ||||
|         mp_target->resizeNonZeros(count); | ||||
|         // pass 2
 | ||||
|         for (Index k=0; k<m_outerPackets; ++k) | ||||
|         { | ||||
|           const Index outerOffset = (1<<OuterPacketBits) * k; | ||||
|           typename HashMapType::iterator end = m_hashmaps[k].end(); | ||||
|           for (typename HashMapType::iterator it = m_hashmaps[k].begin(); it!=end; ++it) | ||||
|           { | ||||
|             const Index inner = (it->first >> m_keyBitsOffset) + outerOffset; | ||||
|             const Index outer = it->first & keyBitsMask; | ||||
|             // sorted insertion
 | ||||
|             // Note that we have to deal with at most 2^OuterPacketBits unsorted coefficients,
 | ||||
|             // moreover those 2^OuterPacketBits coeffs are likely to be sparse, an so only a
 | ||||
|             // small fraction of them have to be sorted, whence the following simple procedure:
 | ||||
|             Index posStart = mp_target->outerIndexPtr()[outer]; | ||||
|             Index i = (positions[outer]++) - 1; | ||||
|             while ( (i >= posStart) && (mp_target->innerIndexPtr()[i] > inner) ) | ||||
|             { | ||||
|               mp_target->valuePtr()[i+1] = mp_target->valuePtr()[i]; | ||||
|               mp_target->innerIndexPtr()[i+1] = mp_target->innerIndexPtr()[i]; | ||||
|               --i; | ||||
|             } | ||||
|             mp_target->innerIndexPtr()[i+1] = inner; | ||||
|             mp_target->valuePtr()[i+1] = it->second.value; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       delete[] m_hashmaps; | ||||
|     } | ||||
| 
 | ||||
|     /** \returns a reference to the coefficient at given coordinates \a row, \a col */ | ||||
|     Scalar& operator() (Index row, Index col) | ||||
|     { | ||||
|       const Index outer = SetterRowMajor ? row : col; | ||||
|       const Index inner = SetterRowMajor ? col : row; | ||||
|       const Index outerMajor = outer >> OuterPacketBits; // index of the packet/map
 | ||||
|       const Index outerMinor = outer & OuterPacketMask;  // index of the inner vector in the packet
 | ||||
|       const KeyType key = internal::convert_index<KeyType>((outerMinor<<m_keyBitsOffset) | inner); | ||||
|       return m_hashmaps[outerMajor][key].value; | ||||
|     } | ||||
| 
 | ||||
|     /** \returns the number of non zero coefficients
 | ||||
|       * | ||||
|       * \note According to the underlying map/hash_map implementation, | ||||
|       * this function might be quite expensive. | ||||
|       */ | ||||
|     Index nonZeros() const | ||||
|     { | ||||
|       Index nz = 0; | ||||
|       for (Index k=0; k<m_outerPackets; ++k) | ||||
|         nz += static_cast<Index>(m_hashmaps[k].size()); | ||||
|       return nz; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|   protected: | ||||
| 
 | ||||
|     HashMapType* m_hashmaps; | ||||
|     SparseMatrixType* mp_target; | ||||
|     Index m_outerPackets; | ||||
|     unsigned char m_keyBitsOffset; | ||||
| }; | ||||
| 
 | ||||
| } // end namespace Eigen
 | ||||
| 
 | ||||
| #endif // EIGEN_RANDOMSETTER_H
 | ||||
|  | @ -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() | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| #ifndef slic3r_Channel_hpp_ | ||||
| #define slic3r_Channel_hpp_ | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <deque> | ||||
| #include <condition_variable> | ||||
| #include <mutex> | ||||
|  | @ -13,32 +14,26 @@ namespace Slic3r { | |||
| 
 | ||||
| template<class T> class Channel | ||||
| { | ||||
| private: | ||||
|     using UniqueLock = std::unique_lock<std::mutex>; | ||||
|     using Queue = std::deque<T>; | ||||
| public: | ||||
|     class Guard | ||||
|     using UniqueLock = std::unique_lock<std::mutex>; | ||||
| 
 | ||||
|     template<class Ptr> class Unlocker | ||||
|     { | ||||
|     public: | ||||
|         Guard(UniqueLock lock, const Queue &queue) : m_lock(std::move(lock)), m_queue(queue) {} | ||||
|         Guard(const Guard &other) = delete; | ||||
|         Guard(Guard &&other) = delete; | ||||
|         ~Guard() {} | ||||
|         Unlocker(UniqueLock lock) : m_lock(std::move(lock)) {} | ||||
|         Unlocker(const Unlocker &other) noexcept : m_lock(std::move(other.m_lock)) {}     // XXX: done beacuse of MSVC 2013 not supporting init of deleter by move
 | ||||
|         Unlocker(Unlocker &&other) noexcept : m_lock(std::move(other.m_lock)) {} | ||||
|         Unlocker& operator=(const Unlocker &other) = delete; | ||||
|         Unlocker& operator=(Unlocker &&other) { m_lock = std::move(other.m_lock); } | ||||
| 
 | ||||
|         // Access trampolines
 | ||||
|         size_t size() const noexcept { return m_queue.size(); } | ||||
|         bool empty() const noexcept { return m_queue.empty(); } | ||||
|         typename Queue::const_iterator begin() const noexcept { return m_queue.begin(); } | ||||
|         typename Queue::const_iterator end() const noexcept { return m_queue.end(); } | ||||
|         typename Queue::const_reference operator[](size_t i) const { return m_queue[i]; } | ||||
| 
 | ||||
|         Guard& operator=(const Guard &other) = delete; | ||||
|         Guard& operator=(Guard &&other) = delete; | ||||
|         void operator()(Ptr*) { m_lock.unlock(); } | ||||
|     private: | ||||
|         UniqueLock m_lock; | ||||
|         const Queue &m_queue; | ||||
|         mutable UniqueLock m_lock;    // XXX: mutable: see above
 | ||||
|     }; | ||||
| 
 | ||||
|     using Queue = std::deque<T>; | ||||
|     using LockedConstPtr = std::unique_ptr<const Queue, Unlocker<const Queue>>; | ||||
|     using LockedPtr = std::unique_ptr<Queue, Unlocker<Queue>>; | ||||
| 
 | ||||
|     Channel() {} | ||||
|     ~Channel() {} | ||||
|  | @ -56,7 +51,7 @@ public: | |||
|     { | ||||
|         { | ||||
|             UniqueLock lock(m_mutex); | ||||
|             m_queue.push_back(std::forward(item)); | ||||
|             m_queue.push_back(std::forward<T>(item)); | ||||
|         } | ||||
|         if (! silent) { m_condition.notify_one(); } | ||||
|     } | ||||
|  | @ -82,19 +77,22 @@ public: | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Unlocked observers
 | ||||
|     // Thread unsafe! Keep in mind you need to re-verify the result after acquiring lock!
 | ||||
|     size_t size() const noexcept { return m_queue.size(); } | ||||
|     bool empty() const noexcept { return m_queue.empty(); } | ||||
|     // Unlocked observers/hints
 | ||||
|     // Thread unsafe! Keep in mind you need to re-verify the result after locking!
 | ||||
|     size_t size_hint() const noexcept { return m_queue.size(); } | ||||
| 
 | ||||
|     Guard read() const | ||||
|     LockedConstPtr lock_read() const | ||||
|     { | ||||
|         return Guard(UniqueLock(m_mutex), m_queue); | ||||
|         return LockedConstPtr(&m_queue, Unlocker<const Queue>(UniqueLock(m_mutex))); | ||||
|     } | ||||
| 
 | ||||
|     LockedPtr lock_rw() | ||||
|     { | ||||
|         return LockedPtr(&m_queue, Unlocker<Queue>(UniqueLock(m_mutex))); | ||||
|     } | ||||
| private: | ||||
|     Queue m_queue; | ||||
|     std::mutex m_mutex; | ||||
|     mutable std::mutex m_mutex; | ||||
|     std::condition_variable m_condition; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -96,7 +96,6 @@ static void extract_model_from_archive( | |||
|     const char *model_xml = strstr(scene_xml_data.data(), model_name_tag); | ||||
|     const char *zero_tag  = "<zero>"; | ||||
|     const char *zero_xml  = strstr(scene_xml_data.data(), zero_tag); | ||||
|     float  trafo[3][4] = { 0 }; | ||||
|     Vec3d instance_rotation = Vec3d::Zero(); | ||||
|     Vec3d instance_scaling_factor = Vec3d::Ones(); | ||||
|     Vec3d instance_offset = Vec3d::Zero(); | ||||
|  | @ -124,19 +123,7 @@ static void extract_model_from_archive( | |||
|                 "[%f, %f, %f]", zero, zero+1, zero+2) == 3) { | ||||
|             instance_scaling_factor = Vec3d((double)scale[0], (double)scale[1], (double)scale[2]); | ||||
|             instance_rotation = Vec3d(-(double)rotation[0], -(double)rotation[1], -(double)rotation[2]); | ||||
|             Eigen::Matrix3f mat_rot, mat_scale, mat_trafo; | ||||
|             mat_rot = Eigen::AngleAxisf(-rotation[2], Eigen::Vector3f::UnitZ()) *  | ||||
|                       Eigen::AngleAxisf(-rotation[1], Eigen::Vector3f::UnitY()) * | ||||
|                       Eigen::AngleAxisf(-rotation[0], Eigen::Vector3f::UnitX()); | ||||
|             mat_scale = Eigen::Scaling(scale[0], scale[1], scale[2]); | ||||
|             mat_trafo = mat_rot * mat_scale; | ||||
|             for (size_t r = 0; r < 3; ++ r) { | ||||
|                 for (size_t c = 0; c < 3; ++ c) | ||||
|                     trafo[r][c] += mat_trafo(r, c); | ||||
|             } | ||||
|             instance_offset = Vec3d((double)(position[0] - zero[0]), (double)(position[1] - zero[1]), (double)(position[2] - zero[2])); | ||||
|             // CHECK_ME -> Is the following correct ?
 | ||||
|             trafo[2][3] = position[2] / (float)instance_scaling_factor(2); | ||||
|             trafo_set = true; | ||||
|         } | ||||
|         const char *group_tag    = "<group>"; | ||||
|  | @ -189,8 +176,6 @@ static void extract_model_from_archive( | |||
|                 // All the faces have been read.
 | ||||
|                 stl_get_size(&stl); | ||||
|                 mesh.repair(); | ||||
|                 // Transform the model.
 | ||||
|                 stl_transform(&stl, &trafo[0][0]); | ||||
|                 if (std::abs(stl.stats.min(2)) < EPSILON) | ||||
|                     stl.stats.min(2) = 0.; | ||||
|                 // Add a mesh to a model.
 | ||||
|  | @ -274,8 +259,6 @@ static void extract_model_from_archive( | |||
|             memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50); | ||||
|             stl_get_size(&stl); | ||||
|             mesh.repair(); | ||||
|             // Transform the model.
 | ||||
|             stl_transform(&stl, &trafo[0][0]); | ||||
|             // Add a mesh to a model.
 | ||||
|             if (mesh.facets_count() > 0) | ||||
|                 mesh_valid = true; | ||||
|  | @ -329,7 +312,7 @@ bool load_prus(const char *path, Model *model) | |||
|             if (! mz_zip_reader_file_stat(&archive, i, &stat)) | ||||
|                 continue; | ||||
|             std::vector<char> 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); | ||||
|  |  | |||
|  | @ -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<LayerToPrint> &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) | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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<GCodeMove::EType, GCodeMovesList> &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]), | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| #include "PreviewData.hpp" | ||||
| #include <float.h> | ||||
| #include <I18N.hpp> | ||||
| #include "Utils.hpp" | ||||
| 
 | ||||
| #include <boost/format.hpp> | ||||
| 
 | ||||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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<float>& tool_colors, const std::vector</*double*/std::pair<double, double>>& 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); | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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<std::vector<float>>& 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.
 | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -209,7 +209,8 @@ namespace Slic3r { | |||
|         typedef std::map<Block::EMoveType, MoveStats> MovesStatsMap; | ||||
| #endif // ENABLE_MOVE_STATS
 | ||||
| 
 | ||||
|         typedef std::map<unsigned int, unsigned int> G1LineIdToBlockIdMap; | ||||
|         typedef std::pair<unsigned int, unsigned int> G1LineIdToBlockId; | ||||
|         typedef std::vector<G1LineIdToBlockId> 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(); | ||||
|  |  | |||
|  | @ -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)); | ||||
|  |  | |||
|  | @ -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 <class T> bool any_internal_region_slice_contains(const T &item) const { | ||||
|  |  | |||
|  | @ -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<double>(); | ||||
|     const Vec2d   va = (point  - line.a).cast<double>(); | ||||
|     const Vec2d   v  = (b - a).cast<double>(); | ||||
|     const Vec2d   va = (point  - a).cast<double>(); | ||||
|     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<double>().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<double>().squaredNorm();  // beyond the 'b' end of the segment
 | ||||
|     return (t * v - va).squaredNorm(); | ||||
| } | ||||
| 
 | ||||
| double Line::perp_distance_to(const Point &point) const | ||||
|  |  | |||
|  | @ -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; | ||||
| }; | ||||
|  |  | |||
|  | @ -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<t_model_material_id> 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) | ||||
| { | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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<Point> MultiPoint::_douglas_peucker(const std::vector<Point>& 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<Point> 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<size_t> 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
 | ||||
|  |  | |||
|  | @ -8,13 +8,14 @@ | |||
| #include "SupportMaterial.hpp" | ||||
| #include "GCode.hpp" | ||||
| #include "GCode/WipeTowerPrusaMM.hpp" | ||||
| #include <algorithm> | ||||
| #include <unordered_set> | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include "Utils.hpp" | ||||
| 
 | ||||
| #include "PrintExport.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <unordered_set> | ||||
| #include <boost/filesystem/path.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| 
 | ||||
| //! macro used to mark string used at localization, 
 | ||||
| //! return same string
 | ||||
|  | @ -213,6 +214,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option | |||
|             || opt_key == "filament_cooling_final_speed" | ||||
|             || opt_key == "filament_ramming_parameters" | ||||
|             || opt_key == "gcode_flavor" | ||||
|             || opt_key == "high_current_on_filament_swap" | ||||
|             || opt_key == "infill_first" | ||||
|             || opt_key == "single_extruder_multi_material" | ||||
|             || opt_key == "spiral_vase" | ||||
|  | @ -1051,10 +1053,12 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co | |||
|                             goto print_object_end; | ||||
|                     } else { | ||||
|                         this_region_config = region_config_from_model_volume(m_default_region_config, volume, num_extruders); | ||||
|                         for (size_t i = 0; i < region_id; ++ i) | ||||
|                             if (m_regions[i]->config().equals(this_region_config)) | ||||
|                                 // Regions were merged. Reset this print_object.
 | ||||
|                                 goto print_object_end; | ||||
| 						for (size_t i = 0; i < region_id; ++i) { | ||||
| 							const PrintRegion ®ion_other = *m_regions[i]; | ||||
| 							if (region_other.m_refcnt != 0 && region_other.config().equals(this_region_config)) | ||||
| 								// Regions were merged. Reset this print_object.
 | ||||
| 								goto print_object_end; | ||||
| 						} | ||||
|                         this_region_config_set = true; | ||||
|                     } | ||||
|                 } | ||||
|  | @ -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();
 | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -925,6 +925,15 @@ void PrintConfigDef::init_fff_params() | |||
|     def->mode = comExpert; | ||||
|     def->default_value = new ConfigOptionEnum<GCodeFlavor>(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<SLADisplayOrientation>::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<SLADisplayOrientation>(sladoPortrait); | ||||
| 
 | ||||
|     def = this->add("printer_correction", coFloats); | ||||
|  |  | |||
|  | @ -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<FilamentType>::ge | |||
| 
 | ||||
| template<> inline const t_config_enum_values& ConfigOptionEnum<SLADisplayOrientation>::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); | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| #include "SupportMaterial.hpp" | ||||
| #include "Surface.hpp" | ||||
| #include "Slicing.hpp" | ||||
| #include "Utils.hpp" | ||||
| 
 | ||||
| #include <utility> | ||||
| #include <boost/log/trivial.hpp> | ||||
|  | @ -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::vector<t_config_ | |||
|             || opt_key == "perimeter_speed" | ||||
|             || opt_key == "small_perimeter_speed" | ||||
|             || opt_key == "solid_infill_speed" | ||||
|             || opt_key == "top_solid_infill_speed" | ||||
|             || opt_key == "wipe_into_infill"    // when these these two are changed, we only need to invalidate the wipe tower,
 | ||||
|             || opt_key == "wipe_into_objects"   // which we already did at the very beginning - nothing more to be done
 | ||||
|             ) { | ||||
|             // these options only affect G-code export, so nothing to invalidate
 | ||||
|             || opt_key == "top_solid_infill_speed") { | ||||
|             invalidated |= m_print->invalidate_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) { | ||||
|  |  | |||
|  | @ -612,7 +612,9 @@ double ray_mesh_intersect(const Vec3d& s, | |||
|                           const Vec3d& dir, | ||||
|                           const EigenMesh3D& m); | ||||
| 
 | ||||
| PointSet normals(const PointSet& points, const EigenMesh3D& mesh); | ||||
| PointSet normals(const PointSet& points, const EigenMesh3D& mesh, | ||||
|                  double eps = 0.05,  // min distance from edges
 | ||||
|                  std::function<void()> throw_on_cancel = [](){}); | ||||
| 
 | ||||
| inline Vec2d to_vec2(const Vec3d& v3) { | ||||
|     return {v3(X), v3(Y)}; | ||||
|  | @ -1049,7 +1051,7 @@ bool SLASupportTree::generate(const PointSet &points, | |||
|         tifcl(); | ||||
| 
 | ||||
|         // calculate the normals to the triangles belonging to filtered points
 | ||||
|         auto nmls = sla::normals(filt_pts, mesh); | ||||
|         auto nmls = sla::normals(filt_pts, mesh, cfg.head_front_radius_mm, tifcl); | ||||
| 
 | ||||
|         head_norm.resize(count, 3); | ||||
|         head_pos.resize(count, 3); | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| #include <cmath> | ||||
| #include "SLA/SLASupportTree.hpp" | ||||
| #include "SLA/SLABoilerPlate.hpp" | ||||
| #include "SLA/SLASpatIndex.hpp" | ||||
|  | @ -9,15 +10,8 @@ | |||
| #include "boost/geometry/index/rtree.hpp" | ||||
| 
 | ||||
| #include <igl/ray_mesh_intersect.h> | ||||
| 
 | ||||
| //#if !defined(_MSC_VER) || defined(_WIN64)
 | ||||
| #if 1 | ||||
| #define IGL_COMPATIBLE | ||||
| #endif | ||||
| 
 | ||||
| #ifdef IGL_COMPATIBLE | ||||
| #include <igl/point_mesh_squared_distance.h> | ||||
| #endif | ||||
| #include <igl/remove_duplicate_vertices.h> | ||||
| 
 | ||||
| #include "SLASpatIndex.hpp" | ||||
| #include "ClipperUtils.hpp" | ||||
|  | @ -84,33 +78,124 @@ size_t SpatIndex::size() const | |||
|     return m_impl->m_store.size(); | ||||
| } | ||||
| 
 | ||||
| PointSet normals(const PointSet& points, const EigenMesh3D& mesh) { | ||||
|     if(points.rows() == 0 || mesh.V.rows() == 0 || mesh.F.rows() == 0) return {}; | ||||
| #ifdef IGL_COMPATIBLE | ||||
| bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, | ||||
|                    double eps = 0.05) | ||||
| { | ||||
|     using Line3D = Eigen::ParametrizedLine<double, 3>; | ||||
| 
 | ||||
|     auto line = Line3D::Through(e1, e2); | ||||
|     double d = line.distance(p); | ||||
|     return std::abs(d) < eps; | ||||
| } | ||||
| 
 | ||||
| template<class Vec> double distance(const Vec& pp1, const Vec& pp2) { | ||||
|     auto p = pp2 - pp1; | ||||
|     return std::sqrt(p.transpose() * p); | ||||
| } | ||||
| 
 | ||||
| PointSet normals(const PointSet& points, const EigenMesh3D& emesh, | ||||
|                  double eps, | ||||
|                  std::function<void()> throw_on_cancel) { | ||||
|     if(points.rows() == 0 || emesh.V.rows() == 0 || emesh.F.rows() == 0) | ||||
|         return {}; | ||||
| 
 | ||||
|     Eigen::VectorXd dists; | ||||
|     Eigen::VectorXi I; | ||||
|     PointSet C; | ||||
| 
 | ||||
|     // We need to remove duplicate vertices and have a true index triangle
 | ||||
|     // structure
 | ||||
|     EigenMesh3D  mesh; | ||||
|     Eigen::VectorXi SVI, SVJ; | ||||
|     igl::remove_duplicate_vertices(emesh.V, emesh.F, 1e-6, | ||||
|                                    mesh.V, SVI, SVJ, mesh.F); | ||||
| 
 | ||||
|     igl::point_mesh_squared_distance( points, mesh.V, mesh.F, dists, I, C); | ||||
| 
 | ||||
|     PointSet ret(I.rows(), 3); | ||||
|     for(int i = 0; i < I.rows(); i++) { | ||||
|         throw_on_cancel(); | ||||
|         auto idx = I(i); | ||||
|         auto trindex = mesh.F.row(idx); | ||||
| 
 | ||||
|         auto& p1 = mesh.V.row(trindex(0)); | ||||
|         auto& p2 = mesh.V.row(trindex(1)); | ||||
|         auto& p3 = mesh.V.row(trindex(2)); | ||||
|         const Vec3d& p1 = mesh.V.row(trindex(0)); | ||||
|         const Vec3d& p2 = mesh.V.row(trindex(1)); | ||||
|         const Vec3d& p3 = mesh.V.row(trindex(2)); | ||||
| 
 | ||||
|         Eigen::Vector3d U = p2 - p1; | ||||
|         Eigen::Vector3d V = p3 - p1; | ||||
|         ret.row(i) = U.cross(V).normalized(); | ||||
|         // We should check if the point lies on an edge of the hosting triangle.
 | ||||
|         // If it does than all the other triangles using the same two points
 | ||||
|         // have to be searched and the final normal should be some kind of
 | ||||
|         // aggregation of the participating triangle normals. We should also
 | ||||
|         // consider the cases where the support point lies right on a vertex
 | ||||
|         // of its triangle. The procedure is the same, get the neighbor
 | ||||
|         // triangles and calculate an average normal.
 | ||||
| 
 | ||||
|         const Vec3d& p = C.row(i); | ||||
| 
 | ||||
|         // mark the vertex indices of the edge. ia and ib marks and edge ic
 | ||||
|         // will mark a single vertex.
 | ||||
|         int ia = -1, ib = -1, ic = -1; | ||||
| 
 | ||||
|         if(std::abs(distance(p, p1)) < eps) { | ||||
|             ic = trindex(0); | ||||
|         } | ||||
|         else if(std::abs(distance(p, p2)) < eps) { | ||||
|             ic = trindex(1); | ||||
|         } | ||||
|         else if(std::abs(distance(p, p3)) < eps) { | ||||
|             ic = trindex(2); | ||||
|         } | ||||
|         else if(point_on_edge(p, p1, p2, eps)) { | ||||
|             ia = trindex(0); ib = trindex(1); | ||||
|         } | ||||
|         else if(point_on_edge(p, p2, p3, eps)) { | ||||
|             ia = trindex(1); ib = trindex(2); | ||||
|         } | ||||
|         else if(point_on_edge(p, p1, p3, eps)) { | ||||
|             ia = trindex(0); ib = trindex(2); | ||||
|         } | ||||
| 
 | ||||
|         std::vector<Vec3i> neigh; | ||||
|         if(ic >= 0) { // The point is right on a vertex of the triangle
 | ||||
|             for(int n = 0; n < mesh.F.rows(); ++n) { | ||||
|                 throw_on_cancel(); | ||||
|                 Vec3i ni = mesh.F.row(n); | ||||
|                 if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic)) | ||||
|                     neigh.emplace_back(ni); | ||||
|             } | ||||
|         } | ||||
|         else if(ia >= 0 && ib >= 0) { // the point is on and edge
 | ||||
|             // now get all the neigboring triangles
 | ||||
|             for(int n = 0; n < mesh.F.rows(); ++n) { | ||||
|                 throw_on_cancel(); | ||||
|                 Vec3i ni = mesh.F.row(n); | ||||
|                 if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) && | ||||
|                    (ni(X) == ib || ni(Y) == ib || ni(Z) == ib)) | ||||
|                     neigh.emplace_back(ni); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(!neigh.empty()) { // there were neighbors to count with
 | ||||
|             Vec3d sumnorm(0, 0, 0); | ||||
|             for(const Vec3i& tri : neigh) { | ||||
|                 const Vec3d& pt1 = mesh.V.row(tri(0)); | ||||
|                 const Vec3d& pt2 = mesh.V.row(tri(1)); | ||||
|                 const Vec3d& pt3 = mesh.V.row(tri(2)); | ||||
|                 Eigen::Vector3d U = pt2 - pt1; | ||||
|                 Eigen::Vector3d V = pt3 - pt1; | ||||
|                 sumnorm += U.cross(V).normalized(); | ||||
|             } | ||||
|             sumnorm /= neigh.size(); | ||||
|             ret.row(i) = sumnorm; | ||||
|         } | ||||
|         else { // point lies safely within its triangle
 | ||||
|             Eigen::Vector3d U = p2 - p1; | ||||
|             Eigen::Vector3d V = p3 - p1; | ||||
|             ret.row(i) = U.cross(V).normalized(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| #else // TODO:  do something on 32 bit windows
 | ||||
|     return {}; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| double ray_mesh_intersect(const Vec3d& s, | ||||
|  | @ -223,7 +308,7 @@ Segments model_boundary(const EigenMesh3D& emesh, double offs) | |||
|         pp.emplace_back(p); | ||||
|     } | ||||
| 
 | ||||
|     ExPolygons merged = union_ex(offset(pp, float(scale_(offs))), true); | ||||
|     ExPolygons merged = union_ex(Slic3r::offset(pp, float(scale_(offs))), true); | ||||
| 
 | ||||
|     for(auto& expoly : merged) { | ||||
|         auto lines = expoly.lines(); | ||||
|  |  | |||
|  | @ -436,10 +436,17 @@ std::vector<float> SLAPrint::calculate_heights(const BoundingBoxf3& bb3d, float | |||
|     return heights; | ||||
| } | ||||
| 
 | ||||
| template<class...Args> | ||||
| 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>(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<SpinMutex> 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<t_config_option_key> &opt_keys) | ||||
|  |  | |||
|  | @ -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_
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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_
 | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ | |||
| 
 | ||||
| #ifdef WIN32 | ||||
| #include <windows.h> | ||||
| #include <psapi.h> | ||||
| #else | ||||
| #include <unistd.h> | ||||
| #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
 | ||||
|  |  | |||
|  | @ -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); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<class T> | ||||
| bool is_defined_input_value(wxWindow* win, const ConfigOptionType& type) | ||||
| { | ||||
|     if (static_cast<wxTextCtrl*>(window)->GetValue().empty() && m_opt.type != coString && m_opt.type != coStrings) | ||||
|     if (static_cast<T*>(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<wxTextCtrl>(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<double>(m_value) : -99999; | ||||
|             if (is_defined_input_value<wxComboBox>(window, m_opt.type)) { | ||||
|                 if (fabs(old_val - boost::any_cast<double>(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<wxComboBox*>(window)->GetSelection();  | ||||
| 		if (m_opt_id.compare("external_fill_pattern") == 0) | ||||
|  | @ -638,7 +684,18 @@ boost::any& Choice::get_value() | |||
| 			m_value = static_cast<SeamPosition>(ret_enum); | ||||
| 		else if (m_opt_id.compare("host_type") == 0) | ||||
| 			m_value = static_cast<PrintHostType>(ret_enum); | ||||
| 	}	 | ||||
| 		else if (m_opt_id.compare("display_orientation") == 0) | ||||
| 			m_value = static_cast<SLADisplayOrientation>(ret_enum); | ||||
| 	} | ||||
|     else if (m_opt.gui_type == "f_enum_open") { | ||||
|         const int ret_enum = static_cast<wxComboBox*>(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<wxSizer*>(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; | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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<EType, GLGizmoBase*> GizmosMap; | ||||
|         GizmosMap m_gizmos; | ||||
| #if ENABLE_TOOLBAR_BACKGROUND_TEXTURE | ||||
|         BackgroundTexture m_background_texture; | ||||
| #endif // ENABLE_TOOLBAR_BACKGROUND_TEXTURE
 | ||||
|         EType m_current; | ||||
| 
 | ||||
|     public: | ||||
|  | @ -696,6 +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; | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -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<GLToolbarItem*> 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
 | ||||
|  |  | |||
|  | @ -195,6 +195,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt | |||
| 				config.set_key_value(opt_key, new ConfigOptionEnum<SeamPosition>(boost::any_cast<SeamPosition>(value))); | ||||
| 			else if (opt_key.compare("host_type") == 0) | ||||
| 				config.set_key_value(opt_key, new ConfigOptionEnum<PrintHostType>(boost::any_cast<PrintHostType>(value))); | ||||
| 			else if (opt_key.compare("display_orientation") == 0) | ||||
| 				config.set_key_value(opt_key, new ConfigOptionEnum<SLADisplayOrientation>(boost::any_cast<SLADisplayOrientation>(value))); | ||||
| 			} | ||||
| 			break; | ||||
| 		case coPoints:{ | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -92,7 +92,7 @@ class GUI_App : public wxApp | |||
|     std::unique_ptr<ImGuiWrapper> m_imgui; | ||||
| #endif // ENABLE_IMGUI
 | ||||
| 
 | ||||
|     std::unique_ptr<PrintHostJobQueue> m_printhost_queue; | ||||
|     std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue; | ||||
| 
 | ||||
| public: | ||||
|     bool            OnInit() override; | ||||
|  | @ -164,7 +164,7 @@ public: | |||
|     ImGuiWrapper* imgui() { return m_imgui.get(); } | ||||
| #endif // ENABLE_IMGUI
 | ||||
| 
 | ||||
|     PrintHostJobQueue& printhost_queue() { return *m_printhost_queue.get(); } | ||||
|     PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); } | ||||
| 
 | ||||
| }; | ||||
| DECLARE_APP(GUI_App) | ||||
|  |  | |||
|  | @ -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]; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<std::string> axes{ "_x", "_y", "_z" }; | ||||
| 
 | ||||
|         if (opt_key == "scale_unit") { | ||||
|             const wxString& selection = boost::any_cast<wxString>(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<std::string> 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<wxString>(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
 | ||||
|  |  | |||
|  | @ -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); | ||||
| }; | ||||
| 
 | ||||
| }} | ||||
|  |  | |||
|  | @ -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"; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -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 }; | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| #include <wx/button.h> | ||||
| #include <wx/statbmp.h> | ||||
| #include <wx/scrolwin.h> | ||||
| #include <wx/clipbrd.h> | ||||
| 
 | ||||
| #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(); | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<int>(model_object->materials_count()))); | ||||
| 
 | ||||
|     auto& stats = model_object->volumes.front()->mesh.stl.stats; | ||||
|     auto sf = model_instance->get_scaling_factor(); | ||||
|     p->object_info->info_volume->SetLabel(wxString::Format("%.2f", size(0) * size(1) * size(2) * sf(0) * sf(1) * sf(2))); | ||||
|     p->object_info->info_volume->SetLabel(wxString::Format("%.2f", size(0) * size(1) * size(2))); | ||||
|     p->object_info->info_facets->SetLabel(wxString::Format(_(L("%d (%d shells)")), static_cast<int>(model_object->facets_count()), stats.number_of_parts)); | ||||
| 
 | ||||
|     int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + | ||||
|  | @ -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<int> &evt) { evt.data == 1 ? q->increase_instances() : q->decrease_instances(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int> &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<bool> &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<int> &evt) { evt.data == 1 ? q->increase_instances() : q->decrease_instances(); }); | ||||
| 	canvas3Dwidget->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int> &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<bool> &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<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode | |||
| #if !ENABLE_MODELVOLUME_TRANSFORM | ||||
|     const Vec3d bed_center = Slic3r::to_3d(bed_shape.center().cast<double>(), 0.0); | ||||
| #endif // !ENABLE_MODELVOLUME_TRANSFORM
 | ||||
|     const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast<double>(), 1.0); | ||||
|     const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast<double>(), 1.0) - 2.0 * Vec3d::Ones(); | ||||
| 
 | ||||
|     bool need_arrange = false; | ||||
|     bool scaled_down = false; | ||||
|  | @ -1517,9 +1528,10 @@ std::vector<size_t> 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<ConfigOptionPoints>("bed_shape")->values); | ||||
|             update_scheduled = true; | ||||
|         } else if (opt_key == "host_type" && this->p->printer_technology == ptSLA) { | ||||
|             p->config->option<ConfigOptionEnum<PrintHostType>>(opt_key)->value = htSL1; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -366,7 +366,8 @@ const std::vector<std::string>& 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<std::string>& 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" | ||||
|         }; | ||||
|  |  | |||
|  | @ -1,20 +1,28 @@ | |||
| #include "PrintHostDialogs.hpp" | ||||
| 
 | ||||
| #include <algorithm> | ||||
| 
 | ||||
| #include <wx/frame.h> | ||||
| #include <wx/event.h> | ||||
| #include <wx/progdlg.h> | ||||
| #include <wx/sizer.h> | ||||
| #include <wx/stattext.h> | ||||
| #include <wx/textctrl.h> | ||||
| #include <wx/checkbox.h> | ||||
| #include <wx/button.h> | ||||
| #include <wx/dataview.h> | ||||
| #include <wx/wupdlock.h> | ||||
| #include <wx/debug.h> | ||||
| 
 | ||||
| #include "slic3r/GUI/GUI.hpp" | ||||
| #include "slic3r/GUI/MsgDialog.hpp" | ||||
| #include "slic3r/GUI/I18N.hpp" | ||||
| #include "GUI.hpp" | ||||
| #include "MsgDialog.hpp" | ||||
| #include "I18N.hpp" | ||||
| #include "../Utils/PrintHost.hpp" | ||||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
| 
 | ||||
| PrintHostSendDialog::PrintHostSendDialog(const fs::path &path) | ||||
|     : MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE) | ||||
|  | @ -45,5 +53,95 @@ fs::path PrintHostSendDialog::filename() const | |||
| 
 | ||||
| bool PrintHostSendDialog::start_print() const | ||||
| { | ||||
|     return box_print->GetValue(); } | ||||
|     return box_print->GetValue(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event); | ||||
| wxDEFINE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event); | ||||
| 
 | ||||
| PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id) | ||||
|     : wxEvent(winid, eventType) | ||||
|     , job_id(job_id) | ||||
| {} | ||||
| 
 | ||||
| PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, int progress) | ||||
|     : wxEvent(winid, eventType) | ||||
|     , job_id(job_id) | ||||
|     , progress(progress) | ||||
| {} | ||||
| 
 | ||||
| PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, wxString error) | ||||
|     : wxEvent(winid, eventType) | ||||
|     , job_id(job_id) | ||||
|     , error(std::move(error)) | ||||
| {} | ||||
| 
 | ||||
| wxEvent *PrintHostQueueDialog::Event::Clone() const | ||||
| { | ||||
|     return new Event(*this); | ||||
| } | ||||
| 
 | ||||
| PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) | ||||
|     : wxDialog(parent, wxID_ANY, _(L("Print host upload queue")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) | ||||
|     , on_progress_evt(this, EVT_PRINTHOST_PROGRESS, &PrintHostQueueDialog::on_progress, this) | ||||
|     , on_error_evt(this, EVT_PRINTHOST_ERROR, &PrintHostQueueDialog::on_error, this) | ||||
| { | ||||
|     enum { HEIGHT = 800, WIDTH = 400, SPACING = 5 }; | ||||
| 
 | ||||
|     SetMinSize(wxSize(HEIGHT, WIDTH)); | ||||
| 
 | ||||
|     auto *topsizer = new wxBoxSizer(wxVERTICAL); | ||||
| 
 | ||||
|     job_list = new wxDataViewListCtrl(this, wxID_ANY); | ||||
|     job_list->AppendTextColumn("ID", wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendProgressColumn("Progress", wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendTextColumn("Status", wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendTextColumn("Host", wxDATAVIEW_CELL_INERT); | ||||
|     job_list->AppendTextColumn("Filename", wxDATAVIEW_CELL_INERT); | ||||
| 
 | ||||
|     auto *btnsizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|     auto *btn_cancel = new wxButton(this, wxID_DELETE, _(L("Cancel selected"))); | ||||
|     auto *btn_close = new wxButton(this, wxID_CANCEL, _(L("Close"))); | ||||
|     btnsizer->Add(btn_cancel, 0, wxRIGHT, SPACING); | ||||
|     btnsizer->AddStretchSpacer(); | ||||
|     btnsizer->Add(btn_close); | ||||
| 
 | ||||
|     topsizer->Add(job_list, 1, wxEXPAND | wxBOTTOM, SPACING); | ||||
|     topsizer->Add(btnsizer, 0, wxEXPAND); | ||||
|     SetSizer(topsizer); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::append_job(const PrintHostJob &job) | ||||
| { | ||||
|     wxCHECK_RET(!job.empty(), "PrintHostQueueDialog: Attempt to append an empty job"); | ||||
| 
 | ||||
|     wxVector<wxVariant> fields; | ||||
|     fields.push_back(wxVariant(wxString::Format("%d", job_list->GetItemCount() + 1))); | ||||
|     fields.push_back(wxVariant(0)); | ||||
|     fields.push_back(wxVariant(_(L("Enqueued")))); | ||||
|     fields.push_back(wxVariant(job.printhost->get_host())); | ||||
|     fields.push_back(wxVariant(job.upload_data.upload_path.string())); | ||||
|     job_list->AppendItem(fields); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_progress(Event &evt) | ||||
| { | ||||
|     wxCHECK_RET(evt.job_id < job_list->GetItemCount(), "Out of bounds access to job list"); | ||||
| 
 | ||||
|     const wxVariant status(evt.progress < 100 ? _(L("Uploading")) : _(L("Complete"))); | ||||
| 
 | ||||
|     job_list->SetValue(wxVariant(evt.progress), evt.job_id, 1); | ||||
|     job_list->SetValue(status, evt.job_id, 2); | ||||
| } | ||||
| 
 | ||||
| void PrintHostQueueDialog::on_error(Event &evt) | ||||
| { | ||||
|     wxCHECK_RET(evt.job_id < job_list->GetItemCount(), "Out of bounds access to job list"); | ||||
| 
 | ||||
|     // TODO
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| }} | ||||
|  |  | |||
|  | @ -2,24 +2,27 @@ | |||
| #define slic3r_PrintHostSendDialog_hpp_ | ||||
| 
 | ||||
| #include <string> | ||||
| 
 | ||||
| #include <boost/filesystem/path.hpp> | ||||
| 
 | ||||
| #include <wx/string.h> | ||||
| #include <wx/frame.h> | ||||
| #include <wx/event.h> | ||||
| #include <wx/progdlg.h> | ||||
| #include <wx/sizer.h> | ||||
| #include <wx/stattext.h> | ||||
| #include <wx/textctrl.h> | ||||
| #include <wx/checkbox.h> | ||||
| #include <wx/dialog.h> | ||||
| 
 | ||||
| #include "slic3r/GUI/GUI.hpp" | ||||
| #include "slic3r/GUI/MsgDialog.hpp" | ||||
| #include "GUI.hpp" | ||||
| #include "GUI_Utils.hpp" | ||||
| #include "MsgDialog.hpp" | ||||
| #include "../Utils/PrintHost.hpp" | ||||
| 
 | ||||
| class wxTextCtrl; | ||||
| class wxCheckBox; | ||||
| class wxDataViewListCtrl; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| struct PrintHostJob; | ||||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
| 
 | ||||
| class PrintHostSendDialog : public GUI::MsgDialog | ||||
| { | ||||
|  | @ -38,12 +41,38 @@ private: | |||
| class PrintHostQueueDialog : public wxDialog | ||||
| { | ||||
| public: | ||||
|     PrintHostQueueDialog(); | ||||
|     class Event : public wxEvent | ||||
|     { | ||||
|     public: | ||||
|         size_t job_id; | ||||
|         int progress = 0;    // in percent
 | ||||
|         wxString error; | ||||
| 
 | ||||
|         Event(wxEventType eventType, int winid, size_t job_id); | ||||
|         Event(wxEventType eventType, int winid, size_t job_id, int progress); | ||||
|         Event(wxEventType eventType, int winid, size_t job_id, wxString error); | ||||
| 
 | ||||
|         virtual wxEvent *Clone() const; | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     PrintHostQueueDialog(wxWindow *parent); | ||||
| 
 | ||||
|     void append_job(const PrintHostJob &job); | ||||
| private: | ||||
|     wxDataViewListCtrl *job_list; | ||||
|     // Note: EventGuard prevents delivery of progress evts to a freed PrintHostQueueDialog
 | ||||
|     EventGuard on_progress_evt; | ||||
|     EventGuard on_error_evt; | ||||
| 
 | ||||
|     void on_progress(Event&); | ||||
|     void on_error(Event&); | ||||
| }; | ||||
| 
 | ||||
| wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event); | ||||
| wxDECLARE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| }} | ||||
| 
 | ||||
| #endif | ||||
|  |  | |||
|  | @ -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<ConfigOptionEnum<PrintHostType>>("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<PrintHost> 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<PrintHost> 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; | ||||
| 	} | ||||
|  |  | |||
|  | @ -325,6 +325,8 @@ class TabPrinter : public Tab | |||
| 
 | ||||
|     std::vector<PageShp>			m_pages_fff; | ||||
|     std::vector<PageShp>			m_pages_sla; | ||||
| 
 | ||||
|     void build_printhost(ConfigOptionsGroup *optgroup); | ||||
| public: | ||||
| 	wxButton*	m_serial_test_btn = nullptr; | ||||
| 	wxButton*	m_print_host_test_btn = nullptr; | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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}; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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"; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ | |||
| #include <curl/curl.h> | ||||
| 
 | ||||
| #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<fs::ifstream> 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<void*>(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); | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ public: | |||
| 
 | ||||
| 	typedef std::shared_ptr<Http> Ptr; | ||||
| 	typedef std::function<void(std::string /* body */, unsigned /* http_status */)> CompleteFn; | ||||
| 	 | ||||
| 
 | ||||
| 	// A HTTP request may fail at various stages of completeness (URL parsing, DNS lookup, TCP connection, ...).
 | ||||
| 	// If the HTTP request could not be made or failed before completion, the `error` arg contains a description
 | ||||
| 	// of the error and `http_status` is zero.
 | ||||
|  |  | |||
|  | @ -4,9 +4,10 @@ | |||
| #include <boost/format.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| 
 | ||||
| #include <wx/progdlg.h> | ||||
| 
 | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| #include "slic3r/GUI/I18N.hpp" | ||||
| #include "slic3r/GUI/PrintHostDialogs.hpp"   // XXX
 | ||||
| #include "Http.hpp" | ||||
| 
 | ||||
| 
 | ||||
|  | @ -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; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,15 +1,22 @@ | |||
| #include "OctoPrint.hpp" | ||||
| #include "Duet.hpp" | ||||
| #include "PrintHost.hpp" | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <thread> | ||||
| #include <boost/optional.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/filesystem.hpp> | ||||
| 
 | ||||
| #include <wx/app.h> | ||||
| 
 | ||||
| #include "libslic3r/PrintConfig.hpp" | ||||
| #include "libslic3r/Channel.hpp" | ||||
| #include "OctoPrint.hpp" | ||||
| #include "Duet.hpp" | ||||
| #include "../GUI/PrintHostDialogs.hpp" | ||||
| 
 | ||||
| namespace fs = boost::filesystem; | ||||
| using boost::optional; | ||||
| 
 | ||||
| using Slic3r::GUI::PrintHostQueueDialog; | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -18,42 +25,152 @@ PrintHost::~PrintHost() {} | |||
| 
 | ||||
| PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) | ||||
| { | ||||
|     PrintHostType kind = config->option<ConfigOptionEnum<PrintHostType>>("host_type")->value; | ||||
|     if (kind == htOctoPrint) { | ||||
|         return new OctoPrint(config); | ||||
|     } else if (kind == htDuet) { | ||||
|         return new Duet(config); | ||||
|     const auto opt = config->option<ConfigOptionEnum<PrintHostType>>("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<PrintHostJob> jobs; | ||||
|     Channel<unsigned> channel; | ||||
|     // XXX: comment on how bg thread works
 | ||||
| 
 | ||||
|     PrintHostJobQueue *q; | ||||
| 
 | ||||
|     Channel<PrintHostJob> channel_jobs; | ||||
|     Channel<size_t> channel_cancels; | ||||
|     size_t job_id = 0; | ||||
|     int prev_progress = -1; | ||||
| 
 | ||||
|     std::thread bg_thread; | ||||
|     optional<PrintHostJob> bg_job; | ||||
|     bool bg_exit = false; | ||||
| 
 | ||||
|     PrintHostQueueDialog *queue_dialog; | ||||
| 
 | ||||
|     priv(PrintHostJobQueue *q) : q(q) {} | ||||
| 
 | ||||
|     void start_bg_thread(); | ||||
|     void bg_thread_main(); | ||||
|     void progress_fn(Http::Progress progress, bool &cancel); | ||||
|     void error_fn(std::string body, std::string error, unsigned http_status); | ||||
|     void perform_job(PrintHostJob the_job); | ||||
| }; | ||||
| 
 | ||||
| PrintHostJobQueue::PrintHostJobQueue() | ||||
|     : p(new priv()) | ||||
| PrintHostJobQueue::PrintHostJobQueue(PrintHostQueueDialog *queue_dialog) | ||||
|     : p(new priv(this)) | ||||
| { | ||||
|     std::shared_ptr<priv> p2 = p; | ||||
|     p->bg_thread = std::thread([p2]() { | ||||
|         // Wait for commands on the channel:
 | ||||
|         auto cmd = p2->channel.pop(); | ||||
|         // TODO
 | ||||
|     }); | ||||
|     p->queue_dialog = queue_dialog; | ||||
| } | ||||
| 
 | ||||
| PrintHostJobQueue::~PrintHostJobQueue() | ||||
| { | ||||
|     // TODO: stop the thread
 | ||||
|     // if (p && p->bg_thread.joinable()) {
 | ||||
|     //     p->bg_thread.detach();
 | ||||
|     // }
 | ||||
|     if (p && p->bg_thread.joinable()) { | ||||
|         p->bg_exit = true; | ||||
|         p->channel_jobs.push(PrintHostJob()); // Push an empty job to wake up bg_thread in case it's sleeping
 | ||||
|         p->bg_thread.detach();                // Let the background thread go, it should exit on its own
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::start_bg_thread() | ||||
| { | ||||
|     if (bg_thread.joinable()) { return; } | ||||
| 
 | ||||
|     std::shared_ptr<priv> p2 = q->p; | ||||
|     bg_thread = std::thread([p2]() { | ||||
|         p2->bg_thread_main(); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::bg_thread_main() | ||||
| { | ||||
|     // bg thread entry point
 | ||||
| 
 | ||||
|     try { | ||||
|         // Pick up jobs from the job channel:
 | ||||
|         while (! bg_exit) { | ||||
|             auto job = channel_jobs.pop();   // Sleeps in a cond var if there are no jobs
 | ||||
|             if (! job.cancelled) { | ||||
|                 perform_job(std::move(job)); | ||||
|             } | ||||
|             job_id++; | ||||
|         } | ||||
|     } catch (...) { | ||||
|         wxTheApp->OnUnhandledException(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::progress_fn(Http::Progress progress, bool &cancel) | ||||
| { | ||||
|     if (bg_exit) { | ||||
|         cancel = true; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (channel_cancels.size_hint() > 0) { | ||||
|         // Lock both queues
 | ||||
|         auto cancels = channel_cancels.lock_rw(); | ||||
|         auto jobs = channel_jobs.lock_rw(); | ||||
| 
 | ||||
|         for (size_t cancel_id : *cancels) { | ||||
|             if (cancel_id == job_id) { | ||||
|                 cancel = true; | ||||
|             } else if (cancel_id > job_id) { | ||||
|                 jobs->at(cancel_id - job_id).cancelled = true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         cancels->clear(); | ||||
|     } | ||||
| 
 | ||||
|     int gui_progress = progress.ultotal > 0 ? 100*progress.ulnow / progress.ultotal : 0; | ||||
|     if (gui_progress != prev_progress) { | ||||
|         auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_PROGRESS, queue_dialog->GetId(), job_id, gui_progress); | ||||
|         wxQueueEvent(queue_dialog, evt); | ||||
|         prev_progress = gui_progress; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::error_fn(std::string body, std::string error, unsigned http_status) | ||||
| { | ||||
|     // TODO
 | ||||
| } | ||||
| 
 | ||||
| void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job) | ||||
| { | ||||
|     if (bg_exit || the_job.empty()) { return; } | ||||
| 
 | ||||
|     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)); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,6 +7,8 @@ | |||
| 
 | ||||
| #include <wx/string.h> | ||||
| 
 | ||||
| #include "Http.hpp" | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -29,11 +31,10 @@ public: | |||
|     virtual bool test(wxString &curl_msg) const = 0; | ||||
|     virtual wxString get_test_ok_msg () const = 0; | ||||
|     virtual wxString get_test_failed_msg (wxString &msg) const = 0; | ||||
|     // Send gcode file to print host, filename is expected to be in UTF-8
 | ||||
|     virtual bool send_gcode(const std::string &filename) const = 0;         // XXX: remove in favor of upload()
 | ||||
|     virtual bool upload(PrintHostUpload upload_data) const = 0; | ||||
|     virtual bool upload(PrintHostUpload upload_data, Http::ProgressFn prorgess_fn, Http::ErrorFn error_fn) const = 0; | ||||
|     virtual bool has_auto_discovery() const = 0; | ||||
|     virtual bool can_test() const = 0; | ||||
|     virtual std::string get_host() const = 0; | ||||
| 
 | ||||
|     static PrintHost* get_print_host(DynamicPrintConfig *config); | ||||
| }; | ||||
|  | @ -43,6 +44,7 @@ struct PrintHostJob | |||
| { | ||||
|     PrintHostUpload upload_data; | ||||
|     std::unique_ptr<PrintHost> printhost; | ||||
|     bool cancelled = false; | ||||
| 
 | ||||
|     PrintHostJob() {} | ||||
|     PrintHostJob(const PrintHostJob&) = delete; | ||||
|  | @ -68,10 +70,12 @@ struct PrintHostJob | |||
| }; | ||||
| 
 | ||||
| 
 | ||||
| namespace GUI { class PrintHostQueueDialog; } | ||||
| 
 | ||||
| class PrintHostJobQueue | ||||
| { | ||||
| public: | ||||
|     PrintHostJobQueue(); | ||||
|     PrintHostJobQueue(GUI::PrintHostQueueDialog *queue_dialog); | ||||
|     PrintHostJobQueue(const PrintHostJobQueue &) = delete; | ||||
|     PrintHostJobQueue(PrintHostJobQueue &&other) = delete; | ||||
|     ~PrintHostJobQueue(); | ||||
|  | @ -79,6 +83,9 @@ public: | |||
|     PrintHostJobQueue& operator=(const PrintHostJobQueue &) = delete; | ||||
|     PrintHostJobQueue& operator=(PrintHostJobQueue &&other) = delete; | ||||
| 
 | ||||
|     void enqueue(PrintHostJob job); | ||||
|     void cancel(size_t id); | ||||
| 
 | ||||
| private: | ||||
|     struct priv; | ||||
|     std::shared_ptr<priv> p; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lukas Matena
						Lukas Matena