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 @@
00
- 400
- 300
+ 300
+ 197Constraint 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 @@
velocityXBoxtoggled(bool)
- velocityXTxt
- setEnabled(bool)
-
-
- 230
- 44
-
-
- 230
- 18
-
-
-
-
- velocityXBox
- toggled(bool)
- velocityXTxt
+ formulaXCBsetDisabled(bool)
- 230
- 44
+ 351
+ 19
- 230
- 18
+ 351
+ 45velocityYBoxtoggled(bool)
- velocityYTxt
- setEnabled(bool)
-
-
- 347
- 53
-
-
- 184
- 53
-
-
-
-
- velocityYBox
- toggled(bool)
- velocityYTxt
+ formulaYCBsetDisabled(bool)
- 347
- 53
+ 351
+ 73
- 184
- 53
+ 351
+ 99velocityZBoxtoggled(bool)
- velocityZTxt
- setEnabled(bool)
-
-
- 347
- 87
-
-
- 184
- 87
-
-
-
-
- velocityZBox
- toggled(bool)
- velocityZTxt
+ formulaZCBsetDisabled(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 @@
00
- 400
- 300
+ 300
+ 174Constraint 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 @@
velocityXBoxtoggled(bool)
- velocityXTxt
- setEnabled(bool)
-
-
- 230
- 44
-
-
- 230
- 18
-
-
-
-
- velocityXBox
- toggled(bool)
- velocityXTxt
+ formulaXCBsetDisabled(bool)
- 230
- 44
+ 351
+ 19
- 230
- 18
+ 351
+ 45velocityYBoxtoggled(bool)
- velocityYTxt
- setEnabled(bool)
-
-
- 347
- 53
-
-
- 184
- 53
-
-
-
-
- velocityYBox
- toggled(bool)
- velocityYTxt
+ formulaYCBsetDisabled(bool)
- 347
- 53
+ 351
+ 73
- 184
- 53
+ 351
+ 99velocityZBoxtoggled(bool)
- velocityZTxt
- setEnabled(bool)
-
-
- 347
- 87
-
-
- 184
- 87
-
-
-
-
- velocityZBox
- toggled(bool)
- velocityZTxt
+ formulaZCBsetDisabled(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.svgicons/general/Sketcher_GridToggle.svgicons/general/Sketcher_GridToggle_Deactivated.svg
+ icons/general/Sketcher_Snap.svg
+ icons/general/Sketcher_Snap_Deactivated.svgicons/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 @@
+
+
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 @@
+
+
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)