Merge branch 'master' into expression-colors

This commit is contained in:
David Holdeman
2023-03-20 18:27:55 -05:00
committed by GitHub
88 changed files with 5937 additions and 1403 deletions
+1
View File
@@ -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();
+2
View File
@@ -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
)
+53
View File
@@ -0,0 +1,53 @@
/****************************************************************************
* Copyright (c) 2020 Zheng, Lei (realthunder) <[email protected]>*
* *
* 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 <unordered_set>
#endif
//#include <boost/functional/hash.hpp>
#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<QByteArray, ByteArrayHasher> PostfixSet;
if (this->postfix.size()) {
auto res = PostfixSet.insert(this->postfix);
if (!res.second)
self->postfix = *res.first;
}
#endif
}
+908
View File
@@ -0,0 +1,908 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* Copyright (c) 2022 Zheng, Lei (realthunder) <[email protected]>*
* 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 *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#ifndef APP_MAPPED_NAME_H
#define APP_MAPPED_NAME_H
#include <string>
#include <boost/algorithm/string/predicate.hpp>
#include <QByteArray>
#include <QHash>
#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<int>(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<int>(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<int>(other.size()));
this->postfix.append(other.c_str(), static_cast<int>(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<int>(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<int>(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<int>(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
+3
View File
@@ -103,6 +103,9 @@
</item>
<item row="1" column="2" colspan="2">
<widget class="Gui::AccelLineEdit" name="accelLineEditShortcut">
<property name="toolTip">
<string>To change a current shortcut enter the new shortcut in the field below and press 'Assign'.</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
+10 -3
View File
@@ -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("</table></p>");
// 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);
}
}
+34 -14
View File
@@ -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<NotificationLabel> instance;
@@ -91,6 +93,8 @@ private:
int minShowTime;
QTimer hideTimer;
QTimer expireTimer;
QRect restrictionArea;
};
qobject_delete_later_unique_ptr<NotificationLabel> 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"));
+8 -3
View File
@@ -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()
{
+1 -1
View File
@@ -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<App::DocumentObject, std::remove_pointer_t<typename std::decay<TNotifier>::type>> ) {
Base::Console().Send<type>(notifier->getFullLabel(), msg.toUtf8());
+25 -33
View File
@@ -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());
+4 -4
View File
@@ -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.
-2
View File
@@ -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 */
-2
View File
@@ -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 */
-2
View File
@@ -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 */
-2
View File
@@ -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 */
-2
View File
@@ -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 */
-2
View File
@@ -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 */
-2
View File
@@ -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 */
-2
View File
@@ -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 */
-2
View File
@@ -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 */
-2
View File
@@ -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 */
-2
View File
@@ -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 */
-2
View File
@@ -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 */
+2 -3
View File
@@ -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<std::string>(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));
+7
View File
@@ -33,6 +33,7 @@
# include <QMenu>
# include <QMessageBox>
# include <QPixmap>
# include <QThread>
# include <QTimer>
# include <QToolTip>
# include <QVBoxLayout>
@@ -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();
+26 -15
View File
@@ -24,6 +24,7 @@
#ifndef _PreComp_
# include <QAction>
# include <QActionGroup>
# include <QCoreApplication>
# include <QDir>
# include <QFile>
# include <QLayout>
@@ -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> 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<UiLoader> 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());
}
+24 -2
View File
@@ -34,6 +34,7 @@
#endif
#include <CXX/Extensions.hxx>
#include <memory>
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<UiLoader> newInstance(QObject *parent=0);
~UiLoader() override;
/**
@@ -149,7 +171,7 @@ private:
static PyObject *PyMake(struct _typeobject *, PyObject *, PyObject *);
private:
UiLoader loader;
std::unique_ptr<UiLoader> loader;
};
} // namespace Gui
+2 -3
View File
@@ -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 (...) {
+1
View File
@@ -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
+137 -143
View File
@@ -6,78 +6,27 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
<width>300</width>
<height>197</height>
</rect>
</property>
<property name="windowTitle">
<string>Constraint Properties</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="1" column="0">
<widget class="QLabel" name="volocityXLbl">
<property name="text">
<string>Velocity x:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="velocityYLbl">
<property name="text">
<string>Velocity y:</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="velocityZLbl">
<property name="text">
<string>Velocity z:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="Gui::InputField" name="velocityZTxt">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="3">
<widget class="QCheckBox" name="formulaXCB">
<property name="enabled">
<bool>false</bool>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="unit" stdset="0">
<string>m/s</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="velocityZBox">
<property name="text">
<string>unspecified</string>
</property>
<property name="checked">
<bool>true</bool>
<string>formula</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="Gui::InputField" name="velocityXTxt">
<property name="enabled">
<bool>false</bool>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="unit" stdset="0">
<string>m/s</string>
</property>
</widget>
</item>
<item>
<item row="0" column="3">
<widget class="QCheckBox" name="velocityXBox">
<property name="text">
<string>unspecified</string>
@@ -87,24 +36,52 @@
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="Gui::InputField" name="velocityYTxt">
<property name="enabled">
<bool>false</bool>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="unit" stdset="0">
<string>m/s</string>
<item row="0" column="0">
<widget class="QLabel" name="volocityXLbl">
<property name="text">
<string>Velocity x:</string>
</property>
</widget>
</item>
<item>
<item row="1" column="1">
<widget class="QLineEdit" name="formulaX">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::QuantitySpinBox" name="velocityX">
<property name="enabled">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="3">
<widget class="QCheckBox" name="formulaYCB">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>formula</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="velocityYLbl">
<property name="text">
<string>Velocity y:</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QCheckBox" name="velocityYBox">
<property name="text">
<string>unspecified</string>
@@ -114,12 +91,77 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="formulaY">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::QuantitySpinBox" name="velocityY">
<property name="enabled">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="0" colspan="2">
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="3">
<widget class="QCheckBox" name="velocityZBox">
<property name="text">
<string>unspecified</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QCheckBox" name="formulaZCB">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>formula</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="velocityZLbl">
<property name="text">
<string>Velocity z:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="formulaZ">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::QuantitySpinBox" name="velocityZ">
<property name="enabled">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="normalBox">
<property name="text">
<string>normal to boundary</string>
<string>Normal to boundary</string>
</property>
</widget>
</item>
@@ -127,9 +169,9 @@
</widget>
<customwidgets>
<customwidget>
<class>Gui::InputField</class>
<extends>QLineEdit</extends>
<header>Gui/InputField.h</header>
<class>Gui::QuantitySpinBox</class>
<extends>QWidget</extends>
<header>Gui/QuantitySpinBox.h</header>
</customwidget>
</customwidgets>
<resources/>
@@ -137,96 +179,48 @@
<connection>
<sender>velocityXBox</sender>
<signal>toggled(bool)</signal>
<receiver>velocityXTxt</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>230</x>
<y>44</y>
</hint>
<hint type="destinationlabel">
<x>230</x>
<y>18</y>
</hint>
</hints>
</connection>
<connection>
<sender>velocityXBox</sender>
<signal>toggled(bool)</signal>
<receiver>velocityXTxt</receiver>
<receiver>formulaXCB</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>230</x>
<y>44</y>
<x>351</x>
<y>19</y>
</hint>
<hint type="destinationlabel">
<x>230</x>
<y>18</y>
<x>351</x>
<y>45</y>
</hint>
</hints>
</connection>
<connection>
<sender>velocityYBox</sender>
<signal>toggled(bool)</signal>
<receiver>velocityYTxt</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>347</x>
<y>53</y>
</hint>
<hint type="destinationlabel">
<x>184</x>
<y>53</y>
</hint>
</hints>
</connection>
<connection>
<sender>velocityYBox</sender>
<signal>toggled(bool)</signal>
<receiver>velocityYTxt</receiver>
<receiver>formulaYCB</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>347</x>
<y>53</y>
<x>351</x>
<y>73</y>
</hint>
<hint type="destinationlabel">
<x>184</x>
<y>53</y>
<x>351</x>
<y>99</y>
</hint>
</hints>
</connection>
<connection>
<sender>velocityZBox</sender>
<signal>toggled(bool)</signal>
<receiver>velocityZTxt</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>347</x>
<y>87</y>
</hint>
<hint type="destinationlabel">
<x>184</x>
<y>87</y>
</hint>
</hints>
</connection>
<connection>
<sender>velocityZBox</sender>
<signal>toggled(bool)</signal>
<receiver>velocityZTxt</receiver>
<receiver>formulaZCB</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>347</x>
<y>87</y>
<x>351</x>
<y>127</y>
</hint>
<hint type="destinationlabel">
<x>184</x>
<y>87</y>
<x>351</x>
<y>153</y>
</hint>
</hints>
</connection>
@@ -6,40 +6,27 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
<width>300</width>
<height>174</height>
</rect>
</property>
<property name="windowTitle">
<string>Constraint Properties</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="1" column="0">
<widget class="QLabel" name="volocityXLbl">
<property name="text">
<string>Velocity x:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="Gui::InputField" name="velocityXTxt">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="3">
<widget class="QCheckBox" name="formulaXCB">
<property name="enabled">
<bool>false</bool>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="unit" stdset="0">
<string>m/s</string>
<property name="text">
<string>formula</string>
</property>
</widget>
</item>
<item>
<item row="0" column="3">
<widget class="QCheckBox" name="velocityXBox">
<property name="text">
<string>unspecified</string>
@@ -49,31 +36,52 @@
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="velocityYLbl">
<property name="text">
<string>Velocity y:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="Gui::InputField" name="velocityYTxt">
<property name="enabled">
<bool>false</bool>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="unit" stdset="0">
<string>m/s</string>
<item row="0" column="0">
<widget class="QLabel" name="volocityXLbl">
<property name="text">
<string>Velocity x:</string>
</property>
</widget>
</item>
<item>
<item row="1" column="1">
<widget class="QLineEdit" name="formulaX">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::QuantitySpinBox" name="velocityX">
<property name="enabled">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="3">
<widget class="QCheckBox" name="formulaYCB">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>formula</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="velocityYLbl">
<property name="text">
<string>Velocity y:</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QCheckBox" name="velocityYBox">
<property name="text">
<string>unspecified</string>
@@ -83,31 +91,28 @@
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QLabel" name="velocityZLbl">
<property name="text">
<string>Velocity z:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="Gui::InputField" name="velocityZTxt">
<item row="1" column="1">
<widget class="QLineEdit" name="formulaY">
<property name="enabled">
<bool>false</bool>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::QuantitySpinBox" name="velocityY">
<property name="enabled">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string>m/s</string>
<string notr="true"/>
</property>
</widget>
</item>
<item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="3">
<widget class="QCheckBox" name="velocityZBox">
<property name="text">
<string>unspecified</string>
@@ -117,15 +122,49 @@
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QCheckBox" name="formulaZCB">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>formula</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="velocityZLbl">
<property name="text">
<string>Velocity z:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="formulaZ">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::QuantitySpinBox" name="velocityZ">
<property name="enabled">
<bool>false</bool>
</property>
<property name="unit" stdset="0">
<string notr="true"/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Gui::InputField</class>
<extends>QLineEdit</extends>
<header>Gui/InputField.h</header>
<class>Gui::QuantitySpinBox</class>
<extends>QWidget</extends>
<header>Gui/QuantitySpinBox.h</header>
</customwidget>
</customwidgets>
<resources/>
@@ -133,96 +172,48 @@
<connection>
<sender>velocityXBox</sender>
<signal>toggled(bool)</signal>
<receiver>velocityXTxt</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>230</x>
<y>44</y>
</hint>
<hint type="destinationlabel">
<x>230</x>
<y>18</y>
</hint>
</hints>
</connection>
<connection>
<sender>velocityXBox</sender>
<signal>toggled(bool)</signal>
<receiver>velocityXTxt</receiver>
<receiver>formulaXCB</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>230</x>
<y>44</y>
<x>351</x>
<y>19</y>
</hint>
<hint type="destinationlabel">
<x>230</x>
<y>18</y>
<x>351</x>
<y>45</y>
</hint>
</hints>
</connection>
<connection>
<sender>velocityYBox</sender>
<signal>toggled(bool)</signal>
<receiver>velocityYTxt</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>347</x>
<y>53</y>
</hint>
<hint type="destinationlabel">
<x>184</x>
<y>53</y>
</hint>
</hints>
</connection>
<connection>
<sender>velocityYBox</sender>
<signal>toggled(bool)</signal>
<receiver>velocityYTxt</receiver>
<receiver>formulaYCB</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>347</x>
<y>53</y>
<x>351</x>
<y>73</y>
</hint>
<hint type="destinationlabel">
<x>184</x>
<y>53</y>
<x>351</x>
<y>99</y>
</hint>
</hints>
</connection>
<connection>
<sender>velocityZBox</sender>
<signal>toggled(bool)</signal>
<receiver>velocityZTxt</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>347</x>
<y>87</y>
</hint>
<hint type="destinationlabel">
<x>184</x>
<y>87</y>
</hint>
</hints>
</connection>
<connection>
<sender>velocityZBox</sender>
<signal>toggled(bool)</signal>
<receiver>velocityZTxt</receiver>
<receiver>formulaZCB</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>347</x>
<y>87</y>
<x>351</x>
<y>127</y>
</hint>
<hint type="destinationlabel">
<x>184</x>
<y>87</y>
<x>351</x>
<y>153</y>
</hint>
</hints>
</connection>
@@ -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)
@@ -0,0 +1,275 @@
# ***************************************************************************
# * Copyright (c) 2023 Uwe Stöhr <[email protected]> *
# * *
# * 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
@@ -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
+2
View File
@@ -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")
+1 -1
View File
@@ -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
@@ -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",
@@ -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"
)
@@ -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)
@@ -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
+27 -4
View File
@@ -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)
@@ -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()
@@ -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()
@@ -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)
+8 -9
View File
@@ -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<Base::QuantityPy*>(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;
+4
View File
@@ -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<int>(constr->FirstPos) % constr->Second % constr->getValue());
+28 -2
View File
@@ -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);
+9
View File
@@ -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
File diff suppressed because it is too large Load Diff
+18 -1
View File
@@ -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
+8
View File
@@ -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)
+2
View File
@@ -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);
+2
View File
@@ -144,6 +144,8 @@ SET(SketcherGui_SRCS
SketchRectangularArrayDialog.cpp
SketcherRegularPolygonDialog.h
SketcherRegularPolygonDialog.cpp
SnapManager.cpp
SnapManager.h
TaskDlgEditSketch.cpp
TaskDlgEditSketch.h
ViewProviderPython.cpp
+258 -32
View File
@@ -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<double>(&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<const char*> &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<const char*> &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<Gui::ActionGroup*>(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<Gui::ActionGroup*>(_pcAction);
QList<QAction*> a = pcAction->actions();
auto* ssa = static_cast<SnapSpaceAction*>(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<Gui::ActionGroup*>(_pcAction);
QList<QAction*> a = pcAction->actions();
auto* ssa = static_cast<SnapSpaceAction*>(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());
}
+111 -3
View File
@@ -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<const Part::GeomCircle*>(geom1);
double radius1 = circleSeg1->getRadius();
Base::Vector3d center1 = circleSeg1->getCenter();
auto circleSeg2 = static_cast<const Part::GeomCircle*>(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<Sketcher::Constraint *> &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<SelIdPair> &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<SelIdPair> &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<const Part::GeomCircle*>(geom1);
double radius1 = circleSeg1->getRadius();
Base::Vector3d center1 = circleSeg1->getCenter();
auto circleSeg2 = static_cast<const Part::GeomCircle*>(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<Sketcher::Constraint *> &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;
}
@@ -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<Base::Vector2d>());
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);
}
+4
View File
@@ -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<unsigned long, unsigned long>& colorMapping = std::map<unsigned long, unsigned long>());
@@ -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;
}
@@ -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;
@@ -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 {
@@ -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;
@@ -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<const Part::GeomLineSegment *>(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<const Part::GeomCircle*>(geo1);
auto circleSeg2 = static_cast<const Part::GeomCircle*>(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<const Part::GeomLineSegment *>(geo);
pnt1 = lineSeg->getStartPoint();
pnt2 = lineSeg->getEndPoint();
@@ -106,6 +106,8 @@
<file>icons/general/Sketcher_ViewSketch.svg</file>
<file>icons/general/Sketcher_GridToggle.svg</file>
<file>icons/general/Sketcher_GridToggle_Deactivated.svg</file>
<file>icons/general/Sketcher_Snap.svg</file>
<file>icons/general/Sketcher_Snap_Deactivated.svg</file>
</qresource>
<qresource>
<file>icons/geometry/Sketcher_AlterFillet.svg</file>
@@ -0,0 +1,386 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="64px"
height="64px"
id="svg3364"
version="1.1"
sodipodi:docname="Sketcher_Snap.svg"
inkscape:version="1.1-beta1 (77e7b44db3, 2021-03-28)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview55"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
objecttolerance="10.0"
gridtolerance="10.0"
guidetolerance="10.0"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="11.765625"
inkscape:cx="39.861886"
inkscape:cy="27.580345"
inkscape:window-width="1725"
inkscape:window-height="1013"
inkscape:window-x="1213"
inkscape:window-y="293"
inkscape:window-maximized="0"
inkscape:current-layer="svg3364" />
<defs
id="defs3366">
<linearGradient
id="linearGradient3864">
<stop
id="stop3866"
offset="0"
style="stop-color:#71b2f8;stop-opacity:1" />
<stop
id="stop3868"
offset="1"
style="stop-color:#002795;stop-opacity:1" />
</linearGradient>
<radialGradient
xlink:href="#linearGradient3864"
id="radialGradient2571"
gradientUnits="userSpaceOnUse"
cx="342.58258"
cy="27.256668"
fx="342.58258"
fy="27.256668"
r="19.571428"
gradientTransform="matrix(1.6258409,0.5434973,-8.8819886e-2,0.2656996,-215.02413,-170.90186)" />
<radialGradient
xlink:href="#linearGradient3593"
id="radialGradient3352"
gradientUnits="userSpaceOnUse"
cx="345.28433"
cy="15.560534"
fx="345.28433"
fy="15.560534"
r="19.571428"
gradientTransform="translate(-0.1767767,-2.6516504)" />
<linearGradient
id="linearGradient3593">
<stop
style="stop-color:#c8e0f9;stop-opacity:1"
offset="0"
id="stop3595" />
<stop
style="stop-color:#637dca;stop-opacity:1"
offset="1"
id="stop3597" />
</linearGradient>
<radialGradient
xlink:href="#linearGradient3593"
id="radialGradient3354"
gradientUnits="userSpaceOnUse"
cx="330.63791"
cy="39.962704"
fx="330.63791"
fy="39.962704"
r="19.571428"
gradientTransform="translate(-0.1767767,-2.6516504)" />
<radialGradient
xlink:href="#linearGradient3864"
id="radialGradient3369"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.6258409,0.5434973,-8.8819886e-2,0.2656996,-461.81066,-173.06271)"
cx="342.58258"
cy="27.256668"
fx="342.58258"
fy="27.256668"
r="19.571428" />
<radialGradient
xlink:href="#linearGradient3593"
id="radialGradient3372"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0012324,0,0,0.9421773,-327.50313,-4.3316646)"
cx="345.28433"
cy="15.560534"
fx="345.28433"
fy="15.560534"
r="19.571428" />
<radialGradient
xlink:href="#linearGradient3593"
id="radialGradient3375"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0012324,0,0,0.9421773,-287.81791,-28.143054)"
cx="330.63791"
cy="39.962704"
fx="330.63791"
fy="39.962704"
r="19.571428" />
<radialGradient
xlink:href="#linearGradient3864"
id="radialGradient3380"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9829174,1.3240854,-1.2330051,0.8105158,-131.04134,-483.74563)"
cx="320.44025"
cy="113.23357"
fx="320.44025"
fy="113.23357"
r="19.571428" />
<linearGradient
xlink:href="#linearGradient3864"
id="linearGradient3914"
x1="6.94525"
y1="36.838673"
x2="48.691113"
y2="36.838673"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0012324,0,0,0.9421773,-4.8699606,-2.3863162)" />
<radialGradient
xlink:href="#linearGradient3794"
id="radialGradient3800"
cx="1"
cy="45"
fx="1"
fy="45"
r="41"
gradientTransform="matrix(0.93348213,-2.2905276e-8,0,0.28687573,0.06651751,32.090592)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3794">
<stop
style="stop-color:#000000;stop-opacity:1"
offset="0"
id="stop3796" />
<stop
style="stop-color:#000000;stop-opacity:0"
offset="1"
id="stop3798" />
</linearGradient>
<linearGradient
gradientTransform="translate(0,-9)"
xlink:href="#linearGradient3777"
id="linearGradient3783"
x1="53.896763"
y1="51.179787"
x2="48"
y2="32"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3777">
<stop
style="stop-color:#204a87;stop-opacity:1"
offset="0"
id="stop3779" />
<stop
style="stop-color:#3465a4;stop-opacity:1"
offset="1"
id="stop3781" />
</linearGradient>
<linearGradient
gradientTransform="translate(22,-17)"
xlink:href="#linearGradient3767"
id="linearGradient3773"
x1="22.116516"
y1="55.717518"
x2="19"
y2="33"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3767">
<stop
style="stop-color:#3465a4;stop-opacity:1"
offset="0"
id="stop3769" />
<stop
style="stop-color:#729fcf;stop-opacity:1"
offset="1"
id="stop3771" />
</linearGradient>
<linearGradient
gradientTransform="translate(-2,-11)"
xlink:href="#linearGradient3777-6"
id="linearGradient3783-3"
x1="53.896763"
y1="51.179787"
x2="48"
y2="32"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3777-6">
<stop
style="stop-color:#204a87;stop-opacity:1"
offset="0"
id="stop3779-7" />
<stop
style="stop-color:#3465a4;stop-opacity:1"
offset="1"
id="stop3781-5" />
</linearGradient>
<linearGradient
y2="32"
x2="48"
y1="51.179787"
x1="53.896763"
gradientTransform="translate(-24,-13)"
gradientUnits="userSpaceOnUse"
id="linearGradient3066"
xlink:href="#linearGradient3777-6" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientTransform="matrix(0.53559025,0,0,0.53556875,-6.4812797,2.8041108)"
gradientUnits="userSpaceOnUse"
id="linearGradient3323-4"
xlink:href="#linearGradient3836-9-3-7-2-7-7-2-6" />
<linearGradient
id="linearGradient3836-9-3-7-2-7-7-2-6">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8-5-4-4-6-4-4-61" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1-6-0-5-1-0-5-8" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3836-9-3-7-2-7-7-2-6"
id="linearGradient898"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.53559025,0,0,0.53556875,-6.4812797,2.8041108)"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientTransform="matrix(0.53559025,0,0,0.53556875,-6.4812797,2.8041108)"
gradientUnits="userSpaceOnUse"
id="linearGradient3323-3"
xlink:href="#linearGradient3836-9-3-7-2-7-7-2-6" />
</defs>
<metadata
id="metadata3369">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>[wmayer]</dc:title>
</cc:Agent>
</dc:creator>
<dc:date>2011-10-10</dc:date>
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Section.svg</dc:identifier>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson</dc:title>
</cc:Agent>
</dc:contributor>
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:none;stroke:#363636;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6, 3;stroke-dashoffset:0;stroke-opacity:1"
d="M 2.3798139,32.218121 H 61.620186"
id="path985-9" />
<path
style="fill:none;stroke:#363636;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6, 3;stroke-dashoffset:0;stroke-opacity:1"
d="M 2.2324867,51.127858 H 61.472858"
id="path985-6" />
<path
style="fill:none;stroke:#4ce642;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6, 3;stroke-dashoffset:0;stroke-opacity:1"
d="M 12.221742,61.620185 V 2.3798148"
id="path985-92" />
<path
style="fill:none;stroke:#363636;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6, 3;stroke-dashoffset:0;stroke-opacity:1"
d="M 31.131479,61.620186 V 2.3798143"
id="path985-9-8" />
<path
style="fill:none;stroke:#363636;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6, 3;stroke-dashoffset:0;stroke-opacity:1"
d="M 50.041216,61.620185 V 2.3798148"
id="path985-6-2" />
<path
style="fill:none;stroke:#4ce642;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6, 3;stroke-dashoffset:0;stroke-opacity:1"
d="M 2.2324859,13.308385 H 61.472857"
id="path985" />
<g
transform="rotate(63.326802,46.528624,-2.5779449)"
id="g3529-7">
<path
id="path3491-7"
d="M 56,8 V 32"
style="fill:none;stroke:#172a04;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path3491-4-7"
d="M 56,8 V 32"
style="fill:none;stroke:#73d216;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path3491-4-1-3"
d="M 55,8 V 32"
style="fill:none;stroke:#8ae234;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
style="stroke-width:6.44377;stroke-miterlimit:4;stroke-dasharray:none"
transform="matrix(0.35922387,0.71507028,-0.71505986,0.35921864,53.759702,19.45672)"
id="g3797-7-2-9-5-4-9-5-96">
<path
style="fill:#ef2929;stroke:#280000;stroke-width:2.4993;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4250-6-9-4-1-2-4-1-3"
d="m -21.570202,4.8706993 a 6.2460052,6.2456584 0.01677682 1 1 9.488239,8.1250407 6.2460052,6.2456584 0.01677682 1 1 -9.488239,-8.1250407 z" />
<path
style="fill:url(#linearGradient898);fill-opacity:1;stroke:#ef2929;stroke-width:2.4993;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4250-7-0-1-8-7-3-8-7-7"
d="m -19.674789,6.4961011 a 3.7491316,3.7489813 0 1 1 5.695246,4.8771099 3.7491316,3.7489813 0 0 1 -5.695246,-4.8771099 z" />
</g>
<g
style="stroke-width:6.44377;stroke-miterlimit:4;stroke-dasharray:none"
transform="matrix(0.35922387,0.71507028,-0.71505986,0.35921864,32.313746,30.230346)"
id="g3797-7-2-9-5-4-9-5-96-5">
<path
style="fill:#ef2929;stroke:#280000;stroke-width:2.4993;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4250-6-9-4-1-2-4-1-3-7"
d="m -21.570202,4.8706993 a 6.2460052,6.2456584 0.01677682 1 1 9.488239,8.1250407 6.2460052,6.2456584 0.01677682 1 1 -9.488239,-8.1250407 z" />
<path
style="fill:url(#linearGradient3323-4);fill-opacity:1;stroke:#ef2929;stroke-width:2.4993;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4250-7-0-1-8-7-3-8-7-7-5"
d="m -19.674789,6.4961011 a 3.7491316,3.7489813 0 1 1 5.695246,4.8771099 3.7491316,3.7489813 0 0 1 -5.695246,-4.8771099 z" />
</g>
<g
id="g4273"
transform="translate(-89.528258,0.54055521)">
<path
style="fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 108.96148,19.97344 c 4.28933,11.161025 8.9416,20.384136 14.87384,32.552457 l 5.94954,-11.134131 12.749,12.154051 c 3.39402,0.638464 5.31688,-1.489816 4.75963,-4.504649 l -13.00398,-11.814076 8.49933,-6.629483 z"
id="path1052"
sodipodi:nodetypes="cccccccc" />
<path
style="fill:#d3d7cf;fill-opacity:1;stroke:#ffffff;stroke-width:1.285;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 112.59215,23.233162 c 3.72959,10.062766 6.25342,13.924184 11.41153,24.895126 l 5.26419,-9.586262 13.8638,13.109482 c 1.42608,0.530564 2.84842,-0.286358 2.07091,-2.088013 l -13.80942,-12.277572 7.29489,-5.92068 z"
id="path1052-8"
sodipodi:nodetypes="cccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

@@ -0,0 +1,377 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="64px"
height="64px"
id="svg3364"
version="1.1"
sodipodi:docname="Sketcher_Snap_Deactivated.svg"
inkscape:version="1.1-beta1 (77e7b44db3, 2021-03-28)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview55"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
objecttolerance="10.0"
gridtolerance="10.0"
guidetolerance="10.0"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="5.8828125"
inkscape:cx="56.180611"
inkscape:cy="32.63745"
inkscape:window-width="1720"
inkscape:window-height="1112"
inkscape:window-x="743"
inkscape:window-y="334"
inkscape:window-maximized="0"
inkscape:current-layer="svg3364" />
<defs
id="defs3366">
<linearGradient
id="linearGradient3864">
<stop
id="stop3866"
offset="0"
style="stop-color:#71b2f8;stop-opacity:1" />
<stop
id="stop3868"
offset="1"
style="stop-color:#002795;stop-opacity:1" />
</linearGradient>
<radialGradient
xlink:href="#linearGradient3864"
id="radialGradient2571"
gradientUnits="userSpaceOnUse"
cx="342.58258"
cy="27.256668"
fx="342.58258"
fy="27.256668"
r="19.571428"
gradientTransform="matrix(1.6258409,0.5434973,-8.8819886e-2,0.2656996,-215.02413,-170.90186)" />
<radialGradient
xlink:href="#linearGradient3593"
id="radialGradient3352"
gradientUnits="userSpaceOnUse"
cx="345.28433"
cy="15.560534"
fx="345.28433"
fy="15.560534"
r="19.571428"
gradientTransform="translate(-0.1767767,-2.6516504)" />
<linearGradient
id="linearGradient3593">
<stop
style="stop-color:#c8e0f9;stop-opacity:1"
offset="0"
id="stop3595" />
<stop
style="stop-color:#637dca;stop-opacity:1"
offset="1"
id="stop3597" />
</linearGradient>
<radialGradient
xlink:href="#linearGradient3593"
id="radialGradient3354"
gradientUnits="userSpaceOnUse"
cx="330.63791"
cy="39.962704"
fx="330.63791"
fy="39.962704"
r="19.571428"
gradientTransform="translate(-0.1767767,-2.6516504)" />
<radialGradient
xlink:href="#linearGradient3864"
id="radialGradient3369"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.6258409,0.5434973,-8.8819886e-2,0.2656996,-461.81066,-173.06271)"
cx="342.58258"
cy="27.256668"
fx="342.58258"
fy="27.256668"
r="19.571428" />
<radialGradient
xlink:href="#linearGradient3593"
id="radialGradient3372"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0012324,0,0,0.9421773,-327.50313,-4.3316646)"
cx="345.28433"
cy="15.560534"
fx="345.28433"
fy="15.560534"
r="19.571428" />
<radialGradient
xlink:href="#linearGradient3593"
id="radialGradient3375"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0012324,0,0,0.9421773,-287.81791,-28.143054)"
cx="330.63791"
cy="39.962704"
fx="330.63791"
fy="39.962704"
r="19.571428" />
<radialGradient
xlink:href="#linearGradient3864"
id="radialGradient3380"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9829174,1.3240854,-1.2330051,0.8105158,-131.04134,-483.74563)"
cx="320.44025"
cy="113.23357"
fx="320.44025"
fy="113.23357"
r="19.571428" />
<linearGradient
xlink:href="#linearGradient3864"
id="linearGradient3914"
x1="6.94525"
y1="36.838673"
x2="48.691113"
y2="36.838673"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0012324,0,0,0.9421773,-4.8699606,-2.3863162)" />
<radialGradient
xlink:href="#linearGradient3794"
id="radialGradient3800"
cx="1"
cy="45"
fx="1"
fy="45"
r="41"
gradientTransform="matrix(0.93348213,-2.2905276e-8,0,0.28687573,0.06651751,32.090592)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3794">
<stop
style="stop-color:#000000;stop-opacity:1"
offset="0"
id="stop3796" />
<stop
style="stop-color:#000000;stop-opacity:0"
offset="1"
id="stop3798" />
</linearGradient>
<linearGradient
gradientTransform="translate(0,-9)"
xlink:href="#linearGradient3777"
id="linearGradient3783"
x1="53.896763"
y1="51.179787"
x2="48"
y2="32"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3777">
<stop
style="stop-color:#204a87;stop-opacity:1"
offset="0"
id="stop3779" />
<stop
style="stop-color:#3465a4;stop-opacity:1"
offset="1"
id="stop3781" />
</linearGradient>
<linearGradient
gradientTransform="translate(22,-17)"
xlink:href="#linearGradient3767"
id="linearGradient3773"
x1="22.116516"
y1="55.717518"
x2="19"
y2="33"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3767">
<stop
style="stop-color:#3465a4;stop-opacity:1"
offset="0"
id="stop3769" />
<stop
style="stop-color:#729fcf;stop-opacity:1"
offset="1"
id="stop3771" />
</linearGradient>
<linearGradient
gradientTransform="translate(-2,-11)"
xlink:href="#linearGradient3777-6"
id="linearGradient3783-3"
x1="53.896763"
y1="51.179787"
x2="48"
y2="32"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3777-6">
<stop
style="stop-color:#204a87;stop-opacity:1"
offset="0"
id="stop3779-7" />
<stop
style="stop-color:#3465a4;stop-opacity:1"
offset="1"
id="stop3781-5" />
</linearGradient>
<linearGradient
y2="32"
x2="48"
y1="51.179787"
x1="53.896763"
gradientTransform="translate(-24,-13)"
gradientUnits="userSpaceOnUse"
id="linearGradient3066"
xlink:href="#linearGradient3777-6" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientTransform="matrix(0.53559025,0,0,0.53556875,-6.4812797,2.8041108)"
gradientUnits="userSpaceOnUse"
id="linearGradient3323-3"
xlink:href="#linearGradient3836-9-3-7-2-7-7-2-13" />
<linearGradient
id="linearGradient3836-9-3-7-2-7-7-2-13">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8-5-4-4-6-4-4-8" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1-6-0-5-1-0-5-9" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3836-9-3-7-2-7-7-2-13"
id="linearGradient1092"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.53559025,0,0,0.53556875,-6.4812797,2.8041108)"
x1="-18"
y1="18"
x2="-22"
y2="5" />
</defs>
<metadata
id="metadata3369">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>[wmayer]</dc:title>
</cc:Agent>
</dc:creator>
<dc:date>2011-10-10</dc:date>
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Section.svg</dc:identifier>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson</dc:title>
</cc:Agent>
</dc:contributor>
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:none;stroke:#363636;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6, 3;stroke-dashoffset:0;stroke-opacity:1"
d="M 2.3798139,32.218121 H 61.620186"
id="path985-9" />
<path
style="fill:none;stroke:#363636;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6, 3;stroke-dashoffset:0;stroke-opacity:1"
d="M 2.2324867,51.127858 H 61.472858"
id="path985-6" />
<path
style="fill:none;stroke:#363636;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6, 3;stroke-dashoffset:0;stroke-opacity:1"
d="M 12.221742,61.620185 V 2.3798148"
id="path985-92" />
<path
style="fill:none;stroke:#363636;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6, 3;stroke-dashoffset:0;stroke-opacity:1"
d="M 31.131479,61.620186 V 2.3798143"
id="path985-9-8" />
<path
style="fill:none;stroke:#363636;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6, 3;stroke-dashoffset:0;stroke-opacity:1"
d="M 50.041216,61.620185 V 2.3798148"
id="path985-6-2" />
<path
style="fill:none;stroke:#363636;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:6, 3;stroke-dashoffset:0;stroke-opacity:1"
d="M 2.2324859,13.308385 H 61.472857"
id="path985" />
<g
id="g4273"
transform="translate(-85.253531,6.1052318)">
<path
style="fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 108.96148,19.97344 c 4.28933,11.161025 8.9416,20.384136 14.87384,32.552457 l 5.94954,-11.134131 12.749,12.154051 c 3.39402,0.638464 5.31688,-1.489816 4.75963,-4.504649 l -13.00398,-11.814076 8.49933,-6.629483 z"
id="path1052"
sodipodi:nodetypes="cccccccc" />
<path
style="fill:#d3d7cf;fill-opacity:1;stroke:#ffffff;stroke-width:1.285;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 112.59215,23.233162 c 3.72959,10.062766 6.25342,13.924184 11.41153,24.895126 l 5.26419,-9.586262 13.8638,13.109482 c 1.42608,0.530564 2.84842,-0.286358 2.07091,-2.088013 l -13.80942,-12.277572 7.29489,-5.92068 z"
id="path1052-8"
sodipodi:nodetypes="cccccccc" />
</g>
<g
transform="rotate(63.436651,45.794909,-8.4224816)"
id="g3529-4">
<path
id="path3491-6"
d="M 56,8 V 32"
style="fill:none;stroke:#2e3436;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path3491-4-07"
d="M 56,8 V 32"
style="fill:none;stroke:#d3d7cf;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path3491-4-1-17"
d="M 55,8 V 32"
style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
style="stroke-width:6.44377;stroke-miterlimit:4;stroke-dasharray:none"
transform="matrix(0.35785226,0.71575769,-0.71574725,0.35784704,48.084512,16.896405)"
id="g3797-7-2-9-5-4-9-5-4">
<path
style="fill:#ef2929;stroke:#280000;stroke-width:2.4993;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4250-6-9-4-1-2-4-1-4"
d="m -21.570202,4.8706993 a 6.2460052,6.2456584 0.01677682 1 1 9.488239,8.1250407 6.2460052,6.2456584 0.01677682 1 1 -9.488239,-8.1250407 z" />
<path
style="fill:url(#linearGradient1092);fill-opacity:1;stroke:#ef2929;stroke-width:2.4993;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4250-7-0-1-8-7-3-8-7-6"
d="m -19.674789,6.4961011 a 3.7491316,3.7489813 0 1 1 5.695246,4.8771099 3.7491316,3.7489813 0 0 1 -5.695246,-4.8771099 z" />
</g>
<g
style="stroke-width:6.44377;stroke-miterlimit:4;stroke-dasharray:none"
transform="matrix(0.35785226,0.71575769,-0.71574725,0.35784704,26.61794,27.628894)"
id="g3797-7-2-9-5-4-9-5-4-0">
<path
style="fill:#ef2929;stroke:#280000;stroke-width:2.4993;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4250-6-9-4-1-2-4-1-4-1"
d="m -21.570202,4.8706993 a 6.2460052,6.2456584 0.01677682 1 1 9.488239,8.1250407 6.2460052,6.2456584 0.01677682 1 1 -9.488239,-8.1250407 z" />
<path
style="fill:url(#linearGradient3323-3);fill-opacity:1;stroke:#ef2929;stroke-width:2.4993;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4250-7-0-1-8-7-3-8-7-6-6"
d="m -19.674789,6.4961011 a 3.7491316,3.7489813 0 1 1 5.695246,4.8771099 3.7491316,3.7489813 0 0 1 -5.695246,-4.8771099 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

+370
View File
@@ -0,0 +1,370 @@
/***************************************************************************
* Copyright (c) 2023 Pierre-Louis Boyer <pierrelouis.boyer@gmail.com> *
* *
* 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 <QApplication>
#endif // #ifndef _PreComp_
#include <Mod/Sketcher/App/SketchObject.h>
#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<const char*>& 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<SnapManager::ParameterObserver>(*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<const Part::GeomCurve*>(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<const Part::GeomLineSegment*>(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<const Part::GeomArcOfCircle*>(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;
}
+127
View File
@@ -0,0 +1,127 @@
/***************************************************************************
* Copyright (c) 2023 Pierre-Louis Boyer <pierrelouis.boyer@gmail.com> *
* *
* 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 <App/Application.h>
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<const char*>& 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<std::string, std::function<void(const std::string&)>> 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<SnapManager::ParameterObserver> pObserver;
};
} // namespace SketcherGui
#endif // SKETCHERGUI_SnapManager_H
@@ -70,7 +70,7 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="showHideButton">
<widget class="QToolButton" name="showHideButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
+93 -15
View File
@@ -41,6 +41,7 @@
#include <Gui/Application.h>
#include <Gui/BitmapFactory.h>
#include <Gui/Command.h>
#include <Gui/Notifications.h>
#include <Gui/Selection.h>
#include <Gui/SelectionObject.h>
#include <Gui/ViewProvider.h>
@@ -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<unsigned int>(Layer::Hidden);
if(State != GeometryState::External) {
const auto geo = sketchView->getSketchObject()->getGeometry(ElementNbr);
if(geo) {
auto layer = getSafeGeomLayerId(geo);
return layer != static_cast<unsigned int>(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<const char *, const int>; // {filter item text, filter item level}
inline static const std::vector<filterItemRepr> 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
+1 -54
View File
@@ -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<const char *, const int>; // {filter item text, filter item level}
inline static const std::vector<filterItemRepr> 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
{
+33
View File
@@ -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<DrawSketchHandler> ptr(handler);
+3
View File
@@ -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
+19 -34
View File
@@ -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<ViewProviderSketch::ParameterObserver>(*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<const Part::GeomLineSegment *>(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<const Part::GeomCircle *>(geo1);
const Part::GeomCircle *circleSeg2 = static_cast<const Part::GeomCircle *>(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<EditModeCoinManager>(*this);
snapManager = std::make_unique<SnapManager>(*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();
+8 -14
View File
@@ -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<EditModeCoinManager> editCoinManager;
std::unique_ptr<SnapManager> snapManager;
std::unique_ptr<ViewProviderSketch::ParameterObserver> pObserver;
std::unique_ptr<DrawSketchHandler> 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
+2 -1
View File
@@ -188,7 +188,8 @@ inline void SketcherAddWorkbenchSketchEditModeActions(Gui::ToolBarItem& sketch)
sketch << "Sketcher_LeaveSketch"
<< "Sketcher_ViewSketch"
<< "Sketcher_ViewSection"
<< "Sketcher_Grid";
<< "Sketcher_Grid"
<< "Sketcher_Snap";
}
template <typename T>
+2
View File
@@ -175,6 +175,8 @@ SET(TechDraw_SRCS
TechDrawExport.h
ProjectionAlgos.cpp
ProjectionAlgos.h
XMLQuery.cpp
XMLQuery.h
)
SET(Geometry_SRCS
+53 -70
View File
@@ -26,10 +26,7 @@
#ifndef _PreComp_
# include <sstream>
# include <QDomDocument>
# include <QDomNodeModel.h>
# include <QFile>
# include <QXmlQuery>
# include <QXmlResultItems>
#endif
#include <App/Application.h>
@@ -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<std::string, std::string> substitutions = EditableTexts.getValues();
// XPath query to select all <tspan> nodes whose <text> 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 <tspan> nodes whose <text> 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<std::string, std::string>::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<std::string, std::string> 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<std::string, std::string>::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<std::string, std::string> 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<std::string, std::string> 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 <tspan> nodes whose <text> 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;
}
+22 -36
View File
@@ -23,11 +23,7 @@
#include "PreCompiled.h"
#ifndef _PreComp_
# include <sstream>
# include "QDomNodeModel.h"
# include <QDomDocument>
# include <QXmlResultItems>
# include <QXmlQuery>
#endif
#include <Base/Console.h>
@@ -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<std::string> DrawViewSymbol::getEditableFields()
{
QDomDocument symbolDocument;
QXmlResultItems queryResult;
std::vector<std::string> 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 <tspan> nodes whose <text> 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 <tspan> nodes whose <text> 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 <tspan> 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());
}
}
+2
View File
@@ -28,6 +28,7 @@
#include <QDomDocument>
#include <QDomNode>
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
#include "QDomNodeModel.h"
#include <QSourceLocation>
#include <QUrl>
@@ -359,3 +360,4 @@ QXmlNodeModelIndex QDomNodeModel::nextFromSimpleAxis ( SimpleAxis axis, const QX
return QXmlNodeModelIndex();
}
#endif
+75
View File
@@ -0,0 +1,75 @@
/***************************************************************************
* Copyright (c) 2023 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* 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 <QDomDocument>
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
# include "QDomNodeModel.h"
# include <QXmlQuery>
# include <QXmlResultItems>
#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<bool(QDomElement&)>& 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<bool(QDomElement&)>& process)
{
//TODO: Port to Qt6
Q_UNUSED(queryStr)
Q_UNUSED(process)
return false;
}
#endif
+49
View File
@@ -0,0 +1,49 @@
/***************************************************************************
* Copyright (c) 2023 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* 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 <Mod/TechDraw/TechDrawGlobal.h>
#include <QList>
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<bool(QDomElement&)>& process);
private:
QDomDocument& domDocument;
};
} //namespace TechDraw
#endif //TECHDRAW_XMLQuery_h_
+10 -22
View File
@@ -29,8 +29,6 @@
# include <QGraphicsSvgItem>
# include <QPen>
# include <QSvgRenderer>
# include <QXmlQuery>
# include <QXmlResultItems>
#endif// #ifndef _PreComp_
#include <App/Application.h>
@@ -39,7 +37,7 @@
#include <Mod/TechDraw/App/DrawSVGTemplate.h>
#include <Mod/TechDraw/App/DrawUtil.h>
#include <Mod/TechDraw/App/QDomNodeModel.h>
#include <Mod/TechDraw/App/XMLQuery.h>
#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 <text> 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<ParameterGrp> 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 <text> 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 <Mod/TechDraw/Gui/moc_QGISVGTemplate.cpp>
@@ -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"
@@ -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
+8 -8
View File
@@ -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;
}
@@ -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)
+1
View File
@@ -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
)
+832
View File
@@ -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 <string>
// 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)