diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 30bd322fad..ef0e383782 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -2009,6 +2009,7 @@ void Application::initTypes() App::PropertyTime ::init(); App::PropertyUltimateTensileStrength ::init(); App::PropertyVacuumPermittivity ::init(); + App::PropertyVelocity ::init(); App::PropertyVolume ::init(); App::PropertyVolumeFlowRate ::init(); App::PropertyVolumetricThermalExpansionCoefficient::init(); diff --git a/src/App/CMakeLists.txt b/src/App/CMakeLists.txt index 918ced0a6f..e4b096aedd 100644 --- a/src/App/CMakeLists.txt +++ b/src/App/CMakeLists.txt @@ -262,6 +262,7 @@ SET(FreeCADApp_CPP_SRCS ComplexGeoDataPyImp.cpp Enumeration.cpp IndexedName.cpp + MappedName.cpp Material.cpp MaterialPyImp.cpp Metadata.cpp @@ -279,6 +280,7 @@ SET(FreeCADApp_HPP_SRCS ComplexGeoData.h Enumeration.h IndexedName.h + MappedName.h Material.h Metadata.h ) diff --git a/src/App/MappedName.cpp b/src/App/MappedName.cpp new file mode 100644 index 0000000000..ba9b90c2ac --- /dev/null +++ b/src/App/MappedName.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** + * Copyright (c) 2020 Zheng, Lei (realthunder) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ****************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +#endif + +//#include + +#include "MappedName.h" + +using namespace Data; + + +void MappedName::compact() +{ + + if (this->raw) { + this->data = QByteArray(this->data.constData(), this->data.size()); + this->raw = false; + } + +#if 0 + static std::unordered_set PostfixSet; + if (this->postfix.size()) { + auto res = PostfixSet.insert(this->postfix); + if (!res.second) + self->postfix = *res.first; + } +#endif +} + diff --git a/src/App/MappedName.h b/src/App/MappedName.h new file mode 100644 index 0000000000..84a6ca6927 --- /dev/null +++ b/src/App/MappedName.h @@ -0,0 +1,908 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/**************************************************************************** + * Copyright (c) 2022 Zheng, Lei (realthunder) * + * Copyright (c) 2023 FreeCAD Project Association * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef APP_MAPPED_NAME_H +#define APP_MAPPED_NAME_H + + +#include + +#include + +#include +#include + +#include "ComplexGeoData.h" +#include "IndexedName.h" + + +namespace Data +{ + +// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) + +/// The MappedName class maintains a two-part name: the first part ("data") is considered immutable +/// once created, while the second part ("postfix") can be modified/appended to by later operations. +/// It uses shared data when possible (see the fromRawData() members). Despite storing data and +/// postfix separately, they can be accessed via calls to size(), operator[], etc. as though they +/// were a single array. +class AppExport MappedName +{ +public: + /// Create a MappedName from a C string, optionally prefixed by an element map prefix, which + /// will be omitted from the stored MappedName. + /// + /// \param name The new name. A deep copy is made. + /// \param size Optional, the length of the name string. If not provided, the string must be + /// null-terminated. + explicit MappedName(const char* name, int size = -1) + : raw(false) + { + if (!name) { + return; + } + if (boost::starts_with(name, ComplexGeoData::elementMapPrefix())) { + name += ComplexGeoData::elementMapPrefix().size(); + } + + data = size < 0 ? QByteArray(name) : QByteArray(name, size); + } + + /// Create a MappedName from a C++ std::string, optionally prefixed by an element map prefix, + /// which will be omitted from the stored MappedName. + /// + /// \param name The new name. A deep copy is made. + explicit MappedName(const std::string& nameString) + : raw(false) + { + auto size = nameString.size(); + const char* name = nameString.c_str(); + if (boost::starts_with(nameString, ComplexGeoData::elementMapPrefix())) { + name += ComplexGeoData::elementMapPrefix().size(); + size -= ComplexGeoData::elementMapPrefix().size(); + } + data = QByteArray(name, static_cast(size)); + } + + /// Create a MappedName from an IndexedName. If non-zero, the numerical part of the IndexedName + /// is appended as text to the MappedName. In that case the memory is *not* shared between the + /// original IndexedName and the MappedName. + explicit MappedName(const IndexedName& element) + : data(QByteArray::fromRawData(element.getType(), qstrlen(element.getType()))), + raw(true) + { + if (element.getIndex() > 0) { + this->data += QByteArray::number(element.getIndex()); + this->raw = false; + } + } + + MappedName() + : raw(false) + {} + + MappedName(const MappedName& other) = default; + + /// Copy constructor with start position offset and optional size. The data is *not* reused. + /// + /// \param other The MappedName to copy + /// \param startPosition an integer offset to start the copy from + /// \param size the number of bytes to copy. + /// \see append() for details about how the copy behaves for various sizes and start positions + MappedName(const MappedName& other, int startPosition, int size = -1) + : raw(false) + { + append(other, startPosition, size); + } + + /// Copy constructor with additional postfix + /// + /// \param other The mapped name to copy. Its data and postfix become the new MappedName's data + /// \param postfix The postfix for the new MappedName + MappedName(const MappedName& other, const char* postfix) + : data(other.data + other.postfix), + postfix(postfix), + raw(false) + {} + + /// Move constructor + MappedName(MappedName&& other) noexcept + : data(std::move(other.data)), + postfix(std::move(other.postfix)), + raw(other.raw) + {} + + ~MappedName() = default; + + /// Construct a MappedName from raw character data (including null characters, if size is + /// provided). No copy is made: the data is used in place. + /// + /// \param name The raw data to use. + /// \param size The number of bytes to access. If omitted, name must be null-terminated. + /// \return a new MappedName with name as its data. + static MappedName fromRawData(const char* name, int size = -1) + { + MappedName res; + if (name) { + res.data = + QByteArray::fromRawData(name, size >= 0 ? size : static_cast(qstrlen(name))); + res.raw = true; + } + return res; + } + + /// Construct a MappedName from QByteArray data (including any embedded null characters). + /// + /// \param data The original data. No copy is made, the data is shared with the other instance. + /// \return a new MappedName with data as its data. + static MappedName fromRawData(const QByteArray& data) + { + return fromRawData(data.constData(), data.size()); + } + + /// Construct a MappedName from another MappedName + /// + /// \param other The MappedName to copy from. The data is usually not copied, but in some + /// cases a partial copy may be made to support a slice that extends across other's data into + /// its postfix. + /// \param startPosition The position to start the reference at. + /// \param size The number of bytes to access. If omitted, continues from startPosition + /// to the end of available data (including postfix). + /// \return a new MappedName sharing (possibly a subset of) data with other. + /// \see append() for details about how the copy behaves for various sizes and start positions + static MappedName fromRawData(const MappedName& other, int startPosition, int size = -1) + { + if (startPosition < 0) { + startPosition = 0; + } + + if (startPosition >= other.size()) { + return {}; + } + + if (startPosition >= other.data.size()) { + return {other, startPosition, size}; + } + + MappedName res; + res.raw = true; + if (size < 0) { + size = other.size() - startPosition; + } + + if (size < other.data.size() - startPosition) { + res.data = QByteArray::fromRawData(other.data.constData() + startPosition, size); + } + else { + res.data = QByteArray::fromRawData(other.data.constData() + startPosition, + other.data.size() - startPosition); + size -= other.data.size() - startPosition; + if (size == other.postfix.size()) { + res.postfix = other.postfix; + } + else if (size != 0) { + res.postfix.append(other.postfix.constData(), size); + } + } + return res; + } + + /// Share data with another MappedName + MappedName& operator=(const MappedName& other) = default; + + /// Create a new MappedName from a std::string: the string's data is copied. + MappedName& operator=(const std::string& other) + { + *this = MappedName(other); + return *this; + } + + /// Create a new MappedName from a const char *. The character data is copied. + MappedName& operator=(const char* other) + { + *this = MappedName(other); + return *this; + } + + + /// Move-construct a MappedName + MappedName& operator=(MappedName&& other) noexcept + { + this->data = std::move(other.data); + this->postfix = std::move(other.postfix); + this->raw = other.raw; + return *this; + } + + /// Write to a stream as the name with postfix directly appended to it. Note that there is no + /// special handling for null or non-ASCII characters, they are simply written to the stream. + friend std::ostream& operator<<(std::ostream& stream, const MappedName& mappedName) + { + stream.write(mappedName.data.constData(), mappedName.data.size()); + stream.write(mappedName.postfix.constData(), mappedName.postfix.size()); + return stream; + } + + /// Two MappedNames are equal if the concatenation of their data and postfix is equal. The + /// individual data and postfix may NOT be equal in this case. + bool operator==(const MappedName& other) const + { + if (this->size() != other.size()) { + return false; + } + + if (this->data.size() == other.data.size()) { + return this->data == other.data && this->postfix == other.postfix; + } + + const auto& smaller = this->data.size() < other.data.size() ? *this : other; + const auto& larger = this->data.size() < other.data.size() ? other : *this; + + if (!larger.data.startsWith(smaller.data)) { + return false; + } + + QByteArray tmp = QByteArray::fromRawData(larger.data.constData() + smaller.data.size(), + larger.data.size() - smaller.data.size()); + + if (!smaller.postfix.startsWith(tmp)) { + return false; + } + + tmp = QByteArray::fromRawData(smaller.postfix.constData() + tmp.size(), + smaller.postfix.size() - tmp.size()); + + return tmp == larger.postfix; + } + + bool operator!=(const MappedName& other) const + { + return !(this->operator==(other)); + } + + /// Returns a new MappedName whose data is the LHS argument's data and whose postfix is the LHS + /// argument's postfix with the RHS argument's data and postfix appended to it. + MappedName operator+(const MappedName& other) const + { + MappedName res(*this); + res += other; + return res; + } + + /// Returns a new MappedName whose data is the LHS argument's data and whose postfix is the LHS + /// argument's postfix with the RHS argument appended to it. The character data is copied. + MappedName operator+(const char* other) const + { + MappedName res(*this); + res += other; + return res; + } + + /// Returns a new MappedName whose data is the LHS argument's data and whose postfix is the LHS + /// argument's postfix with the RHS argument appended to it. The character data is copied. + MappedName operator+(const std::string& other) const + { + MappedName res(*this); + res += other; + return res; + } + + /// Returns a new MappedName whose data is the LHS argument's data and whose postfix is the LHS + /// argument's postfix with the RHS argument appended to it. + MappedName operator+(const QByteArray& other) const + { + MappedName res(*this); + res += other; + return res; + } + + /// Appends other to this instance's postfix. other must be a null-terminated C string. The + /// character data from the string is copied. + MappedName& operator+=(const char* other) + { + if (other && (other[0] != 0)) { + this->postfix.append(other, -1); + } + return *this; + } + + /// Appends other to this instance's postfix. The character data from the string is copied. + MappedName& operator+=(const std::string& other) + { + if (!other.empty()) { + this->postfix.reserve(this->postfix.size() + static_cast(other.size())); + this->postfix.append(other.c_str(), static_cast(other.size())); + } + return *this; + } + + /// Appends other to this instance's postfix. The data may be either copied or shared, depending + /// on whether this->postfix is empty (in which case the data is shared) or non-empty (in which + /// case it is copied). + MappedName& operator+=(const QByteArray& other) + { + this->postfix += other; + return *this; + } + + /// Appends other to this instance's postfix, unless this is empty, in which case this acts + /// like operator=, and makes this instance's data equal to other's data, and this instance's + /// postfix equal to the other instance's postfix. + MappedName& operator+=(const MappedName& other) + { + append(other); + return *this; + } + + /// Add dataToAppend to this MappedName. If the current name is empty, this becomes the new + /// data element. If this MappedName already has data, then the data is appended to the postfix. + /// + /// \param dataToAppend The data to add. A deep copy is made. + /// \param size The number of bytes to copy. If omitted, dataToAppend must be null-terminated. + void append(const char* dataToAppend, int size = -1) + { + if (dataToAppend && (size != 0)) { + if (size < 0) { + size = static_cast(qstrlen(dataToAppend)); + } + if (empty()) { + this->data.append(dataToAppend, size); + } + else { + this->postfix.append(dataToAppend, size); + } + } + } + + /// Treating both this and other as single continuous byte arrays, append other to this. If this + /// is empty, then other's data is shared with this instance's data beginning at startPosition. + /// If this is *not* empty, then all data is appended to the postfix. If the copy crosses the + /// boundary between other's data and its postfix, then if this instance was empty, the new + /// data stops where other's data stops, and the remainder of the copy is placed in the suffix. + /// Otherwise the copy simply continues as though there was no distinction between other's + /// data and suffix. + /// + /// \param other The MappedName to obtain the data from. The data is shared when possible, + /// depending on the details of startPosition, size, and this->empty(). + /// \param startPosition The byte to start the copy at. Must be a positive non-zero integer less + /// than the length of other's combined data + postfix. + /// \param size The number of bytes to copy. Must not overrun the end of other's combined data + /// storage when taking startPosition into consideration. + void append(const MappedName& other, int startPosition = 0, int size = -1) + { + // enforce 0 <= startPosition <= other.size + if (startPosition < 0) { + startPosition = 0; + } + else if (startPosition > other.size()) { + return; + } + + // enforce 0 <= size <= other.size - startPosition + if (size < 0 || size > other.size() - startPosition) { + size = other.size() - startPosition; + } + + + if (startPosition < other.data.size())// if starting inside data + { + int count = size; + // make sure count doesn't exceed data size and end up in postfix + if (count > other.data.size() - startPosition) { + count = other.data.size() - startPosition; + } + + // if this is empty append in data else append in postfix + if (startPosition == 0 && count == other.data.size() && this->empty()) { + this->data = other.data; + this->raw = other.raw; + } + else { + append(other.data.constData() + startPosition, count); + } + + // setup startPosition and count to continue appending the remainder to postfix + startPosition = 0; + size -= count; + } + else// else starting inside postfix + { + startPosition -= other.data.size(); + } + + // if there is still data to be added to postfix + if (size != 0) { + if (startPosition == 0 && size == other.postfix.size()) { + if (this->empty()) { + this->data = other.postfix; + } + else if (this->postfix.isEmpty()) { + this->postfix = other.postfix; + } + else { + this->postfix += other.postfix; + } + } + else { + append(other.postfix.constData() + startPosition, size); + } + } + } + + /// Create a std::string from this instance, starting at startPosition, and extending len bytes. + /// + /// \param startPosition The offset into the data + /// \param len The number of bytes to output + /// \return A new std::string containing the bytes copied from this instance's data and postfix + /// (depending on startPosition and len). + /// \note No effort is made to ensure that these are valid ASCII characters, and it is possible + /// the data includes embedded null characters, non-ASCII data, etc. + std::string toString(int startPosition = 0, int len = -1) const + { + std::string res; + return appendToBuffer(res, startPosition, len); + } + + /// Given a (possibly non-empty) std::string buffer, append this instance to it, starting at a + /// specified position, and continuing for a specified number of bytes. + /// + /// \param buffer The string buffer to append to. + /// \param startPosition The position in this instance's data/postfix to start at (defaults to + /// zero). Must be less than the total length of the data plus the postfix. + /// \param len The number of bytes to append. If omitted, defaults to appending all available + /// data starting at startPosition. + /// \return A pointer to the beginning of the appended data within buffer. + /// \note No effort is made to ensure that these are valid ASCII characters, and it is possible + /// the data includes embedded null characters, non-ASCII data, etc. + const char* appendToBuffer(std::string& buffer, int startPosition = 0, int len = -1) const + { + std::size_t offset = buffer.size(); + int count = this->size(); + if (startPosition < 0) { + startPosition = 0; + } + else if (startPosition >= count) { + return buffer.c_str() + buffer.size(); + } + if (len < 0 || len > count - startPosition) { + len = count - startPosition; + } + buffer.reserve(buffer.size() + len); + if (startPosition < this->data.size()) { + count = this->data.size() - startPosition; + if (len < count) { + count = len; + } + buffer.append(this->data.constData() + startPosition, count); + len -= count; + } + buffer.append(this->postfix.constData(), len); + return buffer.c_str() + offset; + } + + // if offset is inside data return data, if offset is > data.size + //(ends up in postfix) return postfix + const char* toConstString(int offset, int& size) const + { + if (offset < 0) { + offset = 0; + } + if (offset > this->data.size()) { + offset -= this->data.size(); + if (offset > this->postfix.size()) { + size = 0; + return ""; + } + size = this->postfix.size() - offset; + return this->postfix.constData() + offset; + } + size = this->data.size() - offset; + return this->data.constData() + offset; + } + + /// Get access to raw byte data. When possible, data is shared between this instance and the + /// returned QByteArray. If the combination of offset and size results in data that crosses the + /// boundary between this->data and this->postfix, the data must be copied in order to provide + /// access as a continuous array of bytes. + /// + /// \param offset The start position of the raw data access. + /// \param size The number of bytes to access. If omitted, the resulting QByteArray includes + /// everything starting from offset to the end, including any postfix data. + /// \return A new QByteArray that shares data with this instance if possible, or is a new copy + /// if required by offset and size. + QByteArray toRawBytes(int offset = 0, int size = -1) const + { + if (offset < 0) { + offset = 0; + } + if (offset >= this->size()) { + return {}; + } + if (size < 0 || size > this->size() - offset) { + size = this->size() - offset; + } + if (offset >= this->data.size()) { + offset -= this->data.size(); + return QByteArray::fromRawData(this->postfix.constData() + offset, size); + } + if (size <= this->data.size() - offset) { + return QByteArray::fromRawData(this->data.constData() + offset, size); + } + + QByteArray res(this->data.constData() + offset, this->data.size() - offset); + res.append(this->postfix.constData(), size - this->data.size() + offset); + return res; + } + + /// Direct access to the stored QByteArray of data. A copy is never made. + const QByteArray& dataBytes() const + { + return this->data; + } + + /// Direct access to the stored QByteArray of postfix. A copy is never made. + const QByteArray& postfixBytes() const + { + return this->postfix; + } + + /// Convenience function providing access to the pointer to the beginning of the postfix data. + const char* constPostfix() const + { + return this->postfix.constData(); + } + + // No constData() because 'data' is allowed to contain raw data, which may not end with 0. + + /// Provide access to the content of this instance. If either postfix or data is empty, no copy + /// is made and the original QByteArray is returned, sharing data with this instance. If this + /// instance contains both data and postfix, a new QByteArray is created and stores a copy of + /// the data and postfix concatenated together. + QByteArray toBytes() const + { + if (this->postfix.isEmpty()) { + return this->data; + } + if (this->data.isEmpty()) { + return this->postfix; + } + return this->data + this->postfix; + } + + /// Create an IndexedName from the data portion of this MappedName. If this data has a postfix, + /// the function returns an empty IndexedName. The function will fail if this->data contains + /// anything other than the ASCII letter a-z, A-Z, and the underscore, with an optional integer + /// suffix, returning an empty IndexedName (e.g. an IndexedName that evaluates to boolean + /// false and isNull() == true). + /// + /// \return a new IndexedName that shares its data with this instance's data member. + IndexedName toIndexedName() const + { + if (this->postfix.isEmpty()) { + return IndexedName(this->data); + } + return IndexedName(); + } + + /// Create and return a string version of this MappedName prefixed by the ComplexGeoData element + /// map prefix, if this MappedName cannot be converted to an indexed name. + std::string toPrefixedString() const + { + std::string res; + appendToBufferWithPrefix(res); + return res; + } + + /// Append this MappedName to a provided string buffer, including the ComplexGeoData element + /// map prefix if the MappedName cannot be converted to an IndexedName. + /// + /// \param buf A (possibly non-empty) string to append this MappedName to. + /// \return A pointer to the beginning of the buffer. + const char* appendToBufferWithPrefix(std::string& buf) const + { + if (!toIndexedName()) { + buf += ComplexGeoData::elementMapPrefix(); + } + appendToBuffer(buf); + return buf.c_str(); + } + + /// Equivalent to C++20 operator<=>. Performs byte-by-byte comparison of this and other, + /// starting at the first byte and continuing through both data and postfix, ignoring which is + /// which. If the combined data and postfix members are of unequal size but start with the same + /// data, the shorter array is considered "less than" the longer. + int compare(const MappedName& other) const + { + int thisSize = this->size(); + int otherSize = other.size(); + for (int i = 0, count = std::min(thisSize, otherSize); i < count; ++i) { + char thisChar = this->operator[](i); + char otherChar = other[i]; + if (thisChar < otherChar) { + return -1; + } + if (thisChar > otherChar) { + return 1; + } + } + if (thisSize < otherSize) { + return -1; + } + if (thisSize > otherSize) { + return 1; + } + return 0; + } + + /// \see compare() + bool operator<(const MappedName& other) const + { + return compare(other) < 0; + } + + /// Treat this MappedName as a single continuous array of bytes, beginning with data and + /// continuing through postfix. No bounds checking is performed when compiled in release mode. + char operator[](int index) const + { + if (index < 0) { + index = 0; + } + if (index >= this->data.size()) { + if (index - this->data.size() > this->postfix.size() - 1) { + index = this->postfix.size() - 1; + } + return this->postfix[index - this->data.size()]; + } + return this->data[index]; + } + + /// Treat this MappedName as a single continuous array of bytes, returning the combined size + /// of the data and postfix. + int size() const + { + return this->data.size() + this->postfix.size(); + } + + /// Treat this MappedName as a single continuous array of bytes, returning true only if both + /// data and prefix are empty. + bool empty() const + { + return this->data.isEmpty() && this->postfix.isEmpty(); + } + + /// Returns true if this is shared data, or false if a unique copy has been made. + /// It is safe to access data only if it has been copied prior. To force a copy + /// please \see compact() + bool isRaw() const + { + return this->raw; + } + + /// If this is shared data, a new unshared copy is made and returned. If it is already unshared + /// no new copy is made, a new instance is returned that shares is data with the current + /// instance. + MappedName copy() const + { + if (!this->raw) { + return *this; + } + MappedName res; + res.data.append(this->data.constData(), this->data.size()); + res.postfix = this->postfix; + return res; + } + + /// Ensure that this data is unshared, making a copy if necessary. + void compact(); + + /// Boolean conversion is the inverse of empty(), returning true if there is data in either the + /// data or postfix, and false if there is nothing in either. + explicit operator bool() const + { + return !empty(); + } + + /// Reset this instance, clearing anything in data and postfix. + void clear() + { + this->data.clear(); + this->postfix.clear(); + this->raw = false; + } + + /// Find a string of characters in this MappedName. The bytes must occur either entirely in the + /// data, or entirely in the postfix: a string that overlaps the two will not be found. + /// + /// \param searchTarget A null-terminated C string to search for. + /// \param startPosition A byte offset to start the search at. + /// \return The position of the target in this instance, or -1 if the target is not found. + int find(const char* searchTarget, int startPosition = 0) const + { + if (!searchTarget) { + return -1; + } + if (startPosition < 0) { + startPosition = 0; + } + if (startPosition < this->data.size()) { + int res = this->data.indexOf(searchTarget, startPosition); + if (res >= 0) { + return res; + } + startPosition = 0; + } + else { + startPosition -= this->data.size(); + } + int res = this->postfix.indexOf(searchTarget, startPosition); + if (res < 0) { + return res; + } + return res + this->data.size(); + } + + /// Find a string of characters in this MappedName. The bytes must occur either entirely in the + /// data, or entirely in the postfix: a string that overlaps the two will not be found. + /// + /// \param searchTarget A string to search for. + /// \param startPosition A byte offset to start the search at. + /// \return The position of the target in this instance, or -1 if the target is not found. + int find(const std::string& searchTarget, int startPosition = 0) const + { + return find(searchTarget.c_str(), startPosition); + } + + /// Find a string of characters in this MappedName, starting at the back of postfix and + /// proceeding in reverse through the data. The bytes must occur either entirely in the + /// data, or entirely in the postfix: a string that overlaps the two will not be found. + /// + /// \param searchTarget A null-terminated C string to search for. + /// \param startPosition A byte offset to start the search at. Negative numbers are supported + /// and count back from the end of the concatenated data (as in QByteArray::lastIndexOf()). + /// \return The position of the target in this instance, or -1 if the target is not found. + int rfind(const char* searchTarget, int startPosition = -1) const + { + if (!searchTarget) { + return -1; + } + if (startPosition < 0 + || startPosition >= this->data.size()) { + if (startPosition >= data.size()) { + startPosition -= data.size(); + } + int res = this->postfix.lastIndexOf(searchTarget, startPosition); + if (res >= 0) { + return res + this->data.size(); + } + startPosition = -1; + } + return this->data.lastIndexOf(searchTarget, startPosition); + } + + /// Find a string in this MappedName, starting at the back of postfix and proceeding in reverse + /// through the data. The bytes must occur either entirely in the data, or entirely in the + /// postfix: a string that overlaps the two will not be found. + /// + /// \param searchTarget A null-terminated C string to search for. + /// \param startPosition A byte offset to start the search at. Negative numbers are supported + /// and count back from the end of the concatenated data (as in QByteArray::lastIndexOf()). + /// \return The position of the target in this instance, or -1 if the target is not found. + int rfind(const std::string& searchTarget, int startPosition = -1) const + { + return rfind(searchTarget.c_str(), startPosition); + } + + /// Returns true if this MappedName ends with the search target. If there is a postfix, only the + /// postfix is considered. If not, then only the data is considered. A search string that + /// overlaps the two will not be found. + bool endsWith(const char* searchTarget) const + { + if (!searchTarget) { + return false; + } + if (this->postfix.size() != 0) { + return this->postfix.endsWith(searchTarget); + } + return this->data.endsWith(searchTarget); + } + + /// Returns true if this MappedName ends with the search target. If there is a postfix, only the + /// postfix is considered. If not, then only the data is considered. A search string that + /// overlaps the two will not be found. + bool endsWith(const std::string& searchTarget) const + { + return endsWith(searchTarget.c_str()); + } + + /// Returns true if this MappedName starts with the search target. If there is a postfix, only + /// the postfix is considered. If not, then only the data is considered. A search string that + /// overlaps the two will not be found. + /// + /// \param searchTarget An array of bytes to match + /// \param offset An offset to perform the match at + /// \return True if this MappedName begins with the target bytes + bool startsWith(const QByteArray& searchTarget, int offset = 0) const + { + if (searchTarget.size() > size() - offset) { + return false; + } + if ((offset != 0) + || ((this->data.size() != 0) && this->data.size() < searchTarget.size())) { + return toRawBytes(offset, searchTarget.size()) == searchTarget; + } + if (this->data.size() != 0) { + return this->data.startsWith(searchTarget); + } + return this->postfix.startsWith(searchTarget); + } + + /// Returns true if this MappedName starts with the search target. If there is a postfix, only + /// the postfix is considered. If not, then only the data is considered. A search string that + /// overlaps the two will not be found. + /// + /// \param searchTarget An array of bytes to match + /// \param offset An offset to perform the match at + /// \return True if this MappedName begins with the target bytes + bool startsWith(const char* searchTarget, int offset = 0) const + { + if (!searchTarget) { + return false; + } + return startsWith( + QByteArray::fromRawData(searchTarget, static_cast(qstrlen(searchTarget))), offset); + } + + /// Returns true if this MappedName starts with the search target. If there is a postfix, only + /// the postfix is considered. If not, then only the data is considered. A search string that + /// overlaps the two will not be found. + /// + /// \param searchTarget A string to match + /// \param offset An offset to perform the match at + /// \return True if this MappedName begins with the target bytes + bool startsWith(const std::string& searchTarget, int offset = 0) const + { + return startsWith( + QByteArray::fromRawData(searchTarget.c_str(), static_cast(searchTarget.size())), + offset); + } + + /// Get a hash for this MappedName + std::size_t hash() const + { + return qHash(data, qHash(postfix)); + } + +private: + QByteArray data; + QByteArray postfix; + bool raw; +}; + +// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + + +}// namespace Data + + +#endif// APP_MAPPED_NAME_H \ No newline at end of file diff --git a/src/Gui/DlgKeyboard.ui b/src/Gui/DlgKeyboard.ui index 5e2d77ca5e..8ff9076675 100644 --- a/src/Gui/DlgKeyboard.ui +++ b/src/Gui/DlgKeyboard.ui @@ -103,6 +103,9 @@ + + To change a current shortcut enter the new shortcut in the field below and press 'Assign'. + true diff --git a/src/Gui/NotificationArea.cpp b/src/Gui/NotificationArea.cpp index 53e27c644f..8e81a502cc 100644 --- a/src/Gui/NotificationArea.cpp +++ b/src/Gui/NotificationArea.cpp @@ -234,7 +234,7 @@ void NotificationAreaObserver::SendLog(const std::string& notifiername, const st .trimmed();// remove any leading and trailing whitespace character ('\n') // avoid processing empty strings - if(simplifiedstring.isEmpty()) + if (simplifiedstring.isEmpty()) return; if (level == Base::LogStyle::TranslatedNotification) { @@ -827,7 +827,7 @@ void NotificationArea::pushNotification(const QString& notifiername, const QStri auto timer_thread = pImp->inhibitTimer.thread(); auto current_thread = QThread::currentThread(); - if(timer_thread == current_thread) + if (timer_thread == current_thread) pImp->inhibitTimer.start(pImp->inhibitNotificationTime); } @@ -939,7 +939,8 @@ void NotificationArea::showInNotificationArea() iconstr = QStringLiteral(":/icons/info.svg"); } - QString tmpmessage = convertFromPlainText(item->msg, Qt::WhiteSpaceMode::WhiteSpaceNormal); + QString tmpmessage = + convertFromPlainText(item->msg, Qt::WhiteSpaceMode::WhiteSpaceNormal); msgw += QString::fromLatin1( @@ -990,11 +991,17 @@ void NotificationArea::showInNotificationArea() msgw += QString::fromLatin1("

"); + // Calculate the main window QRect in global screen coordinates. + auto mainwindow = getMainWindow(); + auto mainwindowrect = mainwindow->rect(); + auto globalmainwindowrect = + QRect(mainwindow->mapToGlobal(mainwindowrect.topLeft()), mainwindowrect.size()); NotificationBox::showText(this->mapToGlobal(QPoint()), msgw, pImp->notificationExpirationTime, pImp->minimumOnScreenTime, + globalmainwindowrect, pImp->notificationWidth); } } diff --git a/src/Gui/NotificationBox.cpp b/src/Gui/NotificationBox.cpp index 41bfe05d16..79e528d069 100644 --- a/src/Gui/NotificationBox.cpp +++ b/src/Gui/NotificationBox.cpp @@ -73,6 +73,8 @@ public: bool notificationLabelChanged(const QString& text); /// Place the notification at the given position void placeNotificationLabel(const QPoint& pos); + /// Set the windowrect defining an area to which the label should be constrained + void setTipRect(const QRect &restrictionarea); /// The instance static qobject_delete_later_unique_ptr instance; @@ -91,6 +93,8 @@ private: int minShowTime; QTimer hideTimer; QTimer expireTimer; + + QRect restrictionArea; }; qobject_delete_later_unique_ptr NotificationLabel::instance = nullptr; @@ -263,25 +267,36 @@ void NotificationLabel::placeNotificationLabel(const QPoint& pos) p += offset; - QRect screenRect = screen->geometry(); + QRect actinglimit = screen->geometry(); - if (p.x() + this->width() > screenRect.x() + screenRect.width()) - p.rx() -= 4 + this->width(); - if (p.y() + this->height() > screenRect.y() + screenRect.height()) - p.ry() -= 24 + this->height(); - if (p.y() < screenRect.y()) - p.setY(screenRect.y()); - if (p.x() + this->width() > screenRect.x() + screenRect.width()) - p.setX(screenRect.x() + screenRect.width() - this->width()); - if (p.x() < screenRect.x()) - p.setX(screenRect.x()); - if (p.y() + this->height() > screenRect.y() + screenRect.height()) - p.setY(screenRect.y() + screenRect.height() - this->height()); + if(!restrictionArea.isNull()) + actinglimit = restrictionArea; + + const int standard_x_padding = 4; + const int standard_y_padding = 24; + + if (p.x() + this->width() > actinglimit.x() + actinglimit.width()) + p.rx() -= standard_x_padding + this->width(); + if (p.y() + standard_y_padding + this->height() > actinglimit.y() + actinglimit.height()) + p.ry() -= standard_y_padding + this->height(); + if (p.y() < actinglimit.y()) + p.setY(actinglimit.y()); + if (p.x() + this->width() > actinglimit.x() + actinglimit.width()) + p.setX(actinglimit.x() + actinglimit.width() - this->width()); + if (p.x() < actinglimit.x()) + p.setX(actinglimit.x()); + if (p.y() + this->height() > actinglimit.y() + actinglimit.height()) + p.setY(actinglimit.y() + actinglimit.height() - this->height()); } this->move(p); } +void NotificationLabel::setTipRect(const QRect &restrictionarea) +{ + restrictionArea = restrictionarea; +} + bool NotificationLabel::notificationLabelChanged(const QString& text) { return NotificationLabel::instance->text() != text; @@ -290,7 +305,7 @@ bool NotificationLabel::notificationLabelChanged(const QString& text) /***************************** NotificationBox **********************************/ void NotificationBox::showText(const QPoint& pos, const QString& text, int displayTime, - unsigned int minShowTime, int width) + unsigned int minShowTime, const QRect &restrictionarea, int width) { // a label does already exist if (NotificationLabel::instance && NotificationLabel::instance->isVisible()) { @@ -301,6 +316,7 @@ void NotificationBox::showText(const QPoint& pos, const QString& text, int displ else { // If the label has changed, reuse the one that is showing (removes flickering) if (NotificationLabel::instance->notificationLabelChanged(text)) { + NotificationLabel::instance->setTipRect(restrictionarea); NotificationLabel::instance->reuseNotification(text, displayTime, pos, width); NotificationLabel::instance->placeNotificationLabel(pos); } @@ -310,12 +326,16 @@ void NotificationBox::showText(const QPoint& pos, const QString& text, int displ // no label can be reused, create new label: if (!text.isEmpty()) { + // Note: The Label takes no parent, as on windows, we can't use the widget as parent + // otherwise the window will be raised when the tooltip will be shown. We do not use + // it on Linux either for consistency. new NotificationLabel(text, pos, displayTime, minShowTime, width);// sets NotificationLabel::instance to itself + NotificationLabel::instance->setTipRect(restrictionarea); NotificationLabel::instance->placeNotificationLabel(pos); NotificationLabel::instance->setObjectName(QLatin1String("NotificationBox_label")); diff --git a/src/Gui/NotificationBox.h b/src/Gui/NotificationBox.h index 0008d5ad7e..4ddcd93083 100644 --- a/src/Gui/NotificationBox.h +++ b/src/Gui/NotificationBox.h @@ -54,11 +54,16 @@ public: * an event, see class documentation above) * @param minShowTime Time during which the notification can only be made disappear by popping * it out (clicking inside it). - * @param width Fixes the width of the notification. Default value makes the width to be system determined (dependent on - * the text). + * @param restrictionarea Try to keep the NotificationBox within this area. If this area is not + * provided, the whole screen is used as restriction area. This are must be provided in global + * screen coordinates. + * @param width Fixes the width of the notification. Default value makes the width to be system + * determined (dependent on the text). If a fixed width is provided it is enforced over the + * restrictionarea. */ static void showText(const QPoint& pos, const QString& text, int displayTime = -1, - unsigned int minShowTime = 0, int width = 0); + unsigned int minShowTime = 0, const QRect& restrictionarea = {}, + int width = 0); /// Hides a notification. static inline void hideText() { diff --git a/src/Gui/Notifications.h b/src/Gui/Notifications.h index d805f38bf0..8c55d514bf 100644 --- a/src/Gui/Notifications.h +++ b/src/Gui/Notifications.h @@ -153,7 +153,7 @@ inline void Gui::Notify(TNotifier && notifier, TCaption && caption, TMessage && if constexpr( type == Base::LogStyle::TranslatedNotification) { // trailing newline is necessary as this may be shown too in a console requiring them (depending on the configuration). - auto msg = message.append(QStringLiteral("\n")); // QString + auto msg = QStringLiteral("%1. %2\n").arg(caption).arg(message); // QString if constexpr( std::is_base_of_v::type>> ) { Base::Console().Send(notifier->getFullLabel(), msg.toUtf8()); diff --git a/src/Gui/PropertyPage.cpp b/src/Gui/PropertyPage.cpp index b5e92d6ae0..a26772c23e 100644 --- a/src/Gui/PropertyPage.cpp +++ b/src/Gui/PropertyPage.cpp @@ -35,13 +35,9 @@ using namespace Gui::Dialog; /** Construction */ -PropertyPage::PropertyPage(QWidget* parent) : QWidget(parent) -{ - bChanged = false; -} - -/** Destruction */ -PropertyPage::~PropertyPage() +PropertyPage::PropertyPage(QWidget* parent) + : QWidget(parent) + , bChanged{false} { } @@ -61,40 +57,40 @@ void PropertyPage::reset() } /** Returns whether the page was modified or not. */ -bool PropertyPage::isModified() +bool PropertyPage::isModified() const { - return bChanged; + return bChanged; } /** Sets the page to be modified. */ -void PropertyPage::setModified(bool b) +void PropertyPage::setModified(bool value) { - bChanged = b; + bChanged = value; } /** Applies all changes calling @ref apply() and resets the modified state. */ void PropertyPage::onApply() { - if (isModified()) - apply(); + if (isModified()) { + apply(); + } - setModified(false); + setModified(false); } /** Discards all changes calling @ref cancel() and resets the modified state. */ void PropertyPage::onCancel() { - if (isModified()) - { - cancel(); - setModified(false); - } + if (isModified()) { + cancel(); + setModified(false); + } } /** Resets to the default values. */ void PropertyPage::onReset() { - reset(); + reset(); } // ---------------------------------------------------------------- @@ -104,27 +100,23 @@ PreferencePage::PreferencePage(QWidget* parent) : QWidget(parent) { } -/** Destruction */ -PreferencePage::~PreferencePage() +void PreferencePage::changeEvent(QEvent* event) { -} - -void PreferencePage::changeEvent(QEvent *e) -{ - QWidget::changeEvent(e); + QWidget::changeEvent(event); } // ---------------------------------------------------------------- PreferenceUiForm::PreferenceUiForm(const QString& fn, QWidget* parent) - : PreferencePage(parent), form(nullptr) + : PreferencePage(parent) + , form(nullptr) { - UiLoader loader; - loader.setLanguageChangeEnabled(true); - loader.setWorkingDirectory(QFileInfo(fn).absolutePath()); + auto loader = UiLoader::newInstance(); + loader->setWorkingDirectory(QFileInfo(fn).absolutePath()); QFile file(fn); - if (file.open(QFile::ReadOnly)) - form = loader.load(&file, this); + if (file.open(QFile::ReadOnly)) { + form = loader->load(&file, this); + } file.close(); if (form) { this->setWindowTitle(form->windowTitle()); diff --git a/src/Gui/PropertyPage.h b/src/Gui/PropertyPage.h index 4a67b21e36..1794c6800f 100644 --- a/src/Gui/PropertyPage.h +++ b/src/Gui/PropertyPage.h @@ -39,9 +39,9 @@ class GuiExport PropertyPage : public QWidget public: explicit PropertyPage(QWidget* parent = nullptr); - ~PropertyPage() override; + ~PropertyPage() override = default; - bool isModified(); + bool isModified() const; void setModified(bool b); void onApply(); void onCancel(); @@ -69,14 +69,14 @@ class GuiExport PreferencePage : public QWidget public: explicit PreferencePage(QWidget* parent = nullptr); - ~PreferencePage() override; + ~PreferencePage() override = default; public Q_SLOTS: virtual void loadSettings()=0; virtual void saveSettings()=0; protected: - void changeEvent(QEvent *e) override = 0; + void changeEvent(QEvent* event) override = 0; }; /** Subclass that embeds a form from a UI file. diff --git a/src/Gui/Stylesheets/Behave-dark.qss b/src/Gui/Stylesheets/Behave-dark.qss index 13ef21cb80..4c27d8f89b 100644 --- a/src/Gui/Stylesheets/Behave-dark.qss +++ b/src/Gui/Stylesheets/Behave-dark.qss @@ -1662,9 +1662,7 @@ QSint--ActionGroup QToolButton::menu-button { QSint--ActionGroup QToolButton#settingsButton, QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { - padding: 2px; padding-right: 20px; /* make way for the popup button */ - margin: 0px; } /* to give widget inside the menu same look as regular menu */ diff --git a/src/Gui/Stylesheets/Dark-blue.qss b/src/Gui/Stylesheets/Dark-blue.qss index 12f8edb93f..de33c11dfb 100644 --- a/src/Gui/Stylesheets/Dark-blue.qss +++ b/src/Gui/Stylesheets/Dark-blue.qss @@ -1629,9 +1629,7 @@ QSint--ActionGroup QToolButton::menu-button { QSint--ActionGroup QToolButton#settingsButton, QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { - padding: 2px; padding-right: 20px; /* make way for the popup button */ - margin: 0px; } /* to give widget inside the menu same look as regular menu */ diff --git a/src/Gui/Stylesheets/Dark-contrast.qss b/src/Gui/Stylesheets/Dark-contrast.qss index acf351997c..5e3e49f824 100644 --- a/src/Gui/Stylesheets/Dark-contrast.qss +++ b/src/Gui/Stylesheets/Dark-contrast.qss @@ -1629,9 +1629,7 @@ QSint--ActionGroup QToolButton::menu-button { QSint--ActionGroup QToolButton#settingsButton, QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { - padding: 2px; padding-right: 20px; /* make way for the popup button */ - margin: 0px; } /* to give widget inside the menu same look as regular menu */ diff --git a/src/Gui/Stylesheets/Dark-green.qss b/src/Gui/Stylesheets/Dark-green.qss index ed6ab3923b..b772ce1d62 100644 --- a/src/Gui/Stylesheets/Dark-green.qss +++ b/src/Gui/Stylesheets/Dark-green.qss @@ -1628,9 +1628,7 @@ QSint--ActionGroup QToolButton::menu-button { QSint--ActionGroup QToolButton#settingsButton, QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { - padding: 2px; padding-right: 20px; /* make way for the popup button */ - margin: 0px; } /* to give widget inside the menu same look as regular menu */ diff --git a/src/Gui/Stylesheets/Dark-orange.qss b/src/Gui/Stylesheets/Dark-orange.qss index 4e1432f149..bb2a7cdad9 100644 --- a/src/Gui/Stylesheets/Dark-orange.qss +++ b/src/Gui/Stylesheets/Dark-orange.qss @@ -1629,9 +1629,7 @@ QSint--ActionGroup QToolButton::menu-button { QSint--ActionGroup QToolButton#settingsButton, QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { - padding: 2px; padding-right: 20px; /* make way for the popup button */ - margin: 0px; } /* to give widget inside the menu same look as regular menu */ diff --git a/src/Gui/Stylesheets/Darker-blue.qss b/src/Gui/Stylesheets/Darker-blue.qss index 31fc761cb5..cef6ef16e5 100644 --- a/src/Gui/Stylesheets/Darker-blue.qss +++ b/src/Gui/Stylesheets/Darker-blue.qss @@ -1629,9 +1629,7 @@ QSint--ActionGroup QToolButton::menu-button { QSint--ActionGroup QToolButton#settingsButton, QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { - padding: 2px; padding-right: 20px; /* make way for the popup button */ - margin: 0px; } /* to give widget inside the menu same look as regular menu */ diff --git a/src/Gui/Stylesheets/Darker-green.qss b/src/Gui/Stylesheets/Darker-green.qss index 004d55c3e7..970445870b 100644 --- a/src/Gui/Stylesheets/Darker-green.qss +++ b/src/Gui/Stylesheets/Darker-green.qss @@ -1629,9 +1629,7 @@ QSint--ActionGroup QToolButton::menu-button { QSint--ActionGroup QToolButton#settingsButton, QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { - padding: 2px; padding-right: 20px; /* make way for the popup button */ - margin: 0px; } /* to give widget inside the menu same look as regular menu */ diff --git a/src/Gui/Stylesheets/Darker-orange.qss b/src/Gui/Stylesheets/Darker-orange.qss index e19faeb6cc..6e0b70b8cc 100644 --- a/src/Gui/Stylesheets/Darker-orange.qss +++ b/src/Gui/Stylesheets/Darker-orange.qss @@ -1623,9 +1623,7 @@ QSint--ActionGroup QToolButton::menu-button { QSint--ActionGroup QToolButton#settingsButton, QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { - padding: 2px; padding-right: 20px; /* make way for the popup button */ - margin: 0px; } /* to give widget inside the menu same look as regular menu */ diff --git a/src/Gui/Stylesheets/Light-blue.qss b/src/Gui/Stylesheets/Light-blue.qss index cbb3ee70e1..c5ac31173d 100644 --- a/src/Gui/Stylesheets/Light-blue.qss +++ b/src/Gui/Stylesheets/Light-blue.qss @@ -1626,9 +1626,7 @@ QSint--ActionGroup QToolButton::menu-button { QSint--ActionGroup QToolButton#settingsButton, QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { - padding: 2px; padding-right: 20px; /* make way for the popup button */ - margin: 0px; } /* to give widget inside the menu same look as regular menu */ diff --git a/src/Gui/Stylesheets/Light-green.qss b/src/Gui/Stylesheets/Light-green.qss index 6567b974e0..125fc5b725 100644 --- a/src/Gui/Stylesheets/Light-green.qss +++ b/src/Gui/Stylesheets/Light-green.qss @@ -1626,9 +1626,7 @@ QSint--ActionGroup QToolButton::menu-button { QSint--ActionGroup QToolButton#settingsButton, QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { - padding: 2px; padding-right: 20px; /* make way for the popup button */ - margin: 0px; } /* to give widget inside the menu same look as regular menu */ diff --git a/src/Gui/Stylesheets/Light-orange.qss b/src/Gui/Stylesheets/Light-orange.qss index 3d3eee57f2..17b718b95a 100644 --- a/src/Gui/Stylesheets/Light-orange.qss +++ b/src/Gui/Stylesheets/Light-orange.qss @@ -1626,9 +1626,7 @@ QSint--ActionGroup QToolButton::menu-button { QSint--ActionGroup QToolButton#settingsButton, QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { - padding: 2px; padding-right: 20px; /* make way for the popup button */ - margin: 0px; } /* to give widget inside the menu same look as regular menu */ diff --git a/src/Gui/Stylesheets/ProDark.qss b/src/Gui/Stylesheets/ProDark.qss index d29ae2c954..668ccadd1f 100644 --- a/src/Gui/Stylesheets/ProDark.qss +++ b/src/Gui/Stylesheets/ProDark.qss @@ -1815,9 +1815,7 @@ QSint--ActionGroup QToolButton::menu-button { QSint--ActionGroup QToolButton#settingsButton, QSint--ActionGroup QToolButton#filterButton, QSint--ActionGroup QToolButton#manualUpdate { - padding: 2px; padding-right: 20px; /* make way for the popup button */ - margin: 0px; } /* to give widget inside the menu same look as regular menu */ diff --git a/src/Gui/TaskView/TaskDialogPython.cpp b/src/Gui/TaskView/TaskDialogPython.cpp index 7d05b12411..795b7cf43a 100644 --- a/src/Gui/TaskView/TaskDialogPython.cpp +++ b/src/Gui/TaskView/TaskDialogPython.cpp @@ -544,8 +544,7 @@ TaskDialogPython::~TaskDialogPython() bool TaskDialogPython::tryLoadUiFile() { if (dlg.hasAttr(std::string("ui"))) { - UiLoader loader; - loader.setLanguageChangeEnabled(true); + auto loader = UiLoader::newInstance(); QString fn, icon; Py::String ui(dlg.getAttr(std::string("ui"))); std::string path = static_cast(ui); @@ -554,7 +553,7 @@ bool TaskDialogPython::tryLoadUiFile() QFile file(fn); QWidget* form = nullptr; if (file.open(QFile::ReadOnly)) - form = loader.load(&file, nullptr); + form = loader->load(&file, nullptr); file.close(); if (form) { appendForm(form, QPixmap(icon)); diff --git a/src/Gui/Tree.cpp b/src/Gui/Tree.cpp index f13015e066..7cf6c32644 100644 --- a/src/Gui/Tree.cpp +++ b/src/Gui/Tree.cpp @@ -33,6 +33,7 @@ # include # include # include +# include # include # include # include @@ -757,6 +758,12 @@ void TreeWidget::updateStatus(bool delay) { } void TreeWidget::_updateStatus(bool delay) { + // When running from a different thread Qt will raise a warning + // when trying to start the QTimer + if (Q_UNLIKELY(thread() != QThread::currentThread())) { + return; + } + if (!delay) { if (!ChangedObjects.empty() || !NewObjects.empty()) onUpdateStatus(); diff --git a/src/Gui/UiLoader.cpp b/src/Gui/UiLoader.cpp index 002f9affbe..3a8821017c 100644 --- a/src/Gui/UiLoader.cpp +++ b/src/Gui/UiLoader.cpp @@ -24,6 +24,7 @@ #ifndef _PreComp_ # include # include +# include # include # include # include @@ -488,10 +489,20 @@ QString QUiLoader::errorString() const UiLoader::UiLoader(QObject* parent) : QUiLoader(parent) { - // do not use the plugins for additional widgets as we don't need them and - // the application may crash under Linux (tested on Ubuntu 7.04 & 7.10). - clearPluginPaths(); this->cw = availableWidgets(); + setLanguageChangeEnabled(true); +} + +std::unique_ptr UiLoader::newInstance(QObject *parent) +{ + QCoreApplication* app = QCoreApplication::instance(); + QStringList libPaths = app->libraryPaths(); + + app->setLibraryPaths(QStringList{}); //< backup library paths, so QUiLoader won't load plugins by default + std::unique_ptr rv{new UiLoader{parent}}; + app->setLibraryPaths(libPaths); + + return rv; } UiLoader::~UiLoader() @@ -544,8 +555,8 @@ void UiLoaderPy::init_type() } UiLoaderPy::UiLoaderPy() + : loader{UiLoader::newInstance()} { - loader.setLanguageChangeEnabled(true); } UiLoaderPy::~UiLoaderPy() @@ -592,7 +603,7 @@ Py::Object UiLoaderPy::load(const Py::Tuple& args) } if (device) { - QWidget* widget = loader.load(device, parent); + QWidget* widget = loader->load(device, parent); if (widget) { wrap.loadGuiModule(); wrap.loadWidgetsModule(); @@ -613,7 +624,7 @@ Py::Object UiLoaderPy::load(const Py::Tuple& args) Py::Object UiLoaderPy::createWidget(const Py::Tuple& args) { - return wrapFromWidgetFactory(args, std::bind(&UiLoader::createWidget, &loader, + return wrapFromWidgetFactory(args, std::bind(&UiLoader::createWidget, loader.get(), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); @@ -625,7 +636,7 @@ Py::Object UiLoaderPy::addPluginPath(const Py::Tuple& args) if (wrap.loadCoreModule()) { std::string fn; if (wrap.toCString(args[0], fn)) { - loader.addPluginPath(QString::fromStdString(fn)); + loader->addPluginPath(QString::fromStdString(fn)); } } return Py::None(); @@ -633,13 +644,13 @@ Py::Object UiLoaderPy::addPluginPath(const Py::Tuple& args) Py::Object UiLoaderPy::clearPluginPaths(const Py::Tuple& /*args*/) { - loader.clearPluginPaths(); + loader->clearPluginPaths(); return Py::None(); } Py::Object UiLoaderPy::pluginPaths(const Py::Tuple& /*args*/) { - auto list = loader.pluginPaths(); + auto list = loader->pluginPaths(); Py::List py; for (const auto& it : list) { py.append(Py::String(it.toStdString())); @@ -649,7 +660,7 @@ Py::Object UiLoaderPy::pluginPaths(const Py::Tuple& /*args*/) Py::Object UiLoaderPy::availableWidgets(const Py::Tuple& /*args*/) { - auto list = loader.availableWidgets(); + auto list = loader->availableWidgets(); Py::List py; for (const auto& it : list) { py.append(Py::String(it.toStdString())); @@ -665,17 +676,17 @@ Py::Object UiLoaderPy::availableWidgets(const Py::Tuple& /*args*/) Py::Object UiLoaderPy::errorString(const Py::Tuple& /*args*/) { - return Py::String(loader.errorString().toStdString()); + return Py::String(loader->errorString().toStdString()); } Py::Object UiLoaderPy::isLanguageChangeEnabled(const Py::Tuple& /*args*/) { - return Py::Boolean(loader.isLanguageChangeEnabled()); + return Py::Boolean(loader->isLanguageChangeEnabled()); } Py::Object UiLoaderPy::setLanguageChangeEnabled(const Py::Tuple& args) { - loader.setLanguageChangeEnabled(Py::Boolean(args[0])); + loader->setLanguageChangeEnabled(Py::Boolean(args[0])); return Py::None(); } @@ -685,7 +696,7 @@ Py::Object UiLoaderPy::setWorkingDirectory(const Py::Tuple& args) if (wrap.loadCoreModule()) { std::string fn; if (wrap.toCString(args[0], fn)) { - loader.setWorkingDirectory(QString::fromStdString(fn)); + loader->setWorkingDirectory(QString::fromStdString(fn)); } } return Py::None(); @@ -693,7 +704,7 @@ Py::Object UiLoaderPy::setWorkingDirectory(const Py::Tuple& args) Py::Object UiLoaderPy::workingDirectory(const Py::Tuple& /*args*/) { - QDir dir = loader.workingDirectory(); + QDir dir = loader->workingDirectory(); QString path = dir.absolutePath(); return Py::String(path.toStdString()); } diff --git a/src/Gui/UiLoader.h b/src/Gui/UiLoader.h index 112138bd57..461fd3165b 100644 --- a/src/Gui/UiLoader.h +++ b/src/Gui/UiLoader.h @@ -34,6 +34,7 @@ #endif #include +#include QT_BEGIN_NAMESPACE @@ -106,8 +107,29 @@ private: */ class UiLoader : public QUiLoader { -public: +protected: + /** + * A protected construct for UiLoader. + * To create an instance of UiLoader @see UiLoader::newInstance() + */ explicit UiLoader(QObject* parent=nullptr); + +public: + /** + * Creates a new instance of a UiLoader. + * + * Due to its flaw the QUiLoader upon creation loads every available Qt + * designer plugin it can find in QApplication::libraryPaths(). Some of + * those plugins may perform some unexpected actions upon load which may + * interfere with FreeCAD's functionality. Only way to avoid such behaviour + * is to reset QApplication::libraryPaths, create a QUiLoader and then + * restore the libs paths. Hence need for this function to wrap + * construction. + * + * @see https://github.com/FreeCAD/FreeCAD/issues/8708 + */ + static std::unique_ptr newInstance(QObject *parent=0); + ~UiLoader() override; /** @@ -149,7 +171,7 @@ private: static PyObject *PyMake(struct _typeobject *, PyObject *, PyObject *); private: - UiLoader loader; + std::unique_ptr loader; }; } // namespace Gui diff --git a/src/Gui/WidgetFactory.cpp b/src/Gui/WidgetFactory.cpp index 298ab3b02f..6c2503b904 100644 --- a/src/Gui/WidgetFactory.cpp +++ b/src/Gui/WidgetFactory.cpp @@ -461,11 +461,10 @@ void PyResource::load(const char* name) QWidget* w=nullptr; try { - UiLoader loader; - loader.setLanguageChangeEnabled(true); + auto loader = UiLoader::newInstance(); QFile file(fn); if (file.open(QFile::ReadOnly)) - w = loader.load(&file, QApplication::activeWindow()); + w = loader->load(&file, QApplication::activeWindow()); file.close(); } catch (...) { diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index e327feb1cc..6a12f99cf7 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -78,6 +78,7 @@ SET(FemExamples_SRCS femexamples/equation_electrostatics_capacitance_two_balls.py femexamples/equation_electrostatics_electricforce_elmer_nongui6.py femexamples/equation_flow_elmer_2D.py + femexamples/equation_flow_initial_elmer_2D.py femexamples/equation_flow_turbulent_elmer_2D.py femexamples/equation_flux_elmer.py femexamples/equation_magnetodynamics_elmer.py diff --git a/src/Mod/Fem/Gui/Resources/ui/FlowVelocity.ui b/src/Mod/Fem/Gui/Resources/ui/FlowVelocity.ui index 3793c63e4f..088c802cc8 100644 --- a/src/Mod/Fem/Gui/Resources/ui/FlowVelocity.ui +++ b/src/Mod/Fem/Gui/Resources/ui/FlowVelocity.ui @@ -6,78 +6,27 @@ 0 0 - 400 - 300 + 300 + 197 Constraint Properties - - - - - Velocity x: - - - - - - - Velocity y: - - - - - - - Velocity z: - - - - - - - + + + + + false - - 1.000000000000000 - - - m/s - - - - - - unspecified - - - true + formula - - - - - - - - false - - - 1.000000000000000 - - - m/s - - - - + unspecified @@ -87,24 +36,52 @@ - - - - - - - - false - - - 1.000000000000000 - - - m/s + + + + Velocity x: - + + + + false + + + + + + + false + + + + + + + + + + + + + + false + + + formula + + + + + + + Velocity y: + + + + unspecified @@ -114,12 +91,77 @@ + + + + false + + + + + + + false + + + + + + - + + + + + + unspecified + + + true + + + + + + + false + + + formula + + + + + + + Velocity z: + + + + + + + false + + + + + + + false + + + + + + + + + - normal to boundary + Normal to boundary @@ -127,9 +169,9 @@ - Gui::InputField - QLineEdit -
Gui/InputField.h
+ Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
@@ -137,96 +179,48 @@ velocityXBox toggled(bool) - velocityXTxt - setEnabled(bool) - - - 230 - 44 - - - 230 - 18 - - - - - velocityXBox - toggled(bool) - velocityXTxt + formulaXCB setDisabled(bool) - 230 - 44 + 351 + 19 - 230 - 18 + 351 + 45 velocityYBox toggled(bool) - velocityYTxt - setEnabled(bool) - - - 347 - 53 - - - 184 - 53 - - - - - velocityYBox - toggled(bool) - velocityYTxt + formulaYCB setDisabled(bool) - 347 - 53 + 351 + 73 - 184 - 53 + 351 + 99 velocityZBox toggled(bool) - velocityZTxt - setEnabled(bool) - - - 347 - 87 - - - 184 - 87 - - - - - velocityZBox - toggled(bool) - velocityZTxt + formulaZCB setDisabled(bool) - 347 - 87 + 351 + 127 - 184 - 87 + 351 + 153 diff --git a/src/Mod/Fem/Gui/Resources/ui/InitialFlowVelocity.ui b/src/Mod/Fem/Gui/Resources/ui/InitialFlowVelocity.ui index e034b3f3f7..de26fc3c75 100644 --- a/src/Mod/Fem/Gui/Resources/ui/InitialFlowVelocity.ui +++ b/src/Mod/Fem/Gui/Resources/ui/InitialFlowVelocity.ui @@ -6,40 +6,27 @@ 0 0 - 400 - 300 + 300 + 174 Constraint Properties - - - QFormLayout::AllNonFixedFieldsGrow - - - - - Velocity x: - - - - - - - + + + + + false - - 1.000000000000000 - - - m/s + + formula - + unspecified @@ -49,31 +36,52 @@ - - - - - - Velocity y: - - - - - - - - - false - - - 1.000000000000000 - - - m/s + + + + Velocity x: - + + + + false + + + + + + + false + + + + + + + + + + + + + + false + + + formula + + + + + + + Velocity y: + + + + unspecified @@ -83,31 +91,28 @@ - - - - - - Velocity z: - - - - - - - + + false - - 1.000000000000000 + + + + + + false - m/s + - + + + + + unspecified @@ -117,15 +122,49 @@ + + + + false + + + formula + + + + + + + Velocity z: + + + + + + + false + + + + + + + false + + + + + + - Gui::InputField - QLineEdit -
Gui/InputField.h
+ Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
@@ -133,96 +172,48 @@ velocityXBox toggled(bool) - velocityXTxt - setEnabled(bool) - - - 230 - 44 - - - 230 - 18 - - - - - velocityXBox - toggled(bool) - velocityXTxt + formulaXCB setDisabled(bool) - 230 - 44 + 351 + 19 - 230 - 18 + 351 + 45 velocityYBox toggled(bool) - velocityYTxt - setEnabled(bool) - - - 347 - 53 - - - 184 - 53 - - - - - velocityYBox - toggled(bool) - velocityYTxt + formulaYCB setDisabled(bool) - 347 - 53 + 351 + 73 - 184 - 53 + 351 + 99 velocityZBox toggled(bool) - velocityZTxt - setEnabled(bool) - - - 347 - 87 - - - 184 - 87 - - - - - velocityZBox - toggled(bool) - velocityZTxt + formulaZCB setDisabled(bool) - 347 - 87 + 351 + 127 - 184 - 87 + 351 + 153 diff --git a/src/Mod/Fem/femexamples/equation_flow_elmer_2D.py b/src/Mod/Fem/femexamples/equation_flow_elmer_2D.py index 32d09e317d..5bf55721eb 100644 --- a/src/Mod/Fem/femexamples/equation_flow_elmer_2D.py +++ b/src/Mod/Fem/femexamples/equation_flow_elmer_2D.py @@ -40,7 +40,7 @@ def get_information(): "name": "Flow - Elmer 2D", "meshtype": "solid", "meshelement": "Tet10", - "constraints": ["initial pressure", "initial temperature", "initial velocity", + "constraints": ["initial pressure", "initial temperature", "temperature", "velocity"], "solvers": ["elmer"], "material": "fluid", @@ -71,19 +71,19 @@ def setup(doc=None, solvertype="elmer"): # geometric objects # the wire defining the pipe volume in 2D - p1 = Vector(400, 0, -50.000) - p2 = Vector(400, 0, -150.000) - p3 = Vector(1200, 0, -150.000) - p4 = Vector(1200, 0, 50.000) - p5 = Vector(0, 0, 50.000) - p6 = Vector(0, 0, -50.000) + p1 = Vector(400, -50.000, 0) + p2 = Vector(400, -150.000, 0) + p3 = Vector(1200, -150.000, 0) + p4 = Vector(1200, 50.000, 0) + p5 = Vector(0, 50.000, 0) + p6 = Vector(0, -50.000, 0) wire = Draft.make_wire([p1, p2, p3, p4, p5, p6], closed=True) wire.Label = "Wire" # the circle defining the heating rod pCirc = Vector(160, 0, 0) axisCirc = Vector(1, 0, 0) - placementCircle = Placement(pCirc, Rotation(axisCirc, 90)) + placementCircle = Placement(pCirc, Rotation(axisCirc, 0)) circle = Draft.make_circle(10, placement=placementCircle) circle.Label = "HeatingRod" circle.ViewObject.Visibility = False @@ -107,7 +107,6 @@ def setup(doc=None, solvertype="elmer"): doc.recompute() if FreeCAD.GuiUp: BooleanFragments.ViewObject.Transparency = 50 - BooleanFragments.ViewObject.Document.activeView().viewFront() BooleanFragments.ViewObject.Document.activeView().fitAll() # analysis @@ -119,6 +118,7 @@ def setup(doc=None, solvertype="elmer"): # solver if solvertype == "elmer": solver_obj = ObjectsFem.makeSolverElmer(doc, "SolverElmer") + solver_obj.CoordinateSystem = "Cartesian 2D" equation_flow = ObjectsFem.makeEquationFlow(doc, solver_obj) equation_heat = ObjectsFem.makeEquationHeat(doc, solver_obj) else: @@ -133,6 +133,7 @@ def setup(doc=None, solvertype="elmer"): equation_flow.IdrsParameter = 3 equation_flow.LinearIterativeMethod = "Idrs" equation_flow.LinearPreconditioning = "ILU1" + equation_flow.Variable = "Flow Solution[Velocity:2 Pressure:1]" equation_heat.Convection = "Computed" equation_heat.IdrsParameter = 3 equation_heat.LinearIterativeMethod = "Idrs" @@ -178,21 +179,12 @@ def setup(doc=None, solvertype="elmer"): # constraint inlet velocity FlowVelocity_Inlet = ObjectsFem.makeConstraintFlowVelocity(doc, "FlowVelocity_Inlet") FlowVelocity_Inlet.References = [(BooleanFragments, "Edge5")] - FlowVelocity_Inlet.NormalDirection = Vector(-1, 0, 0) - FlowVelocity_Inlet.VelocityX = 0.020 - FlowVelocity_Inlet.VelocityXEnabled = True - FlowVelocity_Inlet.VelocityYEnabled = True - FlowVelocity_Inlet.VelocityZEnabled = True + FlowVelocity_Inlet.VelocityXFormula = "Variable Coordinate 2; Real MATC \"10*(tx+50e-3)*(50e-3-tx)\"" + FlowVelocity_Inlet.VelocityXUnspecified = False + FlowVelocity_Inlet.VelocityXHasFormula = True + FlowVelocity_Inlet.VelocityYUnspecified = False analysis.addObject(FlowVelocity_Inlet) - # constraint outlet velocity - FlowVelocity_Outlet = ObjectsFem.makeConstraintFlowVelocity(doc, "FlowVelocity_Outlet") - FlowVelocity_Outlet.References = [(BooleanFragments, "Edge6")] - FlowVelocity_Outlet.NormalDirection = Vector(1, 0, 0) - FlowVelocity_Outlet.VelocityYEnabled = True - FlowVelocity_Outlet.VelocityZEnabled = True - analysis.addObject(FlowVelocity_Outlet) - # constraint wall velocity FlowVelocity_Wall = ObjectsFem.makeConstraintFlowVelocity(doc, "FlowVelocity_Wall") FlowVelocity_Wall.References = [ @@ -200,21 +192,10 @@ def setup(doc=None, solvertype="elmer"): (BooleanFragments, "Edge3"), (BooleanFragments, "Edge4"), (BooleanFragments, "Edge7")] - FlowVelocity_Wall.NormalDirection = Vector(0, 0, -1) - FlowVelocity_Wall.VelocityXEnabled = True - FlowVelocity_Wall.VelocityYEnabled = True - FlowVelocity_Wall.VelocityZEnabled = True + FlowVelocity_Wall.VelocityXUnspecified = False + FlowVelocity_Wall.VelocityYUnspecified = False analysis.addObject(FlowVelocity_Wall) - # constraint initial velocity - FlowVelocity_Initial = ObjectsFem.makeConstraintInitialFlowVelocity(doc, "FlowVelocity_Initial") - FlowVelocity_Initial.References = [(BooleanFragments, "Face2")] - FlowVelocity_Initial.NormalDirection = Vector(0, -1, 0) - FlowVelocity_Initial.VelocityXEnabled = True - FlowVelocity_Initial.VelocityYEnabled = True - FlowVelocity_Initial.VelocityZEnabled = True - analysis.addObject(FlowVelocity_Initial) - # constraint initial temperature Temperature_Initial = ObjectsFem.makeConstraintInitialTemperature(doc, "Temperature_Initial") Temperature_Initial.initialTemperature = 300.0 @@ -233,7 +214,7 @@ def setup(doc=None, solvertype="elmer"): # constraint inlet temperature Temperature_Inlet = ObjectsFem.makeConstraintTemperature(doc, "Temperature_Inlet") - Temperature_Inlet.Temperature = 350.0 + Temperature_Inlet.Temperature = 300.0 Temperature_Inlet.NormalDirection = Vector(-1, 0, 0) Temperature_Inlet.References = [(BooleanFragments, "Edge5")] analysis.addObject(Temperature_Inlet) diff --git a/src/Mod/Fem/femexamples/equation_flow_initial_elmer_2D.py b/src/Mod/Fem/femexamples/equation_flow_initial_elmer_2D.py new file mode 100644 index 0000000000..7edb3331ca --- /dev/null +++ b/src/Mod/Fem/femexamples/equation_flow_initial_elmer_2D.py @@ -0,0 +1,275 @@ +# *************************************************************************** +# * Copyright (c) 2023 Uwe Stöhr * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import sys +import FreeCAD +from FreeCAD import Placement +from FreeCAD import Rotation +from FreeCAD import Vector + +import Draft +import ObjectsFem + +from BOPTools import SplitFeatures +from . import manager +from .manager import get_meshname +from .manager import init_doc + +def get_information(): + return { + "name": "Initial Flow - Elmer 2D", + "meshtype": "solid", + "meshelement": "Tet10", + "constraints": ["initial pressure", "initial temperature", "initial velocity", + "temperature", "velocity"], + "solvers": ["elmer"], + "material": "fluid", + "equations": ["flow", "heat"] + } + +def get_explanation(header=""): + return header + """ + +To run the example from Python console use: +from femexamples.equation_flow_initial_elmer_2D import setup +setup() + +Flow and Heat equation with initial velocity - Elmer solver + +""" + +def setup(doc=None, solvertype="elmer"): + + # init FreeCAD document + if doc is None: + doc = init_doc() + + # explanation object + # just keep the following line and change text string in get_explanation method + manager.add_explanation_obj(doc, get_explanation(manager.get_header(get_information()))) + + # geometric objects + + # the wire defining the pipe volume in 2D + p1 = Vector(400, -50.000, 0) + p2 = Vector(400, -150.000, 0) + p3 = Vector(1200, -150.000, 0) + p4 = Vector(1200, 50.000, 0) + p5 = Vector(0, 50.000, 0) + p6 = Vector(0, -50.000, 0) + wire = Draft.make_wire([p1, p2, p3, p4, p5, p6], closed=True) + wire.Label = "Wire" + + # the circle defining the heating rod + pCirc = Vector(160, 0, 0) + axisCirc = Vector(1, 0, 0) + placementCircle = Placement(pCirc, Rotation(axisCirc, 0)) + circle = Draft.make_circle(10, placement=placementCircle) + circle.Label = "HeatingRod" + circle.ViewObject.Visibility = False + + # a link of the circle + circleLink = doc.addObject("App::Link", "Link-HeatingRod") + circleLink.LinkTransform = True + circleLink.LinkedObject = circle + + # cut rod from wire to get volume of fluid + cut = doc.addObject("Part::Cut", "Cut") + cut.Base = wire + cut.Tool = circleLink + cut.ViewObject.Visibility = False + + # BooleanFregments object to combine cut with rod + BooleanFragments = SplitFeatures.makeBooleanFragments(name="BooleanFragments") + BooleanFragments.Objects = [cut, circle] + + # set view + doc.recompute() + if FreeCAD.GuiUp: + BooleanFragments.ViewObject.Transparency = 50 + BooleanFragments.ViewObject.Document.activeView().fitAll() + + # analysis + analysis = ObjectsFem.makeAnalysis(doc, "Analysis") + if FreeCAD.GuiUp: + import FemGui + FemGui.setActiveAnalysis(analysis) + + # solver + if solvertype == "elmer": + solver_obj = ObjectsFem.makeSolverElmer(doc, "SolverElmer") + solver_obj.CoordinateSystem = "Cartesian 2D" + equation_flow = ObjectsFem.makeEquationFlow(doc, solver_obj) + equation_heat = ObjectsFem.makeEquationHeat(doc, solver_obj) + else: + FreeCAD.Console.PrintWarning( + "Unknown or unsupported solver type: {}. " + "No solver object was created.\n".format(solvertype) + ) + return doc + analysis.addObject(solver_obj) + + # solver settings + equation_flow.IdrsParameter = 3 + equation_flow.LinearIterativeMethod = "Idrs" + equation_flow.LinearPreconditioning = "ILU1" + equation_flow.NonlinearIterations = 20 + equation_flow.NonlinearNewtonAfterIterations = 20 + equation_flow.RelaxationFactor = 0.15 + equation_flow.Variable = "Flow Solution[Velocity:2 Pressure:1]" + equation_heat.Convection = "Computed" + equation_heat.IdrsParameter = 3 + equation_heat.LinearIterativeMethod = "Idrs" + equation_heat.LinearPreconditioning = "ILU1" + equation_heat.NonlinearIterations = 20 + equation_heat.NonlinearNewtonAfterIterations = 20 + equation_heat.Priority = 5 + equation_heat.RelaxationFactor = 0.15 + equation_heat.Stabilize = True + + # material + + # fluid + material_obj = ObjectsFem.makeMaterialFluid(doc, "Material_Fluid") + mat = material_obj.Material + mat["Name"] = "Carbon dioxide" + mat["Density"] = "1.8393 kg/m^3" + mat["DynamicViscosity"] = "14.7e-6 kg/m/s" + mat["ThermalConductivity"] = "0.016242 W/m/K" + mat["ThermalExpansionCoefficient"] = "0.00343 m/m/K" + mat["SpecificHeat"] = "0.846 kJ/kg/K" + material_obj.Material = mat + material_obj.References = [(BooleanFragments, "Face2")] + analysis.addObject(material_obj) + + # tube wall + material_obj = ObjectsFem.makeMaterialSolid(doc, "Material_Wall") + mat = material_obj.Material + mat["Name"] = "Aluminum Generic" + mat["Density"] = "2700 kg/m^3" + mat["PoissonRatio"] = "0.35" + mat["ShearModulus"] = "25.0 GPa" + mat["UltimateTensileStrength"] = "310 MPa" + mat["YoungsModulus"] = "70000 MPa" + mat["ThermalConductivity"] = "237.0 W/m/K" + mat["ThermalExpansionCoefficient"] = "23.1 µm/m/K" + mat["SpecificHeat"] = "897.0 J/kg/K" + material_obj.Material = mat + material_obj.References = [(BooleanFragments, "Face1")] + analysis.addObject(material_obj) + + # constraint inlet velocity + FlowVelocity_Inlet = ObjectsFem.makeConstraintFlowVelocity(doc, "FlowVelocity_Inlet") + FlowVelocity_Inlet.References = [(BooleanFragments, "Edge5")] + FlowVelocity_Inlet.VelocityX = "20.0 mm/s" + FlowVelocity_Inlet.VelocityXUnspecified = False + analysis.addObject(FlowVelocity_Inlet) + + # constraint wall velocity + FlowVelocity_Wall = ObjectsFem.makeConstraintFlowVelocity(doc, "FlowVelocity_Wall") + FlowVelocity_Wall.References = [ + (BooleanFragments, "Edge2"), + (BooleanFragments, "Edge3"), + (BooleanFragments, "Edge4"), + (BooleanFragments, "Edge7")] + FlowVelocity_Wall.VelocityXUnspecified = False + FlowVelocity_Wall.VelocityYUnspecified = False + analysis.addObject(FlowVelocity_Wall) + + # constraint initial velocity + FlowVelocity_Initial = ObjectsFem.makeConstraintInitialFlowVelocity(doc, "FlowVelocity_Initial") + FlowVelocity_Initial.References = [(BooleanFragments, "Face2")] + FlowVelocity_Initial.VelocityX = "20.0 mm/s" + FlowVelocity_Initial.VelocityY = "-20.0 mm/s" + FlowVelocity_Initial.VelocityXUnspecified = False + FlowVelocity_Initial.VelocityYUnspecified = False + analysis.addObject(FlowVelocity_Initial) + + # constraint initial temperature + Temperature_Initial = ObjectsFem.makeConstraintInitialTemperature(doc, "Temperature_Initial") + Temperature_Initial.initialTemperature = 300.0 + analysis.addObject(Temperature_Initial) + + # constraint wall temperature + Temperature_Wall = ObjectsFem.makeConstraintTemperature(doc, "Temperature_Wall") + Temperature_Wall.Temperature = 300.0 + Temperature_Wall.NormalDirection = Vector(0, 0, -1) + Temperature_Wall.References = [ + (BooleanFragments, "Edge2"), + (BooleanFragments, "Edge3"), + (BooleanFragments, "Edge4"), + (BooleanFragments, "Edge7")] + analysis.addObject(Temperature_Wall) + + # constraint inlet temperature + Temperature_Inlet = ObjectsFem.makeConstraintTemperature(doc, "Temperature_Inlet") + Temperature_Inlet.Temperature = 300.0 + Temperature_Inlet.NormalDirection = Vector(-1, 0, 0) + Temperature_Inlet.References = [(BooleanFragments, "Edge5")] + analysis.addObject(Temperature_Inlet) + + # constraint heating rod temperature + Temperature_HeatingRod = ObjectsFem.makeConstraintTemperature(doc, "Temperature_HeatingRod") + Temperature_HeatingRod.Temperature = 373.0 + Temperature_HeatingRod.NormalDirection = Vector(0, -1, 0) + Temperature_HeatingRod.References = [(BooleanFragments, "Edge1")] + analysis.addObject(Temperature_HeatingRod) + + # constraint initial pressure + Pressure_Initial = ObjectsFem.makeConstraintInitialPressure(doc, "Pressure_Initial") + Pressure_Initial.Pressure = "100.0 kPa" + Pressure_Initial.NormalDirection = Vector(0, -1, 0) + Pressure_Initial.References = [(BooleanFragments, "Face2")] + analysis.addObject(Pressure_Initial) + + # mesh + femmesh_obj = analysis.addObject(ObjectsFem.makeMeshGmsh(doc, get_meshname()))[0] + femmesh_obj.Part = BooleanFragments + femmesh_obj.ElementOrder = "1st" + femmesh_obj.CharacteristicLengthMax = "4 mm" + femmesh_obj.ViewObject.Visibility = False + + # mesh_region + mesh_region = ObjectsFem.makeMeshRegion(doc, femmesh_obj, name="MeshRegion") + mesh_region.CharacteristicLength = "2 mm" + mesh_region.References = [ + (BooleanFragments, "Edge1"), + (BooleanFragments, "Vertex2"), + (BooleanFragments, "Vertex4"), + (BooleanFragments, "Vertex6")] + mesh_region.ViewObject.Visibility = False + + # generate the mesh + from femmesh import gmshtools + gmsh_mesh = gmshtools.GmshTools(femmesh_obj, analysis) + try: + error = gmsh_mesh.create_mesh() + except Exception: + error = sys.exc_info()[1] + FreeCAD.Console.PrintError( + "Unexpected error when creating mesh: {}\n" + .format(error) + ) + + doc.recompute() + return doc diff --git a/src/Mod/Fem/femexamples/equation_flow_turbulent_elmer_2D.py b/src/Mod/Fem/femexamples/equation_flow_turbulent_elmer_2D.py index e21510bc75..454509b974 100644 --- a/src/Mod/Fem/femexamples/equation_flow_turbulent_elmer_2D.py +++ b/src/Mod/Fem/femexamples/equation_flow_turbulent_elmer_2D.py @@ -40,7 +40,7 @@ def get_information(): "name": "Turbulent Flow - Elmer 2D", "meshtype": "solid", "meshelement": "Tet10", - "constraints": ["initial pressure", "initial temperature", "initial velocity", + "constraints": ["initial pressure", "initial temperature", "temperature", "velocity"], "solvers": ["elmer"], "material": "fluid", @@ -54,7 +54,7 @@ To run the example from Python console use: from femexamples.equation_flow_turbulent_elmer_2D import setup setup() -Flow and Heat equation - Elmer solver +Flow and Heat equation in turbulent flow - Elmer solver """ @@ -71,19 +71,19 @@ def setup(doc=None, solvertype="elmer"): # geometric objects # the wire defining the pipe volume in 2D - p1 = Vector(400, 0, -50.000) - p2 = Vector(400, 0, -150.000) - p3 = Vector(1200, 0, -150.000) - p4 = Vector(1200, 0, 50.000) - p5 = Vector(0, 0, 50.000) - p6 = Vector(0, 0, -50.000) + p1 = Vector(400, -50.000, 0) + p2 = Vector(400, -150.000, 0) + p3 = Vector(1200, -150.000, 0) + p4 = Vector(1200, 50.000, 0) + p5 = Vector(0, 50.000, 0) + p6 = Vector(0, -50.000, 0) wire = Draft.make_wire([p1, p2, p3, p4, p5, p6], closed=True) wire.Label = "Wire" # the circle defining the heating rod pCirc = Vector(160, 0, 0) axisCirc = Vector(1, 0, 0) - placementCircle = Placement(pCirc, Rotation(axisCirc, 90)) + placementCircle = Placement(pCirc, Rotation(axisCirc, 0)) circle = Draft.make_circle(10, placement=placementCircle) circle.Label = "HeatingRod" circle.ViewObject.Visibility = False @@ -107,7 +107,6 @@ def setup(doc=None, solvertype="elmer"): doc.recompute() if FreeCAD.GuiUp: BooleanFragments.ViewObject.Transparency = 50 - BooleanFragments.ViewObject.Document.activeView().viewFront() BooleanFragments.ViewObject.Document.activeView().fitAll() # analysis @@ -119,6 +118,7 @@ def setup(doc=None, solvertype="elmer"): # solver if solvertype == "elmer": solver_obj = ObjectsFem.makeSolverElmer(doc, "SolverElmer") + solver_obj.CoordinateSystem = "Cartesian 2D" equation_flow = ObjectsFem.makeEquationFlow(doc, solver_obj) equation_heat = ObjectsFem.makeEquationHeat(doc, solver_obj) else: @@ -131,22 +131,21 @@ def setup(doc=None, solvertype="elmer"): # solver settings equation_flow.IdrsParameter = 3 - equation_flow.LinearIterations = 250 equation_flow.LinearIterativeMethod = "Idrs" equation_flow.LinearPreconditioning = "ILU1" equation_flow.setExpression("LinearTolerance", "1e-6") - equation_flow.NonlinearIterations = 30 - equation_flow.NonlinearNewtonAfterIterations = 30 - equation_flow.setExpression("NonlinearTolerance", "1e-4") + equation_flow.NonlinearIterations = 40 + equation_flow.NonlinearNewtonAfterIterations = 40 equation_flow.RelaxationFactor = 0.1 + equation_flow.setExpression("NonlinearTolerance", "1e-4") + equation_flow.Variable = "Flow Solution[Velocity:2 Pressure:1]" equation_heat.Convection = "Computed" equation_heat.IdrsParameter = 3 - equation_heat.LinearIterations = 250 equation_heat.LinearIterativeMethod = "Idrs" equation_heat.LinearPreconditioning = "ILU1" equation_heat.setExpression("LinearTolerance", "1e-6") - equation_heat.NonlinearIterations = 30 - equation_heat.NonlinearNewtonAfterIterations = 30 + equation_heat.NonlinearIterations = 40 + equation_heat.NonlinearNewtonAfterIterations = 40 equation_heat.setExpression("NonlinearTolerance", "1e-4") equation_heat.Priority = 5 equation_heat.RelaxationFactor = 0.1 @@ -186,21 +185,12 @@ def setup(doc=None, solvertype="elmer"): # constraint inlet velocity FlowVelocity_Inlet = ObjectsFem.makeConstraintFlowVelocity(doc, "FlowVelocity_Inlet") FlowVelocity_Inlet.References = [(BooleanFragments, "Edge5")] - FlowVelocity_Inlet.NormalDirection = Vector(-1, 0, 0) - FlowVelocity_Inlet.VelocityX = 0.020 - FlowVelocity_Inlet.VelocityXEnabled = True - FlowVelocity_Inlet.VelocityYEnabled = True - FlowVelocity_Inlet.VelocityZEnabled = True + FlowVelocity_Inlet.VelocityXFormula = "Variable Coordinate 2; Real MATC \"10*(tx+50e-3)*(50e-3-tx)\"" + FlowVelocity_Inlet.VelocityXUnspecified = False + FlowVelocity_Inlet.VelocityXHasFormula = True + FlowVelocity_Inlet.VelocityYUnspecified = False analysis.addObject(FlowVelocity_Inlet) - # constraint outlet velocity - FlowVelocity_Outlet = ObjectsFem.makeConstraintFlowVelocity(doc, "FlowVelocity_Outlet") - FlowVelocity_Outlet.References = [(BooleanFragments, "Edge6")] - FlowVelocity_Outlet.NormalDirection = Vector(1, 0, 0) - FlowVelocity_Outlet.VelocityYEnabled = True - FlowVelocity_Outlet.VelocityZEnabled = True - analysis.addObject(FlowVelocity_Outlet) - # constraint wall velocity FlowVelocity_Wall = ObjectsFem.makeConstraintFlowVelocity(doc, "FlowVelocity_Wall") FlowVelocity_Wall.References = [ @@ -208,21 +198,10 @@ def setup(doc=None, solvertype="elmer"): (BooleanFragments, "Edge3"), (BooleanFragments, "Edge4"), (BooleanFragments, "Edge7")] - FlowVelocity_Wall.NormalDirection = Vector(0, 0, -1) - FlowVelocity_Wall.VelocityXEnabled = True - FlowVelocity_Wall.VelocityYEnabled = True - FlowVelocity_Wall.VelocityZEnabled = True + FlowVelocity_Wall.VelocityXUnspecified = False + FlowVelocity_Wall.VelocityYUnspecified = False analysis.addObject(FlowVelocity_Wall) - # constraint initial velocity - FlowVelocity_Initial = ObjectsFem.makeConstraintInitialFlowVelocity(doc, "FlowVelocity_Initial") - FlowVelocity_Initial.References = [(BooleanFragments, "Face2")] - FlowVelocity_Initial.NormalDirection = Vector(0, -1, 0) - FlowVelocity_Initial.VelocityXEnabled = True - FlowVelocity_Initial.VelocityYEnabled = True - FlowVelocity_Initial.VelocityZEnabled = True - analysis.addObject(FlowVelocity_Initial) - # constraint initial temperature Temperature_Initial = ObjectsFem.makeConstraintInitialTemperature(doc, "Temperature_Initial") Temperature_Initial.initialTemperature = 300.0 diff --git a/src/Mod/Fem/femexamples/manager.py b/src/Mod/Fem/femexamples/manager.py index a0eda86964..a23316e7e8 100644 --- a/src/Mod/Fem/femexamples/manager.py +++ b/src/Mod/Fem/femexamples/manager.py @@ -71,6 +71,7 @@ def run_all(): run_example("equation_electrostatics_capacitance_two_balls", run_solver=True) run_example("equation_electrostatics_electricforce_elmer_nongui6", run_solver=True) run_example("equation_flow_elmer_2D", run_solver=True) + run_example("equation_flow_initial_elmer_2D", run_solver=True) run_example("equation_flow_turbulent_elmer_2D", run_solver=True) run_example("equation_flux_elmer", run_solver=True) run_example("equation_magnetodynamics_elmer", run_solver=True) @@ -109,6 +110,7 @@ def setup_all(): run_example("equation_electrostatics_capacitance_two_balls") run_example("equation_electrostatics_electricforce_elmer_nongui6") run_example("equation_flow_elmer_2D") + run_example("equation_flow_initial_elmer_2D") run_example("equation_flow_turbulent_elmer_2D") run_example("equation_flux_elmer") run_example("equation_magnetodynamics_elmer") diff --git a/src/Mod/Fem/femguiutils/selection_widgets.py b/src/Mod/Fem/femguiutils/selection_widgets.py index 12986efacc..e9db4068ab 100644 --- a/src/Mod/Fem/femguiutils/selection_widgets.py +++ b/src/Mod/Fem/femguiutils/selection_widgets.py @@ -599,7 +599,7 @@ class FemSelectionObserver: def __init__(self, parseSelectionFunction, print_message=""): self.parseSelectionFunction = parseSelectionFunction FreeCADGui.Selection.addObserver(self) - FreeCAD.Console.PrintMessage(print_message + "!\n") + #FreeCAD.Console.PrintMessage(print_message + "!\n") def addSelection(self, docName, objName, sub, pos): selected_object = FreeCAD.getDocument(docName).getObject(objName) # get the obj objName diff --git a/src/Mod/Fem/femobjects/constraint_flowvelocity.py b/src/Mod/Fem/femobjects/constraint_flowvelocity.py index 5625439fa7..8243c3a622 100644 --- a/src/Mod/Fem/femobjects/constraint_flowvelocity.py +++ b/src/Mod/Fem/femobjects/constraint_flowvelocity.py @@ -40,41 +40,83 @@ class ConstraintFlowVelocity(base_fempythonobject.BaseFemPythonObject): def __init__(self, obj): super(ConstraintFlowVelocity, self).__init__(obj) obj.addProperty( - "App::PropertyFloat", + "App::PropertyVelocity", "VelocityX", "Parameter", "Velocity in x-direction" ) + obj.addProperty( + "App::PropertyString", + "VelocityXFormula", + "Parameter", + "Velocity formula in x-direction" + ) obj.addProperty( "App::PropertyBool", - "VelocityXEnabled", + "VelocityXUnspecified", "Parameter", "Use velocity in x-direction" ) + obj.VelocityXUnspecified = True obj.addProperty( - "App::PropertyFloat", + "App::PropertyBool", + "VelocityXHasFormula", + "Parameter", + "Use formula for velocity in x-direction" + ) + + obj.addProperty( + "App::PropertyVelocity", "VelocityY", "Parameter", "Velocity in y-direction" ) + obj.addProperty( + "App::PropertyString", + "VelocityYFormula", + "Parameter", + "Velocity formula in y-direction" + ) obj.addProperty( "App::PropertyBool", - "VelocityYEnabled", + "VelocityYUnspecified", "Parameter", "Use velocity in y-direction" ) + obj.VelocityYUnspecified = True obj.addProperty( - "App::PropertyFloat", + "App::PropertyBool", + "VelocityYHasFormula", + "Parameter", + "Use formula for velocity in y-direction" + ) + + obj.addProperty( + "App::PropertyVelocity", "VelocityZ", "Parameter", "Velocity in z-direction" ) + obj.addProperty( + "App::PropertyString", + "VelocityZFormula", + "Parameter", + "Velocity formula in z-direction" + ) obj.addProperty( "App::PropertyBool", - "VelocityZEnabled", + "VelocityZUnspecified", "Parameter", "Use velocity in z-direction" ) + obj.VelocityZUnspecified = True + obj.addProperty( + "App::PropertyBool", + "VelocityZHasFormula", + "Parameter", + "Use formula for velocity in z-direction" + ) + obj.addProperty( "App::PropertyBool", "NormalToBoundary", diff --git a/src/Mod/Fem/femobjects/constraint_initialflowvelocity.py b/src/Mod/Fem/femobjects/constraint_initialflowvelocity.py index 0598767641..097e390679 100644 --- a/src/Mod/Fem/femobjects/constraint_initialflowvelocity.py +++ b/src/Mod/Fem/femobjects/constraint_initialflowvelocity.py @@ -40,38 +40,79 @@ class ConstraintInitialFlowVelocity(base_fempythonobject.BaseFemPythonObject): def __init__(self, obj): super(ConstraintInitialFlowVelocity, self).__init__(obj) obj.addProperty( - "App::PropertyFloat", + "App::PropertyVelocity", "VelocityX", "Parameter", "Velocity in x-direction" ) + obj.addProperty( + "App::PropertyString", + "VelocityXFormula", + "Parameter", + "Velocity formula in x-direction" + ) obj.addProperty( "App::PropertyBool", - "VelocityXEnabled", + "VelocityXUnspecified", "Parameter", "Use velocity in x-direction" ) + obj.VelocityXUnspecified = True obj.addProperty( - "App::PropertyFloat", + "App::PropertyBool", + "VelocityXHasFormula", + "Parameter", + "Use formula for velocity in x-direction" + ) + + obj.addProperty( + "App::PropertyVelocity", "VelocityY", "Parameter", "Velocity in y-direction" ) + obj.addProperty( + "App::PropertyString", + "VelocityYFormula", + "Parameter", + "Velocity formula in y-direction" + ) obj.addProperty( "App::PropertyBool", - "VelocityYEnabled", + "VelocityYUnspecified", "Parameter", "Use velocity in y-direction" ) + obj.VelocityYUnspecified = True obj.addProperty( - "App::PropertyFloat", + "App::PropertyBool", + "VelocityYHasFormula", + "Parameter", + "Use formula for velocity in y-direction" + ) + + obj.addProperty( + "App::PropertyVelocity", "VelocityZ", "Parameter", "Velocity in z-direction" ) + obj.addProperty( + "App::PropertyString", + "VelocityZFormula", + "Parameter", + "Velocity formula in z-direction" + ) obj.addProperty( "App::PropertyBool", - "VelocityZEnabled", + "VelocityZUnspecified", "Parameter", "Use velocity in z-direction" ) + obj.VelocityZUnspecified = True + obj.addProperty( + "App::PropertyBool", + "VelocityZHasFormula", + "Parameter", + "Use formula for velocity in z-direction" + ) diff --git a/src/Mod/Fem/femsolver/elmer/equations/flow_writer.py b/src/Mod/Fem/femsolver/elmer/equations/flow_writer.py index 796d8adcd6..176706a56d 100644 --- a/src/Mod/Fem/femsolver/elmer/equations/flow_writer.py +++ b/src/Mod/Fem/femsolver/elmer/equations/flow_writer.py @@ -193,14 +193,23 @@ class Flowwriter: def _outputInitialVelocity(self, obj, name): # flow only makes sense for fluid material if self.write.isBodyMaterialFluid(name): - if obj.VelocityXEnabled: - velocity = self.write.getFromUi(obj.VelocityX, "m/s", "L/T") + if not obj.VelocityXUnspecified: + if not obj.VelocityXHasFormula: + velocity = float(obj.VelocityX.getValueAs("m/s")) + else: + velocity = obj.VelocityXFormula self.write.initial(name, "Velocity 1", velocity) - if obj.VelocityYEnabled: - velocity = self.write.getFromUi(obj.VelocityY, "m/s", "L/T") + if not obj.VelocityYUnspecified: + if not obj.VelocityYHasFormula: + velocity = float(obj.VelocityY.getValueAs("m/s")) + else: + velocity = obj.VelocityYFormula self.write.initial(name, "Velocity 2", velocity) - if obj.VelocityZEnabled: - velocity = self.write.getFromUi(obj.VelocityZ, "m/s", "L/T") + if not obj.VelocityZUnspecified: + if not obj.VelocityZHasFormula: + velocity = float(obj.VelocityZ.getValueAs("m/s")) + else: + velocity = obj.VelocityZFormula self.write.initial(name, "Velocity 3", velocity) def handleFlowInitialVelocity(self, bodies): @@ -227,14 +236,23 @@ class Flowwriter: for obj in self.write.getMember("Fem::ConstraintFlowVelocity"): if obj.References: for name in obj.References[0][1]: - if obj.VelocityXEnabled: - velocity = self.write.getFromUi(obj.VelocityX, "m/s", "L/T") + if not obj.VelocityXUnspecified: + if not obj.VelocityXHasFormula: + velocity = float(obj.VelocityX.getValueAs("m/s")) + else: + velocity = obj.VelocityXFormula self.write.boundary(name, "Velocity 1", velocity) - if obj.VelocityYEnabled: - velocity = self.write.getFromUi(obj.VelocityY, "m/s", "L/T") + if not obj.VelocityYUnspecified: + if not obj.VelocityYHasFormula: + velocity = float(obj.VelocityY.getValueAs("m/s")) + else: + velocity = obj.VelocityYFormula self.write.boundary(name, "Velocity 2", velocity) - if obj.VelocityZEnabled: - velocity = self.write.getFromUi(obj.VelocityZ, "m/s", "L/T") + if not obj.VelocityZUnspecified: + if not obj.VelocityZHasFormula: + velocity = float(obj.VelocityZ.getValueAs("m/s")) + else: + velocity = obj.VelocityZFormula self.write.boundary(name, "Velocity 3", velocity) if obj.NormalToBoundary: self.write.boundary(name, "Normal-Tangential Velocity", True) diff --git a/src/Mod/Fem/femsolver/elmer/equations/nonlinear.py b/src/Mod/Fem/femsolver/elmer/equations/nonlinear.py index a0229a076f..2498ade7b2 100644 --- a/src/Mod/Fem/femsolver/elmer/equations/nonlinear.py +++ b/src/Mod/Fem/femsolver/elmer/equations/nonlinear.py @@ -76,7 +76,7 @@ class Proxy(linear.Proxy): ) ) - obj.NonlinearIterations = (500, 1, int(1e6), 50) + obj.NonlinearIterations = (500, 1, int(1e6), 10) obj.NonlinearNewtonAfterIterations = (3, 1, 100, 1) # for small numbers we must set an expression because we don't have a UI, # the user has to view and edit the tolerance via the property editor and diff --git a/src/Mod/Fem/femsolver/elmer/sifio.py b/src/Mod/Fem/femsolver/elmer/sifio.py index 3bce29ce74..789258e61b 100644 --- a/src/Mod/Fem/femsolver/elmer/sifio.py +++ b/src/Mod/Fem/femsolver/elmer/sifio.py @@ -77,6 +77,7 @@ _TYPE_INTEGER = "Integer" _TYPE_LOGICAL = "Logical" _TYPE_STRING = "String" _TYPE_FILE = "File" +_TYPE_VARIABLE = "Variable" WARN = "\"Warn\"" IGNORE = "\"Ignore\"" @@ -357,9 +358,20 @@ class _Writer(object): self._stream.write(_WHITESPACE) self._stream.write("=") self._stream.write(_WHITESPACE) - self._stream.write(attrType) + # check if we have a variable string + if attrType is _TYPE_STRING: + if data.startswith('Variable'): + attrType = _TYPE_VARIABLE + if attrType is not _TYPE_VARIABLE: + self._stream.write(attrType) self._stream.write(_WHITESPACE) - self._stream.write(self._preprocess(data, type(data))) + output = self._preprocess(data, type(data)) + # in case of a variable the output must be without the quatoation marks + if attrType is _TYPE_VARIABLE: + output = output.lstrip('\"') + # we cannot use rstrip because there are two subsequent " at the end + output = output[:-1] + self._stream.write(output) def _writeArrAttr(self, key, data): attrType = self._getAttrTypeArr(data) @@ -369,10 +381,21 @@ class _Writer(object): self._stream.write(_WHITESPACE) self._stream.write("=") self._stream.write(_WHITESPACE) - self._stream.write(attrType) + # check if we have a variable string + if attrType is _TYPE_STRING: + if data.startswith('Variable'): + attrType = _TYPE_VARIABLE + if attrType is not _TYPE_VARIABLE: + self._stream.write(attrType) for val in data: self._stream.write(_WHITESPACE) - self._stream.write(self._preprocess(val, type(val))) + output = self._preprocess(val, type(val)) + # in case of a variable the output must be without the quatoation marks + if attrType is _TYPE_VARIABLE: + output = output.lstrip('\"') + # we cannot use rstrip because there are two subsequent " at the end + output = output[:-1] + self._stream.write(output) def _writeFileAttr(self, key, data): self._stream.write(_INDENT) diff --git a/src/Mod/Fem/femtaskpanels/task_constraint_flowvelocity.py b/src/Mod/Fem/femtaskpanels/task_constraint_flowvelocity.py index 7e98729b47..41f2d8d072 100644 --- a/src/Mod/Fem/femtaskpanels/task_constraint_flowvelocity.py +++ b/src/Mod/Fem/femtaskpanels/task_constraint_flowvelocity.py @@ -29,9 +29,10 @@ __url__ = "https://www.freecadweb.org" # \ingroup FEM # \brief task panel for constraint flow velocity object +from PySide import QtCore + import FreeCAD import FreeCADGui -from FreeCAD import Units from femguiutils import selection_widgets from femtools import femutils @@ -46,8 +47,7 @@ class _TaskPanel(object): self._paramWidget = FreeCADGui.PySideUic.loadUi( FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/FlowVelocity.ui" ) - self._initParamWidget() - + # geometry selection widget # start with Solid in list! self._selectionWidget = selection_widgets.GeometryElementsSelection( @@ -70,6 +70,96 @@ class _TaskPanel(object): self._partVisible = None self._meshVisible = None + # connect unspecified option + QtCore.QObject.connect( + self._paramWidget.velocityXBox, + QtCore.SIGNAL("toggled(bool)"), + self._velocityXEnable + ) + QtCore.QObject.connect( + self._paramWidget.velocityYBox, + QtCore.SIGNAL("toggled(bool)"), + self._velocityYEnable + ) + QtCore.QObject.connect( + self._paramWidget.velocityZBox, + QtCore.SIGNAL("toggled(bool)"), + self._velocityZEnable + ) + + # connect formula option + QtCore.QObject.connect( + self._paramWidget.formulaXCB, + QtCore.SIGNAL("toggled(bool)"), + self._formulaXEnable + ) + QtCore.QObject.connect( + self._paramWidget.formulaYCB, + QtCore.SIGNAL("toggled(bool)"), + self._formulaYEnable + ) + QtCore.QObject.connect( + self._paramWidget.formulaZCB, + QtCore.SIGNAL("toggled(bool)"), + self._formulaZEnable + ) + + self._initParamWidget() + + def _velocityXEnable(self, toggled): + if toggled: + self._paramWidget.formulaX.setDisabled(toggled) + self._paramWidget.velocityX.setDisabled(toggled) + else: + if self._paramWidget.formulaXCB.isChecked(): + self._paramWidget.formulaX.setDisabled(toggled) + else: + self._paramWidget.velocityX.setDisabled(toggled) + + def _velocityYEnable(self, toggled): + if toggled: + self._paramWidget.formulaY.setDisabled(toggled) + self._paramWidget.velocityY.setDisabled(toggled) + else: + if self._paramWidget.formulaYCB.isChecked(): + self._paramWidget.formulaY.setDisabled(toggled) + else: + self._paramWidget.velocityY.setDisabled(toggled) + + def _velocityZEnable(self, toggled): + if toggled: + self._paramWidget.formulaZ.setDisabled(toggled) + self._paramWidget.velocityZ.setDisabled(toggled) + else: + if self._paramWidget.formulaZCB.isChecked(): + self._paramWidget.formulaZ.setDisabled(toggled) + else: + self._paramWidget.velocityZ.setDisabled(toggled) + + def _formulaXEnable(self, toggled): + FreeCAD.Console.PrintMessage("_formulaXEnable\n") + if self._paramWidget.velocityXBox.isChecked(): + FreeCAD.Console.PrintMessage("velocityXBox isChecked\n") + return + else: + FreeCAD.Console.PrintMessage("velocityXBox not checked\n") + self._paramWidget.formulaX.setEnabled(toggled) + self._paramWidget.velocityX.setDisabled(toggled) + + def _formulaYEnable(self, toggled): + if self._paramWidget.velocityYBox.isChecked(): + return + else: + self._paramWidget.formulaY.setEnabled(toggled) + self._paramWidget.velocityY.setDisabled(toggled) + + def _formulaZEnable(self, toggled): + if self._paramWidget.velocitZXBox.isChecked(): + return + else: + self._paramWidget.formulaZ.setEnabled(toggled) + self._paramWidget.velocityZ.setDisabled(toggled) + def open(self): if self._mesh is not None and self._part is not None: self._meshVisible = self._mesh.ViewObject.isVisible() @@ -104,36 +194,80 @@ class _TaskPanel(object): def _initParamWidget(self): unit = "m/s" - self._paramWidget.velocityXTxt.setText( - str(self._obj.VelocityX) + unit) - self._paramWidget.velocityYTxt.setText( - str(self._obj.VelocityY) + unit) - self._paramWidget.velocityZTxt.setText( - str(self._obj.VelocityZ) + unit) + self._paramWidget.velocityX.setProperty('unit', unit) + self._paramWidget.velocityY.setProperty('unit', unit) + self._paramWidget.velocityZ.setProperty('unit', unit) + + self._paramWidget.velocityX.setProperty( + 'value', self._obj.VelocityX) + FreeCADGui.ExpressionBinding( + self._paramWidget.velocityX).bind(self._obj, "VelocityX") self._paramWidget.velocityXBox.setChecked( - not self._obj.VelocityXEnabled) + self._obj.VelocityXUnspecified) + self._paramWidget.formulaX.setText(self._obj.VelocityXFormula) + self._paramWidget.formulaXCB.setChecked( + self._obj.VelocityXHasFormula) + + self._paramWidget.velocityY.setProperty( + 'value', self._obj.VelocityY) + FreeCADGui.ExpressionBinding( + self._paramWidget.velocityY).bind(self._obj, "VelocityY") self._paramWidget.velocityYBox.setChecked( - not self._obj.VelocityYEnabled) + self._obj.VelocityYUnspecified) + self._paramWidget.formulaY.setText(self._obj.VelocityYFormula) + self._paramWidget.formulaYCB.setChecked( + self._obj.VelocityYHasFormula) + + self._paramWidget.velocityZ.setProperty( + 'value', self._obj.VelocityZ) + FreeCADGui.ExpressionBinding( + self._paramWidget.velocityZ).bind(self._obj, "VelocityZ") self._paramWidget.velocityZBox.setChecked( - not self._obj.VelocityZEnabled) + self._obj.VelocityZUnspecified) + self._paramWidget.formulaZ.setText(self._obj.VelocityZFormula) + self._paramWidget.formulaZCB.setChecked( + self._obj.VelocityZHasFormula) + self._paramWidget.normalBox.setChecked( self._obj.NormalToBoundary) + def _applyVelocityChanges(self, enabledBox, velocityQSB): + enabled = enabledBox.isChecked() + velocity = None + try: + velocity = velocityQSB.property('value') + except ValueError: + FreeCAD.Console.PrintMessage( + "Wrong input. Not recognised input: '{}' " + "Velocity has not been set.\n".format(velocityQSB.text()) + ) + velocity = '0.0 m/s' + return enabled, velocity + def _applyWidgetChanges(self): - unit = "m/s" - self._obj.VelocityXEnabled = \ - not self._paramWidget.velocityXBox.isChecked() - if self._obj.VelocityXEnabled: - quantity = Units.Quantity(self._paramWidget.velocityXTxt.text()) - self._obj.VelocityX = quantity.getValueAs(unit).Value - self._obj.VelocityYEnabled = \ - not self._paramWidget.velocityYBox.isChecked() - if self._obj.VelocityYEnabled: - quantity = Units.Quantity(self._paramWidget.velocityYTxt.text()) - self._obj.VelocityY = quantity.getValueAs(unit).Value - self._obj.VelocityZEnabled = \ - not self._paramWidget.velocityZBox.isChecked() - if self._obj.VelocityZEnabled: - quantity = Units.Quantity(self._paramWidget.velocityZTxt.text()) - self._obj.VelocityZ = quantity.getValueAs(unit).Value + # apply the velocities and their enabled state + self._obj.VelocityXUnspecified, self._obj.VelocityX = \ + self._applyVelocityChanges( + self._paramWidget.velocityXBox, + self._paramWidget.velocityX + ) + self._obj.VelocityXHasFormula = self._paramWidget.formulaXCB.isChecked() + self._obj.VelocityXFormula = self._paramWidget.formulaX.text() + + self._obj.VelocityYUnspecified, self._obj.VelocityY = \ + self._applyVelocityChanges( + self._paramWidget.velocityYBox, + self._paramWidget.velocityY + ) + self._obj.VelocityYHasFormula = self._paramWidget.formulaYCB.isChecked() + self._obj.VelocityYFormula = self._paramWidget.formulaY.text() + + self._obj.VelocityZUnspecified, self._obj.VelocityZ = \ + self._applyVelocityChanges( + self._paramWidget.velocityZBox, + self._paramWidget.velocityZ + ) + self._obj.VelocityZHasFormula = self._paramWidget.formulaZCB.isChecked() + self._obj.VelocityZFormula = self._paramWidget.formulaZ.text() + self._obj.NormalToBoundary = self._paramWidget.normalBox.isChecked() diff --git a/src/Mod/Fem/femtaskpanels/task_constraint_initialflowvelocity.py b/src/Mod/Fem/femtaskpanels/task_constraint_initialflowvelocity.py index 91fafc536a..dcae4622f8 100644 --- a/src/Mod/Fem/femtaskpanels/task_constraint_initialflowvelocity.py +++ b/src/Mod/Fem/femtaskpanels/task_constraint_initialflowvelocity.py @@ -30,6 +30,8 @@ __url__ = "https://www.freecadweb.org" # \ingroup FEM # \brief task panel for constraint initial flow velocity object +from PySide import QtCore + import FreeCAD import FreeCADGui from FreeCAD import Units @@ -46,7 +48,6 @@ class _TaskPanel(object): self._paramWidget = FreeCADGui.PySideUic.loadUi( FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/InitialFlowVelocity.ui") - self._initParamWidget() # geometry selection widget # start with Solid in list! @@ -70,6 +71,96 @@ class _TaskPanel(object): self._partVisible = None self._meshVisible = None + # connect unspecified option + QtCore.QObject.connect( + self._paramWidget.velocityXBox, + QtCore.SIGNAL("toggled(bool)"), + self._velocityXEnable + ) + QtCore.QObject.connect( + self._paramWidget.velocityYBox, + QtCore.SIGNAL("toggled(bool)"), + self._velocityYEnable + ) + QtCore.QObject.connect( + self._paramWidget.velocityZBox, + QtCore.SIGNAL("toggled(bool)"), + self._velocityZEnable + ) + + # connect formula option + QtCore.QObject.connect( + self._paramWidget.formulaXCB, + QtCore.SIGNAL("toggled(bool)"), + self._formulaXEnable + ) + QtCore.QObject.connect( + self._paramWidget.formulaYCB, + QtCore.SIGNAL("toggled(bool)"), + self._formulaYEnable + ) + QtCore.QObject.connect( + self._paramWidget.formulaZCB, + QtCore.SIGNAL("toggled(bool)"), + self._formulaZEnable + ) + + self._initParamWidget() + + def _velocityXEnable(self, toggled): + if toggled: + self._paramWidget.formulaX.setDisabled(toggled) + self._paramWidget.velocityX.setDisabled(toggled) + else: + if self._paramWidget.formulaXCB.isChecked(): + self._paramWidget.formulaX.setDisabled(toggled) + else: + self._paramWidget.velocityX.setDisabled(toggled) + + def _velocityYEnable(self, toggled): + if toggled: + self._paramWidget.formulaY.setDisabled(toggled) + self._paramWidget.velocityY.setDisabled(toggled) + else: + if self._paramWidget.formulaYCB.isChecked(): + self._paramWidget.formulaY.setDisabled(toggled) + else: + self._paramWidget.velocityY.setDisabled(toggled) + + def _velocityZEnable(self, toggled): + if toggled: + self._paramWidget.formulaZ.setDisabled(toggled) + self._paramWidget.velocityZ.setDisabled(toggled) + else: + if self._paramWidget.formulaZCB.isChecked(): + self._paramWidget.formulaZ.setDisabled(toggled) + else: + self._paramWidget.velocityZ.setDisabled(toggled) + + def _formulaXEnable(self, toggled): + FreeCAD.Console.PrintMessage("_formulaXEnable\n") + if self._paramWidget.velocityXBox.isChecked(): + FreeCAD.Console.PrintMessage("velocityXBox isChecked\n") + return + else: + FreeCAD.Console.PrintMessage("velocityXBox not checked\n") + self._paramWidget.formulaX.setEnabled(toggled) + self._paramWidget.velocityX.setDisabled(toggled) + + def _formulaYEnable(self, toggled): + if self._paramWidget.velocityYBox.isChecked(): + return + else: + self._paramWidget.formulaY.setEnabled(toggled) + self._paramWidget.velocityY.setDisabled(toggled) + + def _formulaZEnable(self, toggled): + if self._paramWidget.velocitZXBox.isChecked(): + return + else: + self._paramWidget.formulaZ.setEnabled(toggled) + self._paramWidget.velocityZ.setDisabled(toggled) + def open(self): if self._mesh is not None and self._part is not None: self._meshVisible = self._mesh.ViewObject.isVisible() @@ -104,33 +195,75 @@ class _TaskPanel(object): def _initParamWidget(self): unit = "m/s" - self._paramWidget.velocityXTxt.setText( - str(self._obj.VelocityX) + unit) - self._paramWidget.velocityYTxt.setText( - str(self._obj.VelocityY) + unit) - self._paramWidget.velocityZTxt.setText( - str(self._obj.VelocityZ) + unit) + self._paramWidget.velocityX.setProperty('unit', unit) + self._paramWidget.velocityY.setProperty('unit', unit) + self._paramWidget.velocityZ.setProperty('unit', unit) + + self._paramWidget.velocityX.setProperty( + 'value', self._obj.VelocityX) + FreeCADGui.ExpressionBinding( + self._paramWidget.velocityX).bind(self._obj, "VelocityX") self._paramWidget.velocityXBox.setChecked( - not self._obj.VelocityXEnabled) + self._obj.VelocityXUnspecified) + self._paramWidget.formulaX.setText(self._obj.VelocityXFormula) + self._paramWidget.formulaXCB.setChecked( + self._obj.VelocityXHasFormula) + + self._paramWidget.velocityY.setProperty( + 'value', self._obj.VelocityY) + FreeCADGui.ExpressionBinding( + self._paramWidget.velocityY).bind(self._obj, "VelocityY") self._paramWidget.velocityYBox.setChecked( - not self._obj.VelocityYEnabled) + self._obj.VelocityYUnspecified) + self._paramWidget.formulaY.setText(self._obj.VelocityYFormula) + self._paramWidget.formulaYCB.setChecked( + self._obj.VelocityYHasFormula) + + self._paramWidget.velocityZ.setProperty( + 'value', self._obj.VelocityZ) + FreeCADGui.ExpressionBinding( + self._paramWidget.velocityZ).bind(self._obj, "VelocityZ") self._paramWidget.velocityZBox.setChecked( - not self._obj.VelocityZEnabled) + self._obj.VelocityZUnspecified) + self._paramWidget.formulaZ.setText(self._obj.VelocityZFormula) + self._paramWidget.formulaZCB.setChecked( + self._obj.VelocityZHasFormula) + + def _applyVelocityChanges(self, enabledBox, velocityQSB): + enabled = enabledBox.isChecked() + velocity = None + try: + velocity = velocityQSB.property('value') + except ValueError: + FreeCAD.Console.PrintMessage( + "Wrong input. Not recognised input: '{}' " + "Velocity has not been set.\n".format(velocityQSB.text()) + ) + velocity = '0.0 m/s' + return enabled, velocity def _applyWidgetChanges(self): - unit = "m/s" - self._obj.VelocityXEnabled = \ - not self._paramWidget.velocityXBox.isChecked() - if self._obj.VelocityXEnabled: - quantity = Units.Quantity(self._paramWidget.velocityXTxt.text()) - self._obj.VelocityX = quantity.getValueAs(unit).Value - self._obj.VelocityYEnabled = \ - not self._paramWidget.velocityYBox.isChecked() - if self._obj.VelocityYEnabled: - quantity = Units.Quantity(self._paramWidget.velocityYTxt.text()) - self._obj.VelocityY = quantity.getValueAs(unit).Value - self._obj.VelocityZEnabled = \ - not self._paramWidget.velocityZBox.isChecked() - if self._obj.VelocityZEnabled: - quantity = Units.Quantity(self._paramWidget.velocityZTxt.text()) - self._obj.VelocityZ = quantity.getValueAs(unit).Value + # apply the velocities and their enabled state + self._obj.VelocityXUnspecified, self._obj.VelocityX = \ + self._applyVelocityChanges( + self._paramWidget.velocityXBox, + self._paramWidget.velocityX + ) + self._obj.VelocityXHasFormula = self._paramWidget.formulaXCB.isChecked() + self._obj.VelocityXFormula = self._paramWidget.formulaX.text() + + self._obj.VelocityYUnspecified, self._obj.VelocityY = \ + self._applyVelocityChanges( + self._paramWidget.velocityYBox, + self._paramWidget.velocityY + ) + self._obj.VelocityYHasFormula = self._paramWidget.formulaYCB.isChecked() + self._obj.VelocityYFormula = self._paramWidget.formulaY.text() + + self._obj.VelocityZUnspecified, self._obj.VelocityZ = \ + self._applyVelocityChanges( + self._paramWidget.velocityZBox, + self._paramWidget.velocityZ + ) + self._obj.VelocityZHasFormula = self._paramWidget.formulaZCB.isChecked() + self._obj.VelocityZFormula = self._paramWidget.formulaZ.text() diff --git a/src/Mod/Fem/femtaskpanels/task_solver_ccxtools.py b/src/Mod/Fem/femtaskpanels/task_solver_ccxtools.py index 6a25f7b2d0..417cee429a 100644 --- a/src/Mod/Fem/femtaskpanels/task_solver_ccxtools.py +++ b/src/Mod/Fem/femtaskpanels/task_solver_ccxtools.py @@ -295,7 +295,7 @@ class _TaskPanel: CCX_mesh = self.fea.analysis.Document.getObject("ResultMesh") if CCX_mesh is not None: CCX_mesh.ViewObject.Visibility = self.CCX_mesh_visibility - + def choose_working_dir(self): wd = QtGui.QFileDialog.getExistingDirectory(None, "Choose CalculiX working directory", self.fea.working_dir) diff --git a/src/Mod/Sketcher/App/ConstraintPyImp.cpp b/src/Mod/Sketcher/App/ConstraintPyImp.cpp index 457aee001e..dd33c0a5ca 100644 --- a/src/Mod/Sketcher/App/ConstraintPyImp.cpp +++ b/src/Mod/Sketcher/App/ConstraintPyImp.cpp @@ -224,14 +224,6 @@ int ConstraintPy::PyInit(PyObject* args, PyObject* /*kwd*/) if (PyNumber_Check(index_or_value)) { // can be float or int SecondIndex = any_index; Value = PyFloat_AsDouble(index_or_value); - //if (strcmp("Distance",ConstraintType) == 0) { - // this->getConstraintPtr()->Type = Distance; - // this->getConstraintPtr()->First = FirstIndex; - // this->getConstraintPtr()->Second = SecondIndex; - // this->getConstraintPtr()->Value = Value; - // return 0; - //} - //else if (strcmp("Angle",ConstraintType) == 0) { if (PyObject_TypeCheck(index_or_value, &(Base::QuantityPy::Type))) { Base::Quantity q = *(static_cast(index_or_value)->getQuantityPtr()); @@ -244,6 +236,13 @@ int ConstraintPy::PyInit(PyObject* args, PyObject* /*kwd*/) this->getConstraintPtr()->setValue(Value); return 0; } + else if (strcmp("Distance",ConstraintType) == 0) { + this->getConstraintPtr()->Type = Distance; + this->getConstraintPtr()->First = FirstIndex; + this->getConstraintPtr()->Second = SecondIndex; + this->getConstraintPtr()->setValue(Value); + return 0; + } else if (strcmp("DistanceX",ConstraintType) == 0) { FirstPos = SecondIndex; SecondIndex = -1; @@ -481,7 +480,7 @@ std::string ConstraintPy::representation() const case Coincident : result << "'Coincident'>";break; case Horizontal : result << "'Horizontal' (" << getConstraintPtr()->First << ")>";break; case Vertical : result << "'Vertical' (" << getConstraintPtr()->First << ")>";break; - case Block : result << "'Block' (" << getConstraintPtr()->First << ")>";break; + case Block : result << "'Block' (" << getConstraintPtr()->First << ")>";break; case Radius : result << "'Radius'>";break; case Diameter : result << "'Diameter'>";break; case Weight : result << "'Weight'>";break; diff --git a/src/Mod/Sketcher/App/PythonConverter.cpp b/src/Mod/Sketcher/App/PythonConverter.cpp index 53196e3bb9..e97d3c0203 100644 --- a/src/Mod/Sketcher/App/PythonConverter.cpp +++ b/src/Mod/Sketcher/App/PythonConverter.cpp @@ -296,6 +296,10 @@ std::string PythonConverter::process(const Sketcher::Constraint * constraint) return boost::str(boost::format("Sketcher.Constraint('Distance', %i, %f)") % constr->First % constr->getValue()); } + else if(constr->FirstPos == Sketcher::PointPos::none){ + return boost::str(boost::format("Sketcher.Constraint('Distance', %i, %i, %f)") % + constr->First % constr->Second % constr->getValue()); + } else if(constr->SecondPos == Sketcher::PointPos::none){ return boost::str(boost::format("Sketcher.Constraint('Distance', %i, %i, %i, %f)") % constr->First % static_cast(constr->FirstPos) % constr->Second % constr->getValue()); diff --git a/src/Mod/Sketcher/App/Sketch.cpp b/src/Mod/Sketcher/App/Sketch.cpp index fab57754e0..3a6b9e448b 100644 --- a/src/Mod/Sketcher/App/Sketch.cpp +++ b/src/Mod/Sketcher/App/Sketch.cpp @@ -1797,6 +1797,20 @@ int Sketch::addConstraint(const Constraint *constraint) constraint->Second,constraint->SecondPos, c.value,c.driving); } + else if (constraint->FirstPos == PointPos::none && + constraint->SecondPos == PointPos::none && + constraint->Second != GeoEnum::GeoUndef && + constraint->Third == GeoEnum::GeoUndef) { // circle to circle, circle to arc, etc. + + c.value = new double(constraint->getValue()); + if(c.driving) + FixParameters.push_back(c.value); + else { + Parameters.push_back(c.value); + DrivenParameters.push_back(c.value); + } + rtn = addDistanceConstraint(constraint->First, constraint->Second,c.value,c.driving); + } else if (constraint->Second != GeoEnum::GeoUndef) { if (constraint->FirstPos != PointPos::none) { // point to line distance c.value = new double(constraint->getValue()); @@ -1806,8 +1820,7 @@ int Sketch::addConstraint(const Constraint *constraint) Parameters.push_back(c.value); DrivenParameters.push_back(c.value); } - rtn = addDistanceConstraint(constraint->First,constraint->FirstPos, - constraint->Second,c.value,c.driving); + rtn = addDistanceConstraint(constraint->First,constraint->FirstPos,constraint->Second,c.value,c.driving); } } else {// line length @@ -2740,6 +2753,19 @@ int Sketch::addDistanceConstraint(int geoId1, PointPos pos1, int geoId2, PointPo return -1; } +// circle-circle offset distance constraint +int Sketch::addDistanceConstraint(int geoId1, int geoId2, double * value, bool driving) +{ + if ((Geoms[geoId1].type == Circle) && (Geoms[geoId2].type == Circle)) { + GCS::Circle &c1 = Circles[Geoms[geoId1].index]; + GCS::Circle &c2 = Circles[Geoms[geoId2].index]; + int tag = ++ConstraintsCounter; + GCSsys.addConstraintC2CDistance(c1, c2, value, tag, driving); + return ConstraintsCounter; + } + return -1; +} + int Sketch::addRadiusConstraint(int geoId, double * value, bool driving) { geoId = checkGeoId(geoId); diff --git a/src/Mod/Sketcher/App/Sketch.h b/src/Mod/Sketcher/App/Sketch.h index 6987e55dd2..69e6743c57 100644 --- a/src/Mod/Sketcher/App/Sketch.h +++ b/src/Mod/Sketcher/App/Sketch.h @@ -276,6 +276,15 @@ public: * Parameters array, as the case may be. */ int addDistanceConstraint(int geoId1, PointPos pos1, int geoId2, PointPos pos2, double * value, bool driving = true); + /** + * add a length or distance constraint + * + * double * value is a pointer to double allocated in the heap, containing the + * constraint value and already inserted into either the FixParameters or + * Parameters array, as the case may be. + */ + int addDistanceConstraint(int geoId1, int geoId2, double * value, bool driving = true); + /// add a parallel constraint between two lines int addParallelConstraint(int geoId1, int geoId2); /// add a perpendicular constraint between two lines diff --git a/src/Mod/Sketcher/App/planegcs/Constraints.cpp b/src/Mod/Sketcher/App/planegcs/Constraints.cpp index 32c48e147f..e7674f639a 100644 --- a/src/Mod/Sketcher/App/planegcs/Constraints.cpp +++ b/src/Mod/Sketcher/App/planegcs/Constraints.cpp @@ -20,15 +20,16 @@ * * ***************************************************************************/ -#include -#include "Constraints.h" #include - +#include #define DEBUG_DERIVS 0 #if DEBUG_DERIVS -#include +# include #endif +#include "Constraints.h" + + namespace GCS { @@ -37,20 +38,24 @@ namespace GCS /////////////////////////////////////// Constraint::Constraint() -: origpvec(0), pvec(0), scale(1.), tag(0), pvecChangedFlag(true), driving(true), internalAlignment(Alignment::NoInternalAlignment) -{ -} + : origpvec(0), + pvec(0), + scale(1.), + tag(0), + pvecChangedFlag(true), + driving(true), + internalAlignment(Alignment::NoInternalAlignment) +{} -void Constraint::redirectParams(const MAP_pD_pD & redirectionmap) +void Constraint::redirectParams(const MAP_pD_pD& redirectionmap) { - int i=0; - for (VEC_pD::iterator param=origpvec.begin(); - param != origpvec.end(); ++param, i++) { + int i = 0; + for (VEC_pD::iterator param = origpvec.begin(); param != origpvec.end(); ++param, i++) { MAP_pD_pD::const_iterator it = redirectionmap.find(*param); if (it != redirectionmap.end()) pvec[i] = it->second; } - pvecChangedFlag=true; + pvecChangedFlag = true; } void Constraint::revertParams() @@ -66,17 +71,17 @@ ConstraintType Constraint::getTypeId() void Constraint::rescale(double coef) { - scale = coef * 1.; + scale = coef * 1.0; } double Constraint::error() { - return 0.; + return 0.0; } double Constraint::grad(double * /*param*/) { - return 0.; + return 0.0; } double Constraint::maxStep(MAP_pD_D & /*dir*/, double lim) @@ -84,11 +89,11 @@ double Constraint::maxStep(MAP_pD_D & /*dir*/, double lim) return lim; } -int Constraint::findParamInPvec(double *param) +int Constraint::findParamInPvec(double* param) { int ret = -1; - for( std::size_t i=0 ; i(i); break; } @@ -96,6 +101,8 @@ int Constraint::findParamInPvec(double *param) return ret; } + +// -------------------------------------------------------- // Equal ConstraintEqual::ConstraintEqual(double *p1, double *p2, double p1p2ratio) { @@ -129,14 +136,17 @@ double ConstraintEqual::grad(double *param) return scale * deriv; } -// Weighted Linear Combination -ConstraintWeightedLinearCombination::ConstraintWeightedLinearCombination(size_t givennumpoles, const std::vector& givenpvec, const std::vector& givenfactors) - : factors(givenfactors) - , numpoles(givennumpoles) +// -------------------------------------------------------- +// Weighted Linear Combination +ConstraintWeightedLinearCombination::ConstraintWeightedLinearCombination( + size_t givennumpoles, const std::vector& givenpvec, + const std::vector& givenfactors) + : factors(givenfactors), + numpoles(givennumpoles) { pvec = givenpvec; - assert(pvec.size() == 2*numpoles + 1); + assert(pvec.size() == 2 * numpoles + 1); assert(factors.size() == numpoles); origpvec = pvec; rescale(); @@ -169,12 +179,12 @@ double ConstraintWeightedLinearCombination::error() return scale * ((*thepoint()) * wsum - sum); } -double ConstraintWeightedLinearCombination::grad(double *param) +double ConstraintWeightedLinearCombination::grad(double* param) { // Equations are from here: // https://forum.freecadweb.org/viewtopic.php?f=9&t=71130&start=120#p635538 - double deriv=0.; + double deriv = 0.; if (param == thepoint()) { // Eq. (11) @@ -202,9 +212,11 @@ double ConstraintWeightedLinearCombination::grad(double *param) return scale * deriv; } -// Center of Gravity -ConstraintCenterOfGravity::ConstraintCenterOfGravity(const std::vector& givenpvec, const std::vector& givenweights) +// -------------------------------------------------------- +// Center of Gravity +ConstraintCenterOfGravity::ConstraintCenterOfGravity(const std::vector& givenpvec, + const std::vector& givenweights) : weights(givenweights) { pvec = givenpvec; @@ -247,8 +259,9 @@ double ConstraintCenterOfGravity::grad(double *param) return scale * deriv; } -// Slope at B-spline knot +// -------------------------------------------------------- +// Slope at B-spline knot ConstraintSlopeAtBSplineKnot::ConstraintSlopeAtBSplineKnot(BSpline& b, Line& l, size_t knotindex) { // set up pvec: pole x-coords, pole y-coords, pole weights, @@ -258,7 +271,7 @@ ConstraintSlopeAtBSplineKnot::ConstraintSlopeAtBSplineKnot(BSpline& b, Line& l, // slope at knot doesn't make sense if there's only C0 continuity assert(numpoles >= 2); - pvec.reserve(3*numpoles + 4); + pvec.reserve(3 * numpoles + 4); // `startpole` is the first pole affecting the knot with `knotindex` size_t startpole = 0; @@ -285,13 +298,13 @@ ConstraintSlopeAtBSplineKnot::ConstraintSlopeAtBSplineKnot(BSpline& b, Line& l, slopefactors.resize(numpoles); for (size_t i = 0; i < numpoles + 1; ++i) { tempfactors[i] = - b.getLinCombFactor(*(b.knots[knotindex]), startpole + b.degree, startpole + i, b.degree - 1) / - (b.flattenedknots[startpole + b.degree + i] - b.flattenedknots[startpole + i]); + b.getLinCombFactor( + *(b.knots[knotindex]), startpole + b.degree, startpole + i, b.degree - 1) + / (b.flattenedknots[startpole + b.degree + i] - b.flattenedknots[startpole + i]); } for (size_t i = 0; i < numpoles; ++i) { - factors[i] = - b.getLinCombFactor(*(b.knots[knotindex]), startpole + b.degree, startpole + i); - slopefactors[i] = b.degree * (tempfactors[i] - tempfactors[i+1]); + factors[i] = b.getLinCombFactor(*(b.knots[knotindex]), startpole + b.degree, startpole + i); + slopefactors[i] = b.degree * (tempfactors[i] - tempfactors[i + 1]); } origpvec = pvec; @@ -312,7 +325,7 @@ void ConstraintSlopeAtBSplineKnot::rescale(double coef) slopey += *poleyat(i) * slopefactors[i]; } - scale = coef / sqrt((slopex*slopex + slopey*slopey)); + scale = coef / sqrt((slopex * slopex + slopey * slopey)); } double ConstraintSlopeAtBSplineKnot::error() @@ -335,29 +348,29 @@ double ConstraintSlopeAtBSplineKnot::error() // This is actually wsum^2 * the respective slopes // See Eq (19) from: // https://forum.freecadweb.org/viewtopic.php?f=9&t=71130&start=120#p635538 - double slopex = wsum*xslopesum - wslopesum*xsum; - double slopey = wsum*yslopesum - wslopesum*ysum; + double slopex = wsum * xslopesum - wslopesum * xsum; + double slopey = wsum * yslopesum - wslopesum * ysum; // Normalizing it ensures that the cross product is not zero just because // one vector is zero. double linex = *linep2x() - *linep1x(); double liney = *linep2y() - *linep1y(); - double dirx = linex / sqrt(linex*linex + liney*liney); - double diry = liney / sqrt(linex*linex + liney*liney); + double dirx = linex / sqrt(linex * linex + liney * liney); + double diry = liney / sqrt(linex * linex + liney * liney); // error is the cross product - return scale * (slopex*diry - slopey*dirx); + return scale * (slopex * diry - slopey * dirx); } -double ConstraintSlopeAtBSplineKnot::grad(double *param) +double ConstraintSlopeAtBSplineKnot::grad(double* param) { // Equations are from here: // https://forum.freecadweb.org/viewtopic.php?f=9&t=71130&start=120#p635538 double result = 0.0; double linex = *linep2x() - *linep1x(); double liney = *linep2y() - *linep1y(); - double dirx = linex / sqrt(linex*linex + liney*liney); - double diry = liney / sqrt(linex*linex + liney*liney); + double dirx = linex / sqrt(linex * linex + liney * liney); + double diry = liney / sqrt(linex * linex + liney * liney); for (size_t i = 0; i < numpoles; ++i) { if (param == polexat(i)) { @@ -369,7 +382,7 @@ double ConstraintSlopeAtBSplineKnot::grad(double *param) wsum += wcontrib; wslopesum += wslopecontrib; } - result = (wsum*slopefactors[i] - wslopesum*factors[i]) * diry; + result = (wsum * slopefactors[i] - wslopesum * factors[i]) * diry; return scale * result; } if (param == poleyat(i)) { @@ -381,7 +394,7 @@ double ConstraintSlopeAtBSplineKnot::grad(double *param) wsum += wcontrib; wslopesum += wslopecontrib; } - result = - (wsum*slopefactors[i] - wslopesum*factors[i]) * dirx; + result = -(wsum * slopefactors[i] - wslopesum * factors[i]) * dirx; return scale * result; } if (param == weightat(i)) { @@ -396,9 +409,8 @@ double ConstraintSlopeAtBSplineKnot::grad(double *param) ysum += wcontrib * (*poleyat(j) - *poleyat(i)); yslopesum += wslopecontrib * (*poleyat(j) - *poleyat(i)); } - result = - (factors[i]*xslopesum - slopefactors[i]*xsum) * diry - - (factors[i]*yslopesum - slopefactors[i]*ysum) * dirx; + result = (factors[i] * xslopesum - slopefactors[i] * xsum) * diry + - (factors[i] * yslopesum - slopefactors[i] * ysum) * dirx; return scale * result; } } @@ -422,55 +434,57 @@ double ConstraintSlopeAtBSplineKnot::grad(double *param) } // This is actually wsum^2 * the respective slopes - slopex = wsum*xslopesum - wslopesum*xsum; - slopey = wsum*yslopesum - wslopesum*ysum; + slopex = wsum * xslopesum - wslopesum * xsum; + slopey = wsum * yslopesum - wslopesum * ysum; }; if (param == linep1x()) { getSlopes(); - double dDirxDLinex = (liney*liney) / pow(linex*linex + liney*liney, 1.5); - double dDiryDLinex = -(linex*liney) / pow(linex*linex + liney*liney, 1.5); + double dDirxDLinex = (liney * liney) / pow(linex * linex + liney * liney, 1.5); + double dDiryDLinex = -(linex * liney) / pow(linex * linex + liney * liney, 1.5); // NOTE: d(linex)/d(x1) = -1 - result = slopex*(-dDiryDLinex) - slopey*(-dDirxDLinex); + result = slopex * (-dDiryDLinex) - slopey * (-dDirxDLinex); return scale * result; } if (param == linep2x()) { getSlopes(); - double dDirxDLinex = (liney*liney) / pow(linex*linex + liney*liney, 1.5); - double dDiryDLinex = -(linex*liney) / pow(linex*linex + liney*liney, 1.5); + double dDirxDLinex = (liney * liney) / pow(linex * linex + liney * liney, 1.5); + double dDiryDLinex = -(linex * liney) / pow(linex * linex + liney * liney, 1.5); // NOTE: d(linex)/d(x2) = 1 - result = slopex*dDiryDLinex - slopey*dDirxDLinex; + result = slopex * dDiryDLinex - slopey * dDirxDLinex; return scale * result; } if (param == linep1y()) { getSlopes(); - double dDirxDLiney = -(linex*liney) / pow(linex*linex + liney*liney, 1.5); - double dDiryDLiney = (linex*linex) / pow(linex*linex + liney*liney, 1.5); + double dDirxDLiney = -(linex * liney) / pow(linex * linex + liney * liney, 1.5); + double dDiryDLiney = (linex * linex) / pow(linex * linex + liney * liney, 1.5); // NOTE: d(liney)/d(y1) = -1 - result = slopex*(-dDiryDLiney) - slopey*(-dDirxDLiney); + result = slopex * (-dDiryDLiney) - slopey * (-dDirxDLiney); return scale * result; } if (param == linep2y()) { getSlopes(); - double dDirxDLiney = -(linex*liney) / pow(linex*linex + liney*liney, 1.5); - double dDiryDLiney = (linex*linex) / pow(linex*linex + liney*liney, 1.5); + double dDirxDLiney = -(linex * liney) / pow(linex * linex + liney * liney, 1.5); + double dDiryDLiney = (linex * linex) / pow(linex * linex + liney * liney, 1.5); // NOTE: d(liney)/d(y2) = 1 - result = slopex*dDiryDLiney - slopey*dDirxDLiney; + result = slopex * dDiryDLiney - slopey * dDirxDLiney; return scale * result; } return scale * result; } -// Point On BSpline -ConstraintPointOnBSpline::ConstraintPointOnBSpline(double* point, double* initparam, int coordidx, BSpline& b) +// -------------------------------------------------------- +// Point On BSpline +ConstraintPointOnBSpline::ConstraintPointOnBSpline(double* point, double* initparam, int coordidx, + BSpline& b) : bsp(b) { // This is always going to be true numpoints = bsp.degree + 1; - pvec.reserve(2 + 2*b.poles.size()); + pvec.reserve(2 + 2 * b.poles.size()); pvec.push_back(point); pvec.push_back(initparam); @@ -516,8 +530,8 @@ void ConstraintPointOnBSpline::rescale(double coef) double ConstraintPointOnBSpline::error() { - if (*theparam() < bsp.flattenedknots[startpole + bsp.degree] || - *theparam() > bsp.flattenedknots[startpole + bsp.degree + 1]) + if (*theparam() < bsp.flattenedknots[startpole + bsp.degree] + || *theparam() > bsp.flattenedknots[startpole + bsp.degree + 1]) setStartPole(*theparam()); double sum = 0; @@ -527,51 +541,58 @@ double ConstraintPointOnBSpline::error() VEC_D d(numpoints); for (size_t i = 0; i < numpoints; ++i) d[i] = *poleat(i) * *weightat(i); - sum = BSpline::splineValue(*theparam(), startpole + bsp.degree, bsp.degree, d, bsp.flattenedknots); + sum = BSpline::splineValue( + *theparam(), startpole + bsp.degree, bsp.degree, d, bsp.flattenedknots); for (size_t i = 0; i < numpoints; ++i) d[i] = *weightat(i); - wsum = BSpline::splineValue(*theparam(), startpole + bsp.degree, bsp.degree, d, bsp.flattenedknots); + wsum = BSpline::splineValue( + *theparam(), startpole + bsp.degree, bsp.degree, d, bsp.flattenedknots); // TODO: Change the poles as the point moves between pieces return scale * (*thepoint() * wsum - sum); } -double ConstraintPointOnBSpline::grad(double *gcsparam) +double ConstraintPointOnBSpline::grad(double* gcsparam) { - double deriv=0.; + double deriv = 0.; if (gcsparam == thepoint()) { VEC_D d(numpoints); for (size_t i = 0; i < numpoints; ++i) d[i] = *weightat(i); - double wsum = BSpline::splineValue(*theparam(), startpole + bsp.degree, bsp.degree, d, bsp.flattenedknots); + double wsum = BSpline::splineValue( + *theparam(), startpole + bsp.degree, bsp.degree, d, bsp.flattenedknots); deriv += wsum; } if (gcsparam == theparam()) { VEC_D d(numpoints - 1); for (size_t i = 1; i < numpoints; ++i) { - d[i-1] = - (*poleat(i) * *weightat(i) - *poleat(i-1) * *weightat(i-1)) / - (bsp.flattenedknots[startpole+i+bsp.degree] - bsp.flattenedknots[startpole+i]); + d[i - 1] = (*poleat(i) * *weightat(i) - *poleat(i - 1) * *weightat(i - 1)) + / (bsp.flattenedknots[startpole + i + bsp.degree] + - bsp.flattenedknots[startpole + i]); } - double slopevalue = BSpline::splineValue(*theparam(), startpole + bsp.degree, bsp.degree-1, d, bsp.flattenedknots); + double slopevalue = BSpline::splineValue( + *theparam(), startpole + bsp.degree, bsp.degree - 1, d, bsp.flattenedknots); for (size_t i = 1; i < numpoints; ++i) { - d[i-1] = - (*weightat(i) - *weightat(i-1)) / - (bsp.flattenedknots[startpole+i+bsp.degree] - bsp.flattenedknots[startpole+i]); + d[i - 1] = (*weightat(i) - *weightat(i - 1)) + / (bsp.flattenedknots[startpole + i + bsp.degree] + - bsp.flattenedknots[startpole + i]); } - double wslopevalue = BSpline::splineValue(*theparam(), startpole + bsp.degree, bsp.degree-1, d, bsp.flattenedknots); + double wslopevalue = BSpline::splineValue( + *theparam(), startpole + bsp.degree, bsp.degree - 1, d, bsp.flattenedknots); deriv += (*thepoint() * wslopevalue - slopevalue) * bsp.degree; } for (size_t i = 0; i < numpoints; ++i) { if (gcsparam == poleat(i)) { - auto factorsI = bsp.getLinCombFactor(*theparam(), startpole + bsp.degree, startpole + i); + auto factorsI = + bsp.getLinCombFactor(*theparam(), startpole + bsp.degree, startpole + i); deriv += -(*weightat(i) * factorsI); } if (gcsparam == weightat(i)) { - auto factorsI = bsp.getLinCombFactor(*theparam(), startpole + bsp.degree, startpole + i); + auto factorsI = + bsp.getLinCombFactor(*theparam(), startpole + bsp.degree, startpole + i); deriv += (*thepoint() - *poleat(i)) * factorsI; } } @@ -604,15 +625,20 @@ double ConstraintDifference::error() return scale * (*param2() - *param1() - *difference()); } -double ConstraintDifference::grad(double *param) +double ConstraintDifference::grad(double* param) { - double deriv=0.; - if (param == param1()) deriv += -1; - if (param == param2()) deriv += 1; - if (param == difference()) deriv += -1; + double deriv = 0.; + if (param == param1()) + deriv += -1; + if (param == param2()) + deriv += 1; + if (param == difference()) + deriv += -1; return scale * deriv; } + +// -------------------------------------------------------- // P2PDistance ConstraintP2PDistance::ConstraintP2PDistance(Point &p1, Point &p2, double *d) { @@ -639,30 +665,34 @@ double ConstraintP2PDistance::error() { double dx = (*p1x() - *p2x()); double dy = (*p1y() - *p2y()); - double d = sqrt(dx*dx + dy*dy); - double dist = *distance(); + double d = sqrt(dx * dx + dy * dy); + double dist = *distance(); return scale * (d - dist); } -double ConstraintP2PDistance::grad(double *param) +double ConstraintP2PDistance::grad(double* param) { - double deriv=0.; - if (param == p1x() || param == p1y() || - param == p2x() || param == p2y()) { + double deriv = 0.; + if (param == p1x() || param == p1y() || param == p2x() || param == p2y()) { double dx = (*p1x() - *p2x()); double dy = (*p1y() - *p2y()); - double d = sqrt(dx*dx + dy*dy); - if (param == p1x()) deriv += dx/d; - if (param == p1y()) deriv += dy/d; - if (param == p2x()) deriv += -dx/d; - if (param == p2y()) deriv += -dy/d; + double d = sqrt(dx * dx + dy * dy); + if (param == p1x()) + deriv += dx / d; + if (param == p1y()) + deriv += dy / d; + if (param == p2x()) + deriv += -dx / d; + if (param == p2y()) + deriv += -dy / d; } - if (param == distance()) deriv += -1.; + if (param == distance()) + deriv += -1.; return scale * deriv; } -double ConstraintP2PDistance::maxStep(MAP_pD_D &dir, double lim) +double ConstraintP2PDistance::maxStep(MAP_pD_D& dir, double lim) { MAP_pD_D::iterator it; // distance() >= 0 @@ -672,27 +702,33 @@ double ConstraintP2PDistance::maxStep(MAP_pD_D &dir, double lim) lim = std::min(lim, -(*distance()) / it->second); } // restrict actual distance change - double ddx=0.,ddy=0.; + double ddx = 0., ddy = 0.; it = dir.find(p1x()); - if (it != dir.end()) ddx += it->second; + if (it != dir.end()) + ddx += it->second; it = dir.find(p1y()); - if (it != dir.end()) ddy += it->second; + if (it != dir.end()) + ddy += it->second; it = dir.find(p2x()); - if (it != dir.end()) ddx -= it->second; + if (it != dir.end()) + ddx -= it->second; it = dir.find(p2y()); - if (it != dir.end()) ddy -= it->second; - double dd = sqrt(ddx*ddx+ddy*ddy); - double dist = *distance(); + if (it != dir.end()) + ddy -= it->second; + double dd = sqrt(ddx * ddx + ddy * ddy); + double dist = *distance(); if (dd > dist) { double dx = (*p1x() - *p2x()); double dy = (*p1y() - *p2y()); - double d = sqrt(dx*dx + dy*dy); + double d = sqrt(dx * dx + dy * dy); if (dd > d) - lim = std::min(lim, std::max(d,dist)/dd); + lim = std::min(lim, std::max(d, dist) / dd); } return lim; } + +// -------------------------------------------------------- // P2PAngle ConstraintP2PAngle::ConstraintP2PAngle(Point &p1, Point &p2, double *a, double da_) : da(da_) @@ -723,48 +759,54 @@ double ConstraintP2PAngle::error() double a = *angle() + da; double ca = cos(a); double sa = sin(a); - double x = dx*ca + dy*sa; - double y = -dx*sa + dy*ca; - return scale * atan2(y,x); + double x = dx * ca + dy * sa; + double y = -dx * sa + dy * ca; + return scale * atan2(y, x); } -double ConstraintP2PAngle::grad(double *param) +double ConstraintP2PAngle::grad(double* param) { - double deriv=0.; - if (param == p1x() || param == p1y() || - param == p2x() || param == p2y()) { + double deriv = 0.; + if (param == p1x() || param == p1y() || param == p2x() || param == p2y()) { double dx = (*p2x() - *p1x()); double dy = (*p2y() - *p1y()); double a = *angle() + da; double ca = cos(a); double sa = sin(a); - double x = dx*ca + dy*sa; - double y = -dx*sa + dy*ca; - double r2 = dx*dx+dy*dy; - dx = -y/r2; - dy = x/r2; - if (param == p1x()) deriv += (-ca*dx + sa*dy); - if (param == p1y()) deriv += (-sa*dx - ca*dy); - if (param == p2x()) deriv += ( ca*dx - sa*dy); - if (param == p2y()) deriv += ( sa*dx + ca*dy); + double x = dx * ca + dy * sa; + double y = -dx * sa + dy * ca; + double r2 = dx * dx + dy * dy; + dx = -y / r2; + dy = x / r2; + if (param == p1x()) + deriv += (-ca * dx + sa * dy); + if (param == p1y()) + deriv += (-sa * dx - ca * dy); + if (param == p2x()) + deriv += (ca * dx - sa * dy); + if (param == p2y()) + deriv += (sa * dx + ca * dy); } - if (param == angle()) deriv += -1; + if (param == angle()) + deriv += -1; return scale * deriv; } -double ConstraintP2PAngle::maxStep(MAP_pD_D &dir, double lim) +double ConstraintP2PAngle::maxStep(MAP_pD_D& dir, double lim) { // step(angle()) <= pi/18 = 10° MAP_pD_D::iterator it = dir.find(angle()); if (it != dir.end()) { double step = std::abs(it->second); - if (step > M_PI/18.) - lim = std::min(lim, (M_PI/18.) / step); + if (step > M_PI / 18.0) + lim = std::min(lim, (M_PI / 18.0) / step); } return lim; } + +// -------------------------------------------------------- // P2LDistance ConstraintP2LDistance::ConstraintP2LDistance(Point &p, Line &l, double *d) { @@ -791,47 +833,55 @@ void ConstraintP2LDistance::rescale(double coef) double ConstraintP2LDistance::error() { - double x0=*p0x(), x1=*p1x(), x2=*p2x(); - double y0=*p0y(), y1=*p1y(), y2=*p2y(); + double x0 = *p0x(), x1 = *p1x(), x2 = *p2x(); + double y0 = *p0y(), y1 = *p1y(), y2 = *p2y(); double dist = *distance(); - double dx = x2-x1; - double dy = y2-y1; - double d = sqrt(dx*dx+dy*dy); - double area = std::abs(-x0*dy+y0*dx+x1*y2-x2*y1); // = x1y2 - x2y1 - x0y2 + x2y0 + x0y1 - x1y0 = 2*(triangle area) - return scale * (area/d - dist); + double dx = x2 - x1; + double dy = y2 - y1; + double d = sqrt(dx * dx + dy * dy); + double area = + std::abs(-x0 * dy + y0 * dx + x1 * y2 + - x2 * y1);// = x1y2 - x2y1 - x0y2 + x2y0 + x0y1 - x1y0 = 2*(triangle area) + return scale * (area / d - dist); } -double ConstraintP2LDistance::grad(double *param) +double ConstraintP2LDistance::grad(double* param) { - double deriv=0.; + double deriv = 0.; // darea/dx0 = (y1-y2) darea/dy0 = (x2-x1) // darea/dx1 = (y2-y0) darea/dy1 = (x0-x2) // darea/dx2 = (y0-y1) darea/dy2 = (x1-x0) - if (param == p0x() || param == p0y() || - param == p1x() || param == p1y() || - param == p2x() || param == p2y()) { - double x0=*p0x(), x1=*p1x(), x2=*p2x(); - double y0=*p0y(), y1=*p1y(), y2=*p2y(); - double dx = x2-x1; - double dy = y2-y1; - double d2 = dx*dx+dy*dy; + if (param == p0x() || param == p0y() || param == p1x() || param == p1y() || param == p2x() + || param == p2y()) { + double x0 = *p0x(), x1 = *p1x(), x2 = *p2x(); + double y0 = *p0y(), y1 = *p1y(), y2 = *p2y(); + double dx = x2 - x1; + double dy = y2 - y1; + double d2 = dx * dx + dy * dy; double d = sqrt(d2); - double area = -x0*dy+y0*dx+x1*y2-x2*y1; - if (param == p0x()) deriv += (y1-y2) / d; - if (param == p0y()) deriv += (x2-x1) / d ; - if (param == p1x()) deriv += ((y2-y0)*d + (dx/d)*area) / d2; - if (param == p1y()) deriv += ((x0-x2)*d + (dy/d)*area) / d2; - if (param == p2x()) deriv += ((y0-y1)*d - (dx/d)*area) / d2; - if (param == p2y()) deriv += ((x1-x0)*d - (dy/d)*area) / d2; + double area = -x0 * dy + y0 * dx + x1 * y2 - x2 * y1; + if (param == p0x()) + deriv += (y1 - y2) / d; + if (param == p0y()) + deriv += (x2 - x1) / d; + if (param == p1x()) + deriv += ((y2 - y0) * d + (dx / d) * area) / d2; + if (param == p1y()) + deriv += ((x0 - x2) * d + (dy / d) * area) / d2; + if (param == p2x()) + deriv += ((y0 - y1) * d - (dx / d) * area) / d2; + if (param == p2y()) + deriv += ((x1 - x0) * d - (dy / d) * area) / d2; if (area < 0) deriv *= -1; } - if (param == distance()) deriv += -1; + if (param == distance()) + deriv += -1; return scale * deriv; } -double ConstraintP2LDistance::maxStep(MAP_pD_D &dir, double lim) +double ConstraintP2LDistance::maxStep(MAP_pD_D& dir, double lim) { MAP_pD_D::iterator it; // distance() >= 0 @@ -841,36 +891,44 @@ double ConstraintP2LDistance::maxStep(MAP_pD_D &dir, double lim) lim = std::min(lim, -(*distance()) / it->second); } // restrict actual area change - double darea=0.; - double x0=*p0x(), x1=*p1x(), x2=*p2x(); - double y0=*p0y(), y1=*p1y(), y2=*p2y(); + double darea = 0.; + double x0 = *p0x(), x1 = *p1x(), x2 = *p2x(); + double y0 = *p0y(), y1 = *p1y(), y2 = *p2y(); it = dir.find(p0x()); - if (it != dir.end()) darea += (y1-y2) * it->second; + if (it != dir.end()) + darea += (y1 - y2) * it->second; it = dir.find(p0y()); - if (it != dir.end()) darea += (x2-x1) * it->second; + if (it != dir.end()) + darea += (x2 - x1) * it->second; it = dir.find(p1x()); - if (it != dir.end()) darea += (y2-y0) * it->second; + if (it != dir.end()) + darea += (y2 - y0) * it->second; it = dir.find(p1y()); - if (it != dir.end()) darea += (x0-x2) * it->second; + if (it != dir.end()) + darea += (x0 - x2) * it->second; it = dir.find(p2x()); - if (it != dir.end()) darea += (y0-y1) * it->second; + if (it != dir.end()) + darea += (y0 - y1) * it->second; it = dir.find(p2y()); - if (it != dir.end()) darea += (x1-x0) * it->second; + if (it != dir.end()) + darea += (x1 - x0) * it->second; darea = std::abs(darea); if (darea > 0.) { - double dx = x2-x1; - double dy = y2-y1; - double area = 0.3*(*distance())*sqrt(dx*dx+dy*dy); + double dx = x2 - x1; + double dy = y2 - y1; + double area = 0.3 * (*distance()) * sqrt(dx * dx + dy * dy); if (darea > area) { - area = std::max(area, 0.3*std::abs(-x0*dy+y0*dx+x1*y2-x2*y1)); + area = std::max(area, 0.3 * std::abs(-x0 * dy + y0 * dx + x1 * y2 - x2 * y1)); if (darea > area) - lim = std::min(lim, area/darea); + lim = std::min(lim, area / darea); } } return lim; } + +// -------------------------------------------------------- // PointOnLine ConstraintPointOnLine::ConstraintPointOnLine(Point &p, Line &l) { @@ -908,41 +966,49 @@ void ConstraintPointOnLine::rescale(double coef) double ConstraintPointOnLine::error() { - double x0=*p0x(), x1=*p1x(), x2=*p2x(); - double y0=*p0y(), y1=*p1y(), y2=*p2y(); - double dx = x2-x1; - double dy = y2-y1; - double d = sqrt(dx*dx+dy*dy); - double area = -x0*dy+y0*dx+x1*y2-x2*y1; // = x1y2 - x2y1 - x0y2 + x2y0 + x0y1 - x1y0 = 2*(triangle area) - return scale * area/d; + double x0 = *p0x(), x1 = *p1x(), x2 = *p2x(); + double y0 = *p0y(), y1 = *p1y(), y2 = *p2y(); + double dx = x2 - x1; + double dy = y2 - y1; + double d = sqrt(dx * dx + dy * dy); + double area = -x0 * dy + y0 * dx + x1 * y2 + - x2 * y1;// = x1y2 - x2y1 - x0y2 + x2y0 + x0y1 - x1y0 = 2*(triangle area) + return scale * area / d; } -double ConstraintPointOnLine::grad(double *param) +double ConstraintPointOnLine::grad(double* param) { - double deriv=0.; + double deriv = 0.; // darea/dx0 = (y1-y2) darea/dy0 = (x2-x1) // darea/dx1 = (y2-y0) darea/dy1 = (x0-x2) // darea/dx2 = (y0-y1) darea/dy2 = (x1-x0) - if (param == p0x() || param == p0y() || - param == p1x() || param == p1y() || - param == p2x() || param == p2y()) { - double x0=*p0x(), x1=*p1x(), x2=*p2x(); - double y0=*p0y(), y1=*p1y(), y2=*p2y(); - double dx = x2-x1; - double dy = y2-y1; - double d2 = dx*dx+dy*dy; + if (param == p0x() || param == p0y() || param == p1x() || param == p1y() || param == p2x() + || param == p2y()) { + double x0 = *p0x(), x1 = *p1x(), x2 = *p2x(); + double y0 = *p0y(), y1 = *p1y(), y2 = *p2y(); + double dx = x2 - x1; + double dy = y2 - y1; + double d2 = dx * dx + dy * dy; double d = sqrt(d2); - double area = -x0*dy+y0*dx+x1*y2-x2*y1; - if (param == p0x()) deriv += (y1-y2) / d; - if (param == p0y()) deriv += (x2-x1) / d ; - if (param == p1x()) deriv += ((y2-y0)*d + (dx/d)*area) / d2; - if (param == p1y()) deriv += ((x0-x2)*d + (dy/d)*area) / d2; - if (param == p2x()) deriv += ((y0-y1)*d - (dx/d)*area) / d2; - if (param == p2y()) deriv += ((x1-x0)*d - (dy/d)*area) / d2; + double area = -x0 * dy + y0 * dx + x1 * y2 - x2 * y1; + if (param == p0x()) + deriv += (y1 - y2) / d; + if (param == p0y()) + deriv += (x2 - x1) / d; + if (param == p1x()) + deriv += ((y2 - y0) * d + (dx / d) * area) / d2; + if (param == p1y()) + deriv += ((x0 - x2) * d + (dy / d) * area) / d2; + if (param == p2x()) + deriv += ((y0 - y1) * d - (dx / d) * area) / d2; + if (param == p2y()) + deriv += ((x1 - x0) * d - (dy / d) * area) / d2; } return scale * deriv; } + +// -------------------------------------------------------- // PointOnPerpBisector ConstraintPointOnPerpBisector::ConstraintPointOnPerpBisector(Point &p, Line &l) { @@ -978,11 +1044,11 @@ void ConstraintPointOnPerpBisector::rescale(double coef) scale = coef; } -void ConstraintPointOnPerpBisector::errorgrad(double *err, double *grad, double *param) +void ConstraintPointOnPerpBisector::errorgrad(double* err, double* grad, double* param) { - DeriVector2 p0(Point(p0x(),p0y()), param); - DeriVector2 p1(Point(p1x(),p1y()), param); - DeriVector2 p2(Point(p2x(),p2y()), param); + DeriVector2 p0(Point(p0x(), p0y()), param); + DeriVector2 p1(Point(p1x(), p1y()), param); + DeriVector2 p2(Point(p2x(), p2y()), param); DeriVector2 d1 = p0.subtr(p1); DeriVector2 d2 = p0.subtr(p2); @@ -995,15 +1061,15 @@ void ConstraintPointOnPerpBisector::errorgrad(double *err, double *grad, double projd2 = d2.scalarProd(D, &dprojd2); if (err) - *err = projd1+projd2; + *err = projd1 + projd2; if (grad) - *grad = dprojd1+dprojd2; + *grad = dprojd1 + dprojd2; } double ConstraintPointOnPerpBisector::error() { double err; - errorgrad(&err,nullptr,nullptr); + errorgrad(&err, nullptr, nullptr); return scale * err; } @@ -1019,6 +1085,8 @@ double ConstraintPointOnPerpBisector::grad(double *param) return deriv*scale; } + +// -------------------------------------------------------- // Parallel ConstraintParallel::ConstraintParallel(Line &l1, Line &l2) { @@ -1045,7 +1113,7 @@ void ConstraintParallel::rescale(double coef) double dy1 = (*l1p1y() - *l1p2y()); double dx2 = (*l2p1x() - *l2p2x()); double dy2 = (*l2p1y() - *l2p2y()); - scale = coef / sqrt((dx1*dx1+dy1*dy1)*(dx2*dx2+dy2*dy2)); + scale = coef / sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2)); } double ConstraintParallel::error() @@ -1054,25 +1122,35 @@ double ConstraintParallel::error() double dy1 = (*l1p1y() - *l1p2y()); double dx2 = (*l2p1x() - *l2p2x()); double dy2 = (*l2p1y() - *l2p2y()); - return scale * (dx1*dy2 - dy1*dx2); + return scale * (dx1 * dy2 - dy1 * dx2); } -double ConstraintParallel::grad(double *param) +double ConstraintParallel::grad(double* param) { - double deriv=0.; - if (param == l1p1x()) deriv += (*l2p1y() - *l2p2y()); // = dy2 - if (param == l1p2x()) deriv += -(*l2p1y() - *l2p2y()); // = -dy2 - if (param == l1p1y()) deriv += -(*l2p1x() - *l2p2x()); // = -dx2 - if (param == l1p2y()) deriv += (*l2p1x() - *l2p2x()); // = dx2 + double deriv = 0.; + if (param == l1p1x()) + deriv += (*l2p1y() - *l2p2y());// = dy2 + if (param == l1p2x()) + deriv += -(*l2p1y() - *l2p2y());// = -dy2 + if (param == l1p1y()) + deriv += -(*l2p1x() - *l2p2x());// = -dx2 + if (param == l1p2y()) + deriv += (*l2p1x() - *l2p2x());// = dx2 - if (param == l2p1x()) deriv += -(*l1p1y() - *l1p2y()); // = -dy1 - if (param == l2p2x()) deriv += (*l1p1y() - *l1p2y()); // = dy1 - if (param == l2p1y()) deriv += (*l1p1x() - *l1p2x()); // = dx1 - if (param == l2p2y()) deriv += -(*l1p1x() - *l1p2x()); // = -dx1 + if (param == l2p1x()) + deriv += -(*l1p1y() - *l1p2y());// = -dy1 + if (param == l2p2x()) + deriv += (*l1p1y() - *l1p2y());// = dy1 + if (param == l2p1y()) + deriv += (*l1p1x() - *l1p2x());// = dx1 + if (param == l2p2y()) + deriv += -(*l1p1x() - *l1p2x());// = -dx1 return scale * deriv; } + +// -------------------------------------------------------- // Perpendicular ConstraintPerpendicular::ConstraintPerpendicular(Line &l1, Line &l2) { @@ -1114,7 +1192,7 @@ void ConstraintPerpendicular::rescale(double coef) double dy1 = (*l1p1y() - *l1p2y()); double dx2 = (*l2p1x() - *l2p2x()); double dy2 = (*l2p1y() - *l2p2y()); - scale = coef / sqrt((dx1*dx1+dy1*dy1)*(dx2*dx2+dy2*dy2)); + scale = coef / sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2)); } double ConstraintPerpendicular::error() @@ -1123,25 +1201,35 @@ double ConstraintPerpendicular::error() double dy1 = (*l1p1y() - *l1p2y()); double dx2 = (*l2p1x() - *l2p2x()); double dy2 = (*l2p1y() - *l2p2y()); - return scale * (dx1*dx2 + dy1*dy2); + return scale * (dx1 * dx2 + dy1 * dy2); } -double ConstraintPerpendicular::grad(double *param) +double ConstraintPerpendicular::grad(double* param) { - double deriv=0.; - if (param == l1p1x()) deriv += (*l2p1x() - *l2p2x()); // = dx2 - if (param == l1p2x()) deriv += -(*l2p1x() - *l2p2x()); // = -dx2 - if (param == l1p1y()) deriv += (*l2p1y() - *l2p2y()); // = dy2 - if (param == l1p2y()) deriv += -(*l2p1y() - *l2p2y()); // = -dy2 + double deriv = 0.; + if (param == l1p1x()) + deriv += (*l2p1x() - *l2p2x());// = dx2 + if (param == l1p2x()) + deriv += -(*l2p1x() - *l2p2x());// = -dx2 + if (param == l1p1y()) + deriv += (*l2p1y() - *l2p2y());// = dy2 + if (param == l1p2y()) + deriv += -(*l2p1y() - *l2p2y());// = -dy2 - if (param == l2p1x()) deriv += (*l1p1x() - *l1p2x()); // = dx1 - if (param == l2p2x()) deriv += -(*l1p1x() - *l1p2x()); // = -dx1 - if (param == l2p1y()) deriv += (*l1p1y() - *l1p2y()); // = dy1 - if (param == l2p2y()) deriv += -(*l1p1y() - *l1p2y()); // = -dy1 + if (param == l2p1x()) + deriv += (*l1p1x() - *l1p2x());// = dx1 + if (param == l2p2x()) + deriv += -(*l1p1x() - *l1p2x());// = -dx1 + if (param == l2p1y()) + deriv += (*l1p1y() - *l1p2y());// = dy1 + if (param == l2p2y()) + deriv += -(*l1p1y() - *l1p2y());// = -dy1 return scale * deriv; } + +// -------------------------------------------------------- // L2LAngle ConstraintL2LAngle::ConstraintL2LAngle(Line &l1, Line &l2, double *a) { @@ -1190,63 +1278,72 @@ double ConstraintL2LAngle::error() double dy1 = (*l1p2y() - *l1p1y()); double dx2 = (*l2p2x() - *l2p1x()); double dy2 = (*l2p2y() - *l2p1y()); - double a = atan2(dy1,dx1) + *angle(); + double a = atan2(dy1, dx1) + *angle(); double ca = cos(a); double sa = sin(a); - double x2 = dx2*ca + dy2*sa; - double y2 = -dx2*sa + dy2*ca; - return scale * atan2(y2,x2); + double x2 = dx2 * ca + dy2 * sa; + double y2 = -dx2 * sa + dy2 * ca; + return scale * atan2(y2, x2); } -double ConstraintL2LAngle::grad(double *param) +double ConstraintL2LAngle::grad(double* param) { - double deriv=0.; - if (param == l1p1x() || param == l1p1y() || - param == l1p2x() || param == l1p2y()) { + double deriv = 0.; + if (param == l1p1x() || param == l1p1y() || param == l1p2x() || param == l1p2y()) { double dx1 = (*l1p2x() - *l1p1x()); double dy1 = (*l1p2y() - *l1p1y()); - double r2 = dx1*dx1+dy1*dy1; - if (param == l1p1x()) deriv += -dy1/r2; - if (param == l1p1y()) deriv += dx1/r2; - if (param == l1p2x()) deriv += dy1/r2; - if (param == l1p2y()) deriv += -dx1/r2; + double r2 = dx1 * dx1 + dy1 * dy1; + if (param == l1p1x()) + deriv += -dy1 / r2; + if (param == l1p1y()) + deriv += dx1 / r2; + if (param == l1p2x()) + deriv += dy1 / r2; + if (param == l1p2y()) + deriv += -dx1 / r2; } - if (param == l2p1x() || param == l2p1y() || - param == l2p2x() || param == l2p2y()) { + if (param == l2p1x() || param == l2p1y() || param == l2p2x() || param == l2p2y()) { double dx1 = (*l1p2x() - *l1p1x()); double dy1 = (*l1p2y() - *l1p1y()); double dx2 = (*l2p2x() - *l2p1x()); double dy2 = (*l2p2y() - *l2p1y()); - double a = atan2(dy1,dx1) + *angle(); + double a = atan2(dy1, dx1) + *angle(); double ca = cos(a); double sa = sin(a); - double x2 = dx2*ca + dy2*sa; - double y2 = -dx2*sa + dy2*ca; - double r2 = dx2*dx2+dy2*dy2; - dx2 = -y2/r2; - dy2 = x2/r2; - if (param == l2p1x()) deriv += (-ca*dx2 + sa*dy2); - if (param == l2p1y()) deriv += (-sa*dx2 - ca*dy2); - if (param == l2p2x()) deriv += ( ca*dx2 - sa*dy2); - if (param == l2p2y()) deriv += ( sa*dx2 + ca*dy2); + double x2 = dx2 * ca + dy2 * sa; + double y2 = -dx2 * sa + dy2 * ca; + double r2 = dx2 * dx2 + dy2 * dy2; + dx2 = -y2 / r2; + dy2 = x2 / r2; + if (param == l2p1x()) + deriv += (-ca * dx2 + sa * dy2); + if (param == l2p1y()) + deriv += (-sa * dx2 - ca * dy2); + if (param == l2p2x()) + deriv += (ca * dx2 - sa * dy2); + if (param == l2p2y()) + deriv += (sa * dx2 + ca * dy2); } - if (param == angle()) deriv += -1; + if (param == angle()) + deriv += -1; return scale * deriv; } -double ConstraintL2LAngle::maxStep(MAP_pD_D &dir, double lim) +double ConstraintL2LAngle::maxStep(MAP_pD_D& dir, double lim) { // step(angle()) <= pi/18 = 10° MAP_pD_D::iterator it = dir.find(angle()); if (it != dir.end()) { double step = std::abs(it->second); - if (step > M_PI/18.) - lim = std::min(lim, (M_PI/18.) / step); + if (step > M_PI / 18.0) + lim = std::min(lim, (M_PI / 18.0) / step); } return lim; } + +// -------------------------------------------------------- // MidpointOnLine ConstraintMidpointOnLine::ConstraintMidpointOnLine(Line &l1, Line &l2) { @@ -1262,7 +1359,8 @@ ConstraintMidpointOnLine::ConstraintMidpointOnLine(Line &l1, Line &l2) rescale(); } -ConstraintMidpointOnLine::ConstraintMidpointOnLine(Point &l1p1, Point &l1p2, Point &l2p1, Point &l2p2) +ConstraintMidpointOnLine::ConstraintMidpointOnLine(Point& l1p1, Point& l1p2, Point& l2p1, + Point& l2p2) { pvec.push_back(l1p1.x); pvec.push_back(l1p1.y); @@ -1288,51 +1386,60 @@ void ConstraintMidpointOnLine::rescale(double coef) double ConstraintMidpointOnLine::error() { - double x0=((*l1p1x())+(*l1p2x()))/2; - double y0=((*l1p1y())+(*l1p2y()))/2; - double x1=*l2p1x(), x2=*l2p2x(); - double y1=*l2p1y(), y2=*l2p2y(); - double dx = x2-x1; - double dy = y2-y1; - double d = sqrt(dx*dx+dy*dy); - double area = -x0*dy+y0*dx+x1*y2-x2*y1; // = x1y2 - x2y1 - x0y2 + x2y0 + x0y1 - x1y0 = 2*(triangle area) - return scale * area/d; + double x0 = ((*l1p1x()) + (*l1p2x())) / 2; + double y0 = ((*l1p1y()) + (*l1p2y())) / 2; + double x1 = *l2p1x(), x2 = *l2p2x(); + double y1 = *l2p1y(), y2 = *l2p2y(); + double dx = x2 - x1; + double dy = y2 - y1; + double d = sqrt(dx * dx + dy * dy); + double area = -x0 * dy + y0 * dx + x1 * y2 + - x2 * y1;// = x1y2 - x2y1 - x0y2 + x2y0 + x0y1 - x1y0 = 2*(triangle area) + return scale * area / d; } -double ConstraintMidpointOnLine::grad(double *param) +double ConstraintMidpointOnLine::grad(double* param) { - double deriv=0.; + double deriv = 0.; // darea/dx0 = (y1-y2) darea/dy0 = (x2-x1) // darea/dx1 = (y2-y0) darea/dy1 = (x0-x2) // darea/dx2 = (y0-y1) darea/dy2 = (x1-x0) - if (param == l1p1x() || param == l1p1y() || - param == l1p2x() || param == l1p2y()|| - param == l2p1x() || param == l2p1y() || - param == l2p2x() || param == l2p2y()) { - double x0=((*l1p1x())+(*l1p2x()))/2; - double y0=((*l1p1y())+(*l1p2y()))/2; - double x1=*l2p1x(), x2=*l2p2x(); - double y1=*l2p1y(), y2=*l2p2y(); - double dx = x2-x1; - double dy = y2-y1; - double d2 = dx*dx+dy*dy; + if (param == l1p1x() || param == l1p1y() || param == l1p2x() || param == l1p2y() + || param == l2p1x() || param == l2p1y() || param == l2p2x() || param == l2p2y()) { + double x0 = ((*l1p1x()) + (*l1p2x())) / 2; + double y0 = ((*l1p1y()) + (*l1p2y())) / 2; + double x1 = *l2p1x(), x2 = *l2p2x(); + double y1 = *l2p1y(), y2 = *l2p2y(); + double dx = x2 - x1; + double dy = y2 - y1; + double d2 = dx * dx + dy * dy; double d = sqrt(d2); - double area = -x0*dy+y0*dx+x1*y2-x2*y1; - if (param == l1p1x()) deriv += (y1-y2) / (2*d); - if (param == l1p1y()) deriv += (x2-x1) / (2*d); - if (param == l1p2x()) deriv += (y1-y2) / (2*d); - if (param == l1p2y()) deriv += (x2-x1) / (2*d); - if (param == l2p1x()) deriv += ((y2-y0)*d + (dx/d)*area) / d2; - if (param == l2p1y()) deriv += ((x0-x2)*d + (dy/d)*area) / d2; - if (param == l2p2x()) deriv += ((y0-y1)*d - (dx/d)*area) / d2; - if (param == l2p2y()) deriv += ((x1-x0)*d - (dy/d)*area) / d2; + double area = -x0 * dy + y0 * dx + x1 * y2 - x2 * y1; + if (param == l1p1x()) + deriv += (y1 - y2) / (2 * d); + if (param == l1p1y()) + deriv += (x2 - x1) / (2 * d); + if (param == l1p2x()) + deriv += (y1 - y2) / (2 * d); + if (param == l1p2y()) + deriv += (x2 - x1) / (2 * d); + if (param == l2p1x()) + deriv += ((y2 - y0) * d + (dx / d) * area) / d2; + if (param == l2p1y()) + deriv += ((x0 - x2) * d + (dy / d) * area) / d2; + if (param == l2p2x()) + deriv += ((y0 - y1) * d - (dx / d) * area) / d2; + if (param == l2p2y()) + deriv += ((x1 - x0) * d - (dy / d) * area) / d2; } return scale * deriv; } + +// -------------------------------------------------------- // TangentCircumf -ConstraintTangentCircumf::ConstraintTangentCircumf(Point &p1, Point &p2, - double *rad1, double *rad2, bool internal_) +ConstraintTangentCircumf::ConstraintTangentCircumf(Point& p1, Point& p2, double* rad1, double* rad2, + bool internal_) { internal = internal_; pvec.push_back(p1.x); @@ -1360,36 +1467,45 @@ double ConstraintTangentCircumf::error() double dx = (*c1x() - *c2x()); double dy = (*c1y() - *c2y()); if (internal) - return scale * (sqrt(dx*dx + dy*dy) - std::abs(*r1() - *r2())); + return scale * (sqrt(dx * dx + dy * dy) - std::abs(*r1() - *r2())); else - return scale * (sqrt(dx*dx + dy*dy) - (*r1() + *r2())); + return scale * (sqrt(dx * dx + dy * dy) - (*r1() + *r2())); } -double ConstraintTangentCircumf::grad(double *param) +double ConstraintTangentCircumf::grad(double* param) { - double deriv=0.; - if (param == c1x() || param == c1y() || - param == c2x() || param == c2y()|| - param == r1() || param == r2()) { + double deriv = 0.; + if (param == c1x() || param == c1y() || param == c2x() || param == c2y() || param == r1() + || param == r2()) { double dx = (*c1x() - *c2x()); double dy = (*c1y() - *c2y()); - double d = sqrt(dx*dx + dy*dy); - if (param == c1x()) deriv += dx/d; - if (param == c1y()) deriv += dy/d; - if (param == c2x()) deriv += -dx/d; - if (param == c2y()) deriv += -dy/d; + double d = sqrt(dx * dx + dy * dy); + if (param == c1x()) + deriv += dx / d; + if (param == c1y()) + deriv += dy / d; + if (param == c2x()) + deriv += -dx / d; + if (param == c2y()) + deriv += -dy / d; if (internal) { - if (param == r1()) deriv += (*r1() > *r2()) ? -1 : 1; - if (param == r2()) deriv += (*r1() > *r2()) ? 1 : -1; + if (param == r1()) + deriv += (*r1() > *r2()) ? -1 : 1; + if (param == r2()) + deriv += (*r1() > *r2()) ? 1 : -1; } else { - if (param == r1()) deriv += -1; - if (param == r2()) deriv += -1; + if (param == r1()) + deriv += -1; + if (param == r2()) + deriv += -1; } } return scale * deriv; } + +// -------------------------------------------------------- // ConstraintPointOnEllipse ConstraintPointOnEllipse::ConstraintPointOnEllipse(Point &p, Ellipse &e) { @@ -1424,19 +1540,17 @@ double ConstraintPointOnEllipse::error() double Y_F1 = *f1y(); double b = *rmin(); - double err=sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + sqrt(pow(X_0 + - X_F1 - 2*X_c, 2) + pow(Y_0 + Y_F1 - 2*Y_c, 2)) - 2*sqrt(pow(b, 2) + - pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)); + double err = sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + + sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)) + - 2 * sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)); return scale * err; } -double ConstraintPointOnEllipse::grad(double *param) +double ConstraintPointOnEllipse::grad(double* param) { - double deriv=0.; - if (param == p1x() || param == p1y() || - param == f1x() || param == f1y() || - param == cx() || param == cy() || - param == rmin()) { + double deriv = 0.; + if (param == p1x() || param == p1y() || param == f1x() || param == f1y() || param == cx() + || param == cy() || param == rmin()) { double X_0 = *p1x(); double Y_0 = *p1y(); @@ -1447,38 +1561,39 @@ double ConstraintPointOnEllipse::grad(double *param) double b = *rmin(); if (param == p1x()) - deriv += (X_0 - X_F1)/sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + - (X_0 + X_F1 - 2*X_c)/sqrt(pow(X_0 + X_F1 - 2*X_c, 2) + pow(Y_0 + Y_F1 - - 2*Y_c, 2)); + deriv += (X_0 - X_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + + (X_0 + X_F1 - 2 * X_c) + / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); if (param == p1y()) - deriv += (Y_0 - Y_F1)/sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + - (Y_0 + Y_F1 - 2*Y_c)/sqrt(pow(X_0 + X_F1 - 2*X_c, 2) + pow(Y_0 + Y_F1 - - 2*Y_c, 2)); + deriv += (Y_0 - Y_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + + (Y_0 + Y_F1 - 2 * Y_c) + / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); if (param == f1x()) - deriv += -(X_0 - X_F1)/sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) - - 2*(X_F1 - X_c)/sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) - + (X_0 + X_F1 - 2*X_c)/sqrt(pow(X_0 + X_F1 - 2*X_c, 2) + pow(Y_0 + Y_F1 - - 2*Y_c, 2)); + deriv += -(X_0 - X_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + - 2 * (X_F1 - X_c) / sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) + + (X_0 + X_F1 - 2 * X_c) + / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); if (param == f1y()) - deriv +=-(Y_0 - Y_F1)/sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) - - 2*(Y_F1 - Y_c)/sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) - + (Y_0 + Y_F1 - 2*Y_c)/sqrt(pow(X_0 + X_F1 - 2*X_c, 2) + pow(Y_0 + Y_F1 - - 2*Y_c, 2)); + deriv += -(Y_0 - Y_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + - 2 * (Y_F1 - Y_c) / sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) + + (Y_0 + Y_F1 - 2 * Y_c) + / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); if (param == cx()) - deriv += 2*(X_F1 - X_c)/sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - - Y_c, 2)) - 2*(X_0 + X_F1 - 2*X_c)/sqrt(pow(X_0 + X_F1 - 2*X_c, 2) + - pow(Y_0 + Y_F1 - 2*Y_c, 2)); + deriv += 2 * (X_F1 - X_c) / sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) + - 2 * (X_0 + X_F1 - 2 * X_c) + / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); if (param == cy()) - deriv +=2*(Y_F1 - Y_c)/sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - - Y_c, 2)) - 2*(Y_0 + Y_F1 - 2*Y_c)/sqrt(pow(X_0 + X_F1 - 2*X_c, 2) + - pow(Y_0 + Y_F1 - 2*Y_c, 2)); + deriv += 2 * (Y_F1 - Y_c) / sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) + - 2 * (Y_0 + Y_F1 - 2 * Y_c) + / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); if (param == rmin()) - deriv += -2*b/sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, - 2)); + deriv += -2 * b / sqrt(pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)); } return scale * deriv; } + +// -------------------------------------------------------- // ConstraintEllipseTangentLine ConstraintEllipseTangentLine::ConstraintEllipseTangentLine(Line &l, Ellipse &e) { @@ -1510,57 +1625,58 @@ void ConstraintEllipseTangentLine::rescale(double coef) scale = coef * 1; } -void ConstraintEllipseTangentLine::errorgrad(double *err, double *grad, double *param) +void ConstraintEllipseTangentLine::errorgrad(double* err, double* grad, double* param) { // DeepSOIC equation // http://forum.freecadweb.org/viewtopic.php?f=10&t=7520&start=140 - if (pvecChangedFlag) ReconstructGeomPointers(); - DeriVector2 p1 (l.p1, param); - DeriVector2 p2 (l.p2, param); - DeriVector2 f1 (e.focus1, param); - DeriVector2 c (e.center, param); - DeriVector2 f2 = c.linCombi(2.0, f1, -1.0); // 2*cv - f1v + if (pvecChangedFlag) + ReconstructGeomPointers(); + DeriVector2 p1(l.p1, param); + DeriVector2 p2(l.p2, param); + DeriVector2 f1(e.focus1, param); + DeriVector2 c(e.center, param); + DeriVector2 f2 = c.linCombi(2.0, f1, -1.0);// 2*cv - f1v - //mirror F1 against the line + // mirror F1 against the line DeriVector2 nl = l.CalculateNormal(l.p1, param).getNormalized(); - double distF1L = 0, ddistF1L = 0; //distance F1 to line - distF1L = f1.subtr(p1).scalarProd(nl,&ddistF1L); - DeriVector2 f1m = f1.sum(nl.multD(-2*distF1L,-2*ddistF1L));//f1m = f1 mirrored + double distF1L = 0, ddistF1L = 0;// distance F1 to line + distF1L = f1.subtr(p1).scalarProd(nl, &ddistF1L); + DeriVector2 f1m = f1.sum(nl.multD(-2 * distF1L, -2 * ddistF1L));// f1m = f1 mirrored - //calculate distance form f1m to f2 + // calculate distance form f1m to f2 double distF1mF2, ddistF1mF2; distF1mF2 = f2.subtr(f1m).length(ddistF1mF2); - //calculate major radius (to compare the distance to) + // calculate major radius (to compare the distance to) double dradmin = (param == e.radmin) ? 1.0 : 0.0; double radmaj, dradmaj; - radmaj = e.getRadMaj(c,f1,*e.radmin, dradmin, dradmaj); + radmaj = e.getRadMaj(c, f1, *e.radmin, dradmin, dradmaj); if (err) - *err = distF1mF2 - 2*radmaj; + *err = distF1mF2 - 2 * radmaj; if (grad) - *grad = ddistF1mF2 - 2*dradmaj; + *grad = ddistF1mF2 - 2 * dradmaj; } double ConstraintEllipseTangentLine::error() { double err; - errorgrad(&err,nullptr,nullptr); + errorgrad(&err, nullptr, nullptr); return scale * err; } -double ConstraintEllipseTangentLine::grad(double *param) +double ConstraintEllipseTangentLine::grad(double* param) { - //first of all, check that we need to compute anything. - if ( findParamInPvec(param) == -1 ) + // first of all, check that we need to compute anything. + if (findParamInPvec(param) == -1) return 0.0; double deriv; errorgrad(nullptr, &deriv, param); - //use numeric for testing - #if 0 +// use numeric for testing +#if 0 double const eps = 0.00001; double oldparam = *param; double v0 = this->error(); @@ -1570,18 +1686,20 @@ double ConstraintEllipseTangentLine::grad(double *param) double vl = this->error(); *param = oldparam; //If not nasty, real derivative should be between left one and right one - double numretl = (v0-vl)/eps; - double numretr = (vr-v0)/eps; - assert(deriv <= std::max(numretl,numretr) ); - assert(deriv >= std::min(numretl,numretr) ); - #endif + double numretl = (v0 - vl) / eps; + double numretr = (vr - v0) / eps; + assert(deriv <= std::max(numretl, numretr)); + assert(deriv >= std::min(numretl, numretr)); +#endif - - return deriv*scale; + return deriv * scale; } + +// -------------------------------------------------------- // ConstraintInternalAlignmentPoint2Ellipse -ConstraintInternalAlignmentPoint2Ellipse::ConstraintInternalAlignmentPoint2Ellipse(Ellipse &e, Point &p1, InternalAlignmentType alignmentType) +ConstraintInternalAlignmentPoint2Ellipse::ConstraintInternalAlignmentPoint2Ellipse( + Ellipse& e, Point& p1, InternalAlignmentType alignmentType) { this->p = p1; pvec.push_back(p.x); @@ -1596,8 +1714,10 @@ ConstraintInternalAlignmentPoint2Ellipse::ConstraintInternalAlignmentPoint2Ellip void ConstraintInternalAlignmentPoint2Ellipse::ReconstructGeomPointers() { int i = 0; - p.x = pvec[i]; i++; - p.y = pvec[i]; i++; + p.x = pvec[i]; + i++; + p.y = pvec[i]; + i++; e.ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } @@ -1612,82 +1732,84 @@ void ConstraintInternalAlignmentPoint2Ellipse::rescale(double coef) scale = coef * 1; } -void ConstraintInternalAlignmentPoint2Ellipse::errorgrad(double *err, double *grad, double *param) +void ConstraintInternalAlignmentPoint2Ellipse::errorgrad(double* err, double* grad, double* param) { - if (pvecChangedFlag) ReconstructGeomPointers(); + if (pvecChangedFlag) + ReconstructGeomPointers(); - //todo: prefill only what's needed, not everything + // todo: prefill only what's needed, not everything DeriVector2 c(e.center, param); DeriVector2 f1(e.focus1, param); DeriVector2 emaj = f1.subtr(c).getNormalized(); DeriVector2 emin = emaj.rotate90ccw(); - DeriVector2 pv (p, param); - double b, db;//minor radius - b = *e.radmin; db = (e.radmin == param) ? 1.0 : 0.0; + DeriVector2 pv(p, param); + double b, db;// minor radius + b = *e.radmin; + db = (e.radmin == param) ? 1.0 : 0.0; - //major radius + // major radius double a, da; - a = e.getRadMaj(c,f1,b,db,da); + a = e.getRadMaj(c, f1, b, db, da); - DeriVector2 poa;//point to align to - bool by_y_not_by_x = false;//a flag to indicate if the alignment error function is for y (false - x, true - y). + DeriVector2 poa;// point to align to + bool by_y_not_by_x = + false;// a flag to indicate if the alignment error function is for y (false - x, true - y) - switch(AlignmentType){ + switch (AlignmentType) { case EllipsePositiveMajorX: case EllipsePositiveMajorY: poa = c.sum(emaj.multD(a, da)); by_y_not_by_x = AlignmentType == EllipsePositiveMajorY; - break; + break; case EllipseNegativeMajorX: case EllipseNegativeMajorY: poa = c.sum(emaj.multD(-a, -da)); by_y_not_by_x = AlignmentType == EllipseNegativeMajorY; - break; + break; case EllipsePositiveMinorX: case EllipsePositiveMinorY: poa = c.sum(emin.multD(b, db)); by_y_not_by_x = AlignmentType == EllipsePositiveMinorY; - break; + break; case EllipseNegativeMinorX: case EllipseNegativeMinorY: poa = c.sum(emin.multD(-b, -db)); by_y_not_by_x = AlignmentType == EllipseNegativeMinorY; - break; + break; case EllipseFocus2X: case EllipseFocus2Y: poa = c.linCombi(2.0, f1, -1.0); by_y_not_by_x = AlignmentType == EllipseFocus2Y; - break; + break; default: - //shouldn't happen - poa = pv;//align to the point itself, doing nothing essentially + // shouldn't happen + poa = pv;// align to the point itself, doing nothing essentially } - if(err) + if (err) *err = by_y_not_by_x ? pv.y - poa.y : pv.x - poa.x; - if(grad) + if (grad) *grad = by_y_not_by_x ? pv.dy - poa.dy : pv.dx - poa.dx; } double ConstraintInternalAlignmentPoint2Ellipse::error() { double err; - errorgrad(&err,nullptr,nullptr); + errorgrad(&err, nullptr, nullptr); return scale * err; - } -double ConstraintInternalAlignmentPoint2Ellipse::grad(double *param) +double ConstraintInternalAlignmentPoint2Ellipse::grad(double* param) { - //first of all, check that we need to compute anything. - if ( findParamInPvec(param) == -1 ) + // first of all, check that we need to compute anything. + if (findParamInPvec(param) == -1) return 0.0; double deriv; errorgrad(nullptr, &deriv, param); - //use numeric for testing - #if 0 +// use numeric for testing +#if 0 double const eps = 0.00001; double oldparam = *param; double v0 = this->error(); @@ -1697,18 +1819,20 @@ double ConstraintInternalAlignmentPoint2Ellipse::grad(double *param) double vl = this->error(); *param = oldparam; //If not nasty, real derivative should be between left one and right one - double numretl = (v0-vl)/eps; - double numretr = (vr-v0)/eps; - assert(deriv <= std::max(numretl,numretr) ); - assert(deriv >= std::min(numretl,numretr) ); - #endif - - return deriv*scale; + double numretl = (v0 - vl) / eps; + double numretr = (vr - v0) / eps; + assert(deriv <= std::max(numretl, numretr)); + assert(deriv >= std::min(numretl, numretr)); +#endif + return deriv * scale; } + +// -------------------------------------------------------- // ConstraintInternalAlignmentPoint2Hyperbola -ConstraintInternalAlignmentPoint2Hyperbola::ConstraintInternalAlignmentPoint2Hyperbola(Hyperbola &e, Point &p1, InternalAlignmentType alignmentType) +ConstraintInternalAlignmentPoint2Hyperbola::ConstraintInternalAlignmentPoint2Hyperbola( + Hyperbola& e, Point& p1, InternalAlignmentType alignmentType) { this->p = p1; pvec.push_back(p.x); @@ -1739,29 +1863,32 @@ void ConstraintInternalAlignmentPoint2Hyperbola::rescale(double coef) scale = coef * 1; } -void ConstraintInternalAlignmentPoint2Hyperbola::errorgrad(double *err, double *grad, double *param) +void ConstraintInternalAlignmentPoint2Hyperbola::errorgrad(double* err, double* grad, double* param) { - if (pvecChangedFlag) ReconstructGeomPointers(); + if (pvecChangedFlag) + ReconstructGeomPointers(); - //todo: prefill only what's needed, not everything + // todo: prefill only what's needed, not everything DeriVector2 c(e.center, param); DeriVector2 f1(e.focus1, param); DeriVector2 emaj = f1.subtr(c).getNormalized(); DeriVector2 emin = emaj.rotate90ccw(); - DeriVector2 pv (p, param); + DeriVector2 pv(p, param); - double b, db;//minor radius - b = *e.radmin; db = (e.radmin == param) ? 1.0 : 0.0; + double b, db;// minor radius + b = *e.radmin; + db = (e.radmin == param) ? 1.0 : 0.0; - //major radius + // major radius double a, da; - a = e.getRadMaj(c,f1,b,db,da); + a = e.getRadMaj(c, f1, b, db, da); - DeriVector2 poa;//point to align to - bool by_y_not_by_x = false;//a flag to indicate if the alignment error function is for y (false - x, true - y). + DeriVector2 poa;// point to align to + bool by_y_not_by_x = + false;// a flag to indicate if the alignment error function is for y (false - x, true - y) - switch(AlignmentType){ + switch (AlignmentType) { case HyperbolaPositiveMajorX: case HyperbolaPositiveMajorY: poa = c.sum(emaj.multD(a, da)); @@ -1773,59 +1900,58 @@ void ConstraintInternalAlignmentPoint2Hyperbola::errorgrad(double *err, double * by_y_not_by_x = AlignmentType == HyperbolaNegativeMajorY; break; case HyperbolaPositiveMinorX: - case HyperbolaPositiveMinorY: - { + case HyperbolaPositiveMinorY: { DeriVector2 pa = c.sum(emaj.multD(a, da)); - //DeriVector2 A(pa.x,pa.y); - //poa = A.sum(emin.multD(b, db)); + // DeriVector2 A(pa.x,pa.y); + // poa = A.sum(emin.multD(b, db)); poa = pa.sum(emin.multD(b, db)); by_y_not_by_x = AlignmentType == HyperbolaPositiveMinorY; break; } case HyperbolaNegativeMinorX: - case HyperbolaNegativeMinorY: - { + case HyperbolaNegativeMinorY: { DeriVector2 pa = c.sum(emaj.multD(a, da)); - //DeriVector2 A(pa.x,pa.y); - //poa = A.sum(emin.multD(-b, -db)); + // DeriVector2 A(pa.x,pa.y); + // poa = A.sum(emin.multD(-b, -db)); poa = pa.sum(emin.multD(-b, -db)); by_y_not_by_x = AlignmentType == HyperbolaNegativeMinorY; break; } default: - //shouldn't happen - poa = pv;//align to the point itself, doing nothing essentially + // shouldn't happen + poa = pv;// align to the point itself, doing nothing essentially } - if(err) + if (err) *err = by_y_not_by_x ? pv.y - poa.y : pv.x - poa.x; - if(grad) + if (grad) *grad = by_y_not_by_x ? pv.dy - poa.dy : pv.dx - poa.dx; } double ConstraintInternalAlignmentPoint2Hyperbola::error() { double err; - errorgrad(&err,nullptr,nullptr); + errorgrad(&err, nullptr, nullptr); return scale * err; - } -double ConstraintInternalAlignmentPoint2Hyperbola::grad(double *param) +double ConstraintInternalAlignmentPoint2Hyperbola::grad(double* param) { - //first of all, check that we need to compute anything. - if ( findParamInPvec(param) == -1 ) + // first of all, check that we need to compute anything. + if (findParamInPvec(param) == -1) return 0.0; double deriv; errorgrad(nullptr, &deriv, param); - return deriv*scale; - + return deriv * scale; } + +// -------------------------------------------------------- // ConstraintEqualMajorAxesEllipse -ConstraintEqualMajorAxesConic:: ConstraintEqualMajorAxesConic(MajorRadiusConic * a1, MajorRadiusConic * a2) +ConstraintEqualMajorAxesConic::ConstraintEqualMajorAxesConic(MajorRadiusConic* a1, + MajorRadiusConic* a2) { this->e1 = a1; this->e1->PushOwnParams(pvec); @@ -1838,7 +1964,7 @@ ConstraintEqualMajorAxesConic:: ConstraintEqualMajorAxesConic(MajorRadiusConic * void ConstraintEqualMajorAxesConic::ReconstructGeomPointers() { - int i =0; + int i = 0; e1->ReconstructOnNewPvec(pvec, i); e2->ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; @@ -1870,14 +1996,14 @@ void ConstraintEqualMajorAxesConic::errorgrad(double *err, double *grad, double double ConstraintEqualMajorAxesConic::error() { double err; - errorgrad(&err,nullptr,nullptr); + errorgrad(&err, nullptr, nullptr); return scale * err; } -double ConstraintEqualMajorAxesConic::grad(double *param) +double ConstraintEqualMajorAxesConic::grad(double* param) { - //first of all, check that we need to compute anything. - if ( findParamInPvec(param) == -1 ) + // first of all, check that we need to compute anything. + if (findParamInPvec(param) == -1) return 0.0; double deriv; @@ -1887,7 +2013,7 @@ double ConstraintEqualMajorAxesConic::grad(double *param) } // ConstraintEqualFocalDistance -ConstraintEqualFocalDistance:: ConstraintEqualFocalDistance(ArcOfParabola * a1, ArcOfParabola * a2) +ConstraintEqualFocalDistance::ConstraintEqualFocalDistance(ArcOfParabola* a1, ArcOfParabola* a2) { this->e1 = a1; this->e1->PushOwnParams(pvec); @@ -1951,10 +2077,10 @@ double ConstraintEqualFocalDistance::error() return scale * err; } -double ConstraintEqualFocalDistance::grad(double *param) +double ConstraintEqualFocalDistance::grad(double* param) { - //first of all, check that we need to compute anything. - if ( findParamInPvec(param) == -1 ) + // first of all, check that we need to compute anything. + if (findParamInPvec(param) == -1) return 0.0; double deriv; @@ -1963,6 +2089,8 @@ double ConstraintEqualFocalDistance::grad(double *param) return deriv * scale; } + +// -------------------------------------------------------- // ConstraintCurveValue ConstraintCurveValue::ConstraintCurveValue(Point &p, double* pcoord, Curve& crv, double *u) { @@ -1984,11 +2112,13 @@ ConstraintCurveValue::~ConstraintCurveValue() void ConstraintCurveValue::ReconstructGeomPointers() { - int i=0; - p.x=pvec[i]; i++; - p.y=pvec[i]; i++; - i++;//we have an inline function for point coordinate - i++;//we have an inline function for the parameterU + int i = 0; + p.x = pvec[i]; + i++; + p.y = pvec[i]; + i++; + i++;// we have an inline function for point coordinate + i++;// we have an inline function for the parameterU this->crv->ReconstructOnNewPvec(pvec, i); pvecChangedFlag = false; } @@ -2008,7 +2138,7 @@ void ConstraintCurveValue::errorgrad(double *err, double *grad, double *param) if (pvecChangedFlag) ReconstructGeomPointers(); double u, du; - u = *(this->u()); du = ( param == this->u() ) ? 1.0 : 0.0; + u = *(this->u()); du = ( param == this->u() ) ? 1.0 : 0.0; DeriVector2 P_to; //point of curve at parameter value of u, in global coordinates P_to = this->crv->Value(u,du,param); @@ -2040,16 +2170,16 @@ double ConstraintCurveValue::error() return scale * err; } -double ConstraintCurveValue::grad(double *param) +double ConstraintCurveValue::grad(double* param) { - //first of all, check that we need to compute anything. - if ( findParamInPvec(param) == -1 ) + // first of all, check that we need to compute anything. + if (findParamInPvec(param) == -1) return 0.0; double deriv; errorgrad(nullptr, &deriv, param); - return deriv*scale; + return deriv * scale; } double ConstraintCurveValue::maxStep(MAP_pD_D &/*dir*/, double lim) @@ -2066,6 +2196,8 @@ double ConstraintCurveValue::maxStep(MAP_pD_D &/*dir*/, double lim) return lim; } + +// -------------------------------------------------------- // ConstraintPointOnHyperbola ConstraintPointOnHyperbola::ConstraintPointOnHyperbola(Point &p, Hyperbola &e) { @@ -2126,19 +2258,17 @@ double ConstraintPointOnHyperbola::error() // show(a) // DM=sqrt((P-F2)*(P-F2))-sqrt((P-F1)*(P-F1))-2*a // show(DM.simplify_radical()) - double err=-sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + sqrt(pow(X_0 - + X_F1 - 2*X_c, 2) + pow(Y_0 + Y_F1 - 2*Y_c, 2)) - 2*sqrt(-pow(b, 2) + - pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)); + double err = -sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + + sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)) + - 2 * sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)); return scale * err; } -double ConstraintPointOnHyperbola::grad(double *param) +double ConstraintPointOnHyperbola::grad(double* param) { - double deriv=0.; - if (param == p1x() || param == p1y() || - param == f1x() || param == f1y() || - param == cx() || param == cy() || - param == rmin()) { + double deriv = 0.; + if (param == p1x() || param == p1y() || param == f1x() || param == f1y() || param == cx() + || param == cy() || param == rmin()) { double X_0 = *p1x(); double Y_0 = *p1y(); @@ -2149,37 +2279,39 @@ double ConstraintPointOnHyperbola::grad(double *param) double b = *rmin(); if (param == p1x()) - deriv += -(X_0 - X_F1)/sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + - (X_0 + X_F1 - 2*X_c)/sqrt(pow(X_0 + X_F1 - 2*X_c, 2) + pow(Y_0 + Y_F1 - - 2*Y_c, 2)); + deriv += -(X_0 - X_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + + (X_0 + X_F1 - 2 * X_c) + / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); if (param == p1y()) - deriv += -(Y_0 - Y_F1)/sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + - (Y_0 + Y_F1 - 2*Y_c)/sqrt(pow(X_0 + X_F1 - 2*X_c, 2) + pow(Y_0 + Y_F1 - - 2*Y_c, 2)); + deriv += -(Y_0 - Y_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + + (Y_0 + Y_F1 - 2 * Y_c) + / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); if (param == f1x()) - deriv += (X_0 - X_F1)/sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) - - 2*(X_F1 - X_c)/sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, - 2)) + (X_0 + X_F1 - 2*X_c)/sqrt(pow(X_0 + X_F1 - 2*X_c, 2) + pow(Y_0 + - Y_F1 - 2*Y_c, 2)); + deriv += (X_0 - X_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + - 2 * (X_F1 - X_c) / sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) + + (X_0 + X_F1 - 2 * X_c) + / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); if (param == f1y()) - deriv +=(Y_0 - Y_F1)/sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) - - 2*(Y_F1 - Y_c)/sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, - 2)) + (Y_0 + Y_F1 - 2*Y_c)/sqrt(pow(X_0 + X_F1 - 2*X_c, 2) + pow(Y_0 + - Y_F1 - 2*Y_c, 2)); + deriv += (Y_0 - Y_F1) / sqrt(pow(X_0 - X_F1, 2) + pow(Y_0 - Y_F1, 2)) + - 2 * (Y_F1 - Y_c) / sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) + + (Y_0 + Y_F1 - 2 * Y_c) + / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); if (param == cx()) - deriv += 2*(X_F1 - X_c)/sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - - Y_c, 2)) - 2*(X_0 + X_F1 - 2*X_c)/sqrt(pow(X_0 + X_F1 - 2*X_c, 2) + - pow(Y_0 + Y_F1 - 2*Y_c, 2)); + deriv += 2 * (X_F1 - X_c) / sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) + - 2 * (X_0 + X_F1 - 2 * X_c) + / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); if (param == cy()) - deriv +=2*(Y_F1 - Y_c)/sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - - Y_c, 2)) - 2*(Y_0 + Y_F1 - 2*Y_c)/sqrt(pow(X_0 + X_F1 - 2*X_c, 2) + - pow(Y_0 + Y_F1 - 2*Y_c, 2)); + deriv += 2 * (Y_F1 - Y_c) / sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)) + - 2 * (Y_0 + Y_F1 - 2 * Y_c) + / sqrt(pow(X_0 + X_F1 - 2 * X_c, 2) + pow(Y_0 + Y_F1 - 2 * Y_c, 2)); if (param == rmin()) - deriv += 2*b/sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c,2)); - } - return scale * deriv; + deriv += 2 * b / sqrt(-pow(b, 2) + pow(X_F1 - X_c, 2) + pow(Y_F1 - Y_c, 2)); + } + return scale * deriv; } + +// -------------------------------------------------------- // ConstraintPointOnParabola ConstraintPointOnParabola::ConstraintPointOnParabola(Point &p, Parabola &e) { @@ -2267,20 +2399,22 @@ double ConstraintPointOnParabola::error() return scale * err; } -double ConstraintPointOnParabola::grad(double *param) +double ConstraintPointOnParabola::grad(double* param) { - //first of all, check that we need to compute anything. - if ( findParamInPvec(param) == -1 ) - return 0.0; + // first of all, check that we need to compute anything. + if (findParamInPvec(param) == -1) + return 0.0; double deriv; errorgrad(nullptr, &deriv, param); - return deriv*scale; + return deriv * scale; } + +// -------------------------------------------------------- // ConstraintAngleViaPoint -ConstraintAngleViaPoint::ConstraintAngleViaPoint(Curve &acrv1, Curve &acrv2, Point p, double* angle) +ConstraintAngleViaPoint::ConstraintAngleViaPoint(Curve& acrv1, Curve& acrv2, Point p, double* angle) { pvec.push_back(angle); pvec.push_back(p.x); @@ -2290,9 +2424,10 @@ ConstraintAngleViaPoint::ConstraintAngleViaPoint(Curve &acrv1, Curve &acrv2, Poi crv1 = acrv1.Copy(); crv2 = acrv2.Copy(); origpvec = pvec; - pvecChangedFlag=true; + pvecChangedFlag = true; rescale(); } + ConstraintAngleViaPoint::~ConstraintAngleViaPoint() { delete crv1; crv1 = nullptr; @@ -2322,26 +2457,28 @@ void ConstraintAngleViaPoint::rescale(double coef) double ConstraintAngleViaPoint::error() { - if (pvecChangedFlag) ReconstructGeomPointers(); - double ang=*angle(); + if (pvecChangedFlag) + ReconstructGeomPointers(); + double ang = *angle(); DeriVector2 n1 = crv1->CalculateNormal(poa); DeriVector2 n2 = crv2->CalculateNormal(poa); - //rotate n1 by angle - DeriVector2 n1r (n1.x*cos(ang) - n1.y*sin(ang), n1.x*sin(ang) + n1.y*cos(ang) ); + // rotate n1 by angle + DeriVector2 n1r(n1.x * cos(ang) - n1.y * sin(ang), n1.x * sin(ang) + n1.y * cos(ang)); - //calculate angle between n1r and n2. Since we have rotated the n1, the angle is the error function. - //for our atan2, y is a dot product (n2) * (n1r rotated ccw by 90 degrees). - // x is a dot product (n2) * (n1r) - double err = atan2(-n2.x*n1r.y+n2.y*n1r.x, n2.x*n1r.x + n2.y*n1r.y); - //essentially, the function is equivalent to atan2(n2)-(atan2(n1)+angle). The only difference is behavior when normals are zero (the intended result is also zero in this case). + // calculate angle between n1r and n2. Since we have rotated the n1, the angle is the error + // function. for our atan2, y is a dot product (n2) * (n1r rotated ccw by 90 degrees). + // x is a dot product (n2) * (n1r) + double err = atan2(-n2.x * n1r.y + n2.y * n1r.x, n2.x * n1r.x + n2.y * n1r.y); + // essentially, the function is equivalent to atan2(n2)-(atan2(n1)+angle). The only difference + // is behavior when normals are zero (the intended result is also zero in this case). return scale * err; } double ConstraintAngleViaPoint::grad(double *param) { - //first of all, check that we need to compute anything. - if ( findParamInPvec(param) == -1 ) + // first of all, check that we need to compute anything. + if (findParamInPvec(param) == -1) return 0.0; double deriv=0.; @@ -2351,11 +2488,11 @@ double ConstraintAngleViaPoint::grad(double *param) if (param == angle()) deriv += -1.0; DeriVector2 n1 = crv1->CalculateNormal(poa, param); DeriVector2 n2 = crv2->CalculateNormal(poa, param); - deriv -= ( (-n1.dx)*n1.y / pow(n1.length(),2) + n1.dy*n1.x / pow(n1.length(),2) ); - deriv += ( (-n2.dx)*n2.y / pow(n2.length(),2) + n2.dy*n2.x / pow(n2.length(),2) ); + deriv -= ( (-n1.dx) * n1.y / pow(n1.length(), 2) + n1.dy * n1.x / pow(n1.length(), 2) ); + deriv += ( (-n2.dx) * n2.y / pow(n2.length(), 2) + n2.dy * n2.x / pow(n2.length(), 2) ); -//use numeric for testing +// use numeric for testing #if 0 double const eps = 0.00001; double oldparam = *param; @@ -2375,9 +2512,11 @@ double ConstraintAngleViaPoint::grad(double *param) return scale * deriv; } -//ConstraintSnell -ConstraintSnell::ConstraintSnell(Curve &ray1, Curve &ray2, Curve &boundary, Point p, double* n1, double* n2, bool flipn1, bool flipn2) +// -------------------------------------------------------- +// ConstraintSnell +ConstraintSnell::ConstraintSnell(Curve& ray1, Curve& ray2, Curve& boundary, Point p, double* n1, + double* n2, bool flipn1, bool flipn2) { pvec.push_back(n1); pvec.push_back(n2); @@ -2390,13 +2529,14 @@ ConstraintSnell::ConstraintSnell(Curve &ray1, Curve &ray2, Curve &boundary, Poin this->ray2 = ray2.Copy(); this->boundary = boundary.Copy(); origpvec = pvec; - pvecChangedFlag=true; + pvecChangedFlag = true; this->flipn1 = flipn1; this->flipn2 = flipn2; rescale(); } + ConstraintSnell::~ConstraintSnell() { delete ray1; ray1 = nullptr; @@ -2406,14 +2546,17 @@ ConstraintSnell::~ConstraintSnell() void ConstraintSnell::ReconstructGeomPointers() { - int cnt=0; - cnt++; cnt++;//skip n1, n2 - we have an inline function for that - poa.x = pvec[cnt]; cnt++; - poa.y = pvec[cnt]; cnt++; - ray1->ReconstructOnNewPvec(pvec,cnt); - ray2->ReconstructOnNewPvec(pvec,cnt); - boundary->ReconstructOnNewPvec(pvec,cnt); - pvecChangedFlag=false; + int cnt = 0; + cnt++; + cnt++;// skip n1, n2 - we have an inline function for that + poa.x = pvec[cnt]; + cnt++; + poa.y = pvec[cnt]; + cnt++; + ray1->ReconstructOnNewPvec(pvec, cnt); + ray2->ReconstructOnNewPvec(pvec, cnt); + boundary->ReconstructOnNewPvec(pvec, cnt); + pvecChangedFlag = false; } ConstraintType ConstraintSnell::getTypeId() @@ -2426,25 +2569,32 @@ void ConstraintSnell::rescale(double coef) scale = coef * 1.; } -//error and gradient combined. Values are returned through pointers. -void ConstraintSnell::errorgrad(double *err, double *grad, double* param) +// error and gradient combined. Values are returned through pointers. +void ConstraintSnell::errorgrad(double* err, double* grad, double* param) { - if (pvecChangedFlag) ReconstructGeomPointers(); + if (pvecChangedFlag) + ReconstructGeomPointers(); DeriVector2 tang1 = ray1->CalculateNormal(poa, param).rotate90cw().getNormalized(); DeriVector2 tang2 = ray2->CalculateNormal(poa, param).rotate90cw().getNormalized(); DeriVector2 tangB = boundary->CalculateNormal(poa, param).rotate90cw().getNormalized(); double sin1, dsin1, sin2, dsin2; - sin1 = tang1.scalarProd(tangB, &dsin1);//sinus of angle of incidence + sin1 = tang1.scalarProd(tangB, &dsin1);// sinus of angle of incidence sin2 = tang2.scalarProd(tangB, &dsin2); - if (flipn1) {sin1 = -sin1; dsin1 = -dsin1;} - if (flipn2) {sin2 = -sin2; dsin2 = -dsin2;} + if (flipn1) { + sin1 = -sin1; + dsin1 = -dsin1; + } + if (flipn2) { + sin2 = -sin2; + dsin2 = -dsin2; + } double dn1 = (param == n1()) ? 1.0 : 0.0; double dn2 = (param == n2()) ? 1.0 : 0.0; if (err) - *err = *n1()*sin1 - *n2()*sin2; + *err = *n1() * sin1 - *n2() * sin2; if (grad) - *grad = dn1*sin1 + *n1()*dsin1 - dn2*sin2 - *n2()*dsin2; + *grad = dn1 * sin1 + *n1() * dsin1 - dn2 * sin2 - *n2() * dsin2; } double ConstraintSnell::error() @@ -2456,16 +2606,14 @@ double ConstraintSnell::error() double ConstraintSnell::grad(double *param) { - - //first of all, check that we need to compute anything. - if ( findParamInPvec(param) == -1 ) + // first of all, check that we need to compute anything. + if (findParamInPvec(param) == -1) return 0.0; double deriv; errorgrad(nullptr, &deriv, param); - -//use numeric for testing +// use numeric for testing #if 0 double const eps = 0.00001; double oldparam = *param; @@ -2476,17 +2624,19 @@ double ConstraintSnell::grad(double *param) double vl = this->error(); *param = oldparam; //If not nasty, real derivative should be between left one and right one - double numretl = (v0-vl)/eps; - double numretr = (vr-v0)/eps; - assert(deriv <= std::max(numretl,numretr) ); - assert(deriv >= std::min(numretl,numretr) ); + double numretl = (v0 - vl) / eps; + double numretr = (vr - v0) / eps; + assert(deriv <= std::max(numretl, numretr)); + assert(deriv >= std::min(numretl, numretr)); #endif return scale * deriv; } + +// -------------------------------------------------------- // ConstraintEqualLineLength -ConstraintEqualLineLength::ConstraintEqualLineLength(Line &l1, Line &l2) +ConstraintEqualLineLength::ConstraintEqualLineLength(Line& l1, Line& l2) { this->l1 = l1; this->l1.PushOwnParams(pvec); @@ -2545,23 +2695,23 @@ void ConstraintEqualLineLength::errorgrad(double *err, double *grad, double *par // So here we maintain the very small derivative of 1e-10 when the gradient is under such value, such // that the diagnose function with pivot threshold of 1e-13 treats the value as non-zero and correctly // detects and can tell apart when a parameter is fully constrained or just locked into a maximum/minimum - if(fabs(*grad) < 1e-10) { + if (fabs(*grad) < 1e-10) { double surrogate = 1e-10; - if( param == l1.p1.x ) + if (param == l1.p1.x) *grad = v1.x > 0 ? surrogate : -surrogate; - if( param == l1.p1.y ) + if (param == l1.p1.y) *grad = v1.y > 0 ? surrogate : -surrogate; - if( param == l1.p2.x ) + if (param == l1.p2.x) *grad = v1.x > 0 ? -surrogate : surrogate; - if( param == l1.p2.y ) + if (param == l1.p2.y) *grad = v1.y > 0 ? -surrogate : surrogate; - if( param == l2.p1.x ) + if (param == l2.p1.x) *grad = v2.x > 0 ? surrogate : -surrogate; - if( param == l2.p1.y ) + if (param == l2.p1.y) *grad = v2.y > 0 ? surrogate : -surrogate; - if( param == l2.p2.x ) + if (param == l2.p2.x) *grad = v2.x > 0 ? -surrogate : surrogate; - if( param == l2.p2.y ) + if (param == l2.p2.y) *grad = v2.y > 0 ? -surrogate : surrogate; } } @@ -2570,20 +2720,129 @@ void ConstraintEqualLineLength::errorgrad(double *err, double *grad, double *par double ConstraintEqualLineLength::error() { double err; - errorgrad(&err,nullptr,nullptr); + errorgrad(&err, nullptr, nullptr); return scale * err; } -double ConstraintEqualLineLength::grad(double *param) +double ConstraintEqualLineLength::grad(double* param) { - if ( findParamInPvec(param) == -1 ) + if (findParamInPvec(param) == -1) return 0.0; double deriv; errorgrad(nullptr, &deriv, param); - return deriv*scale; + return deriv * scale; } +// -------------------------------------------------------- +// ConstraintC2CDistance +ConstraintC2CDistance::ConstraintC2CDistance(Circle& c1, Circle& c2, double* d) +{ + this->d = d; + pvec.push_back(d); + + this->c1 = c1; + this->c1.PushOwnParams(pvec); + + this->c2 = c2; + this->c2.PushOwnParams(pvec); + + origpvec = pvec; + pvecChangedFlag = true; + rescale(); +} + +void ConstraintC2CDistance::ReconstructGeomPointers() +{ + int i = 0; + i++;// skip the first parameter as there is the inline function distance for it + c1.ReconstructOnNewPvec(pvec, i); + c2.ReconstructOnNewPvec(pvec, i); + pvecChangedFlag = false; +} + +ConstraintType ConstraintC2CDistance::getTypeId() +{ + return C2CDistance; +} + +void ConstraintC2CDistance::rescale(double coef) +{ + scale = coef * 1; +} + +void ConstraintC2CDistance::errorgrad(double *err, double *grad, double *param) +{ + if (pvecChangedFlag) ReconstructGeomPointers(); + + DeriVector2 ct1 (c1.center, param); + DeriVector2 ct2 (c2.center, param); + + DeriVector2 vector_ct12 = ct1.subtr(ct2); + + double length_ct12, dlength_ct12; + length_ct12 = vector_ct12.length(dlength_ct12); + + // outer case (defined as the centers of the circles are outside the center of the other circles) + // it may well be that the circles intersect. + if (length_ct12 >= *c1.rad && length_ct12 >= *c2.rad) { + if (err) { + *err = length_ct12 - (*c2.rad + *c1.rad + *distance()); + } + else if (grad) { + double drad = (param == c2.rad || param == c1.rad || param == distance()) ? -1.0 : 0.0; + *grad = dlength_ct12 + drad; + } + } + else { + double * bigradius = (*c1.rad >= *c2.rad)?c1.rad:c2.rad; + double * smallradius = (*c1.rad >= *c2.rad)?c2.rad:c1.rad; + + double smallspan = *smallradius + length_ct12 + *distance(); + + if (err) { + *err = *bigradius - smallspan; + } + else if (grad) { + double drad = 0.0; + + if (param == bigradius) { + drad = 1.0; + } + else if (param == smallradius) { + drad = -1.0; + } + else if (param == distance()) { + drad = (*distance() < 0.) ? 1.0 : -1.0; + } + if (length_ct12 > 1e-13) { + *grad = -dlength_ct12 + drad; + } + else {// concentric case + *grad = drad; + } + } + } +} + +double ConstraintC2CDistance::error() +{ + double err; + errorgrad(&err,nullptr,nullptr); + return scale * err; +} + +double ConstraintC2CDistance::grad(double *param) +{ + if (findParamInPvec(param) == -1) + return 0.0; + + double deriv; + errorgrad(nullptr, &deriv, param); + + return deriv * scale; +} + } //namespace GCS diff --git a/src/Mod/Sketcher/App/planegcs/Constraints.h b/src/Mod/Sketcher/App/planegcs/Constraints.h index 0ff4b40349..957e021a0b 100644 --- a/src/Mod/Sketcher/App/planegcs/Constraints.h +++ b/src/Mod/Sketcher/App/planegcs/Constraints.h @@ -72,7 +72,8 @@ namespace GCS CenterOfGravity = 26, WeightedLinearCombination = 27, SlopeAtBSplineKnot = 28, - PointOnBSpline = 29 + PointOnBSpline = 29, + C2CDistance = 30 }; enum InternalAlignmentType { @@ -745,6 +746,22 @@ namespace GCS double grad(double *) override; }; + class ConstraintC2CDistance : public Constraint + { + private: + Circle c1; + Circle c2; + double *d; + inline double* distance() { return pvec[0]; } + void ReconstructGeomPointers(); //writes pointers in pvec to the parameters of c1, c2 + void errorgrad(double* err, double* grad, double *param); //error and gradient combined. Values are returned through pointers. + public: + ConstraintC2CDistance(Circle &c1, Circle &c2, double *d); + ConstraintType getTypeId() override; + void rescale(double coef=1.) override; + double error() override; + double grad(double *) override; + }; } //namespace GCS diff --git a/src/Mod/Sketcher/App/planegcs/GCS.cpp b/src/Mod/Sketcher/App/planegcs/GCS.cpp index f80d5e03bc..0ee8e39366 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.cpp +++ b/src/Mod/Sketcher/App/planegcs/GCS.cpp @@ -817,6 +817,14 @@ int System::addConstraintTangentAtBSplineKnot(BSpline &b, Line &l, unsigned int return addConstraint(constr); } +int System::addConstraintC2CDistance(Circle &c1, Circle &c2, double *dist, int tagId, bool driving) +{ + Constraint *constr = new ConstraintC2CDistance(c1, c2, dist); + constr->setTag(tagId); + constr->setDriving(driving); + return addConstraint(constr); +} + // derived constraints int System::addConstraintP2PCoincident(Point &p1, Point &p2, int tagId, bool driving) diff --git a/src/Mod/Sketcher/App/planegcs/GCS.h b/src/Mod/Sketcher/App/planegcs/GCS.h index 29e9162f18..fe11547151 100644 --- a/src/Mod/Sketcher/App/planegcs/GCS.h +++ b/src/Mod/Sketcher/App/planegcs/GCS.h @@ -315,6 +315,8 @@ namespace GCS bool flipn1, bool flipn2, int tagId, bool driving = true); + int addConstraintC2CDistance(Circle &c1, Circle &c2, double *dist, int tagId, bool driving = true); + // internal alignment constraints int addConstraintInternalAlignmentPoint2Ellipse(Ellipse &e, Point &p1, InternalAlignmentType alignmentType, int tagId=0, bool driving = true); int addConstraintInternalAlignmentEllipseMajorDiameter(Ellipse &e, Point &p1, Point &p2, int tagId=0, bool driving = true); diff --git a/src/Mod/Sketcher/Gui/CMakeLists.txt b/src/Mod/Sketcher/Gui/CMakeLists.txt index 3320d06710..5de66a2124 100644 --- a/src/Mod/Sketcher/Gui/CMakeLists.txt +++ b/src/Mod/Sketcher/Gui/CMakeLists.txt @@ -144,6 +144,8 @@ SET(SketcherGui_SRCS SketchRectangularArrayDialog.cpp SketcherRegularPolygonDialog.h SketcherRegularPolygonDialog.cpp + SnapManager.cpp + SnapManager.h TaskDlgEditSketch.cpp TaskDlgEditSketch.h ViewProviderPython.cpp diff --git a/src/Mod/Sketcher/Gui/Command.cpp b/src/Mod/Sketcher/Gui/Command.cpp index 1af8fcbeb4..0cecb39bd2 100644 --- a/src/Mod/Sketcher/Gui/Command.cpp +++ b/src/Mod/Sketcher/Gui/Command.cpp @@ -995,8 +995,6 @@ public: updateCheckBox(checkbox, propvalue); }; - updateCheckBox(gridSnap, sketchView->getSnapMode() == SnapMode::SnapToGrid); - updateCheckBoxFromProperty(gridAutoSpacing, sketchView->GridAuto); gridSizeBox->setValue(sketchView->GridSize.getValue()); @@ -1005,10 +1003,6 @@ public: void languageChange() { - gridSnap->setText(tr("Grid Snap")); - gridSnap->setToolTip(tr("New points will snap to the nearest grid line.\nPoints must be set closer than a fifth of the grid spacing to a grid line to snap.")); - gridSnap->setStatusTip(gridSnap->toolTip()); - gridAutoSpacing->setText(tr("Grid Auto Spacing")); gridAutoSpacing->setToolTip(tr("Resize grid automatically depending on zoom.")); gridAutoSpacing->setStatusTip(gridAutoSpacing->toolTip()); @@ -1020,8 +1014,6 @@ public: protected: QWidget* createWidget(QWidget* parent) override { - gridSnap = new QCheckBox(); - gridAutoSpacing = new QCheckBox(); sizeLabel = new QLabel(); @@ -1034,27 +1026,12 @@ protected: QWidget* gridSizeW = new QWidget(parent); auto* layout = new QGridLayout(gridSizeW); - layout->addWidget(gridSnap, 0, 0); - layout->addWidget(gridAutoSpacing, 1, 0); - layout->addWidget(sizeLabel, 2, 0); - layout->addWidget(gridSizeBox, 2, 1); + layout->addWidget(gridAutoSpacing, 0, 0, 1, 2); + layout->addWidget(sizeLabel, 1, 0); + layout->addWidget(gridSizeBox, 1, 1); languageChange(); - QObject::connect(gridSnap, &QCheckBox::stateChanged, [this](int state) { - auto* sketchView = getView(); - - if(sketchView) { - if(state == Qt::Checked) { - sketchView->setSnapMode(SnapMode::SnapToGrid); - } - else { - sketchView->setSnapMode(SnapMode::None); - } - } - }); - - QObject::connect(gridAutoSpacing, &QCheckBox::stateChanged, [this](int state) { auto* sketchView = getView(); @@ -1086,7 +1063,6 @@ private: } private: - QCheckBox * gridSnap; QCheckBox * gridAutoSpacing; QLabel * sizeLabel; Gui::QuantitySpinBox * gridSizeBox; @@ -1097,13 +1073,13 @@ class CmdSketcherGrid : public Gui::Command public: CmdSketcherGrid(); virtual ~CmdSketcherGrid(){} - virtual const char* className() const + virtual const char* className() const override { return "CmdSketcherGrid"; } - virtual void languageChange(); + virtual void languageChange() override; protected: - virtual void activated(int iMsg); - virtual bool isActive(void); - virtual Gui::Action * createAction(void); + virtual void activated(int iMsg) override; + virtual bool isActive(void) override; + virtual Gui::Action * createAction(void) override; private: void updateIcon(bool value); void updateInactiveHandlerIcon(); @@ -1213,6 +1189,255 @@ bool CmdSketcherGrid::isActive() return false; } +/* Snap tool */ +class SnapSpaceAction : public QWidgetAction +{ +public: + SnapSpaceAction(QObject* parent) : QWidgetAction(parent) { + setEnabled(false); + } + + void updateWidget(bool snapenabled) { + + auto updateCheckBox = [](QCheckBox* checkbox, bool value) { + auto checked = checkbox->checkState() == Qt::Checked; + + if (value != checked) { + const QSignalBlocker blocker(checkbox); + checkbox->setChecked(value); + } + }; + + auto updateSpinBox = [](Gui::QuantitySpinBox* spinbox, double value) { + auto currentvalue = spinbox->rawValue(); + + if (currentvalue != value) { + const QSignalBlocker blocker(spinbox); + spinbox->setValue(value); + } + }; + + ParameterGrp::handle hGrp = getParameterPath(); + + updateCheckBox(snapToObjects, hGrp->GetBool("SnapToObjects", true)); + + updateCheckBox(snapToGrid, hGrp->GetBool("SnapToGrid", false)); + + updateSpinBox(snapAngle, hGrp->GetFloat("SnapAngle", 5.0)); + + snapToObjects->setEnabled(snapenabled); + snapToGrid->setEnabled(snapenabled); + angleLabel->setEnabled(snapenabled); + snapAngle->setEnabled(snapenabled); + } + + void languageChange() + { + snapToObjects->setText(tr("Snap to objects")); + snapToObjects->setToolTip(tr("New points will snap to the currently preselected object. It will also snap to the middle of lines and arcs.")); + snapToObjects->setStatusTip(snapToObjects->toolTip()); + + snapToGrid->setText(tr("Snap to Grid")); + snapToGrid->setToolTip(tr("New points will snap to the nearest grid line.\nPoints must be set closer than a fifth of the grid spacing to a grid line to snap.")); + snapToGrid->setStatusTip(snapToGrid->toolTip()); + + angleLabel->setText(tr("Snap angle")); + snapAngle->setToolTip(tr("Angular step for tools that use 'Snap at Angle' (line for instance). Hold CTRL to enable 'Snap at Angle'. The angle start from the East axis (horizontal right)")); + } + +protected: + QWidget* createWidget(QWidget* parent) override + { + snapToObjects = new QCheckBox(); + + snapToGrid = new QCheckBox(); + + angleLabel = new QLabel(); + + snapAngle = new Gui::QuantitySpinBox(); + snapAngle->setProperty("unit", QVariant(QStringLiteral("deg"))); + snapAngle->setObjectName(QStringLiteral("snapAngle")); + snapAngle->setMaximum(99999999.0); + snapAngle->setMinimum(0); + + QWidget* snapW = new QWidget(parent); + auto* layout = new QGridLayout(snapW); + layout->addWidget(snapToGrid, 0, 0, 1, 2); + layout->addWidget(snapToObjects, 1, 0, 1, 2); + layout->addWidget(angleLabel, 2, 0); + layout->addWidget(snapAngle, 2, 1); + + languageChange(); + + QObject::connect(snapToObjects, &QCheckBox::stateChanged, [this](int state) { + ParameterGrp::handle hGrp = this->getParameterPath(); + hGrp->SetBool("SnapToObjects", state == Qt::Checked); + }); + + QObject::connect(snapToGrid, &QCheckBox::stateChanged, [this](int state) { + ParameterGrp::handle hGrp = this->getParameterPath(); + hGrp->SetBool("SnapToGrid", state == Qt::Checked); + }); + + QObject::connect(snapAngle, qOverload(&Gui::QuantitySpinBox::valueChanged), [this](double val) { + ParameterGrp::handle hGrp = this->getParameterPath(); + hGrp->SetFloat("SnapAngle", val); + }); + + return snapW; + } + +private: + ParameterGrp::handle getParameterPath() { + return App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/Snap"); + } + +private: + QCheckBox* snapToObjects; + QCheckBox* snapToGrid; + QLabel* angleLabel; + Gui::QuantitySpinBox* snapAngle; +}; + +class CmdSketcherSnap : public Gui::Command, public ParameterGrp::ObserverType +{ +public: + CmdSketcherSnap(); + virtual ~CmdSketcherSnap(); + virtual const char* className() const override + { + return "CmdSketcherSnap"; + } + virtual void languageChange() override; + + void OnChange(Base::Subject &rCaller, const char * sReason) override; +protected: + virtual void activated(int iMsg) override; + virtual bool isActive(void) override; + virtual Gui::Action* createAction(void) override; +private: + void updateIcon(bool value); + + ParameterGrp::handle getParameterPath() { + return App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/Snap"); + } + + CmdSketcherSnap(const CmdSketcherSnap&) = delete; + CmdSketcherSnap(CmdSketcherSnap&&) = delete; + CmdSketcherSnap& operator= (const CmdSketcherSnap&) = delete; + CmdSketcherSnap& operator= (CmdSketcherSnap&&) = delete; + + bool snapEnabled; +}; + +CmdSketcherSnap::CmdSketcherSnap() + : Command("Sketcher_Snap") +{ + sAppModule = "Sketcher"; + sGroup = "Sketcher"; + sMenuText = QT_TR_NOOP("Toggle Snap"); + sToolTipText = QT_TR_NOOP("Toggle all snapping functionalities. In the menu you can toggle individually 'Snap to Grid', 'Snap to Objects' and further snap settings"); + sWhatsThis = "Sketcher_Snap"; + sStatusTip = sToolTipText; + eType = 0; + + ParameterGrp::handle hGrp = this->getParameterPath(); + hGrp->Attach(this); +} + +CmdSketcherSnap::~CmdSketcherSnap() { + + ParameterGrp::handle hGrp = this->getParameterPath(); + hGrp->Detach(this); +} + +void CmdSketcherSnap::OnChange(Base::Subject &rCaller, const char * sReason) +{ + Q_UNUSED(rCaller) + + if (strcmp(sReason, "Snap") == 0) { + snapEnabled = getParameterPath()->GetBool("Snap", true); + } +} + +void CmdSketcherSnap::updateIcon(bool value) +{ + static QIcon active = Gui::BitmapFactory().iconFromTheme("Sketcher_Snap"); + static QIcon inactive = Gui::BitmapFactory().iconFromTheme("Sketcher_Snap_Deactivated"); + + auto* pcAction = qobject_cast(getAction()); + pcAction->setIcon(value ? active : inactive); +} + +void CmdSketcherSnap::activated(int iMsg) +{ + Q_UNUSED(iMsg); + + getParameterPath()->SetBool("Snap", !snapEnabled); + + // snapEnable updated via observer + updateIcon(snapEnabled); + + //Update the widget : + if (!_pcAction) + return; + + Gui::ActionGroup* pcAction = qobject_cast(_pcAction); + QList a = pcAction->actions(); + + auto* ssa = static_cast(a[0]); + ssa->updateWidget(snapEnabled); +} + +Gui::Action* CmdSketcherSnap::createAction() +{ + auto* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); + pcAction->setDropDownMenu(true); + pcAction->setExclusive(false); + applyCommandData(this->className(), pcAction); + + SnapSpaceAction* ssa = new SnapSpaceAction(pcAction); + pcAction->addAction(ssa); + + _pcAction = pcAction; + + QObject::connect(pcAction, &Gui::ActionGroup::aboutToShow, [ssa, this](QMenu* menu) { + Q_UNUSED(menu) + ssa->updateWidget(snapEnabled); + }); + + // set the right pixmap + updateIcon(snapEnabled); + + return pcAction; +} + +void CmdSketcherSnap::languageChange() +{ + Command::languageChange(); + + if (!_pcAction) + return; + + Gui::ActionGroup* pcAction = qobject_cast(_pcAction); + QList a = pcAction->actions(); + + auto* ssa = static_cast(a[0]); + ssa->languageChange(); +} + +bool CmdSketcherSnap::isActive() +{ + auto* vp = getInactiveHandlerEditModeSketchViewProvider(); + + if (vp) { + updateIcon(snapEnabled); + + return true; + } + + return false; +} void CreateSketcherCommands() { @@ -1230,4 +1455,5 @@ void CreateSketcherCommands() rcCmdMgr.addCommand(new CmdSketcherMergeSketches()); rcCmdMgr.addCommand(new CmdSketcherViewSection()); rcCmdMgr.addCommand(new CmdSketcherGrid()); + rcCmdMgr.addCommand(new CmdSketcherSnap()); } diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index a1dd198a80..7f878661f8 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -2150,7 +2150,7 @@ CmdSketcherConstrainDistance::CmdSketcherConstrainDistance() sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Constrain distance"); - sToolTipText = QT_TR_NOOP("Fix a length of a line or the distance between a line and a vertex"); + sToolTipText = QT_TR_NOOP("Fix a length of a line or the distance between a line and a vertex or between two circles"); sWhatsThis = "Sketcher_ConstrainDistance"; sStatusTip = sToolTipText; sPixmap = "Constraint_Length"; @@ -2160,7 +2160,8 @@ CmdSketcherConstrainDistance::CmdSketcherConstrainDistance() allowedSelSequences = {{SelVertex, SelVertexOrRoot}, {SelRoot, SelVertex}, {SelEdge}, {SelExternalEdge}, {SelVertex, SelEdgeOrAxis}, {SelRoot, SelEdge}, - {SelVertex, SelExternalEdge}, {SelRoot, SelExternalEdge}}; + {SelVertex, SelExternalEdge}, {SelRoot, SelExternalEdge}, + {SelEdge, SelEdge}}; } void CmdSketcherConstrainDistance::activated(int iMsg) @@ -2289,6 +2290,55 @@ void CmdSketcherConstrainDistance::activated(int iMsg) return; } } + else if (isEdge(GeoId1,PosId1) && isEdge(GeoId2,PosId2)) { // circle to circle distance + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + if (geom1->getTypeId() == Part::GeomCircle::getClassTypeId() + && geom2->getTypeId() == Part::GeomCircle::getClassTypeId() ) { + auto circleSeg1 = static_cast(geom1); + double radius1 = circleSeg1->getRadius(); + Base::Vector3d center1 = circleSeg1->getCenter(); + + auto circleSeg2 = static_cast(geom2); + double radius2 = circleSeg2->getRadius(); + Base::Vector3d center2 = circleSeg2->getCenter(); + + double ActDist = 0.; + + Base::Vector3d intercenter = center1 - center2; + double intercenterdistance = intercenter.Length(); + + if( intercenterdistance >= radius1 && + intercenterdistance >= radius2 ) { + + ActDist = intercenterdistance - radius1 - radius2; + } + else { + double bigradius = std::max(radius1,radius2); + double smallradius = std::min(radius1,radius2); + + ActDist = bigradius - smallradius - intercenterdistance; + } + + openCommand(QT_TRANSLATE_NOOP("Command", "Add circle to circle distance constraint")); + Gui::cmdAppObjectArgs(selection[0].getObject(), + "addConstraint(Sketcher.Constraint('Distance',%d,%d,%f)) ", + GeoId1,GeoId2,ActDist); + + if (arebothpointsorsegmentsfixed || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving + const std::vector &ConStr = Obj->Constraints.getValues(); + + Gui::cmdAppObjectArgs(selection[0].getObject(), + "setDriving(%i,%s)", + ConStr.size()-1,"False"); + finishDatumConstraint (this, Obj, false); + } + else + finishDatumConstraint (this, Obj, true); + + return; + } + } else if (isEdge(GeoId1,PosId1)) { // line length if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) { Gui::TranslatedNotification(Obj, @@ -2327,7 +2377,7 @@ void CmdSketcherConstrainDistance::activated(int iMsg) Gui::TranslatedNotification(Obj, QObject::tr("Wrong selection"), - QObject::tr("Select exactly one line or one point and one line or two points from the sketch.")); + QObject::tr("Select exactly one line or one point and one line or two points or two circles from the sketch.")); return; } @@ -2416,6 +2466,9 @@ void CmdSketcherConstrainDistance::applyConstraint(std::vector &selSe else finishDatumConstraint (this, Obj, true); } + else if (geom->getTypeId() == Part::GeomCircle::getClassTypeId()) { + // allow this selection but do nothing as it needs 2 circles + } else { Gui::TranslatedNotification(Obj, QObject::tr("Wrong selection"), @@ -2460,6 +2513,61 @@ void CmdSketcherConstrainDistance::applyConstraint(std::vector &selSe return; } + case 8: // {SelEdge, SelEdge} + { + GeoId1 = selSeq.at(0).GeoId; GeoId2 = selSeq.at(1).GeoId; + const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); + const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); + if (geom1->getTypeId() == Part::GeomCircle::getClassTypeId() + && geom2->getTypeId() == Part::GeomCircle::getClassTypeId() ) { // circle to circle distance + auto circleSeg1 = static_cast(geom1); + double radius1 = circleSeg1->getRadius(); + Base::Vector3d center1 = circleSeg1->getCenter(); + + auto circleSeg2 = static_cast(geom2); + double radius2 = circleSeg2->getRadius(); + Base::Vector3d center2 = circleSeg2->getCenter(); + + double ActDist = 0.; + + Base::Vector3d intercenter = center1 - center2; + double intercenterdistance = intercenter.Length(); + + if( intercenterdistance >= radius1 && + intercenterdistance >= radius2 ) { + + ActDist = intercenterdistance - radius1 - radius2; + } + else { + double bigradius = std::max(radius1,radius2); + double smallradius = std::min(radius1,radius2); + + ActDist = bigradius - smallradius - intercenterdistance; + } + + openCommand(QT_TRANSLATE_NOOP("Command", "Add circle to circle distance constraint")); + Gui::cmdAppObjectArgs(Obj, + "addConstraint(Sketcher.Constraint('Distance',%d,%d,%f)) ", + GeoId1,GeoId2,ActDist); + + if (arebothpointsorsegmentsfixed || constraintCreationMode==Reference) { // it is a constraint on a external line, make it non-driving + const std::vector &ConStr = Obj->Constraints.getValues(); + + Gui::cmdAppObjectArgs(Obj, "setDriving(%i,%s)", + ConStr.size()-1,"False"); + finishDatumConstraint (this, Obj, false); + } + else + finishDatumConstraint (this, Obj, true); + + return; + } else { + Gui::TranslatedNotification(Obj, + QObject::tr("Wrong selection"), + QObject::tr("Select exactly one line or one point and one line or two points or two circles from the sketch.")); + + } + } default: break; } diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp index a3161dcec5..190f1febd2 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp +++ b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp @@ -114,6 +114,11 @@ inline int ViewProviderSketchDrawSketchHandlerAttorney::getPreselectCross(const return vp.getPreselectCross(); } +inline void ViewProviderSketchDrawSketchHandlerAttorney::setAngleSnapping(ViewProviderSketch &vp, bool enable, Base::Vector2d referencePoint) +{ + vp.setAngleSnapping(enable, referencePoint); +} + /**************************** CurveConverter **********************************************/ @@ -250,6 +255,7 @@ void DrawSketchHandler::deactivate() drawEditMarkers(std::vector()); resetPositionText(); unsetCursor(); + setAngleSnapping(false); } void DrawSketchHandler::preActivated() @@ -992,3 +998,7 @@ Sketcher::SketchObject * DrawSketchHandler::getSketchObject() return sketchgui->getSketchObject(); } +void DrawSketchHandler::setAngleSnapping(bool enable, Base::Vector2d referencePoint) +{ + ViewProviderSketchDrawSketchHandlerAttorney::setAngleSnapping(*sketchgui, enable, referencePoint); +} \ No newline at end of file diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandler.h b/src/Mod/Sketcher/Gui/DrawSketchHandler.h index 2cd1cf4c5d..750db4e87e 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandler.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandler.h @@ -86,11 +86,13 @@ private: static inline void setAxisPickStyle(ViewProviderSketch &vp, bool on); static inline void moveCursorToSketchPoint(ViewProviderSketch &vp, Base::Vector2d point); static inline void preselectAtPoint(ViewProviderSketch &vp, Base::Vector2d point); + static inline void setAngleSnapping(ViewProviderSketch &vp, bool enable, Base::Vector2d referencePoint = Base::Vector2d(0., 0.)); static inline int getPreselectPoint(const ViewProviderSketch &vp); static inline int getPreselectCurve(const ViewProviderSketch &vp); static inline int getPreselectCross(const ViewProviderSketch &vp); + friend class DrawSketchHandler; }; @@ -203,6 +205,8 @@ protected: Sketcher::SketchObject * getSketchObject(); + void setAngleSnapping(bool enable, Base::Vector2d referencePoint = Base::Vector2d(0., 0.)); + private: void setSvgCursor(const QString &svgName, int x, int y, const std::map& colorMapping = std::map()); diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerArc.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerArc.h index c0cd8ce294..6a4ca2ac2f 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerArc.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerArc.h @@ -131,6 +131,7 @@ public: CenterPoint = onSketchPos; EditCurve.resize(34); EditCurve[0] = onSketchPos; + setAngleSnapping(true, EditCurve[0]); Mode = STATUS_SEEK_Second; } else if (Mode==STATUS_SEEK_Second){ @@ -158,6 +159,7 @@ public: drawEdit(EditCurve); applyCursor(); + setAngleSnapping(false); Mode = STATUS_End; } diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfEllipse.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfEllipse.h index dfe28a105d..4a74f8b386 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfEllipse.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfEllipse.h @@ -176,6 +176,7 @@ public: if (Mode==STATUS_SEEK_First){ EditCurve[0] = onSketchPos; centerPoint = onSketchPos; + setAngleSnapping(true, centerPoint); Mode = STATUS_SEEK_Second; } else if(Mode==STATUS_SEEK_Second) { @@ -192,6 +193,7 @@ public: else { // Fourth endPoint = onSketchPos; + setAngleSnapping(false); Mode = STATUS_Close; } return true; diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerEllipse.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerEllipse.h index 0c2ddc335a..61fade5d12 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerEllipse.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerEllipse.h @@ -216,10 +216,12 @@ public: if (method == PERIAPSIS_APOAPSIS_B) { if (mode == STATUS_SEEK_PERIAPSIS) { periapsis = onSketchPos; + setAngleSnapping(true, periapsis); mode = STATUS_SEEK_APOAPSIS; } else if (mode == STATUS_SEEK_APOAPSIS) { apoapsis = onSketchPos; + setAngleSnapping(false); mode = STATUS_SEEK_B; } else { @@ -228,10 +230,12 @@ public: } else { // method is CENTER_PERIAPSIS_B if (mode == STATUS_SEEK_CENTROID) { centroid = onSketchPos; + setAngleSnapping(true, centroid); mode = STATUS_SEEK_PERIAPSIS; } else if (mode == STATUS_SEEK_PERIAPSIS) { periapsis = onSketchPos; + setAngleSnapping(false); mode = STATUS_SEEK_B; } else { diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerLine.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerLine.h index b8772a3250..eb3fa27ea5 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerLine.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerLine.h @@ -76,11 +76,13 @@ public: if (Mode==STATUS_SEEK_First){ EditCurve[0] = onSketchPos; + setAngleSnapping(true, EditCurve[0]); Mode = STATUS_SEEK_Second; } else { EditCurve[1] = onSketchPos; drawEdit(EditCurve); + setAngleSnapping(false); Mode = STATUS_End; } return true; diff --git a/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.cpp b/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.cpp index c365b2cede..ffeb8283ee 100644 --- a/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.cpp +++ b/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.cpp @@ -62,6 +62,7 @@ #include "SoZoomTranslation.h" #include "ViewProviderSketch.h" #include "ViewProviderSketchCoinAttorney.h" +#include "Utils.h" using namespace SketcherGui; @@ -654,24 +655,33 @@ Restart: if (Constr->SecondPos != Sketcher::PointPos::none) { // point to point distance pnt1 = geolistfacade.getPoint(Constr->First, Constr->FirstPos); pnt2 = geolistfacade.getPoint(Constr->Second, Constr->SecondPos); - } else if (Constr->Second != GeoEnum::GeoUndef) { // point to line distance + } else if (Constr->Second != GeoEnum::GeoUndef) { pnt1 = geolistfacade.getPoint(Constr->First, Constr->FirstPos); const Part::Geometry *geo = geolistfacade.getGeometryFromGeoId(Constr->Second); - if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { // point to line distance const Part::GeomLineSegment *lineSeg = static_cast(geo); Base::Vector3d l2p1 = lineSeg->getStartPoint(); Base::Vector3d l2p2 = lineSeg->getEndPoint(); // calculate the projection of p1 onto line2 pnt2.ProjectToLine(pnt1-l2p1, l2p2-l2p1); pnt2 += pnt1; + + } else if (geo->getTypeId() == Part::GeomCircle::getClassTypeId()) { // circle to circle distance + const Part::Geometry *geo1 = geolistfacade.getGeometryFromGeoId(Constr->First); + if (geo1->getTypeId() == Part::GeomCircle::getClassTypeId()) { + const Part::GeomCircle *circleSeg1 = static_cast(geo1); + auto circleSeg2 = static_cast(geo); + GetCirclesMinimalDistance(circleSeg1, circleSeg2, pnt1, pnt2); + } + } else break; } else if (Constr->FirstPos != Sketcher::PointPos::none) { pnt2 = geolistfacade.getPoint(Constr->First, Constr->FirstPos); } else if (Constr->First != GeoEnum::GeoUndef) { const Part::Geometry *geo = geolistfacade.getGeometryFromGeoId(Constr->First); - if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { // segment distance const Part::GeomLineSegment *lineSeg = static_cast(geo); pnt1 = lineSeg->getStartPoint(); pnt2 = lineSeg->getEndPoint(); diff --git a/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc b/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc index 207021d7e5..8786bcdf87 100644 --- a/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc +++ b/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc @@ -106,6 +106,8 @@ icons/general/Sketcher_ViewSketch.svg icons/general/Sketcher_GridToggle.svg icons/general/Sketcher_GridToggle_Deactivated.svg + icons/general/Sketcher_Snap.svg + icons/general/Sketcher_Snap_Deactivated.svg icons/geometry/Sketcher_AlterFillet.svg diff --git a/src/Mod/Sketcher/Gui/Resources/icons/general/Sketcher_Snap.svg b/src/Mod/Sketcher/Gui/Resources/icons/general/Sketcher_Snap.svg new file mode 100644 index 0000000000..612a8fac4d --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/general/Sketcher_Snap.svg @@ -0,0 +1,386 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + [wmayer] + + + 2011-10-10 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Section.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Sketcher/Gui/Resources/icons/general/Sketcher_Snap_Deactivated.svg b/src/Mod/Sketcher/Gui/Resources/icons/general/Sketcher_Snap_Deactivated.svg new file mode 100644 index 0000000000..833108cf87 --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/general/Sketcher_Snap_Deactivated.svg @@ -0,0 +1,377 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + [wmayer] + + + 2011-10-10 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Section.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Sketcher/Gui/SnapManager.cpp b/src/Mod/Sketcher/Gui/SnapManager.cpp new file mode 100644 index 0000000000..d6385b901d --- /dev/null +++ b/src/Mod/Sketcher/Gui/SnapManager.cpp @@ -0,0 +1,370 @@ +/*************************************************************************** + * Copyright (c) 2023 Pierre-Louis Boyer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#include +#endif // #ifndef _PreComp_ + +#include + +#include "SnapManager.h" +#include "ViewProviderSketch.h" + + +using namespace SketcherGui; +using namespace Sketcher; + +/************************************ Attorney *******************************************/ + +inline int ViewProviderSketchSnapAttorney::getPreselectPoint(const ViewProviderSketch& vp) +{ + return vp.getPreselectPoint(); +} + +inline int ViewProviderSketchSnapAttorney::getPreselectCross(const ViewProviderSketch& vp) +{ + return vp.getPreselectCross(); +} + +inline int ViewProviderSketchSnapAttorney::getPreselectCurve(const ViewProviderSketch& vp) +{ + return vp.getPreselectCurve(); +} + +/**************************** ParameterObserver nested class *****************************/ +SnapManager::ParameterObserver::ParameterObserver(SnapManager& client) : client(client) +{ + initParameters(); + subscribeToParameters(); +} + +SnapManager::ParameterObserver::~ParameterObserver() +{ + unsubscribeToParameters(); +} + +void SnapManager::ParameterObserver::initParameters() +{ + // static map to avoid substantial if/else branching + // + // key->first => String of parameter, + // key->second => Update function to be called for the parameter, + str2updatefunction = { + {"Snap", + [this](const std::string& param) {updateSnapParameter(param); }}, + {"SnapToObjects", + [this](const std::string& param) {updateSnapToObjectParameter(param); }}, + {"SnapToGrid", + [this](const std::string& param) {updateSnapToGridParameter(param); }}, + {"SnapAngle", + [this](const std::string& param) {updateSnapAngleParameter(param); }}, + }; + + for (auto& val : str2updatefunction) { + auto string = val.first; + auto function = val.second; + + function(string); + } +} + +void SnapManager::ParameterObserver::updateSnapParameter(const std::string& parametername) +{ + ParameterGrp::handle hGrp = getParameterGrpHandle(); + + client.snapRequested = hGrp->GetBool(parametername.c_str(), true); +} + +void SnapManager::ParameterObserver::updateSnapToObjectParameter(const std::string& parametername) +{ + ParameterGrp::handle hGrp = getParameterGrpHandle(); + + client.snapToObjectsRequested = hGrp->GetBool(parametername.c_str(), true); +} + +void SnapManager::ParameterObserver::updateSnapToGridParameter(const std::string& parametername) +{ + ParameterGrp::handle hGrp = getParameterGrpHandle(); + + client.snapToGridRequested = hGrp->GetBool(parametername.c_str(), false); +} + +void SnapManager::ParameterObserver::updateSnapAngleParameter(const std::string& parametername) +{ + ParameterGrp::handle hGrp = getParameterGrpHandle(); + + client.snapAngle = fmod(hGrp->GetFloat(parametername.c_str(), 5.) * M_PI / 180, 2 * M_PI); +} + +void SnapManager::ParameterObserver::subscribeToParameters() +{ + try { + ParameterGrp::handle hGrp = getParameterGrpHandle(); + hGrp->Attach(this); + } + catch (const Base::ValueError& e) { // ensure that if parameter strings are not well-formed, the exception is not propagated + Base::Console().Error("SnapManager: Malformed parameter string: %s\n", e.what()); + } +} + +void SnapManager::ParameterObserver::unsubscribeToParameters() +{ + try { + ParameterGrp::handle hGrp = getParameterGrpHandle(); + hGrp->Detach(this); + } + catch (const Base::ValueError& e) {// ensure that if parameter strings are not well-formed, the program is not terminated when calling the noexcept destructor. + Base::Console().Error("SnapManager: Malformed parameter string: %s\n", e.what()); + } +} + +void SnapManager::ParameterObserver::OnChange(Base::Subject& rCaller, const char* sReason) +{ + (void)rCaller; + + auto key = str2updatefunction.find(sReason); + if (key != str2updatefunction.end()) { + auto string = key->first; + auto function = key->second; + + function(string); + } +} + +ParameterGrp::handle SnapManager::ParameterObserver::getParameterGrpHandle() +{ + return App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/Snap"); +} + +//**************************** SnapManager class ****************************** + +SnapManager::SnapManager(ViewProviderSketch &vp):viewProvider(vp), angleSnapRequested(false), referencePoint(Base::Vector2d(0.,0.)), lastMouseAngle(0.0) +{ + // Create parameter observer and initialise watched parameters + pObserver = std::make_unique(*this); +} + +SnapManager::~SnapManager() {} + +bool SnapManager::snap(double& x, double& y) +{ + if (!snapRequested) + { + return false; + } + + //In order of priority : + + // 1 - Snap at an angle + if (angleSnapRequested && QApplication::keyboardModifiers() == Qt::ControlModifier) { + return snapAtAngle(x, y); + } + else { + lastMouseAngle = 0.0; + } + + // 2 - Snap to objects + if (snapToObjectsRequested + && snapToObject(x, y)) { + return true; + } + + // 3 - Snap to grid + if (snapToGridRequested /*&& viewProvider.ShowGrid.getValue() */ ) { //Snap to grid is enabled even if the grid is not visible. + return snapToGrid(x, y); + } + + return false; +} + +bool SnapManager::snapAtAngle(double& x, double& y) +{ + Base::Vector2d pointToOverride(x, y); + double length = (pointToOverride - referencePoint).Length(); + + double angle1 = (pointToOverride - referencePoint).Angle(); + double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI; + lastMouseAngle = abs(angle1 - lastMouseAngle) < abs(angle2 - lastMouseAngle) ? angle1 : angle2; + + double angle = round(lastMouseAngle / snapAngle) * snapAngle; + pointToOverride = referencePoint + length * Base::Vector2d(cos(angle), sin(angle)); + x = pointToOverride.x; + y = pointToOverride.y; + + return true; +} + +bool SnapManager::snapToObject(double& x, double& y) +{ + Sketcher::SketchObject* Obj = viewProvider.getSketchObject(); + int geoId = GeoEnum::GeoUndef; + Sketcher::PointPos posId = Sketcher::PointPos::none; + + int VtId = ViewProviderSketchSnapAttorney::getPreselectPoint(viewProvider); + int CrsId = ViewProviderSketchSnapAttorney::getPreselectCross(viewProvider); + int CrvId = ViewProviderSketchSnapAttorney::getPreselectCurve(viewProvider); + + if (CrsId == 0 || VtId >= 0) { + if (CrsId == 0) { + geoId = Sketcher::GeoEnum::RtPnt; + posId = Sketcher::PointPos::start; + } + else if (VtId >= 0) { + Obj->getGeoVertexIndex(VtId, geoId, posId); + } + + x = Obj->getPoint(geoId, posId).x; + y = Obj->getPoint(geoId, posId).y; + return true; + } + else if (CrsId == 1) { //H_Axis + y = 0; + return true; + } + else if (CrsId == 2) { //V_Axis + x = 0; + return true; + } + else if (CrvId >= 0 || CrvId <= Sketcher::GeoEnum::RefExt) { //Curves + + const Part::Geometry* geo = Obj->getGeometry(CrvId); + + Base::Vector3d pointToOverride(x, y, 0.); + + double pointParam = 0.0; + auto curve = dynamic_cast(geo); + if (curve) { + try { + curve->closestParameter(pointToOverride, pointParam); + pointToOverride = curve->pointAtParameter(pointParam); + } + catch (Base::CADKernelError& e) { + e.ReportException(); + return false; + } + + //If it is a line, then we check if we need to snap to the middle. + if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + const Part::GeomLineSegment* line = static_cast(geo); + snapToLineMiddle(pointToOverride, line); + } + + //If it is an arc, then we check if we need to snap to the middle (not the center). + if (geo->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + const Part::GeomArcOfCircle* arc = static_cast(geo); + snapToArcMiddle(pointToOverride, arc); + } + + x = pointToOverride.x; + y = pointToOverride.y; + + return true; + } + } + + return false; +} + +bool SnapManager::snapToGrid(double& x, double& y) +{ + // Snap Tolerance in pixels + const double snapTol = viewProvider.getGridSize() / 5; + + double tmpX = x, tmpY = y; + + viewProvider.getClosestGridPoint(tmpX, tmpY); + + bool snapped = false; + + // Check if x within snap tolerance + if (x < tmpX + snapTol && x > tmpX - snapTol) { + x = tmpX; // Snap X Mouse Position + snapped = true; + } + + // Check if y within snap tolerance + if (y < tmpY + snapTol && y > tmpY - snapTol) { + y = tmpY; // Snap Y Mouse Position + snapped = true; + } + + return snapped; +} + +bool SnapManager::snapToLineMiddle(Base::Vector3d& pointToOverride, const Part::GeomLineSegment* line) +{ + Base::Vector3d startPoint = line->getStartPoint(); + Base::Vector3d endPoint = line->getEndPoint(); + Base::Vector3d midPoint = (startPoint + endPoint) / 2; + + //Check if we are at middle of the line and if so snap to it. + if ((pointToOverride - midPoint).Length() < (endPoint - startPoint).Length() * 0.05) { + pointToOverride = midPoint; + return true; + } + + return false; +} + +bool SnapManager::snapToArcMiddle(Base::Vector3d& pointToOverride, const Part::GeomArcOfCircle* arc) +{ + Base::Vector3d centerPoint = arc->getCenter(); + Base::Vector3d startVec = (arc->getStartPoint() - centerPoint); + Base::Vector3d middleVec = startVec + (arc->getEndPoint() - centerPoint); + + /* Handle the case of arc angle = 180 */ + if (middleVec.Length() < Precision::Confusion()) { + middleVec.x = startVec.y; + middleVec.y = -startVec.x; + } + else { + middleVec = middleVec / middleVec.Length() * arc->getRadius(); + } + + Base::Vector2d mVec = Base::Vector2d(middleVec.x, middleVec.y); + Base::Vector3d pointVec = pointToOverride - centerPoint; + Base::Vector2d pVec = Base::Vector2d(pointVec.x, pointVec.y); + + double u, v; + arc->getRange(u, v, true); + if (v < u) + v += 2 * M_PI; + double angle = v - u; + int revert = angle < M_PI ? 1 : -1; + + /*To know if we are close to the middle of the arc, we are going to compare the angle of the + * (mouse cursor - center) to the angle of the middle of the arc. If it's less than 10% of the arc angle, then we snap. + */ + if (fabs(pVec.Angle() - (revert * mVec).Angle()) < 0.10 * angle) { + pointToOverride = centerPoint + middleVec * revert; + return true; + } + + return false; +} + +void SnapManager::setAngleSnapping(bool enable, Base::Vector2d referencepoint) +{ + angleSnapRequested = enable; + referencePoint = referencepoint; +} diff --git a/src/Mod/Sketcher/Gui/SnapManager.h b/src/Mod/Sketcher/Gui/SnapManager.h new file mode 100644 index 0000000000..8fc4a7134a --- /dev/null +++ b/src/Mod/Sketcher/Gui/SnapManager.h @@ -0,0 +1,127 @@ +/*************************************************************************** + * Copyright (c) 2023 Pierre-Louis Boyer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + * SnapManager initially funded by the Open Toolchain Foundation * + ***************************************************************************/ + +#ifndef SKETCHERGUI_SnapManager_H +#define SKETCHERGUI_SnapManager_H + + +#include + + +namespace SketcherGui { + +class ViewProviderSketch; + + +class ViewProviderSketchSnapAttorney { +private: + + static inline int getPreselectPoint(const ViewProviderSketch& vp); + static inline int getPreselectCross(const ViewProviderSketch& vp); + static inline int getPreselectCurve(const ViewProviderSketch& vp); + + friend class SnapManager; +}; + +/* This class is used to manage the overriding of mouse pointer coordinates in Sketcher +* (in Edit-Mode) depending on the situation. Those situations are in priority order : +* 1 - Snap at angle: For tools like Slot, Arc, Line, Ellipse, this enables to constrain the angle at steps of 5° (or customized angle). +* This is useful to make features at a certain angle (45° for example) +* 2 - Snap to object: This snaps the mouse pointer onto objects. +* 3 - Snap to grid: This snaps the mouse pointer on the grid. +*/ +class SnapManager +{ + + /** @brief Class for monitoring changes in parameters affecting Snapping + * @details + * + * This nested class is a helper responsible for attaching to the parameters relevant for + * SnapManager, initialising the SnapManager to the current configuration + * and handle in real time any change to their values. + */ + class ParameterObserver : public ParameterGrp::ObserverType + { + public: + explicit ParameterObserver(SnapManager& client); + ~ParameterObserver() override; + + void subscribeToParameters(); + + void unsubscribeToParameters(); + + /** Observer for parameter group. */ + void OnChange(Base::Subject& rCaller, const char* sReason) override; + + private: + void initParameters(); + void updateSnapParameter(const std::string& parametername); + void updateSnapToObjectParameter(const std::string& parametername); + void updateSnapToGridParameter(const std::string& parametername); + void updateSnapAngleParameter(const std::string& parametername); + + static ParameterGrp::handle getParameterGrpHandle(); + + private: + std::map> str2updatefunction; + SnapManager& client; + }; + +public: + explicit SnapManager(ViewProviderSketch &vp); + ~SnapManager(); + + bool snap(double& x, double& y); + bool snapAtAngle(double& x, double& y); + bool snapToObject(double& x, double& y); + bool snapToGrid(double& x, double& y); + + bool snapToLineMiddle(Base::Vector3d& pointToOverride, const Part::GeomLineSegment* line); + bool snapToArcMiddle(Base::Vector3d& pointToOverride, const Part::GeomArcOfCircle* arc); + + void setAngleSnapping(bool enable, Base::Vector2d referencepoint); + +private: + /// Reference to ViewProviderSketch in order to access the public and the Attorney Interface + ViewProviderSketch & viewProvider; + + bool angleSnapRequested; + bool snapRequested; + bool snapToObjectsRequested; + bool snapToGridRequested; + + Base::Vector2d referencePoint; + double lastMouseAngle; + + double snapAngle; + + /// Observer to track all the needed parameters. + std::unique_ptr pObserver; +}; + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_SnapManager_H + diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstraints.ui b/src/Mod/Sketcher/Gui/TaskSketcherConstraints.ui index be68c34d72..0c72fdb6c7 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstraints.ui +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstraints.ui @@ -70,7 +70,7 @@
- + 0 diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp index 7baf45f4a3..dc8e52e1f4 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,29 @@ void ElementView::FUNC(){ \ namespace SketcherGui { + +class ElementItemDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + explicit ElementItemDelegate(ElementView* parent); + ~ElementItemDelegate() override; + + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) override; + + ElementItem* getElementtItem(const QModelIndex& index) const; + + const int border = 1; //1px, looks good around buttons. + const int leftMargin = 4; //4px on the left of icons, looks good. + mutable int customIconsMargin = 4; + const int textBottomMargin = 5; //5px center the text. + +Q_SIGNALS: + void itemHovered(QModelIndex); + void itemChecked(QModelIndex, Qt::CheckState state); +}; + // helper class to store additional information about the listWidget entry. class ElementItem : public QListWidgetItem { @@ -93,7 +117,7 @@ class ElementItem : public QListWidgetItem }; ElementItem(int elementnr, int startingVertex, int midVertex, int endVertex, - Base::Type geometryType, GeometryState state, const QString & lab, const Part::Geometry * geo) : + Base::Type geometryType, GeometryState state, const QString & lab, ViewProviderSketch *sketchView) : ElementNbr(elementnr) , StartingVertex(startingVertex) , MidVertex(midVertex) @@ -108,7 +132,7 @@ class ElementItem : public QListWidgetItem , hovered(SubElementType::none) , rightClicked(false) , label(lab) - , geo(geo) + , sketchView(sketchView) { } @@ -117,9 +141,20 @@ class ElementItem : public QListWidgetItem } bool isVisible() { - auto layer = getSafeGeomLayerId(geo); - return layer != static_cast(Layer::Hidden); + if(State != GeometryState::External) { + const auto geo = sketchView->getSketchObject()->getGeometry(ElementNbr); + if(geo) { + auto layer = getSafeGeomLayerId(geo); + + return layer != static_cast(Layer::Hidden); + } + } + + // 1. external geometry currently is always visible. + // 2. if internal and ElementNbr is out of range, the element + // needs to be updated and the return value is not important. + return true; } int ElementNbr; @@ -142,7 +177,41 @@ class ElementItem : public QListWidgetItem QString label; - const Part::Geometry * geo; + private: + ViewProviderSketch *sketchView; +}; + +class ElementFilterList : public QListWidget +{ + Q_OBJECT + +public: + explicit ElementFilterList(QWidget* parent = nullptr); + ~ElementFilterList() override; + +protected: + void changeEvent(QEvent* e) override; + virtual void languageChange(); + +private: + using filterItemRepr = std::pair; // {filter item text, filter item level} + inline static const std::vector filterItems = { + {QT_TR_NOOP("Normal"),0}, + {QT_TR_NOOP("Construction"),0}, + {QT_TR_NOOP("Internal"),0}, + {QT_TR_NOOP("External"),0}, + {QT_TR_NOOP("All types"),0}, + {QT_TR_NOOP("Point"),1}, + {QT_TR_NOOP("Line"),1}, + {QT_TR_NOOP("Circle"),1}, + {QT_TR_NOOP("Ellipse"),1}, + {QT_TR_NOOP("Arc of circle"),1}, + {QT_TR_NOOP("Arc of ellipse"),1}, + {QT_TR_NOOP("Arc of hyperbola"),1}, + {QT_TR_NOOP("Arc of parabola"),1}, + {QT_TR_NOOP("B-Spline"),1} + }; + }; } // SketcherGui @@ -363,12 +432,19 @@ void ElementView::changeLayer(int layer) bool anychanged = false; for(auto geoid : geoids) { - auto currentlayer = getSafeGeomLayerId(geometry[geoid]); - if( currentlayer != layer) { - auto geo = geometry[geoid]->clone(); - setSafeGeomLayerId(geo, layer); - newgeometry[geoid] = geo; - anychanged = true; + if(geoid >= 0) { // currently only internal geometry can be changed from one layer to another + auto currentlayer = getSafeGeomLayerId(geometry[geoid]); + if( currentlayer != layer) { + auto geo = geometry[geoid]->clone(); + setSafeGeomLayerId(geo, layer); + newgeometry[geoid] = geo; + anychanged = true; + } + } + else { + Gui::TranslatedNotification(sketchobject, + QObject::tr("Unsupported visual layer operation"), + QObject::tr("It is currently unsupported to move external geometry to another visual layer. External geometry will be omitted")); } } @@ -644,6 +720,7 @@ ElementItem* ElementItemDelegate::getElementtItem(const QModelIndex& index) cons } /* Filter element list widget ------------------------------------------------------ */ + enum class GeoFilterType { NormalGeos, ConstructionGeos, @@ -1304,8 +1381,8 @@ void TaskSketcherElements::slotElementsChanged(void) (isNamingBoxChecked ? (tr("Other") + IdInformation()) + (construction ? (QString::fromLatin1("-") + tr("Construction")) : (internalAligned ? (QString::fromLatin1("-") + tr("Internal")) : QString::fromLatin1(""))) : - (QString::fromLatin1("%1-").arg(i) + tr("Other"))) - , (*it) // geometry + (QString::fromLatin1("%1-").arg(i) + tr("Other"))), + sketchView ); ui->listWidgetElements->addItem(itemN); @@ -1396,8 +1473,8 @@ void TaskSketcherElements::slotElementsChanged(void) (QString::fromLatin1("%1-").arg(i - 2) + tr("BSpline"))) : (isNamingBoxChecked ? (tr("Other") + linkname) : - (QString::fromLatin1("%1-").arg(i - 2) + tr("Other"))) - , (*it) // geometry + (QString::fromLatin1("%1-").arg(i - 2) + tr("Other"))), + sketchView ); ui->listWidgetElements->addItem(itemN); @@ -1465,3 +1542,4 @@ void TaskSketcherElements::onSettingsExtendedInformationChanged() } #include "moc_TaskSketcherElements.cpp" +#include "TaskSketcherElements.moc" // For Delegate as it is QOBJECT diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.h b/src/Mod/Sketcher/Gui/TaskSketcherElements.h index 7efa0a7158..5392d0fe85 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.h @@ -55,28 +55,6 @@ enum class SubElementType { none }; -class ElementItemDelegate : public QStyledItemDelegate -{ - Q_OBJECT -public: - explicit ElementItemDelegate(ElementView* parent); - ~ElementItemDelegate() override; - - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; - bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) override; - - ElementItem* getElementtItem(const QModelIndex& index) const; - - const int border = 1; //1px, looks good around buttons. - const int leftMargin = 4; //4px on the left of icons, looks good. - mutable int customIconsMargin = 4; - const int textBottomMargin = 5; //5px center the text. - -Q_SIGNALS: - void itemHovered(QModelIndex); - void itemChecked(QModelIndex, Qt::CheckState state); -}; - class ElementView : public QListWidget { Q_OBJECT @@ -131,38 +109,7 @@ private: void changeLayer(int layer); }; -class ElementFilterList : public QListWidget -{ - Q_OBJECT - -public: - explicit ElementFilterList(QWidget* parent = nullptr); - ~ElementFilterList() override; - -protected: - void changeEvent(QEvent* e) override; - virtual void languageChange(); - -private: - using filterItemRepr = std::pair; // {filter item text, filter item level} - inline static const std::vector filterItems = { - {QT_TR_NOOP("Normal"),0}, - {QT_TR_NOOP("Construction"),0}, - {QT_TR_NOOP("Internal"),0}, - {QT_TR_NOOP("External"),0}, - {QT_TR_NOOP("All types"),0}, - {QT_TR_NOOP("Point"),1}, - {QT_TR_NOOP("Line"),1}, - {QT_TR_NOOP("Circle"),1}, - {QT_TR_NOOP("Ellipse"),1}, - {QT_TR_NOOP("Arc of circle"),1}, - {QT_TR_NOOP("Arc of ellipse"),1}, - {QT_TR_NOOP("Arc of hyperbola"),1}, - {QT_TR_NOOP("Arc of parabola"),1}, - {QT_TR_NOOP("B-Spline"),1} - }; - -}; +class ElementFilterList; class TaskSketcherElements : public Gui::TaskView::TaskBox, public Gui::SelectionObserver { diff --git a/src/Mod/Sketcher/Gui/Utils.cpp b/src/Mod/Sketcher/Gui/Utils.cpp index d4d92316da..daf6c19b3f 100644 --- a/src/Mod/Sketcher/Gui/Utils.cpp +++ b/src/Mod/Sketcher/Gui/Utils.cpp @@ -327,6 +327,39 @@ double SketcherGui::GetPointAngle(const Base::Vector2d& p1, const Base::Vector2d return dY >= 0 ? atan2(dY, dX) : atan2(dY, dX) + 2 * M_PI; } +// Set the two points on circles at minimal distance +// in concentric case set points on relative X axis +void SketcherGui::GetCirclesMinimalDistance(const Part::GeomCircle *circle1, const Part::GeomCircle *circle2, Base::Vector3d &point1, Base::Vector3d &point2) +{ + double radius1 = circle1->getRadius(); + double radius2 = circle2->getRadius(); + + point1 = circle1->getCenter(); + point2 = circle2->getCenter(); + + Base::Vector3d v = point2 - point1; + double length = v.Length(); + + if (length == 0) { //concentric case + point1.x += radius1; + point2.x += radius2; + } else { + v = v.Normalize(); + if (length <= std::max(radius1, radius2)){ //inner case + if (radius1 > radius2){ + point1 += v * radius1; + point2 += v * radius2; + } else { + point1 += -v * radius1; + point2 += -v * radius2; + } + } else { //outer case + point1 += v * radius1; + point2 += -v * radius2; + } + } +} + void SketcherGui::ActivateHandler(Gui::Document* doc, DrawSketchHandler* handler) { std::unique_ptr ptr(handler); diff --git a/src/Mod/Sketcher/Gui/Utils.h b/src/Mod/Sketcher/Gui/Utils.h index 547246a6a2..662d9f941f 100644 --- a/src/Mod/Sketcher/Gui/Utils.h +++ b/src/Mod/Sketcher/Gui/Utils.h @@ -119,6 +119,9 @@ inline bool isEdge(int GeoId, Sketcher::PointPos PosId) // Return counter-clockwise angle from horizontal out of p1 to p2 in radians. double GetPointAngle (const Base::Vector2d &p1, const Base::Vector2d &p2); +// Set the two points on circles at minimal distance +void GetCirclesMinimalDistance(const Part::GeomCircle *circle1, const Part::GeomCircle *circle2, Base::Vector3d &point1, Base::Vector3d &point2); + void ActivateHandler(Gui::Document *doc, DrawSketchHandler *handler); /// Returns if a sketch is in edit mode diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 37fda52260..17febf8329 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -65,6 +65,7 @@ #include "DrawSketchHandler.h" #include "EditDatumDialog.h" #include "EditModeCoinManager.h" +#include "SnapManager.h" #include "TaskDlgEditSketch.h" #include "TaskSketcherValidation.h" #include "Utils.h" @@ -304,6 +305,7 @@ ViewProviderSketch::ViewProviderSketch() Mode(STATUS_NONE), listener(nullptr), editCoinManager(nullptr), + snapManager(nullptr), pObserver(std::make_unique(*this)), sketchHandler(nullptr), viewOrientationFactor(1) @@ -551,36 +553,10 @@ bool ViewProviderSketch::keyPressed(bool pressed, int key) return true; // handle all other key events } -void ViewProviderSketch::setSnapMode(SnapMode mode) +void ViewProviderSketch::setAngleSnapping(bool enable, Base::Vector2d referencePoint) { - snapMode = mode; // to be redirected to SnapManager -} - -SnapMode ViewProviderSketch::getSnapMode() const -{ - return snapMode; // to be redirected to SnapManager -} - -void ViewProviderSketch::snapToGrid(double &x, double &y) // Paddle, when resolving this conflict, make sure to use the function in ViewProviderGridExtension -{ - if (snapMode == SnapMode::SnapToGrid && ShowGrid.getValue()) { - // Snap Tolerance in pixels - const double snapTol = getGridSize() / 5; - - double tmpX = x, tmpY = y; - - getClosestGridPoint(tmpX, tmpY); - - // Check if x within snap tolerance - if (x < tmpX + snapTol && x > tmpX - snapTol) { - x = tmpX; // Snap X Mouse Position - } - - // Check if y within snap tolerance - if (y < tmpY + snapTol && y > tmpY - snapTol) { - y = tmpY; // Snap Y Mouse Position - } - } + assert(snapManager); + snapManager->setAngleSnapping(enable, referencePoint); } void ViewProviderSketch::getProjectingLine(const SbVec2s& pnt, const Gui::View3DInventorViewer *viewer, SbLine& line) const @@ -679,7 +655,7 @@ bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVe try { getCoordsOnSketchPlane(pos,normal,x,y); - snapToGrid(x, y); + snapManager->snap(x, y); } catch (const Base::ZeroDivisionError&) { return false; @@ -1148,7 +1124,7 @@ bool ViewProviderSketch::mouseMove(const SbVec2s &cursorPos, Gui::View3DInventor double x,y; try { getCoordsOnSketchPlane(line.getPosition(),line.getDirection(),x,y); - snapToGrid(x, y); + snapManager->snap(x, y); } catch (const Base::ZeroDivisionError&) { return false; @@ -1275,7 +1251,7 @@ bool ViewProviderSketch::mouseMove(const SbVec2s &cursorPos, Gui::View3DInventor SbLine line2; getProjectingLine(DoubleClick::prvCursorPos, viewer, line2); getCoordsOnSketchPlane(line2.getPosition(),line2.getDirection(),drag.xInit,drag.yInit); - snapToGrid(drag.xInit, drag.yInit); + snapManager->snap(drag.xInit, drag.yInit); } else { drag.resetVector(); } @@ -1416,16 +1392,23 @@ void ViewProviderSketch::moveConstraint(int constNum, const Base::Vector2d &toPo if (Constr->SecondPos != Sketcher::PointPos::none) { // point to point distance p1 = getSolvedSketch().getPoint(Constr->First, Constr->FirstPos); p2 = getSolvedSketch().getPoint(Constr->Second, Constr->SecondPos); - } else if (Constr->Second != GeoEnum::GeoUndef) { // point to line distance + } else if (Constr->Second != GeoEnum::GeoUndef) { p1 = getSolvedSketch().getPoint(Constr->First, Constr->FirstPos); const Part::Geometry *geo = GeoList::getGeometryFromGeoId (geomlist, Constr->Second); - if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { // point to line distance const Part::GeomLineSegment *lineSeg = static_cast(geo); Base::Vector3d l2p1 = lineSeg->getStartPoint(); Base::Vector3d l2p2 = lineSeg->getEndPoint(); // calculate the projection of p1 onto line2 p2.ProjectToLine(p1-l2p1, l2p2-l2p1); p2 += p1; + } else if (geo->getTypeId() == Part::GeomCircle::getClassTypeId()) { // circle to circle distance + const Part::Geometry *geo1 = GeoList::getGeometryFromGeoId (geomlist, Constr->First); + if (geo1->getTypeId() == Part::GeomCircle::getClassTypeId()) { + const Part::GeomCircle *circleSeg1 = static_cast(geo1); + const Part::GeomCircle *circleSeg2 = static_cast(geo); + GetCirclesMinimalDistance(circleSeg1, circleSeg2, p1, p2); + } } else return; } else if (Constr->FirstPos != Sketcher::PointPos::none) { @@ -2870,6 +2853,7 @@ bool ViewProviderSketch::setEdit(int ModNum) preselection.reset(); selection.reset(); editCoinManager = std::make_unique(*this); + snapManager = std::make_unique(*this); auto editDoc = Gui::Application::Instance->editDocument(); App::DocumentObject *editObj = getSketchObject(); @@ -3116,6 +3100,7 @@ void ViewProviderSketch::unsetEdit(int ModNum) deactivateHandler(); editCoinManager = nullptr; + snapManager = nullptr; preselection.reset(); selection.reset(); this->detachSelection(); diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.h b/src/Mod/Sketcher/Gui/ViewProviderSketch.h index eb1765f864..f8280420ff 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.h +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.h @@ -85,15 +85,9 @@ namespace Sketcher { namespace SketcherGui { class EditModeCoinManager; +class SnapManager; class DrawSketchHandler; -enum class SnapMode { // to be moved to SnapManager - None, - SnapToObject, - SnapToAngle, - SnapToGrid, -}; - using GeoList = Sketcher::GeoList; using GeoListFacade = Sketcher::GeoListFacade; @@ -484,8 +478,10 @@ public: void onSelectionChanged(const Gui::SelectionChanges& msg) override; //@} - void setSnapMode(SnapMode mode); - SnapMode getSnapMode() const; + /** @name Toggle angle snapping and set the reference point */ + //@{ + /// Toggle angle snapping and set the reference point + void setAngleSnapping(bool enable, Base::Vector2d referencePoint = Base::Vector2d(0., 0.)); /** @name Access to Sketch and Solver objects */ //@{ @@ -566,6 +562,7 @@ public: //@{ friend class ViewProviderSketchDrawSketchHandlerAttorney; friend class ViewProviderSketchCoinAttorney; + friend class ViewProviderSketchSnapAttorney; friend class ViewProviderSketchShortcutListenerAttorney; //@} protected: @@ -660,9 +657,6 @@ private: /** @name miscelanea utilities */ //@{ - /// snap points x,y (mouse coordinates) onto grid if enabled - void snapToGrid(double &x, double &y); - /// moves a selected constraint void moveConstraint(int constNum, const Base::Vector2d &toPos); @@ -784,6 +778,8 @@ private: std::unique_ptr editCoinManager; + std::unique_ptr snapManager; + std::unique_ptr pObserver; std::unique_ptr sketchHandler; @@ -792,8 +788,6 @@ private: SoNodeSensor cameraSensor; int viewOrientationFactor; // stores if sketch viewed from front or back - - SnapMode snapMode = SnapMode::None; // temporary - to be moved to SnapManager }; } // namespace PartGui diff --git a/src/Mod/Sketcher/Gui/Workbench.cpp b/src/Mod/Sketcher/Gui/Workbench.cpp index 5b8bf0fef0..9babd176dc 100644 --- a/src/Mod/Sketcher/Gui/Workbench.cpp +++ b/src/Mod/Sketcher/Gui/Workbench.cpp @@ -188,7 +188,8 @@ inline void SketcherAddWorkbenchSketchEditModeActions(Gui::ToolBarItem& sketch) sketch << "Sketcher_LeaveSketch" << "Sketcher_ViewSketch" << "Sketcher_ViewSection" - << "Sketcher_Grid"; + << "Sketcher_Grid" + << "Sketcher_Snap"; } template diff --git a/src/Mod/TechDraw/App/CMakeLists.txt b/src/Mod/TechDraw/App/CMakeLists.txt index 8eb26da5f9..52e8f1c43c 100644 --- a/src/Mod/TechDraw/App/CMakeLists.txt +++ b/src/Mod/TechDraw/App/CMakeLists.txt @@ -175,6 +175,8 @@ SET(TechDraw_SRCS TechDrawExport.h ProjectionAlgos.cpp ProjectionAlgos.h + XMLQuery.cpp + XMLQuery.h ) SET(Geometry_SRCS diff --git a/src/Mod/TechDraw/App/DrawSVGTemplate.cpp b/src/Mod/TechDraw/App/DrawSVGTemplate.cpp index f9e58f5704..f6ff7edfb7 100644 --- a/src/Mod/TechDraw/App/DrawSVGTemplate.cpp +++ b/src/Mod/TechDraw/App/DrawSVGTemplate.cpp @@ -26,10 +26,7 @@ #ifndef _PreComp_ # include # include -# include # include -# include -# include #endif #include @@ -44,6 +41,7 @@ #include "DrawSVGTemplate.h" #include "DrawSVGTemplatePy.h" #include "DrawUtil.h" +#include "XMLQuery.h" using namespace TechDraw; @@ -113,72 +111,64 @@ QString DrawSVGTemplate::processTemplate() return QString(); } - QDomDocument templateDocument; - if (!templateDocument.setContent(&templateFile)) { + QDomDocument templateDocument; + if (!templateDocument.setContent(&templateFile)) { Base::Console().Error("DrawSVGTemplate::processTemplate - failed to parse file: %s\n", PageResult.getValue()); - return QString(); - } + return QString(); + } - QXmlQuery query(QXmlQuery::XQuery10); - QDomNodeModel model(query.namePool(), templateDocument); - query.setFocus(QXmlItem(model.fromDomNode(templateDocument.documentElement()))); + XMLQuery query(templateDocument); + std::map substitutions = EditableTexts.getValues(); - // XPath query to select all nodes whose parent - // has "freecad:editable" attribute - query.setQuery(QString::fromUtf8( - "declare default element namespace \"" SVG_NS_URI "\"; " - "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " - "//text[@freecad:editable]/tspan")); + // XPath query to select all nodes whose parent + // has "freecad:editable" attribute + query.processItems(QString::fromUtf8( + "declare default element namespace \"" SVG_NS_URI "\"; " + "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " + "//text[@freecad:editable]/tspan"), + [&substitutions, &templateDocument](QDomElement& tspan) -> bool { + // Replace the editable text spans with new nodes holding actual values + QString editableName = tspan.parentNode().toElement().attribute(QString::fromUtf8("freecad:editable")); + std::map::iterator item = + substitutions.find(editableName.toStdString()); + if (item != substitutions.end()) { + // Keep all spaces in the text node + tspan.setAttribute(QString::fromUtf8("xml:space"), QString::fromUtf8("preserve")); - QXmlResultItems queryResult; - query.evaluateTo(&queryResult); + // Remove all child nodes and append text node with editable replacement as the only descendant + while (!tspan.lastChild().isNull()) { + tspan.removeChild(tspan.lastChild()); + } + tspan.appendChild(templateDocument.createTextNode(QString::fromUtf8(item->second.c_str()))); + } + return true; + }); - std::map substitutions = EditableTexts.getValues(); - while (!queryResult.next().isNull()) - { - QDomElement tspan = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement(); + // Calculate the dimensions of the page and store for retrieval + // Obtain the size of the SVG document by reading the document attributes + QDomElement docElement = templateDocument.documentElement(); + Base::Quantity quantity; - // Replace the editable text spans with new nodes holding actual values - QString editableName = tspan.parentNode().toElement().attribute(QString::fromUtf8("freecad:editable")); - std::map::iterator item = - substitutions.find(std::string(editableName.toUtf8().constData())); - if (item != substitutions.end()) { - // Keep all spaces in the text node - tspan.setAttribute(QString::fromUtf8("xml:space"), QString::fromUtf8("preserve")); + // Obtain the width + QString str = docElement.attribute(QString::fromLatin1("width")); + quantity = Base::Quantity::parse(str); + quantity.setUnit(Base::Unit::Length); - // Remove all child nodes and append text node with editable replacement as the only descendant - while (!tspan.lastChild().isNull()) { - tspan.removeChild(tspan.lastChild()); - } - tspan.appendChild(templateDocument.createTextNode(QString::fromUtf8(item->second.c_str()))); - } - } + Width.setValue(quantity.getValue()); - // Calculate the dimensions of the page and store for retrieval - // Obtain the size of the SVG document by reading the document attributes - QDomElement docElement = templateDocument.documentElement(); - Base::Quantity quantity; + str = docElement.attribute(QString::fromLatin1("height")); + quantity = Base::Quantity::parse(str); + quantity.setUnit(Base::Unit::Length); - // Obtain the width - QString str = docElement.attribute(QString::fromLatin1("width")); - quantity = Base::Quantity::parse(str); - quantity.setUnit(Base::Unit::Length); + Height.setValue(quantity.getValue()); - Width.setValue(quantity.getValue()); + bool isLandscape = getWidth() / getHeight() >= 1.; - str = docElement.attribute(QString::fromLatin1("height")); - quantity = Base::Quantity::parse(str); - quantity.setUnit(Base::Unit::Length); + Orientation.setValue(isLandscape ? 1 : 0); - Height.setValue(quantity.getValue()); - - bool isLandscape = getWidth() / getHeight() >= 1.; - - Orientation.setValue(isLandscape ? 1 : 0); - - //all Qt holds on files should be released on exit #4085 - return templateDocument.toString(); + //all Qt holds on files should be released on exit #4085 + return templateDocument.toString(); } double DrawSVGTemplate::getWidth() const @@ -218,7 +208,7 @@ std::map DrawSVGTemplate::getEditableTextsFromTemplate Base::FileInfo tfi(templateFilename); if (!tfi.isReadable()) { - // if there is a old absolute template file set use a redirect + // if there is an old absolute template file set use a redirect tfi.setFile(App::Application::getResourceDir() + "Mod/Drawing/Templates/" + tfi.fileName()); // try the redirect if (!tfi.isReadable()) { @@ -240,29 +230,22 @@ std::map DrawSVGTemplate::getEditableTextsFromTemplate return editables; } - QXmlQuery query(QXmlQuery::XQuery10); - QDomNodeModel model(query.namePool(), templateDocument, true); - query.setFocus(QXmlItem(model.fromDomNode(templateDocument.documentElement()))); + XMLQuery query(templateDocument); // XPath query to select all nodes whose parent // has "freecad:editable" attribute - query.setQuery(QString::fromUtf8( + query.processItems(QString::fromUtf8( "declare default element namespace \"" SVG_NS_URI "\"; " "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " - "//text[@freecad:editable]/tspan")); - - QXmlResultItems queryResult; - query.evaluateTo(&queryResult); - - while (!queryResult.next().isNull()) { - QDomElement tspan = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement(); - + "//text[@freecad:editable]/tspan"), + [&editables](QDomElement& tspan) -> bool { QString editableName = tspan.parentNode().toElement().attribute(QString::fromUtf8("freecad:editable")); QString editableValue = tspan.firstChild().nodeValue(); editables[std::string(editableName.toUtf8().constData())] = std::string(editableValue.toUtf8().constData()); - } + return true; + }); return editables; } diff --git a/src/Mod/TechDraw/App/DrawViewSymbol.cpp b/src/Mod/TechDraw/App/DrawViewSymbol.cpp index 90dc8aa606..4b83b34b77 100644 --- a/src/Mod/TechDraw/App/DrawViewSymbol.cpp +++ b/src/Mod/TechDraw/App/DrawViewSymbol.cpp @@ -23,11 +23,7 @@ #include "PreCompiled.h" #ifndef _PreComp_ # include - -# include "QDomNodeModel.h" # include -# include -# include #endif #include @@ -36,6 +32,7 @@ #include "DrawViewSymbolPy.h" // generated from DrawViewSymbolPy.xml #include "DrawPage.h" #include "DrawUtil.h" +#include "XMLQuery.h" using namespace TechDraw; @@ -105,30 +102,22 @@ bool DrawViewSymbol::checkFit(TechDraw::DrawPage* p) const std::vector DrawViewSymbol::getEditableFields() { QDomDocument symbolDocument; - QXmlResultItems queryResult; std::vector editables; bool rc = loadQDomDocument(symbolDocument); if (rc) { - QDomElement symbolDocElem = symbolDocument.documentElement(); - QXmlQuery query(QXmlQuery::XQuery10); - QDomNodeModel model(query.namePool(), symbolDocument); - query.setFocus(QXmlItem(model.fromDomNode(symbolDocument.documentElement()))); + XMLQuery query(symbolDocument); // XPath query to select all nodes whose parent // has "freecad:editable" attribute - query.setQuery(QString::fromUtf8("declare default element namespace \"" SVG_NS_URI "\"; " - "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " - "//text[@freecad:editable]/tspan")); - - query.evaluateTo(&queryResult); - - while (!queryResult.next().isNull()) { - QDomElement tspan = - model.toDomNode(queryResult.current().toNodeModelIndex()).toElement(); + query.processItems(QString::fromUtf8("declare default element namespace \"" SVG_NS_URI "\"; " + "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " + "//text[@freecad:editable]/tspan"), + [&editables](QDomElement& tspan) -> bool { QString editableValue = tspan.firstChild().nodeValue(); - editables.emplace_back(editableValue.toUtf8().constData()); - } + editables.emplace_back(editableValue.toStdString()); + return true; + }); } return editables; } @@ -142,27 +131,22 @@ void DrawViewSymbol::updateFieldsInSymbol() } QDomDocument symbolDocument; - QXmlResultItems queryResult; bool rc = loadQDomDocument(symbolDocument); if (rc) { - QDomElement symbolDocElem = symbolDocument.documentElement(); - QXmlQuery query(QXmlQuery::XQuery10); - QDomNodeModel model(query.namePool(), symbolDocument); - query.setFocus(QXmlItem(model.fromDomNode(symbolDocElem))); + XMLQuery query(symbolDocument); + std::size_t count = 0; // XPath query to select all nodes whose parent // has "freecad:editable" attribute - query.setQuery(QString::fromUtf8("declare default element namespace \"" SVG_NS_URI "\"; " - "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " - "//text[@freecad:editable]/tspan")); - query.evaluateTo(&queryResult); - - unsigned int count = 0; - while (!queryResult.next().isNull()) { - QDomElement tspanElement = - model.toDomNode(queryResult.current().toNodeModelIndex()).toElement(); + query.processItems(QString::fromUtf8("declare default element namespace \"" SVG_NS_URI "\"; " + "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " + "//text[@freecad:editable]/tspan"), + [&symbolDocument, &editText, &count](QDomElement& tspanElement) -> bool { + if (count >= editText.size()) { + return false; + } // Keep all spaces in the text node tspanElement.setAttribute(QString::fromUtf8("xml:space"), QString::fromUtf8("preserve")); @@ -174,9 +158,11 @@ void DrawViewSymbol::updateFieldsInSymbol() // Finally append text node with editable replacement as the only descendant tspanElement.appendChild( - symbolDocument.createTextNode(QString::fromUtf8(editText[count].c_str()))); + symbolDocument.createTextNode(QString::fromStdString(editText[count]))); ++count; - } + return true; + }); + Symbol.setValue(symbolDocument.toString(1).toStdString()); } } diff --git a/src/Mod/TechDraw/App/QDomNodeModel.cpp b/src/Mod/TechDraw/App/QDomNodeModel.cpp index 901aeadb40..702902ff34 100644 --- a/src/Mod/TechDraw/App/QDomNodeModel.cpp +++ b/src/Mod/TechDraw/App/QDomNodeModel.cpp @@ -28,6 +28,7 @@ #include #include +#if QT_VERSION < QT_VERSION_CHECK(6,0,0) #include "QDomNodeModel.h" #include #include @@ -359,3 +360,4 @@ QXmlNodeModelIndex QDomNodeModel::nextFromSimpleAxis ( SimpleAxis axis, const QX return QXmlNodeModelIndex(); } +#endif diff --git a/src/Mod/TechDraw/App/XMLQuery.cpp b/src/Mod/TechDraw/App/XMLQuery.cpp new file mode 100644 index 0000000000..9090907c1a --- /dev/null +++ b/src/Mod/TechDraw/App/XMLQuery.cpp @@ -0,0 +1,75 @@ +/*************************************************************************** + * Copyright (c) 2023 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +#if QT_VERSION < QT_VERSION_CHECK(6,0,0) +# include "QDomNodeModel.h" +# include +# include +#endif +#endif + +#include "XMLQuery.h" + + +using namespace TechDraw; + +XMLQuery::XMLQuery(QDomDocument& dom) + : domDocument(dom) +{ + +} + +#if QT_VERSION < QT_VERSION_CHECK(6,0,0) +bool XMLQuery::processItems(const QString& queryStr, const std::function& process) +{ + QXmlQuery query(QXmlQuery::XQuery10); + QDomNodeModel model(query.namePool(), domDocument); + QDomElement symbolDocElem = domDocument.documentElement(); + query.setFocus(QXmlItem(model.fromDomNode(symbolDocElem))); + + query.setQuery(queryStr); + QXmlResultItems queryResult; + query.evaluateTo(&queryResult); + + while (!queryResult.next().isNull()) { + QDomElement tspanElement = + model.toDomNode(queryResult.current().toNodeModelIndex()).toElement(); + if (!process(tspanElement)) { + return false; + } + } + + return true; +} +#else +bool XMLQuery::processItems(const QString& queryStr, const std::function& process) +{ + //TODO: Port to Qt6 + Q_UNUSED(queryStr) + Q_UNUSED(process) + return false; +} +#endif diff --git a/src/Mod/TechDraw/App/XMLQuery.h b/src/Mod/TechDraw/App/XMLQuery.h new file mode 100644 index 0000000000..309fe4c17e --- /dev/null +++ b/src/Mod/TechDraw/App/XMLQuery.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * Copyright (c) 2023 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#ifndef TECHDRAW_XMLQuery_h_ +#define TECHDRAW_XMLQuery_h_ + +#include +#include + +QT_BEGIN_NAMESPACE +class QDomDocument; +class QDomElement; +QT_END_NAMESPACE + +namespace TechDraw +{ + +class TechDrawExport XMLQuery +{ +public: + XMLQuery(QDomDocument&); + bool processItems(const QString& queryStr, const std::function& process); + +private: + QDomDocument& domDocument; +}; + +} //namespace TechDraw + +#endif //TECHDRAW_XMLQuery_h_ diff --git a/src/Mod/TechDraw/Gui/QGISVGTemplate.cpp b/src/Mod/TechDraw/Gui/QGISVGTemplate.cpp index 2cdf7f8bca..298e3ab972 100644 --- a/src/Mod/TechDraw/Gui/QGISVGTemplate.cpp +++ b/src/Mod/TechDraw/Gui/QGISVGTemplate.cpp @@ -29,8 +29,6 @@ # include # include # include -# include -# include #endif// #ifndef _PreComp_ #include @@ -39,7 +37,7 @@ #include #include -#include +#include #include "QGISVGTemplate.h" #include "PreferencesGui.h" @@ -165,20 +163,6 @@ void QGISVGTemplate::createClickHandles() } file.close(); - QDomElement templateDocElem = templateDocument.documentElement(); - - QXmlQuery query(QXmlQuery::XQuery10); - QDomNodeModel model(query.namePool(), templateDocument); - query.setFocus(QXmlItem(model.fromDomNode(templateDocElem))); - - // XPath query to select all nodes with "freecad:editable" attribute - query.setQuery(QString::fromUtf8("declare default element namespace \"" SVG_NS_URI "\"; " - "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " - "//text[@freecad:editable]")); - - QXmlResultItems queryResult; - query.evaluateTo(&queryResult); - //TODO: Find location of special fields (first/third angle) and make graphics items for them Base::Reference hGrp = App::GetApplication() @@ -194,10 +178,13 @@ void QGISVGTemplate::createClickHandles() double width = editClickBoxSize; double height = editClickBoxSize; - while (!queryResult.next().isNull()) { - QDomElement textElement = - model.toDomNode(queryResult.current().toNodeModelIndex()).toElement(); + TechDraw::XMLQuery query(templateDocument); + // XPath query to select all nodes with "freecad:editable" attribute + query.processItems(QString::fromUtf8("declare default element namespace \"" SVG_NS_URI "\"; " + "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; " + "//text[@freecad:editable]"), + [&](QDomElement& textElement) -> bool { QString name = textElement.attribute(QString::fromUtf8("freecad:editable")); double x = Rez::guiX( textElement.attribute(QString::fromUtf8("x"), QString::fromUtf8("0.0")).toDouble()); @@ -207,7 +194,7 @@ void QGISVGTemplate::createClickHandles() if (name.isEmpty()) { Base::Console().Warning( "QGISVGTemplate::createClickHandles - no name for editable text at %f, %f\n", x, y); - continue; + return true; } auto item(new TemplateTextField(this, svgTemplate, name.toStdString())); @@ -229,7 +216,8 @@ void QGISVGTemplate::createClickHandles() addToGroup(item); textFields.push_back(item); - } + return true; + }); } #include diff --git a/src/Mod/TechDraw/TDTest/DrawViewDimensionTest.py b/src/Mod/TechDraw/TDTest/DrawViewDimensionTest.py index 4dab44f35b..8657a42fb8 100644 --- a/src/Mod/TechDraw/TDTest/DrawViewDimensionTest.py +++ b/src/Mod/TechDraw/TDTest/DrawViewDimensionTest.py @@ -32,9 +32,9 @@ class DrawViewDimensionTest(unittest.TestCase): self.view1.Source = [self.document.Sphere] self.view1.X = 220 self.view1.Y = 150 - + self.document.recompute() - + #wait for threads to complete before checking result loop = QtCore.QEventLoop() @@ -53,7 +53,7 @@ class DrawViewDimensionTest(unittest.TestCase): """Tests if a length dimension can be added to view""" # make length dimension print("making length dimension") - + dimension = self.document.addObject("TechDraw::DrawViewDimension", "Dimension") self.page.addView(dimension) dimension.Type = "Distance" diff --git a/src/Mod/TechDraw/TDTest/TechDrawTestUtilities.py b/src/Mod/TechDraw/TDTest/TechDrawTestUtilities.py index b65a634645..532354ce37 100644 --- a/src/Mod/TechDraw/TDTest/TechDrawTestUtilities.py +++ b/src/Mod/TechDraw/TDTest/TechDrawTestUtilities.py @@ -6,7 +6,7 @@ def createPageWithSVGTemplate(doc=None): """Returns a page with an SVGTemplate added on the ActiveDocument""" path = os.path.dirname(os.path.abspath(__file__)) templateFileSpec = path + "/TestTemplate.svg" - + if not doc: doc = FreeCAD.ActiveDocument diff --git a/src/Tools/ThumbnailProvider/Main.cpp b/src/Tools/ThumbnailProvider/Main.cpp index 38cc6a7374..5612f404c2 100644 --- a/src/Tools/ThumbnailProvider/Main.cpp +++ b/src/Tools/ThumbnailProvider/Main.cpp @@ -49,8 +49,8 @@ STDAPI CreateRegistryKeys(REGKEY_SUBKEY_AND_VALUE* aKeys, ULONG cKeys); STDAPI DeleteRegistryKeys(REGKEY_DELETEKEY* aKeys, ULONG cKeys); -BOOL APIENTRY DllMain(HINSTANCE hinstDll, - DWORD dwReason, +BOOL APIENTRY DllMain(HINSTANCE hinstDll, + DWORD dwReason, LPVOID pvReserved) { switch (dwReason) @@ -88,8 +88,8 @@ STDAPI_(ULONG) DllRelease() STDAPI DllRegisterServer() { - // This tells the shell to invalidate the thumbnail cache. This is important because any .recipe files - // viewed before registering this handler would otherwise show cached blank thumbnails. + // This tells the shell to invalidate the thumbnail cache. This is important because any .recipe files + // viewed before registering this handler would otherwise show cached blank thumbnails. SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); WCHAR szModule[MAX_PATH]; @@ -101,12 +101,12 @@ STDAPI DllRegisterServer() REGKEY_SUBKEY_AND_VALUE keys[] = { {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SampleThumbnailProvider, NULL, REG_SZ, (DWORD_PTR)L"FCStd Thumbnail Provider"}, #if 1 - //{HKEY_CLASSES_ROOT, L"CLSID\\DisableProcessIsolation", NULL, REG_DWORD, (DWORD) 1}, - {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SampleThumbnailProvider, L"DisableProcessIsolation", REG_DWORD, (DWORD) 1}, + //{HKEY_CLASSES_ROOT, L"CLSID\\DisableProcessIsolation", NULL, REG_DWORD, (DWORD) 1}, + {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SampleThumbnailProvider, L"DisableProcessIsolation", REG_DWORD, (DWORD) 1}, #endif {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SampleThumbnailProvider L"\\InprocServer32", NULL, REG_SZ, (DWORD_PTR)szModule}, {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SampleThumbnailProvider L"\\InprocServer32", L"ThreadingModel", REG_SZ, (DWORD_PTR)L"Apartment"}, - //{HKEY_CLASSES_ROOT, L".FCStd\\shellex", L"Trick only here to create shellex when not existing",REG_DWORD, 1}, + //{HKEY_CLASSES_ROOT, L".FCStd\\shellex", L"Trick only here to create shellex when not existing",REG_DWORD, 1}, {HKEY_CLASSES_ROOT, L".FCStd\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}", NULL, REG_SZ, (DWORD_PTR)szCLSID_SampleThumbnailProvider}, {HKEY_CLASSES_ROOT, L".FCBak\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}", NULL, REG_SZ, (DWORD_PTR)szCLSID_SampleThumbnailProvider} }; @@ -143,7 +143,7 @@ STDAPI CreateRegistryKey(REGKEY_SUBKEY_AND_VALUE* pKey) cbData += sizeof(WCHAR); } break; - + default: hr = E_INVALIDARG; } diff --git a/src/Tools/ThumbnailProvider/ThumbnailProvider.cpp b/src/Tools/ThumbnailProvider/ThumbnailProvider.cpp index acf1d60b68..b980c7c55e 100644 --- a/src/Tools/ThumbnailProvider/ThumbnailProvider.cpp +++ b/src/Tools/ThumbnailProvider/ThumbnailProvider.cpp @@ -178,7 +178,7 @@ CThumbnailProvider::~CThumbnailProvider() STDMETHODIMP CThumbnailProvider::QueryInterface(REFIID riid, void** ppvObject) { - static const QITAB qit[] = + static const QITAB qit[] = { //QITABENT(CThumbnailProvider, IInitializeWithStream), QITABENT(CThumbnailProvider, IInitializeWithFile), @@ -206,16 +206,16 @@ STDMETHODIMP_(ULONG) CThumbnailProvider::Release() } -STDMETHODIMP CThumbnailProvider::Initialize(IStream *pstm, +STDMETHODIMP CThumbnailProvider::Initialize(IStream *pstm, DWORD grfMode) { return S_OK; } -STDMETHODIMP CThumbnailProvider::Initialize(LPCWSTR pszFilePath, +STDMETHODIMP CThumbnailProvider::Initialize(LPCWSTR pszFilePath, DWORD grfMode) { - wcscpy_s(m_szFile, pszFilePath); + wcscpy_s(m_szFile, pszFilePath); return S_OK; } @@ -235,8 +235,8 @@ bool CThumbnailProvider::CheckZip() const return true; } -STDMETHODIMP CThumbnailProvider::GetThumbnail(UINT cx, - HBITMAP *phbmp, +STDMETHODIMP CThumbnailProvider::GetThumbnail(UINT cx, + HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha) { try { @@ -277,11 +277,11 @@ STDMETHODIMP CThumbnailProvider::GetThumbnail(UINT cx, // or whatever could go wrong } - return NOERROR; + return NOERROR; } -STDMETHODIMP CThumbnailProvider::GetSite(REFIID riid, +STDMETHODIMP CThumbnailProvider::GetSite(REFIID riid, void** ppvSite) { if (m_pSite) diff --git a/tests/src/App/CMakeLists.txt b/tests/src/App/CMakeLists.txt index fbde1bb2fa..c64ae328f5 100644 --- a/tests/src/App/CMakeLists.txt +++ b/tests/src/App/CMakeLists.txt @@ -5,5 +5,6 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/Expression.cpp ${CMAKE_CURRENT_SOURCE_DIR}/IndexedName.cpp ${CMAKE_CURRENT_SOURCE_DIR}/License.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/MappedName.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Metadata.cpp ) diff --git a/tests/src/App/MappedName.cpp b/tests/src/App/MappedName.cpp new file mode 100644 index 0000000000..99f833782c --- /dev/null +++ b/tests/src/App/MappedName.cpp @@ -0,0 +1,832 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gtest/gtest.h" + +#include "App/MappedName.h" +#include "App/ComplexGeoData.h" + +#include + +// NOLINTBEGIN(readability-magic-numbers) + +TEST(MappedName, defaultConstruction) +{ + // Act + Data::MappedName mappedName; + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), true); + EXPECT_EQ(mappedName.size(), 0); + EXPECT_EQ(mappedName.dataBytes(), QByteArray()); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray()); +} + +TEST(MappedName, namedConstruction) +{ + // Act + Data::MappedName mappedName("TEST"); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 4); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray()); +} + +TEST(MappedName, namedConstructionWithMaxSize) +{ + // Act + Data::MappedName mappedName("TEST", 2); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 2); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TE")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray()); +} + +TEST(MappedName, namedConstructionDiscardPrefix) +{ + // Arrange + std::string name = Data::ComplexGeoData::elementMapPrefix() + "TEST"; + + // Act + Data::MappedName mappedName(name.c_str()); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 4); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray()); +} + +TEST(MappedName, stringNamedConstruction) +{ + // Act + Data::MappedName mappedName(std::string("TEST")); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 4); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray()); +} + +TEST(MappedName, stringNamedConstructionDiscardPrefix) +{ + // Arrange + std::string name = Data::ComplexGeoData::elementMapPrefix() + "TEST"; + + // Act + Data::MappedName mappedName(name); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 4); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray()); +} + +TEST(MappedName, constructFromIndexedNameNoIndex) +{ + // Arrange + Data::IndexedName indexedName {"INDEXED_NAME"}; + + // Act + Data::MappedName mappedName {indexedName}; + + // Assert + EXPECT_EQ(mappedName.dataBytes().constData(), indexedName.getType()); // shared memory + EXPECT_EQ(mappedName.isRaw(), true); +} + +TEST(MappedName, constructFromIndexedNameWithIndex) +{ + // Arrange + Data::IndexedName indexedName {"INDEXED_NAME", 1}; + + // Act + Data::MappedName mappedName {indexedName}; + + // Assert + EXPECT_NE(mappedName.dataBytes().constData(), indexedName.getType()); // NOT shared memory + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.toString(), indexedName.toString()); +} + +TEST(MappedName, copyConstructor) +{ + // Arrange + Data::MappedName temp("TEST"); + + // Act + Data::MappedName mappedName(temp); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 4); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray()); +} + +TEST(MappedName, copyConstructorWithPostfix) +{ + // Arrange + Data::MappedName temp("TEST"); + + // Act + Data::MappedName mappedName(temp, "POSTFIXTEST"); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 15); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("POSTFIXTEST")); + + // Act + Data::MappedName mappedName2(mappedName, "ANOTHERPOSTFIX"); + + // Assert + EXPECT_EQ(mappedName2.isRaw(), false); + EXPECT_EQ(mappedName2.empty(), false); + EXPECT_EQ(mappedName2.size(), 29); + EXPECT_EQ(mappedName2.dataBytes(), QByteArray("TESTPOSTFIXTEST")); + EXPECT_EQ(mappedName2.postfixBytes(), QByteArray("ANOTHERPOSTFIX")); +} + +TEST(MappedName, copyConstructorStartpos) +{ + // Arrange + Data::MappedName temp(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act + Data::MappedName mappedName(temp, 2, -1); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 13); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("ST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("POSTFIXTEST")); +} + +TEST(MappedName, copyConstructorStartposAndSize) +{ + // Arrange + Data::MappedName temp(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act + Data::MappedName mappedName(temp, 2, 6); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 6); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("ST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("POST")); +} + +TEST(MappedName, moveConstructor) +{ + // Arrange + Data::MappedName temp(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act + Data::MappedName mappedName(std::move(temp)); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 15); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("POSTFIXTEST")); +} + +TEST(MappedName, fromRawData) +{ + // Act + Data::MappedName mappedName = Data::MappedName::fromRawData("TESTTEST", 10); + + // Assert + EXPECT_EQ(mappedName.isRaw(), true); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 10); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TESTTEST", 10)); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray()); +} + +TEST(MappedName, fromRawDataQByteArray) +{ + // Act + Data::MappedName mappedName = Data::MappedName::fromRawData(QByteArray("TESTTEST", 10)); + + // Assert + EXPECT_EQ(mappedName.isRaw(), true); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 10); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TESTTEST", 10)); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray()); +} + +TEST(MappedName, fromRawDataCopy) +{ + // Arrange + Data::MappedName temp = Data::MappedName::fromRawData(QByteArray("TESTTEST", 10)); + temp.append("TESTPOSTFIX"); + temp.compact(); //Always call compact before accessing data! + + // Act + Data::MappedName mappedName = Data::MappedName::fromRawData(temp, 0); + + // Assert + EXPECT_EQ(mappedName.isRaw(), true); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 21); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TESTTEST", 10)); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("TESTPOSTFIX")); +} + +TEST(MappedName, fromRawDataCopyStartposAndSize) +{ + // Arrange + Data::MappedName temp = Data::MappedName::fromRawData(QByteArray("TESTTEST", 8)); + temp.append("ABCDEFGHIJKLM"); //postfix + temp.compact(); //Always call compact before accessing data! + + // Act + Data::MappedName mappedName = Data::MappedName::fromRawData(temp, 2, 13); + + // Assert + EXPECT_EQ(mappedName.isRaw(), true); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 13); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("STTEST", 6)); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("ABCDEFG")); +} + +TEST(MappedName, assignmentOperator) +{ + // Arrange + Data::MappedName temp(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act + Data::MappedName mappedName = temp; + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 15); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("POSTFIXTEST")); +} + +TEST(MappedName, assignmentOperatorString) +{ + // Arrange + Data::MappedName mappedName; + + // Act + mappedName = std::string("TEST"); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 4); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray()); +} + +TEST(MappedName, assignmentOperatorConstCharPtr) +{ + // Arrange + Data::MappedName mappedName; + + // Act + mappedName = "TEST"; + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 4); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray()); +} + +TEST(MappedName, assignmentOperatorMove) +{ + // Arrange + Data::MappedName temp(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act + Data::MappedName mappedName = std::move(temp); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 15); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("POSTFIXTEST")); +} + +TEST(MappedName, streamInsertionOperator) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + std::stringstream ss; + + // Act + ss << mappedName; + + // Assert + EXPECT_EQ(ss.str(), std::string("TESTPOSTFIXTEST")); +} + +TEST(MappedName, comparisonOperators) +{ + // Arrange + Data::MappedName mappedName1(Data::MappedName("TEST"), "POSTFIXTEST"); + Data::MappedName mappedName2(Data::MappedName("TEST"), "POSTFIXTEST"); + Data::MappedName mappedName3(Data::MappedName("TESTPOST"), "FIXTEST"); + Data::MappedName mappedName4(Data::MappedName("THIS"), "ISDIFFERENT"); + + // Act & Assert + EXPECT_EQ(mappedName1 == mappedName1, true); + EXPECT_EQ(mappedName1 == mappedName2, true); + EXPECT_EQ(mappedName1 == mappedName3, true); + EXPECT_EQ(mappedName1 == mappedName4, false); + + EXPECT_EQ(mappedName1 != mappedName1, false); + EXPECT_EQ(mappedName1 != mappedName2, false); + EXPECT_EQ(mappedName1 != mappedName3, false); + EXPECT_EQ(mappedName1 != mappedName4, true); +} + +TEST(MappedName, additionOperators) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act + mappedName += "POST1"; + mappedName += std::string("POST2"); + mappedName += QByteArray("POST3"); + mappedName += Data::MappedName("POST4"); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 35); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("POSTFIXTESTPOST1POST2POST3POST4")); + + // Arrange + mappedName = Data::MappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act + mappedName = mappedName + Data::MappedName("POST5"); + mappedName = mappedName + "POST6"; + mappedName = mappedName + std::string("POST7"); + mappedName = mappedName + QByteArray("POST8"); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 35); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("POSTFIXTESTPOST5POST6POST7POST8")); +} + +TEST(MappedName, append) +{ + // Arrange + Data::MappedName mappedName; + + // Act + mappedName.append("TEST"); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 4); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("")); + + // Act + mappedName.append("POSTFIX"); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 11); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("POSTFIX")); + + // Act + mappedName.append("ANOTHERPOSTFIX", 5); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 16); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("POSTFIXANOTH")); +} + +TEST(MappedName, appendMappedNameObj) +{ + // Arrange + Data::MappedName mappedName; + Data::MappedName temp(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act + mappedName.append(temp); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 15); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("POSTFIXTEST")); + + // Act + mappedName.append(temp, 2, 7); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 22); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TEST")); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray("POSTFIXTESTSTPOSTF")); +} + +TEST(MappedName, toString) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act & Assert + EXPECT_EQ(mappedName.toString(0), "TESTPOSTFIXTEST"); + EXPECT_EQ(mappedName.toString(0), std::string("TESTPOSTFIXTEST")); + EXPECT_EQ(mappedName.toString(2, 8), "STPOSTFI"); + EXPECT_EQ(mappedName.toString(2, 8), std::string("STPOSTFI")); +} + +TEST(MappedName, toConstString) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + int size{0}; + + // Act + const char *temp = mappedName.toConstString(0, size); + + // Assert + EXPECT_EQ(QByteArray(temp, size), QByteArray("TEST")); + EXPECT_EQ(size, 4); + + // Act + const char *temp2 = mappedName.toConstString(7, size); + + // Assert + EXPECT_EQ(QByteArray(temp2, size), QByteArray("TFIXTEST")); + EXPECT_EQ(size, 8); +} + +TEST(MappedName, toRawBytes) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act & Assert + EXPECT_EQ(mappedName.toRawBytes(), QByteArray("TESTPOSTFIXTEST")); + EXPECT_EQ(mappedName.toRawBytes(3), QByteArray("TPOSTFIXTEST")); + EXPECT_EQ(mappedName.toRawBytes(7, 3), QByteArray("TFI")); + EXPECT_EQ(mappedName.toRawBytes(502, 5), QByteArray()); +} + +TEST(MappedName, toIndexedNameASCIIOnly) +{ + // Arrange + Data::MappedName mappedName {"MAPPED_NAME"}; + + // Act + auto indexedName = mappedName.toIndexedName(); + + // Assert + EXPECT_FALSE(indexedName.isNull()); +} + +TEST(MappedName, toIndexedNameInvalid) +{ + // Arrange + Data::MappedName mappedName {"MAPPED-NAME"}; + + // Act + auto indexedName = mappedName.toIndexedName(); + + // Assert + EXPECT_TRUE(indexedName.isNull()); +} + +TEST(MappedName, appendToBuffer) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + std::string buffer("STUFF"); + + // Act + mappedName.appendToBuffer(buffer); + + // Assert + EXPECT_EQ(buffer, std::string("STUFFTESTPOSTFIXTEST")); + + // Act + mappedName.appendToBuffer(buffer, 2, 7); + + // Assert + EXPECT_EQ(buffer, std::string("STUFFTESTPOSTFIXTESTSTPOSTF")); +} + +TEST(MappedName, appendToBufferWithPrefix) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + std::string buffer("STUFF"); + std::string elemMapPrefix = Data::ComplexGeoData::elementMapPrefix(); + + // Act + mappedName.appendToBufferWithPrefix(buffer); + + // Assert + EXPECT_EQ(buffer, std::string("STUFF") + elemMapPrefix + std::string("TESTPOSTFIXTEST")); + + // Arrange + Data::MappedName mappedName2("TEST"); //If mappedName does not have a postfix and is a valid indexedName: prefix is not added + + // Act + mappedName2.appendToBufferWithPrefix(buffer); + + // Assert + EXPECT_EQ(buffer, std::string("STUFF") + elemMapPrefix + std::string("TESTPOSTFIXTEST") + /*missing prefix*/ std::string("TEST")); +} + +TEST(MappedName, toPrefixedString) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + std::string buffer("STUFF"); + std::string elemMapPrefix = Data::ComplexGeoData::elementMapPrefix(); + + // Act + buffer += mappedName.toPrefixedString(); + + // Assert + EXPECT_EQ(buffer, std::string("STUFF") + elemMapPrefix + std::string("TESTPOSTFIXTEST")); + + // Arrange + Data::MappedName mappedName2("TEST"); //If mappedName does not have a postfix and is a valid indexedName: prefix is not added + + // Act + buffer += mappedName2.toPrefixedString(); + + // Assert + EXPECT_EQ(buffer, std::string("STUFF") + elemMapPrefix + std::string("TESTPOSTFIXTEST") + /*missing prefix*/ std::string("TEST")); +} + +TEST(MappedName, toBytes) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act & Assert + EXPECT_EQ(mappedName.toBytes(), QByteArray("TESTPOSTFIXTEST")); +} + +TEST(MappedName, compare) +{ + // Arrange + Data::MappedName mappedName1(Data::MappedName("TEST"), "POSTFIXTEST"); + Data::MappedName mappedName2(Data::MappedName("TEST"), "POSTFIXTEST"); + Data::MappedName mappedName3(Data::MappedName("TESTPOST"), "FIXTEST"); + Data::MappedName mappedName4(Data::MappedName("THIS"), "ISDIFFERENT"); + Data::MappedName mappedName5(Data::MappedName("SH"), "ORTHER"); + Data::MappedName mappedName6(Data::MappedName("VERYVERYVERY"), "VERYMUCHLONGER"); + + // Act & Assert + EXPECT_EQ(mappedName1.compare(mappedName1), 0); + EXPECT_EQ(mappedName1.compare(mappedName2), 0); + EXPECT_EQ(mappedName1.compare(mappedName3), 0); + EXPECT_EQ(mappedName1.compare(mappedName4), -1); + EXPECT_EQ(mappedName1.compare(mappedName5), 1); + EXPECT_EQ(mappedName1.compare(mappedName6), -1); + + EXPECT_EQ(mappedName1 < mappedName1, false); + EXPECT_EQ(mappedName1 < mappedName2, false); + EXPECT_EQ(mappedName1 < mappedName3, false); + EXPECT_EQ(mappedName1 < mappedName4, true); + EXPECT_EQ(mappedName1 < mappedName5, false); + EXPECT_EQ(mappedName1 < mappedName6, true); +} + +TEST(MappedName, subscriptOperator) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act & Assert + EXPECT_EQ(mappedName[0], 'T'); + EXPECT_EQ(mappedName[1], 'E'); + EXPECT_EQ(mappedName[2], 'S'); + EXPECT_EQ(mappedName[3], 'T'); + EXPECT_EQ(mappedName[4], 'P'); + EXPECT_EQ(mappedName[5], 'O'); + EXPECT_EQ(mappedName[6], 'S'); + EXPECT_EQ(mappedName[7], 'T'); + EXPECT_EQ(mappedName[8], 'F'); + EXPECT_EQ(mappedName[9], 'I'); +} + +TEST(MappedName, copy) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act + Data::MappedName mappedName2 = mappedName.copy(); + + // Assert + EXPECT_EQ(mappedName, mappedName2); +} + +TEST(MappedName, compact) +{ + // Arrange + Data::MappedName mappedName = Data::MappedName::fromRawData("TESTTEST", 10); + + // Act + mappedName.compact(); + + // Assert + EXPECT_EQ(mappedName.isRaw(), false); + EXPECT_EQ(mappedName.empty(), false); + EXPECT_EQ(mappedName.size(), 10); + EXPECT_EQ(mappedName.dataBytes(), QByteArray("TESTTEST", 10)); + EXPECT_EQ(mappedName.postfixBytes(), QByteArray()); +} + +TEST(MappedName, boolOperator) +{ + // Arrange + Data::MappedName mappedName; + + // Act & Assert + EXPECT_EQ((bool)mappedName, false); + + // Arrange + mappedName.append("TEST"); + + // Act & Assert + EXPECT_EQ((bool)mappedName, true); +} + +TEST(MappedName, clear) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act + mappedName.clear(); + + // Assert + EXPECT_EQ(mappedName.empty(), true); +} + +TEST(MappedName, find) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act & Assert + EXPECT_EQ(mappedName.find(nullptr), -1); + EXPECT_EQ(mappedName.find(""), 0); + EXPECT_EQ(mappedName.find(std::string("")), 0); + EXPECT_EQ(mappedName.find("TEST"), 0); + EXPECT_EQ(mappedName.find("STPO"), -1); //sentence must be fully contained in data or postfix + EXPECT_EQ(mappedName.find("POST"), 4); + EXPECT_EQ(mappedName.find("POST", 4), 4); + EXPECT_EQ(mappedName.find("POST", 5), -1); + + EXPECT_EQ(mappedName.rfind("ST"), 13); + EXPECT_EQ(mappedName.rfind("ST", 15), 13); + EXPECT_EQ(mappedName.rfind("ST", 14), 13); + EXPECT_EQ(mappedName.rfind("ST", 13), 13); + EXPECT_EQ(mappedName.rfind("ST", 12), 6); + EXPECT_EQ(mappedName.rfind("ST", 11), 6); + EXPECT_EQ(mappedName.rfind("ST", 10), 6); + EXPECT_EQ(mappedName.rfind("ST", 9), 6); + EXPECT_EQ(mappedName.rfind("ST", 8), 6); + EXPECT_EQ(mappedName.rfind("ST", 7), 6); + EXPECT_EQ(mappedName.rfind("ST", 6), 6); + EXPECT_EQ(mappedName.rfind("ST", 5), 2); + EXPECT_EQ(mappedName.rfind("ST", 4), 2); + EXPECT_EQ(mappedName.rfind("ST", 3), 2); + EXPECT_EQ(mappedName.rfind("ST", 2), 2); + EXPECT_EQ(mappedName.rfind("ST", 1), -1); + EXPECT_EQ(mappedName.rfind("ST", 0), -1); +} + +TEST(MappedName, rfind) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act & Assert + EXPECT_EQ(mappedName.rfind(nullptr), -1); + EXPECT_EQ(mappedName.rfind(""), mappedName.size()); + EXPECT_EQ(mappedName.rfind(std::string("")), mappedName.size()); + EXPECT_EQ(mappedName.rfind("TEST"), 11); + EXPECT_EQ(mappedName.rfind("STPO"), -1); //sentence must be fully contained in data or postfix + EXPECT_EQ(mappedName.rfind("POST"), 4); + EXPECT_EQ(mappedName.rfind("POST", 4), 4); + EXPECT_EQ(mappedName.rfind("POST", 3), -1); + + EXPECT_EQ(mappedName.rfind("ST"), 13); + EXPECT_EQ(mappedName.rfind("ST", 0), -1); + EXPECT_EQ(mappedName.rfind("ST", 1), -1); + EXPECT_EQ(mappedName.rfind("ST", 2), 2); + EXPECT_EQ(mappedName.rfind("ST", 3), 2); + EXPECT_EQ(mappedName.rfind("ST", 4), 2); + EXPECT_EQ(mappedName.rfind("ST", 5), 2); + EXPECT_EQ(mappedName.rfind("ST", 6), 6); + EXPECT_EQ(mappedName.rfind("ST", 7), 6); + EXPECT_EQ(mappedName.rfind("ST", 8), 6); + EXPECT_EQ(mappedName.rfind("ST", 9), 6); + EXPECT_EQ(mappedName.rfind("ST", 10), 6); + EXPECT_EQ(mappedName.rfind("ST", 11), 6); + EXPECT_EQ(mappedName.rfind("ST", 12), 6); + EXPECT_EQ(mappedName.rfind("ST", 13), 13); + EXPECT_EQ(mappedName.rfind("ST", 14), 13); + EXPECT_EQ(mappedName.rfind("ST", 15), 13); +} + +TEST(MappedName, endswith) +{ + // Arrange + Data::MappedName mappedName("TEST"); + + // Act & Assert + EXPECT_EQ(mappedName.endsWith(nullptr), false); + EXPECT_EQ(mappedName.endsWith("TEST"), true); + EXPECT_EQ(mappedName.endsWith(std::string("TEST")), true); + EXPECT_EQ(mappedName.endsWith("WASD"), false); + + // Arrange + mappedName.append("POSTFIX"); + + // Act & Assert + EXPECT_EQ(mappedName.endsWith(nullptr), false); + EXPECT_EQ(mappedName.endsWith("TEST"), false); + EXPECT_EQ(mappedName.endsWith("FIX"), true); +} + +TEST(MappedName, startsWith) +{ + // Arrange + Data::MappedName mappedName; + + // Act & Assert + EXPECT_EQ(mappedName.startsWith(nullptr), false); + EXPECT_EQ(mappedName.startsWith(QByteArray()), true); + EXPECT_EQ(mappedName.startsWith(""), true); + EXPECT_EQ(mappedName.startsWith(std::string("")), true); + EXPECT_EQ(mappedName.startsWith("WASD"), false); + + // Arrange + mappedName.append("TEST"); + + // Act & Assert + EXPECT_EQ(mappedName.startsWith(nullptr), false); + EXPECT_EQ(mappedName.startsWith(QByteArray()), true); + EXPECT_EQ(mappedName.startsWith("TEST"), true); + EXPECT_EQ(mappedName.startsWith(std::string("TEST")), true); + EXPECT_EQ(mappedName.startsWith("WASD"), false); +} + +TEST(MappedName, hash) +{ + // Arrange + Data::MappedName mappedName(Data::MappedName("TEST"), "POSTFIXTEST"); + + // Act & Assert + EXPECT_EQ(mappedName.hash(), qHash(QByteArray("TEST"), qHash(QByteArray("POSTFIXTEST")))); +} + +// NOLINTEND(readability-magic-numbers)