diff --git a/.github/workflows/actions/windows/getCcache/action.yml b/.github/workflows/actions/windows/getCcache/action.yml index 7d0ceff711..be56349f46 100644 --- a/.github/workflows/actions/windows/getCcache/action.yml +++ b/.github/workflows/actions/windows/getCcache/action.yml @@ -41,11 +41,11 @@ inputs: ccachedownloadpath: description: "Path where to download ccache" required: false - default: https://github.com/ccache/ccache/releases/download/v4.8.2/ + default: https://github.com/ccache/ccache/releases/download/v4.9/ ccacheversion: description: "Ccache version to be downloaded" required: false - default: ccache-4.8.2-windows-x86_64 + default: ccache-4.9-windows-x86_64 runs: using: "composite" diff --git a/cMake/FreeCAD_Helpers/SetupShibokenAndPyside.cmake b/cMake/FreeCAD_Helpers/SetupShibokenAndPyside.cmake index 7556a25a89..aca36bc439 100644 --- a/cMake/FreeCAD_Helpers/SetupShibokenAndPyside.cmake +++ b/cMake/FreeCAD_Helpers/SetupShibokenAndPyside.cmake @@ -45,9 +45,9 @@ macro(SetupShibokenAndPyside) if(NOT SHIBOKEN_INCLUDE_DIR) find_pip_package(Shiboken${SHIBOKEN_MAJOR_VERSION}) if (Shiboken${SHIBOKEN_MAJOR_VERSION}_FOUND) - set(Shiboken_INCLUDE_DIR ${Shiboken${SHIBOKEN_MAJOR_VERSION}_INCLUDE_DIRS}) - set(Shiboken_LIBRARY ${Shiboken${SHIBOKEN_MAJOR_VERSION}_LIBRARIES}) - set(Shiboken_FOUND TRUE) + set(SHIBOKEN_INCLUDE_DIR ${Shiboken${SHIBOKEN_MAJOR_VERSION}_INCLUDE_DIRS}) + set(SHIBOKEN_LIBRARY ${Shiboken${SHIBOKEN_MAJOR_VERSION}_LIBRARIES}) + set(SHIBOKEN_FOUND TRUE) endif() endif() @@ -60,9 +60,9 @@ macro(SetupShibokenAndPyside) if(NOT PYSIDE_INCLUDE_DIR) find_pip_package(PySide${PYSIDE_MAJOR_VERSION}) if (PySide${PYSIDE_MAJOR_VERSION}_FOUND) - set(PySide_INCLUDE_DIR ${PySide${PYSIDE_MAJOR_VERSION}_INCLUDE_DIRS}) - set(PySide_LIBRARY ${PySide${PYSIDE_MAJOR_VERSION}_LIBRARIES}) - set(PySide_FOUND TRUE) + set(PYSIDE_INCLUDE_DIR ${PySide${PYSIDE_MAJOR_VERSION}_INCLUDE_DIRS}) + set(PYSIDE_LIBRARY ${PySide${PYSIDE_MAJOR_VERSION}_LIBRARIES}) + set(PYSIDE_FOUND TRUE) endif() endif() @@ -192,9 +192,9 @@ macro(find_pip_package PACKAGE) endforeach() file(TO_NATIVE_PATH "${PIP_PACKAGE_LOCATION}/${PIP_PACKAGE_NAME}/include" INCLUDE_DIR) file(TO_NATIVE_PATH "${PIP_PACKAGE_LOCATION}/${PIP_PACKAGE_NAME}/lib" LIBRARY) - set(${PACKAGE}_INCLUDE_DIRS ${INCLUDE_DIR} PARENT_SCOPE) - set(${PACKAGE}_LIBRARIES ${LIBRARY} PARENT_SCOPE) - set(${PACKAGE}_FOUND ${LIBRARY} TRUE) + set(${PACKAGE}_INCLUDE_DIRS ${INCLUDE_DIR}) + set(${PACKAGE}_LIBRARIES ${LIBRARY}) + set(${PACKAGE}_FOUND TRUE) message(STATUS "Found pip-installed ${PACKAGE} in ${PIP_PACKAGE_LOCATION}/${PIP_PACKAGE_NAME}") endif() endmacro() diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 741cc97d36..c02e2ebbad 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -2027,6 +2027,7 @@ void Application::initTypes() App::PropertySpecificHeat ::init(); App::PropertySpeed ::init(); App::PropertyStiffness ::init(); + App::PropertyStiffnessDensity ::init(); App::PropertyStress ::init(); App::PropertyTemperature ::init(); App::PropertyThermalConductivity ::init(); diff --git a/src/App/ComplexGeoData.h b/src/App/ComplexGeoData.h index 7bf68b3bea..0bacdd6229 100644 --- a/src/App/ComplexGeoData.h +++ b/src/App/ComplexGeoData.h @@ -247,6 +247,10 @@ public: } // NOTE: getElementHistory is now in ElementMap + long getElementHistory(const MappedName & name, + MappedName *original=nullptr, std::vector *history=nullptr) const { + return _elementMap->getElementHistory(name, Tag, original, history); + }; void setMappedChildElements(const std::vector & children); std::vector getMappedChildElements() const; @@ -261,7 +265,10 @@ public: * * @return Returns the existing element map. */ - virtual ElementMapPtr resetElementMap(ElementMapPtr elementMap=ElementMapPtr()) { + virtual ElementMapPtr resetElementMap(ElementMapPtr elementMap=ElementMapPtr(), bool forceEmpty=false) { + if ( ! elementMap && ! forceEmpty ) { + elementMap = std::make_shared(); + } _elementMap.swap(elementMap); return elementMap; } diff --git a/src/App/DocumentObject.cpp b/src/App/DocumentObject.cpp index 2950697434..708b6ea3b9 100644 --- a/src/App/DocumentObject.cpp +++ b/src/App/DocumentObject.cpp @@ -292,6 +292,15 @@ std::string DocumentObject::getFullLabel() const { return name; } +const char* DocumentObject::getDagKey() const +{ + if(!pcNameInDocument) + { + return nullptr; + } + return pcNameInDocument->c_str(); +} + const char *DocumentObject::getNameInDocument() const { // Note: It can happen that we query the internal name of an object even if it is not @@ -808,7 +817,7 @@ DocumentObject *DocumentObject::getSubObject(const char *subname, if(outList.size()!=_outListMap.size()) { _outListMap.clear(); for(auto obj : outList) - _outListMap[obj->getNameInDocument()] = obj; + _outListMap[obj->getDagKey()] = obj; } auto it = _outListMap.find(name.c_str()); if(it != _outListMap.end()) diff --git a/src/App/DocumentObject.h b/src/App/DocumentObject.h index a274a60c0c..b9b745ad54 100644 --- a/src/App/DocumentObject.h +++ b/src/App/DocumentObject.h @@ -132,6 +132,8 @@ public: DocumentObject(); ~DocumentObject() override; + /// returns a value that uniquely identifies this DocumentObject. + const char* getDagKey() const; /// returns the name which is set in the document for this object (not the name property!) const char *getNameInDocument() const; /// Return the object ID that is unique within its owner document diff --git a/src/App/Link.cpp b/src/App/Link.cpp index a63cd4bf8f..4328a2ebe1 100644 --- a/src/App/Link.cpp +++ b/src/App/Link.cpp @@ -468,7 +468,7 @@ void LinkBaseExtension::setOnChangeCopyObject( } } - const char *key = flags.testFlag(OnChangeCopyOptions::ApplyAll) ? "*" : parent->getNameInDocument(); + const char *key = flags.testFlag(OnChangeCopyOptions::ApplyAll) ? "*" : parent->getDagKey(); if (external) prop->setValue(key, exclude ? "" : "+"); else diff --git a/src/App/MappedElement.cpp b/src/App/MappedElement.cpp index 14eb6f99ba..3a0c134945 100644 --- a/src/App/MappedElement.cpp +++ b/src/App/MappedElement.cpp @@ -20,5 +20,145 @@ * * **************************************************************************************************/ +// NOLINTNEXTLINE #include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +#endif + #include "MappedElement.h" + +using namespace Data; + +bool ElementNameComparator::operator()(const MappedName& leftName, + const MappedName& rightName) const +{ + int size = static_cast(std::min(leftName.size(), rightName.size())); + if (size == 0U) { + return leftName.size() < rightName.size(); + } + int currentIndex = 0; + if (rightName[0] == '#') { + if (leftName[0] != '#') { + return true; + } + // If both string starts with '#', compare the following hex digits by + // its integer value. + int res = 0; + for (currentIndex = 1; currentIndex < size; ++currentIndex) { + auto ac = (unsigned char)leftName[currentIndex]; + auto bc = (unsigned char)rightName[currentIndex]; + if (std::isxdigit(bc) != 0) { + if (std::isxdigit(ac) == 0) { + return true; + } + if (res == 0) { + if (ac < bc) { + res = -1; + } + else if (ac > bc) { + res = 1; + } + } + } + else if (std::isxdigit(ac) != 0) { + res = 1; + } + else { + break; + } + } + if (res < 0) { + return true; + } + if (res > 0) { + return false; + } + + for (; currentIndex < size; ++currentIndex) { + char ac = leftName[currentIndex]; + char bc = rightName[currentIndex]; + if (ac < bc) { + return true; + } + if (ac > bc) { + return false; + } + } + return leftName.size() < rightName.size(); + } + if (leftName[0] == '#') { + return false; + } + + // If the string does not start with '#', compare the non-digits prefix + // using lexical order. + for (currentIndex = 0; currentIndex < size; ++currentIndex) { + auto ac = (unsigned char)leftName[currentIndex]; + auto bc = (unsigned char)rightName[currentIndex]; + if (std::isdigit(bc) == 0) { + if (std::isdigit(ac) != 0) { + return true; + } + if (ac < bc) { + return true; + } + if (ac > bc) { + return false; + } + } + else if (std::isdigit(ac) == 0) { + return false; + } + else { + break; + } + } + + // Then compare the following digits part by integer value + int res = 0; + for (; currentIndex < size; ++currentIndex) { + auto ac = (unsigned char)leftName[currentIndex]; + auto bc = (unsigned char)rightName[currentIndex]; + if (std::isdigit(bc) != 0) { + if (std::isdigit(ac) == 0) { + return true; + } + if (res == 0) { + if (ac < bc) { + res = -1; + } + else if (ac > bc) { + res = 1; + } + } + } + else if (std::isdigit(ac) != 0) { + return false; + } + else { + break; + } + } + if (res < 0) { + return true; + } + if (res > 0) { + return false; + } + + // Finally, compare the remaining tail using lexical order + for (; currentIndex < size; ++currentIndex) { + char ac = leftName[currentIndex]; + char bc = rightName[currentIndex]; + if (ac < bc) { + return true; + } + if (ac > bc) { + return false; + } + } + return leftName.size() < rightName.size(); +} \ No newline at end of file diff --git a/src/App/MappedElement.h b/src/App/MappedElement.h index 7f6ee68f01..2e1d63e60a 100644 --- a/src/App/MappedElement.h +++ b/src/App/MappedElement.h @@ -26,6 +26,7 @@ #include "IndexedName.h" #include "MappedName.h" + namespace App { class DocumentObject; @@ -98,6 +99,22 @@ struct AppExport MappedElement } }; +struct AppExport ElementNameComparator { + /** Comparison function to make topo name more stable + * + * The sorting decomposes the name into either of the following two forms + * '#' + hex_digits + tail + * non_digits + digits + tail + * + * The non-digits part is compared lexically, while the digits part is + * compared by its integer value. + * + * The reason for this is to prevent names with bigger digits (which usually means + * they come later in history) from coming earlier when sorting. + */ + bool operator()(const MappedName & leftName, const MappedName & rightName) const; +}; + }// namespace Data diff --git a/src/App/PreCompiled.h b/src/App/PreCompiled.h index 046783405f..86389849a5 100644 --- a/src/App/PreCompiled.h +++ b/src/App/PreCompiled.h @@ -49,6 +49,7 @@ #include #include #include +#include #ifdef FC_OS_WIN32 # include diff --git a/src/App/PropertyUnits.cpp b/src/App/PropertyUnits.cpp index f7106d1061..6c0f60f8a8 100644 --- a/src/App/PropertyUnits.cpp +++ b/src/App/PropertyUnits.cpp @@ -621,6 +621,17 @@ PropertyStiffness::PropertyStiffness() setUnit(Base::Unit::Stiffness); } +//************************************************************************** +// PropertyStiffnessDensity +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +TYPESYSTEM_SOURCE(App::PropertyStiffnessDensity, App::PropertyQuantity) + +PropertyStiffnessDensity::PropertyStiffnessDensity() +{ + setUnit(Base::Unit::StiffnessDensity); +} + //************************************************************************** // PropertyTemperature //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/src/App/PropertyUnits.h b/src/App/PropertyUnits.h index c10782ebe0..fc32e080b1 100644 --- a/src/App/PropertyUnits.h +++ b/src/App/PropertyUnits.h @@ -620,7 +620,7 @@ public: /** Stiffness property * This is a property for representing stiffness. It is basically a float - * property. On the Gui it has a quantity like m/s^2. + * property. On the Gui it has a quantity like N/m. */ class AppExport PropertyStiffness: public PropertyQuantity { @@ -631,8 +631,21 @@ public: ~PropertyStiffness() override = default; }; +/** StiffnessDensity property + * This is a property for representing stiffness per area unit. It is basically a float + * property. On the Gui it has a quantity like Pa/m. + */ +class AppExport PropertyStiffnessDensity: public PropertyQuantity +{ + TYPESYSTEM_HEADER_WITH_OVERRIDE(); + +public: + PropertyStiffnessDensity(); + ~PropertyStiffnessDensity() override = default; +}; + /** Stress property - * This is a property for representing . It is basically a float + * This is a property for representing stress. It is basically a float * property. On the Gui it has a quantity like Pa. */ class AppExport PropertyStress: public PropertyQuantity diff --git a/src/Base/CMakeLists.txt b/src/Base/CMakeLists.txt index 478bb59a8b..f2528e7155 100644 --- a/src/Base/CMakeLists.txt +++ b/src/Base/CMakeLists.txt @@ -243,7 +243,6 @@ SET(FreeCADBase_CPP_SRCS Matrix.cpp MatrixPyImp.cpp MemDebug.cpp - Mutex.cpp Observer.cpp Parameter.xsd Parameter.cpp @@ -314,7 +313,6 @@ SET(FreeCADBase_HPP_SRCS Interpreter.h Matrix.h MemDebug.h - Mutex.h Observer.h Parameter.h Persistence.h diff --git a/src/Base/Console.h b/src/Base/Console.h index ab2a2b3616..ed350bd5ca 100644 --- a/src/Base/Console.h +++ b/src/Base/Console.h @@ -579,23 +579,19 @@ public: */ bool isActive(Base::LogStyle category) const { - if (category == Base::LogStyle::Log) { - return bLog; - } - if (category == Base::LogStyle::Warning) { - return bWrn; - } - if (category == Base::LogStyle::Error) { - return bErr; - } - if (category == Base::LogStyle::Message) { - return bMsg; - } - if (category == Base::LogStyle::Critical) { - return bCritical; - } - if (category == Base::LogStyle::Notification) { - return bNotification; + switch (category) { + case Base::LogStyle::Log: + return bLog; + case Base::LogStyle::Warning: + return bWrn; + case Base::LogStyle::Error: + return bErr; + case Base::LogStyle::Message: + return bMsg; + case Base::LogStyle::Critical: + return bCritical; + case Base::LogStyle::Notification: + return bNotification; } return false; diff --git a/src/Base/Interpreter.cpp b/src/Base/Interpreter.cpp index 9c5b7be311..b1e9870f27 100644 --- a/src/Base/Interpreter.cpp +++ b/src/Base/Interpreter.cpp @@ -475,14 +475,11 @@ void InterpreterSingleton::runFile(const char* pxFileName, bool local) if (PyErr_ExceptionMatches(PyExc_SystemExit)) { throw SystemExitException(); } - throw PyException(); } Py_DECREF(result); } - else { - throw FileException("Unknown file", pxFileName); - } + throw FileException("Unknown file", pxFileName); } bool InterpreterSingleton::loadModule(const char* psModName) diff --git a/src/Base/MatrixPyImp.cpp b/src/Base/MatrixPyImp.cpp index 8d0229b0ec..0fdf71b054 100644 --- a/src/Base/MatrixPyImp.cpp +++ b/src/Base/MatrixPyImp.cpp @@ -256,11 +256,9 @@ PyObject* MatrixPy::richCompare(PyObject* v, PyObject* w, int op) Py_INCREF(res); return res; } - else { - // This always returns False - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } + // This always returns False + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; } PyObject* MatrixPy::move(PyObject* args) diff --git a/src/Base/Mutex.cpp b/src/Base/Mutex.cpp deleted file mode 100644 index e3d07c4a4e..0000000000 --- a/src/Base/Mutex.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2022 Werner Mayer * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - - -#include "PreCompiled.h" - -#include "Mutex.h" - - -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) -QRecursiveMutex::QRecursiveMutex() - : QMutex(QMutex::Recursive) -{} - -QRecursiveMutex::~QRecursiveMutex() -{} -#endif diff --git a/src/Base/Mutex.h b/src/Base/Mutex.h deleted file mode 100644 index e65db63092..0000000000 --- a/src/Base/Mutex.h +++ /dev/null @@ -1,39 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2022 Werner Mayer * - * * - * This file is part of the FreeCAD CAx development system. * - * * - * This library is free software; you can redistribute it and/or * - * modify it under the terms of the GNU Library General Public * - * License as published by the Free Software Foundation; either * - * version 2 of the License, or (at your option) any later version. * - * * - * This library is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU Library General Public License for more details. * - * * - * You should have received a copy of the GNU Library General Public * - * License along with this library; see the file COPYING.LIB. If not, * - * write to the Free Software Foundation, Inc., 59 Temple Place, * - * Suite 330, Boston, MA 02111-1307, USA * - * * - ***************************************************************************/ - - -#ifndef BASE_MUTEX_H -#define BASE_MUTEX_H - -#include -#include - -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) -class BaseExport QRecursiveMutex: public QMutex -{ -public: - QRecursiveMutex(); - ~QRecursiveMutex(); -}; -#endif - -#endif // BASE_MUTEX_H diff --git a/src/Base/Parameter.cpp b/src/Base/Parameter.cpp index 47163d7259..54a33bf907 100644 --- a/src/Base/Parameter.cpp +++ b/src/Base/Parameter.cpp @@ -501,11 +501,7 @@ std::vector> ParameterGrp::GetGroups() /// test if this group is empty bool ParameterGrp::IsEmpty() const { - if (_pGroupNode && _pGroupNode->getFirstChild()) { - return false; - } - - return true; + return !(_pGroupNode && _pGroupNode->getFirstChild()); } /// test if a special sub group is in this group diff --git a/src/Base/Persistence.cpp b/src/Base/Persistence.cpp index 094b85767f..7dc3f7d42e 100644 --- a/src/Base/Persistence.cpp +++ b/src/Base/Persistence.cpp @@ -76,32 +76,34 @@ std::string Persistence::encodeAttribute(const std::string& str) { std::string tmp; for (char it : str) { - if (it == '<') { - tmp += "<"; - } - else if (it == '\"') { - tmp += """; - } - else if (it == '\'') { - tmp += "'"; - } - else if (it == '&') { - tmp += "&"; - } - else if (it == '>') { - tmp += ">"; - } - else if (it == '\r') { - tmp += " "; - } - else if (it == '\n') { - tmp += " "; - } - else if (it == '\t') { - tmp += " "; - } - else { - tmp += it; + switch (it) { + case '<': + tmp += "<"; + break; + case '\"': + tmp += """; + break; + case '\'': + tmp += "'"; + break; + case '&': + tmp += "&"; + break; + case '>': + tmp += ">"; + break; + case '\r': + tmp += " "; + break; + case '\n': + tmp += " "; + break; + case '\t': + tmp += " "; + break; + default: + tmp += it; + break; } } diff --git a/src/Base/PlacementPyImp.cpp b/src/Base/PlacementPyImp.cpp index fb5367b591..d57791d69f 100644 --- a/src/Base/PlacementPyImp.cpp +++ b/src/Base/PlacementPyImp.cpp @@ -148,22 +148,18 @@ PyObject* PlacementPy::richCompare(PyObject* v, PyObject* w, int op) PyErr_SetString(PyExc_TypeError, "no ordering relation is defined for Placement"); return nullptr; } - else if (op == Py_EQ) { + if (op == Py_EQ) { res = (p1 == p2) ? Py_True : Py_False; Py_INCREF(res); return res; } - else { - res = (p1 != p2) ? Py_True : Py_False; - Py_INCREF(res); - return res; - } - } - else { - // This always returns False - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; + res = (p1 != p2) ? Py_True : Py_False; + Py_INCREF(res); + return res; } + // This always returns False + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; } PyObject* PlacementPy::move(PyObject* args) diff --git a/src/Base/PreCompiled.h b/src/Base/PreCompiled.h index f81b2e771b..b38b44a68d 100644 --- a/src/Base/PreCompiled.h +++ b/src/Base/PreCompiled.h @@ -73,6 +73,7 @@ #include #include #include +#include #include // streams @@ -130,8 +131,6 @@ #include #include #include -#include -#include #include #include diff --git a/src/Base/PyObjectBase.cpp b/src/Base/PyObjectBase.cpp index 4f00c0b4ae..0c4000ddfa 100644 --- a/src/Base/PyObjectBase.cpp +++ b/src/Base/PyObjectBase.cpp @@ -365,7 +365,7 @@ int PyObjectBase::__setattro(PyObject *obj, PyObject *attro, PyObject *value) PyErr_Format(PyExc_AttributeError, "Cannot delete attribute: '%s'", attr); return -1; } - else if (!static_cast(obj)->isValid()){ + if (!static_cast(obj)->isValid()){ PyErr_Format(PyExc_ReferenceError, "Cannot access attribute '%s' of deleted object", attr); return -1; } @@ -404,36 +404,32 @@ PyObject *PyObjectBase::_getattr(const char *attr) // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return reinterpret_cast(Py_TYPE(this)); } - else if (streq(attr, "__members__")) { + if (streq(attr, "__members__")) { // Use __dict__ instead as __members__ is deprecated return nullptr; } - else if (streq(attr,"__dict__")) { + if (streq(attr,"__dict__")) { // Return the default dict PyTypeObject *tp = Py_TYPE(this); Py_XINCREF(tp->tp_dict); return tp->tp_dict; } - else if (streq(attr,"softspace")) { + if (streq(attr,"softspace")) { // Internal Python stuff return nullptr; } - else { - // As fallback solution use Python's default method to get generic attributes - PyObject *w{}; - PyObject *res{}; - w = PyUnicode_InternFromString(attr); - if (w) { - res = PyObject_GenericGetAttr(this, w); - Py_XDECREF(w); - return res; - } else { - // Throw an exception for unknown attributes - PyTypeObject *tp = Py_TYPE(this); - PyErr_Format(PyExc_AttributeError, "%.50s instance has no attribute '%.400s'", tp->tp_name, attr); - return nullptr; - } + // As fallback solution use Python's default method to get generic attributes + PyObject *w{}, *res{}; + w = PyUnicode_InternFromString(attr); + if (w) { + res = PyObject_GenericGetAttr(this, w); + Py_XDECREF(w); + return res; } + // Throw an exception for unknown attributes + PyTypeObject *tp = Py_TYPE(this); + PyErr_Format(PyExc_AttributeError, "%.50s instance has no attribute '%.400s'", tp->tp_name, attr); + return nullptr; } int PyObjectBase::_setattr(const char *attr, PyObject *value) @@ -449,12 +445,11 @@ int PyObjectBase::_setattr(const char *attr, PyObject *value) int res = PyObject_GenericSetAttr(this, w, value); Py_DECREF(w); return res; - } else { - // Throw an exception for unknown attributes - PyTypeObject *tp = Py_TYPE(this); - PyErr_Format(PyExc_AttributeError, "%.50s instance has no attribute '%.400s'", tp->tp_name, attr); - return -1; } + // Throw an exception for unknown attributes + PyTypeObject *tp = Py_TYPE(this); + PyErr_Format(PyExc_AttributeError, "%.50s instance has no attribute '%.400s'", tp->tp_name, attr); + return -1; } /*------------------------------ diff --git a/src/Base/PyTools.c b/src/Base/PyTools.c index 4a90a3becd..e71216a224 100644 --- a/src/Base/PyTools.c +++ b/src/Base/PyTools.c @@ -359,27 +359,25 @@ PP_Convert_Result(PyObject *presult, const char *resFormat, void *resTarget) Py_DECREF(presult); /* procedures and stmts return None */ return 0; } - else if (! PyArg_Parse(presult, resFormat, resTarget)) { /* convert Python->C */ Py_DECREF(presult); /* need not be tuple */ return -1; /* error in convert */ } - else { - if (strcmp(resFormat, "O") != 0) { /* free object unless exported */ - if (strcmp(resFormat, "s") == 0) { /* copy string: caller owns it */ - char **target = (char**) resTarget; + if (strcmp(resFormat, "O") != 0) { /* free object unless exported */ + if (strcmp(resFormat, "s") == 0) { /* copy string: caller owns it */ + char **target = (char**) resTarget; #if defined (__GNUC__) - *target = strdup(*target); + *target = strdup(*target); #else - *target = _strdup(*target); + *target = _strdup(*target); #endif - } - Py_DECREF(presult); } - return 0; /* returns 0=success, -1=failure */ - } /* if 0: C result in *resTarget */ -} /* caller must decref if fmt="O" */ - /* caller must free() if fmt="s" */ + Py_DECREF(presult); + } + return 0; /* returns 0=success, -1=failure */ + /* if 0: C result in *resTarget */ +} /* caller must decref if fmt="O" */ + /* caller must free() if fmt="s" */ int PP_Get_Global(const char *modname, const char *varname, const char *resfmt, void *cresult) @@ -430,27 +428,26 @@ int PP_DEBUG = 0; /* debug embedded code with pdb? */ const char *PP_Init(const char *modname) { Py_Initialize(); /* init python if needed */ - if (modname!=NULL) return modname; - { /* we assume here that the caller frees allocated memory */ - return "__main__"; - } + if (modname) + return modname; + /* we assume here that the caller frees allocated memory */ + return "__main__"; } int -PP_Make_Dummy_Module(const char *modname) /* namespace for strings, if no file */ -{ /* instead of sharing __main__ for all */ - PyObject *module = NULL, *dict = NULL; /* note: __main__ is created in py_init */ +PP_Make_Dummy_Module(const char *modname) /* namespace for strings, if no file */ +{ /* instead of sharing __main__ for all */ + PyObject *module = NULL, *dict = NULL; /* note: __main__ is created in py_init */ Py_Initialize(); module = PyImport_AddModule(modname); /* fetch or make, no load */ if (module == NULL) /* module not incref'd */ return -1; - else { /* module.__dict__ */ - dict = PyModule_GetDict(module); /* ['__dummy__'] = None */ - PyDict_SetItemString(dict, "__dummy__", Py_None); - PyDict_SetItemString(dict, "__builtins__", PyEval_GetBuiltins()); - return 0; - } + /* module.__dict__ */ + dict = PyModule_GetDict(module); /* ['__dummy__'] = None */ + PyDict_SetItemString(dict, "__dummy__", Py_None); + PyDict_SetItemString(dict, "__builtins__", PyEval_GetBuiltins()); + return 0; } @@ -479,17 +476,14 @@ PP_Load_Module(const char *modname) /* modname can be "package.module" for PyDict_GetItemString(PyModule_GetDict(module), "__dummy__")) { return module; /* not increfd */ } - else if (PP_RELOAD && module != NULL && PyModule_Check(module)) { module = PyImport_ReloadModule(module); /* reload file,run code */ Py_XDECREF(module); /* still on sys.modules */ return module; /* not increfd */ } - else { - module = PyImport_ImportModule(modname); /* fetch or load module */ - Py_XDECREF(module); /* still on sys.modules */ - return module; /* not increfd */ - } + module = PyImport_ImportModule(modname); /* fetch or load module */ + Py_XDECREF(module); /* still on sys.modules */ + return module; /* not increfd */ } diff --git a/src/Base/QuantityPyImp.cpp b/src/Base/QuantityPyImp.cpp index 6069342ebf..4a59496f42 100644 --- a/src/Base/QuantityPyImp.cpp +++ b/src/Base/QuantityPyImp.cpp @@ -508,20 +508,18 @@ PyObject* QuantityPy::number_power_handler(PyObject* self, PyObject* other, PyOb return new QuantityPy(new Quantity(q)); } - else if (PyFloat_Check(other)) { + if (PyFloat_Check(other)) { Base::Quantity* a = static_cast(self)->getQuantityPtr(); double b = PyFloat_AsDouble(other); return new QuantityPy(new Quantity(a->pow(b))); } - else if (PyLong_Check(other)) { + if (PyLong_Check(other)) { Base::Quantity* a = static_cast(self)->getQuantityPtr(); double b = (double)PyLong_AsLong(other); return new QuantityPy(new Quantity(a->pow(b))); } - else { - PyErr_SetString(PyExc_TypeError, "Expected quantity or number"); - return nullptr; - } + PyErr_SetString(PyExc_TypeError, "Expected quantity or number"); + return nullptr; } PY_CATCH } @@ -543,35 +541,31 @@ PyObject* QuantityPy::richCompare(PyObject* v, PyObject* w, int op) const Quantity* u2 = static_cast(w)->getQuantityPtr(); PyObject* res = nullptr; - if (op == Py_NE) { - res = (!(*u1 == *u2)) ? Py_True : Py_False; - Py_INCREF(res); - return res; - } - else if (op == Py_LT) { - res = (*u1 < *u2) ? Py_True : Py_False; - Py_INCREF(res); - return res; - } - else if (op == Py_LE) { - res = (*u1 < *u2) || (*u1 == *u2) ? Py_True : Py_False; - Py_INCREF(res); - return res; - } - else if (op == Py_GT) { - res = (!(*u1 < *u2)) && (!(*u1 == *u2)) ? Py_True : Py_False; - Py_INCREF(res); - return res; - } - else if (op == Py_GE) { - res = (!(*u1 < *u2)) ? Py_True : Py_False; - Py_INCREF(res); - return res; - } - else if (op == Py_EQ) { - res = (*u1 == *u2) ? Py_True : Py_False; - Py_INCREF(res); - return res; + switch (op) { + case Py_NE: + res = (!(*u1 == *u2)) ? Py_True : Py_False; + Py_INCREF(res); + return res; + case Py_LT: + res = (*u1 < *u2) ? Py_True : Py_False; + Py_INCREF(res); + return res; + case Py_LE: + res = (*u1 < *u2) || (*u1 == *u2) ? Py_True : Py_False; + Py_INCREF(res); + return res; + case Py_GT: + res = (!(*u1 < *u2)) && (!(*u1 == *u2)) ? Py_True : Py_False; + Py_INCREF(res); + return res; + case Py_GE: + res = (!(*u1 < *u2)) ? Py_True : Py_False; + Py_INCREF(res); + return res; + case Py_EQ: + res = (*u1 == *u2) ? Py_True : Py_False; + Py_INCREF(res); + return res; } } else if (PyNumber_Check(v) && PyNumber_Check(w)) { @@ -579,35 +573,31 @@ PyObject* QuantityPy::richCompare(PyObject* v, PyObject* w, int op) double u1 = PyFloat_AsDouble(v); double u2 = PyFloat_AsDouble(w); PyObject* res = nullptr; - if (op == Py_NE) { - res = (u1 != u2) ? Py_True : Py_False; - Py_INCREF(res); - return res; - } - else if (op == Py_LT) { - res = (u1 < u2) ? Py_True : Py_False; - Py_INCREF(res); - return res; - } - else if (op == Py_LE) { - res = (u1 <= u2) ? Py_True : Py_False; - Py_INCREF(res); - return res; - } - else if (op == Py_GT) { - res = (u1 > u2) ? Py_True : Py_False; - Py_INCREF(res); - return res; - } - else if (op == Py_GE) { - res = (u1 >= u2) ? Py_True : Py_False; - Py_INCREF(res); - return res; - } - else if (op == Py_EQ) { - res = (u1 == u2) ? Py_True : Py_False; - Py_INCREF(res); - return res; + switch (op) { + case Py_NE: + res = (u1 != u2) ? Py_True : Py_False; + Py_INCREF(res); + return res; + case Py_LT: + res = (u1 < u2) ? Py_True : Py_False; + Py_INCREF(res); + return res; + case Py_LE: + res = (u1 <= u2) ? Py_True : Py_False; + Py_INCREF(res); + return res; + case Py_GT: + res = (u1 > u2) ? Py_True : Py_False; + Py_INCREF(res); + return res; + case Py_GE: + res = (u1 >= u2) ? Py_True : Py_False; + Py_INCREF(res); + return res; + case Py_EQ: + res = (u1 == u2) ? Py_True : Py_False; + Py_INCREF(res); + return res; } } diff --git a/src/Base/Reader.cpp b/src/Base/Reader.cpp index 6b6068bf12..3fb14a803c 100644 --- a/src/Base/Reader.cpp +++ b/src/Base/Reader.cpp @@ -116,12 +116,10 @@ long Base::XMLReader::getAttributeAsInteger(const char* AttrName) const if (pos != AttrMap.end()) { return atol(pos->second.c_str()); } - else { - // wrong name, use hasAttribute if not sure! - std::ostringstream msg; - msg << "XML Attribute: \"" << AttrName << "\" not found"; - throw Base::XMLAttributeError(msg.str()); - } + // wrong name, use hasAttribute if not sure! + std::ostringstream msg; + msg << "XML Attribute: \"" << AttrName << "\" not found"; + throw Base::XMLAttributeError(msg.str()); } unsigned long Base::XMLReader::getAttributeAsUnsigned(const char* AttrName) const @@ -131,12 +129,10 @@ unsigned long Base::XMLReader::getAttributeAsUnsigned(const char* AttrName) cons if (pos != AttrMap.end()) { return strtoul(pos->second.c_str(), nullptr, 10); } - else { - // wrong name, use hasAttribute if not sure! - std::ostringstream msg; - msg << "XML Attribute: \"" << AttrName << "\" not found"; - throw Base::XMLAttributeError(msg.str()); - } + // wrong name, use hasAttribute if not sure! + std::ostringstream msg; + msg << "XML Attribute: \"" << AttrName << "\" not found"; + throw Base::XMLAttributeError(msg.str()); } double Base::XMLReader::getAttributeAsFloat(const char* AttrName) const @@ -146,12 +142,10 @@ double Base::XMLReader::getAttributeAsFloat(const char* AttrName) const if (pos != AttrMap.end()) { return atof(pos->second.c_str()); } - else { - // wrong name, use hasAttribute if not sure! - std::ostringstream msg; - msg << "XML Attribute: \"" << AttrName << "\" not found"; - throw Base::XMLAttributeError(msg.str()); - } + // wrong name, use hasAttribute if not sure! + std::ostringstream msg; + msg << "XML Attribute: \"" << AttrName << "\" not found"; + throw Base::XMLAttributeError(msg.str()); } const char* Base::XMLReader::getAttribute(const char* AttrName) const @@ -161,12 +155,10 @@ const char* Base::XMLReader::getAttribute(const char* AttrName) const if (pos != AttrMap.end()) { return pos->second.c_str(); } - else { - // wrong name, use hasAttribute if not sure! - std::ostringstream msg; - msg << "XML Attribute: \"" << AttrName << "\" not found"; - throw Base::XMLAttributeError(msg.str()); - } + // wrong name, use hasAttribute if not sure! + std::ostringstream msg; + msg << "XML Attribute: \"" << AttrName << "\" not found"; + throw Base::XMLAttributeError(msg.str()); } bool Base::XMLReader::hasAttribute(const char* AttrName) const @@ -216,7 +208,7 @@ void Base::XMLReader::readElement(const char* ElementName) // thus we must stop reading on. break; } - else if (ReadType == EndDocument) { + if (ReadType == EndDocument) { // the end of the document has been reached but we still try to continue on reading throw Base::XMLParseException("End of document reached"); } @@ -276,7 +268,7 @@ void Base::XMLReader::readEndElement(const char* ElementName, int level) && (level < 0 || level == Level)) { return; } - else if (ReadType == EndDocument) { + if (ReadType == EndDocument) { // the end of the document has been reached but we still try to continue on reading throw Base::XMLParseException("End of document reached"); } diff --git a/src/Base/RotationPyImp.cpp b/src/Base/RotationPyImp.cpp index 8d0b019a1c..a4747ac6f1 100644 --- a/src/Base/RotationPyImp.cpp +++ b/src/Base/RotationPyImp.cpp @@ -271,22 +271,18 @@ PyObject* RotationPy::richCompare(PyObject* v, PyObject* w, int op) PyErr_SetString(PyExc_TypeError, "no ordering relation is defined for Rotation"); return nullptr; } - else if (op == Py_EQ) { + if (op == Py_EQ) { res = (r1 == r2) ? Py_True : Py_False; Py_INCREF(res); return res; } - else { - res = (r1 != r2) ? Py_True : Py_False; - Py_INCREF(res); - return res; - } - } - else { - // This always returns False - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; + res = (r1 != r2) ? Py_True : Py_False; + Py_INCREF(res); + return res; } + // This always returns False + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; } PyObject* RotationPy::invert(PyObject* args) diff --git a/src/Base/Sequencer.cpp b/src/Base/Sequencer.cpp index 4d41dc0009..3ab27b74be 100644 --- a/src/Base/Sequencer.cpp +++ b/src/Base/Sequencer.cpp @@ -24,12 +24,11 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#include +#include +#include #endif #include "Sequencer.h" -#include "Mutex.h" - using namespace Base; @@ -40,7 +39,7 @@ struct SequencerP // members static std::vector _instances; /**< A vector of all created instances */ static SequencerLauncher* _topLauncher; /**< The outermost launcher */ - static QRecursiveMutex mutex; /**< A mutex-locker for the launcher */ + static std::recursive_mutex mutex; /**< A mutex-locker for the launcher */ /** Sets a global sequencer object. * Access to the last registered object is performed by @see Sequencer(). */ @@ -66,7 +65,7 @@ struct SequencerP */ std::vector SequencerP::_instances; SequencerLauncher* SequencerP::_topLauncher = nullptr; -QRecursiveMutex SequencerP::mutex; +std::recursive_mutex SequencerP::mutex; } // namespace Base SequencerBase& SequencerBase::Instance() @@ -160,7 +159,7 @@ bool SequencerBase::isBlocking() const bool SequencerBase::setLocked(bool bLocked) { - QMutexLocker locker(&SequencerP::mutex); + std::lock_guard locker(SequencerP::mutex); bool old = this->_bLocked; this->_bLocked = bLocked; return old; @@ -168,19 +167,19 @@ bool SequencerBase::setLocked(bool bLocked) bool SequencerBase::isLocked() const { - QMutexLocker locker(&SequencerP::mutex); + std::lock_guard locker(SequencerP::mutex); return this->_bLocked; } bool SequencerBase::isRunning() const { - QMutexLocker locker(&SequencerP::mutex); + std::lock_guard locker(SequencerP::mutex); return (SequencerP::_topLauncher != nullptr); } bool SequencerBase::wasCanceled() const { - QMutexLocker locker(&SequencerP::mutex); + std::lock_guard locker(SequencerP::mutex); return this->_bCanceled; } @@ -236,7 +235,7 @@ void ConsoleSequencer::resetData() SequencerLauncher::SequencerLauncher(const char* pszStr, size_t steps) { - QMutexLocker locker(&SequencerP::mutex); + std::lock_guard locker(SequencerP::mutex); // Have we already an instance of SequencerLauncher created? if (!SequencerP::_topLauncher) { SequencerBase::Instance().start(pszStr, steps); @@ -246,24 +245,22 @@ SequencerLauncher::SequencerLauncher(const char* pszStr, size_t steps) SequencerLauncher::~SequencerLauncher() { - QMutexLocker locker(&SequencerP::mutex); + std::lock_guard locker(SequencerP::mutex); if (SequencerP::_topLauncher == this) { SequencerBase::Instance().stop(); - } - if (SequencerP::_topLauncher == this) { SequencerP::_topLauncher = nullptr; } } void SequencerLauncher::setText(const char* pszTxt) { - QMutexLocker locker(&SequencerP::mutex); + std::lock_guard locker(SequencerP::mutex); SequencerBase::Instance().setText(pszTxt); } bool SequencerLauncher::next(bool canAbort) { - QMutexLocker locker(&SequencerP::mutex); + std::lock_guard locker(SequencerP::mutex); if (SequencerP::_topLauncher != this) { return true; // ignore } @@ -272,13 +269,13 @@ bool SequencerLauncher::next(bool canAbort) void SequencerLauncher::setProgress(size_t pos) { - QMutexLocker locker(&SequencerP::mutex); + std::lock_guard locker(SequencerP::mutex); SequencerBase::Instance().setProgress(pos); } size_t SequencerLauncher::numberOfSteps() const { - QMutexLocker locker(&SequencerP::mutex); + std::lock_guard locker(SequencerP::mutex); return SequencerBase::Instance().numberOfSteps(); } diff --git a/src/Base/StackWalker.cpp b/src/Base/StackWalker.cpp index c3c4a7159c..fcc7fa4dc7 100644 --- a/src/Base/StackWalker.cpp +++ b/src/Base/StackWalker.cpp @@ -1118,10 +1118,7 @@ BOOL __stdcall StackWalker::myReadProcMem( //printf("ReadMemory: hProcess: %p, baseAddr: %p, buffer: %p, size: %d, read: %d, result: %d\n", hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, (DWORD) st, (DWORD) bRet); return bRet; } - else - { - return s_readMemoryFunction(hProcess, qwBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead, s_readMemoryFunction_UserData); - } + return s_readMemoryFunction(hProcess, qwBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead, s_readMemoryFunction_UserData); } void StackWalker::OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion) diff --git a/src/Base/Tools.cpp b/src/Base/Tools.cpp index 1f315ce721..9e313be85e 100644 --- a/src/Base/Tools.cpp +++ b/src/Base/Tools.cpp @@ -264,17 +264,19 @@ std::string Base::Tools::escapeEncodeString(const std::string& s) std::string result; size_t len = s.size(); for (size_t i = 0; i < len; ++i) { - if (s.at(i) == '\\') { - result += "\\\\"; - } - else if (s.at(i) == '\"') { - result += "\\\""; - } - else if (s.at(i) == '\'') { - result += "\\\'"; - } - else { - result += s.at(i); + switch (s.at(i)) { + case '\\': + result += "\\\\"; + break; + case '\"': + result += "\\\""; + break; + case '\'': + result += "\\\'"; + break; + default: + result += s.at(i); + break; } } return result; @@ -305,14 +307,16 @@ std::string Base::Tools::escapeEncodeFilename(const std::string& s) std::string result; size_t len = s.size(); for (size_t i = 0; i < len; ++i) { - if (s.at(i) == '\"') { - result += "\\\""; - } - else if (s.at(i) == '\'') { - result += "\\\'"; - } - else { - result += s.at(i); + switch (s.at(i)) { + case '\"': + result += "\\\""; + break; + case '\'': + result += "\\\'"; + break; + default: + result += s.at(i); + break; } } return result; diff --git a/src/Base/Unit.cpp b/src/Base/Unit.cpp index 64cefecf85..7aa14c7526 100644 --- a/src/Base/Unit.cpp +++ b/src/Base/Unit.cpp @@ -575,6 +575,9 @@ QString Unit::getTypeString() const if (*this == Unit::Stiffness) { return QString::fromLatin1("Stiffness"); } + if (*this == Unit::StiffnessDensity) { + return QString::fromLatin1("StiffnessDensity"); + } if (*this == Unit::Stress) { return QString::fromLatin1("Stress"); } @@ -667,6 +670,7 @@ const Unit Unit::ShearModulus (-1,1,-2); const Unit Unit::SpecificEnergy (2, 0, -2); const Unit Unit::SpecificHeat (2, 0, -2, 0, -1); const Unit Unit::Stiffness (0, 1, -2); +const Unit Unit::StiffnessDensity (-2, 1, -2); const Unit Unit::Stress (-1,1,-2); const Unit Unit::ThermalConductivity (1, 1, -3, 0, -1); const Unit Unit::ThermalExpansionCoefficient(0, 0, 0, 0, -1); diff --git a/src/Base/Unit.h b/src/Base/Unit.h index 8b4a4aec70..77b325a784 100644 --- a/src/Base/Unit.h +++ b/src/Base/Unit.h @@ -150,6 +150,7 @@ public: static const Unit YoungsModulus; static const Unit Stiffness; + static const Unit StiffnessDensity; static const Unit Force; static const Unit Work; diff --git a/src/Base/UnitPyImp.cpp b/src/Base/UnitPyImp.cpp index e8db65865a..364ffb6641 100644 --- a/src/Base/UnitPyImp.cpp +++ b/src/Base/UnitPyImp.cpp @@ -175,10 +175,8 @@ PyObject* UnitPy::number_multiply_handler(PyObject* self, PyObject* other) return new UnitPy(new Unit((*a) * (*b))); } - else { - PyErr_SetString(PyExc_TypeError, "A Unit can only be multiplied by a Unit"); - return nullptr; - } + PyErr_SetString(PyExc_TypeError, "A Unit can only be multiplied by a Unit"); + return nullptr; } PyObject* UnitPy::richCompare(PyObject* v, PyObject* w, int op) @@ -192,22 +190,18 @@ PyObject* UnitPy::richCompare(PyObject* v, PyObject* w, int op) PyErr_SetString(PyExc_TypeError, "no ordering relation is defined for Units"); return nullptr; } - else if (op == Py_EQ) { + if (op == Py_EQ) { res = (*u1 == *u2) ? Py_True : Py_False; // NOLINT Py_INCREF(res); return res; } - else { - res = (*u1 != *u2) ? Py_True : Py_False; // NOLINT - Py_INCREF(res); - return res; - } - } - else { - // This always returns False - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; + res = (*u1 != *u2) ? Py_True : Py_False; // NOLINT + Py_INCREF(res); + return res; } + // This always returns False + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; } Py::String UnitPy::getType() const diff --git a/src/Base/UnitsApi.cpp b/src/Base/UnitsApi.cpp index f0c118bd20..9984a4dee8 100644 --- a/src/Base/UnitsApi.cpp +++ b/src/Base/UnitsApi.cpp @@ -193,6 +193,7 @@ double UnitsApi::toDouble(PyObject* args, const Base::Unit& u) } throw Base::UnitsMismatchError("Wrong unit type!"); } + if (PyFloat_Check(args)) { return PyFloat_AsDouble(args); } diff --git a/src/Base/UnitsSchemaInternal.cpp b/src/Base/UnitsSchemaInternal.cpp index c3d13cd38e..355e8307c7 100644 --- a/src/Base/UnitsSchemaInternal.cpp +++ b/src/Base/UnitsSchemaInternal.cpp @@ -223,7 +223,7 @@ UnitsSchemaInternal::schemaTranslate(const Quantity& quant, double& factor, QStr unitString = QString::fromLatin1("mN/m"); factor = 1e-3; } - if (UnitValue < 1e3) { + else if (UnitValue < 1e3) { unitString = QString::fromLatin1("N/m"); factor = 1.0; } @@ -236,6 +236,24 @@ UnitsSchemaInternal::schemaTranslate(const Quantity& quant, double& factor, QStr factor = 1e6; } } + else if ((unit == Unit::StiffnessDensity)) { + if (UnitValue < 1e-3) { + unitString = QString::fromLatin1("Pa/m"); + factor = 1e-6; + } + else if (UnitValue < 1) { + unitString = QString::fromLatin1("kPa/m"); + factor = 1e-3; + } + else if (UnitValue < 1e3) { + unitString = QString::fromLatin1("MPa/m"); + factor = 1.0; + } + else { + unitString = QString::fromLatin1("GPa/m"); + factor = 1e3; + } + } else if (unit == Unit::Force) { if (UnitValue < 1e3) { unitString = QString::fromLatin1("mN"); diff --git a/src/Base/UnitsSchemaMKS.cpp b/src/Base/UnitsSchemaMKS.cpp index e15a721ad3..aba27295ab 100644 --- a/src/Base/UnitsSchemaMKS.cpp +++ b/src/Base/UnitsSchemaMKS.cpp @@ -174,7 +174,7 @@ QString UnitsSchemaMKS::schemaTranslate(const Quantity& quant, double& factor, Q unitString = QString::fromLatin1("mN/m"); factor = 1e-3; } - if (UnitValue < 1e3) { + else if (UnitValue < 1e3) { unitString = QString::fromLatin1("N/m"); factor = 1.0; } @@ -187,6 +187,24 @@ QString UnitsSchemaMKS::schemaTranslate(const Quantity& quant, double& factor, Q factor = 1e6; } } + else if ((unit == Unit::StiffnessDensity)) { + if (UnitValue < 1e-3) { + unitString = QString::fromLatin1("Pa/m"); + factor = 1e-6; + } + else if (UnitValue < 1) { + unitString = QString::fromLatin1("kPa/m"); + factor = 1e-3; + } + else if (UnitValue < 1e3) { + unitString = QString::fromLatin1("MPa/m"); + factor = 1.0; + } + else { + unitString = QString::fromLatin1("GPa/m"); + factor = 1e3; + } + } else if (unit == Unit::ThermalConductivity) { if (UnitValue > 1000000) { unitString = QString::fromLatin1("W/mm/K"); diff --git a/src/Base/VectorPyImp.cpp b/src/Base/VectorPyImp.cpp index 8e0ea738e9..80d730c39d 100644 --- a/src/Base/VectorPyImp.cpp +++ b/src/Base/VectorPyImp.cpp @@ -150,30 +150,24 @@ PyObject* VectorPy::number_multiply_handler(PyObject* self, PyObject* other) Py::Float mult(a * b); return Py::new_reference_to(mult); } - else if (PyNumber_Check(other)) { + if (PyNumber_Check(other)) { double b = PyFloat_AsDouble(other); return new VectorPy(a * b); } - else { - PyErr_SetString(PyExc_TypeError, "A Vector can only be multiplied by Vector or number"); - return nullptr; - } + PyErr_SetString(PyExc_TypeError, "A Vector can only be multiplied by Vector or number"); + return nullptr; } - else if (PyObject_TypeCheck(other, &(VectorPy::Type))) { + if (PyObject_TypeCheck(other, &(VectorPy::Type))) { Base::Vector3d a = static_cast(other)->value(); if (PyNumber_Check(self)) { double b = PyFloat_AsDouble(self); return new VectorPy(a * b); } - else { - PyErr_SetString(PyExc_TypeError, "A Vector can only be multiplied by Vector or number"); - return nullptr; - } - } - else { - PyErr_SetString(PyExc_TypeError, "First or second arg must be Vector"); + PyErr_SetString(PyExc_TypeError, "A Vector can only be multiplied by Vector or number"); return nullptr; } + PyErr_SetString(PyExc_TypeError, "First or second arg must be Vector"); + return nullptr; } Py_ssize_t VectorPy::sequence_length(PyObject* /*unused*/) @@ -243,13 +237,8 @@ PyObject* VectorPy::mapping_subscript(PyObject* self, PyObject* item) } return sequence_item(self, i); } - else if (PySlice_Check(item)) { - Py_ssize_t start = 0; - Py_ssize_t stop = 0; - Py_ssize_t step = 0; - Py_ssize_t slicelength = 0; - Py_ssize_t cur = 0; - Py_ssize_t i = 0; + if (PySlice_Check(item)) { + Py_ssize_t start = 0, stop = 0, step = 0, slicelength = 0, cur = 0, i = 0; PyObject* slice = item; if (PySlice_GetIndicesEx(slice, sequence_length(self), &start, &stop, &step, &slicelength) @@ -260,8 +249,8 @@ PyObject* VectorPy::mapping_subscript(PyObject* self, PyObject* item) if (slicelength <= 0) { return PyTuple_New(0); } - else if (start == 0 && step == 1 && slicelength == sequence_length(self) - && PyObject_TypeCheck(self, &(VectorPy::Type))) { + if (start == 0 && step == 1 && slicelength == sequence_length(self) + && PyObject_TypeCheck(self, &(VectorPy::Type))) { Base::Vector3d v = static_cast(self)->value(); Py::Tuple xyz(3); xyz.setItem(0, Py::Float(v.x)); @@ -269,7 +258,7 @@ PyObject* VectorPy::mapping_subscript(PyObject* self, PyObject* item) xyz.setItem(2, Py::Float(v.z)); return Py::new_reference_to(xyz); } - else if (PyObject_TypeCheck(self, &(VectorPy::Type))) { + if (PyObject_TypeCheck(self, &(VectorPy::Type))) { Base::Vector3d v = static_cast(self)->value(); Py::Tuple xyz(static_cast(slicelength)); @@ -342,22 +331,18 @@ PyObject* VectorPy::richCompare(PyObject* v, PyObject* w, int op) PyErr_SetString(PyExc_TypeError, "no ordering relation is defined for Vector"); return nullptr; } - else if (op == Py_EQ) { + if (op == Py_EQ) { res = (v1 == v2) ? Py_True : Py_False; // NOLINT Py_INCREF(res); return res; } - else { - res = (v1 != v2) ? Py_True : Py_False; // NOLINT - Py_INCREF(res); - return res; - } - } - else { - // This always returns False - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; + res = (v1 != v2) ? Py_True : Py_False; // NOLINT + Py_INCREF(res); + return res; } + // This always returns False + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; } PyObject* VectorPy::isEqual(PyObject* args) diff --git a/src/Gui/CommandDoc.cpp b/src/Gui/CommandDoc.cpp index 6e1ffefefc..55d4b91d64 100644 --- a/src/Gui/CommandDoc.cpp +++ b/src/Gui/CommandDoc.cpp @@ -50,6 +50,7 @@ #include "BitmapFactory.h" #include "Command.h" #include "Control.h" +#include "DockWindowManager.h" #include "FileDialog.h" #include "MainWindow.h" #include "Selection.h" @@ -1715,6 +1716,41 @@ bool StdCmdEdit::isActive() return (!Selection().getCompleteSelection().empty()) || (Gui::Control().activeDialog() != nullptr); } +//=========================================================================== +// Std_Properties +//=========================================================================== +DEF_STD_CMD_A(StdCmdProperties) + +StdCmdProperties::StdCmdProperties() + : Command("Std_Properties") +{ + sGroup = "Edit"; + sMenuText = QT_TR_NOOP("Properties"); + sToolTipText = QT_TR_NOOP("Show the property view, which displays the properties of the selected object."); + sWhatsThis = "Std_Properties"; + sStatusTip = sToolTipText; + sAccel = "Alt+Return"; + sPixmap = "document-properties"; + eType = Alter3DView; +} + +void StdCmdProperties::activated(int iMsg) +{ + Q_UNUSED(iMsg); + QWidget* propertyView = Gui::DockWindowManager::instance()->getDockWindow("Property view"); + if (propertyView) { + QWidget* parent = propertyView->parentWidget(); + if (parent && !parent->isVisible()) { + parent->show(); + } + } +} + +bool StdCmdProperties::isActive() +{ + return !Selection().getCompleteSelection().empty(); +} + //====================================================================== // StdCmdExpression //=========================================================================== @@ -1969,6 +2005,7 @@ void CreateDocCommands() rcCmdMgr.addCommand(new StdCmdTransformManip()); rcCmdMgr.addCommand(new StdCmdAlignment()); rcCmdMgr.addCommand(new StdCmdEdit()); + rcCmdMgr.addCommand(new StdCmdProperties()); rcCmdMgr.addCommand(new StdCmdExpression()); } diff --git a/src/Gui/DlgPreferencesImp.cpp b/src/Gui/DlgPreferencesImp.cpp index 1c31064a85..cd37e6fc88 100644 --- a/src/Gui/DlgPreferencesImp.cpp +++ b/src/Gui/DlgPreferencesImp.cpp @@ -56,6 +56,28 @@ using namespace Gui::Dialog; +bool isParentOf(const QModelIndex& parent, const QModelIndex& child) +{ + for (auto it = child; it.isValid(); it = it.parent()) { + if (it == parent) { + return true; + } + } + + return false; +} + +QModelIndex findRootIndex(const QModelIndex& index) +{ + auto root = index; + + while (root.parent().isValid()) { + root = root.parent(); + } + + return root; +} + QWidget* PreferencesPageItem::getWidget() const { return _widget; @@ -714,17 +736,6 @@ void DlgPreferencesImp::showEvent(QShowEvent* ev) } } -QModelIndex findRootIndex(const QModelIndex& index) -{ - auto root = index; - - while (root.parent().isValid()) { - root = root.parent(); - } - - return root; -} - void DlgPreferencesImp::onPageSelected(const QModelIndex& index) { auto* currentItem = static_cast(_model.itemFromIndex(index)); @@ -790,14 +801,20 @@ void DlgPreferencesImp::onStackWidgetChange(int index) return; } - ui->groupsTreeView->selectionModel()->select(currentItem->index(), QItemSelectionModel::ClearAndSelect); + auto currentIndex = currentItem->index(); auto root = _model.invisibleRootItem(); for (int i = 0; i < root->rowCount(); i++) { auto currentGroup = static_cast(root->child(i)); + auto currentGroupIndex = currentGroup->index(); + + // don't do anything to group of selected item + if (isParentOf(currentGroupIndex, currentIndex)) { + continue; + } if (!currentGroup->isExpanded()) { - ui->groupsTreeView->collapse(currentGroup->index()); + ui->groupsTreeView->collapse(currentGroupIndex); } } @@ -807,6 +824,8 @@ void DlgPreferencesImp::onStackWidgetChange(int index) ui->groupsTreeView->expand(parentItem->index()); parentItem->setExpanded(wasExpanded); } + + ui->groupsTreeView->selectionModel()->select(currentIndex, QItemSelectionModel::ClearAndSelect); } void DlgPreferencesImp::changeEvent(QEvent *e) diff --git a/src/Gui/PreferencePages/DlgSettingsEditor.cpp b/src/Gui/PreferencePages/DlgSettingsEditor.cpp index a8a4bb35a8..65b44ced5f 100644 --- a/src/Gui/PreferencePages/DlgSettingsEditor.cpp +++ b/src/Gui/PreferencePages/DlgSettingsEditor.cpp @@ -163,9 +163,10 @@ DlgSettingsEditor::DlgSettingsEditor( QWidget* parent ) QStringList labels; labels << tr("Items"); ui->displayItems->setHeaderLabels(labels); ui->displayItems->header()->hide(); - for (QVector >::Iterator it = d->colormap.begin(); it != d->colormap.end(); ++it) { + for (const auto &[textType, textColor]: d->colormap) + { auto item = new QTreeWidgetItem(ui->displayItems); - item->setText(0, tr((*it).first.toLatin1())); + item->setText(0, tr(textType.toLatin1())); } pythonSyntax = new PythonSyntaxHighlighter(ui->textEdit1); pythonSyntax->setDocument(ui->textEdit1->document()); @@ -243,11 +244,11 @@ void DlgSettingsEditor::saveSettings() // Saves the color map ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Editor"); - for (QVector >::Iterator it = d->colormap.begin(); it != d->colormap.end(); ++it) { - auto col = static_cast((*it).second); - hGrp->SetUnsigned((*it).first.toLatin1(), col); + for (const auto &[textType, textColor] : d->colormap) + { + auto col = static_cast(textColor); + hGrp->SetUnsigned(textType.toLatin1(), col); } - hGrp->SetInt( "FontSize", ui->fontSize->value() ); hGrp->SetASCII( "Font", ui->fontFamily->currentText().toLatin1() ); @@ -280,13 +281,13 @@ void DlgSettingsEditor::loadSettings() // Restores the color map ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("Editor"); - for (QVector>::Iterator it = d->colormap.begin(); - it != d->colormap.end(); ++it) { - auto col = static_cast((*it).second); - col = hGrp->GetUnsigned((*it).first.toLatin1(), col); - (*it).second = static_cast(col); + for (auto &[textType, textColor] : d->colormap) + { + auto col = static_cast(textColor); + col = hGrp->GetUnsigned(textType.toLatin1(), col); + textColor = static_cast(col); QColor color = App::Color::fromPackedRGB(col); - pythonSyntax->setColor((*it).first, color); + pythonSyntax->setColor(textType, color); } // fill up font styles @@ -332,8 +333,9 @@ void DlgSettingsEditor::resetSettingsToDefaults() ParameterGrp::handle hGrp; hGrp = WindowParameter::getDefaultParameter()->GetGroup("Editor"); //reset the parameters in the "Editor" group - for (QVector >::Iterator it = d->colormap.begin(); it != d->colormap.end(); ++it) { - hGrp->RemoveUnsigned((*it).first.toLatin1()); + for (const auto &[textType, textColor] : d->colormap) + { + hGrp->RemoveUnsigned(textType.toLatin1()); } //reset "FontSize" parameter hGrp->RemoveInt("FontSize"); @@ -351,8 +353,8 @@ void DlgSettingsEditor::changeEvent(QEvent *e) { if (e->type() == QEvent::LanguageChange) { int index = 0; - for (QVector >::Iterator it = d->colormap.begin(); it != d->colormap.end(); ++it) - ui->displayItems->topLevelItem(index++)->setText(0, tr((*it).first.toLatin1())); + for (const auto &[textType, textColor]: d->colormap) + ui->displayItems->topLevelItem(index++)->setText(0, tr(textType.toLatin1())); ui->retranslateUi(this); } else { QWidget::changeEvent(e); diff --git a/src/Gui/PythonWrapper.cpp b/src/Gui/PythonWrapper.cpp index 412b56c47f..7a230bb30d 100644 --- a/src/Gui/PythonWrapper.cpp +++ b/src/Gui/PythonWrapper.cpp @@ -263,7 +263,7 @@ PyTypeObject *getPyTypeObjectForTypeName(); */ class WrapperManager : public QObject { - std::unordered_map> wrappers; + public: static WrapperManager& instance() @@ -275,52 +275,38 @@ public: * \brief addQObject * \param obj * \param pyobj - * Add the QObject and its Python wrapper to the list. + * Connects destruction event of a QObject with invalidation of its PythonWrapper via a helper QObject. */ void addQObject(QObject* obj, PyObject* pyobj) { - if (wrappers.find(obj) == wrappers.end()) { - QObject::connect(obj, &QObject::destroyed, this, &WrapperManager::destroyed); - } + const auto PyW_unique_name = QString::number(reinterpret_cast (pyobj)); + auto PyW_invalidator = findChild (PyW_unique_name, Qt::FindDirectChildrenOnly); - auto& pylist = wrappers[obj]; - if (std::find_if(pylist.cbegin(), pylist.cend(), - [pyobj](const Py::Object& py) { - return py.ptr() == pyobj; - }) == pylist.end()) { + if (PyW_invalidator == nullptr) { + PyW_invalidator = new QObject(this); + PyW_invalidator->setObjectName(PyW_unique_name); - pylist.emplace_back(pyobj); - } - } + Py_INCREF (pyobj); + } else + PyW_invalidator->disconnect(); + + auto destroyedFun = [pyobj](){ + Base::PyGILStateLocker lock; +#if defined (HAVE_SHIBOKEN) + auto sbk_ptr = reinterpret_cast (pyobj); + if (sbk_ptr != nullptr) + Shiboken::Object::setValidCpp(sbk_ptr, false); + else + Base::Console().DeveloperError("WrapperManager", "A QObject has just been destroyed after its Pythonic wrapper.\n"); +#endif + Py_DECREF (pyobj); + }; + + QObject::connect(PyW_invalidator, &QObject::destroyed, this, destroyedFun); + QObject::connect(obj, &QObject::destroyed, PyW_invalidator, &QObject::deleteLater); +} private: - /*! - * \brief destroyed - * \param obj - * The listed QObject is about to be destroyed. Invalidate its Python wrappers now. - */ - void destroyed(QObject* obj = nullptr) - { - if (obj) { -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - auto key = wrappers.find(obj); - if (key != wrappers.end()) { - Base::PyGILStateLocker lock; - for (const auto& it : key->second) { - auto value = it.ptr(); - Shiboken::Object::setValidCpp(reinterpret_cast(value), false); - } - - wrappers.erase(key); - } -#endif - } - } - void clear() - { - Base::PyGILStateLocker lock; - wrappers.clear(); - } void wrapQApplication() { // We have to explicitly hold a reference to the wrapper of the QApplication @@ -347,13 +333,18 @@ private: WrapperManager() { - connect(QApplication::instance(), &QCoreApplication::aboutToQuit, - this, &WrapperManager::clear); wrapQApplication(); } ~WrapperManager() override = default; }; +static std::string formatModuleError(std::string name) +{ + std::string error = "Cannot load " + name + " module"; + PyErr_Print(); + return error; +} + template Py::Object qt_wrapInstance(qttype object, const std::string& className, @@ -363,10 +354,7 @@ Py::Object qt_wrapInstance(qttype object, { PyObject* module = PyImport_ImportModule(shiboken.c_str()); if (!module) { - std::string error = "Cannot load "; - error += shiboken; - error += " module"; - throw Py::Exception(PyExc_ImportError, error); + throw Py::Exception(PyExc_ImportError, formatModuleError(shiboken)); } Py::Module mainmod(module, true); @@ -377,10 +365,7 @@ Py::Object qt_wrapInstance(qttype object, module = PyImport_ImportModule(pyside.c_str()); if (!module) { - std::string error = "Cannot load "; - error += pyside; - error += " module"; - throw Py::Exception(PyExc_ImportError, error); + throw Py::Exception(PyExc_ImportError, formatModuleError(pyside)); } Py::Module qtmod(module); @@ -392,10 +377,7 @@ const char* qt_identifyType(QObject* ptr, const std::string& pyside) { PyObject* module = PyImport_ImportModule(pyside.c_str()); if (!module) { - std::string error = "Cannot load "; - error += pyside; - error += " module"; - throw Py::Exception(PyExc_ImportError, error); + throw Py::Exception(PyExc_ImportError, formatModuleError(pyside)); } Py::Module qtmod(module); @@ -415,10 +397,7 @@ void* qt_getCppPointer(const Py::Object& pyobject, const std::string& shiboken, // https://github.com/PySide/Shiboken/blob/master/shibokenmodule/typesystem_shiboken.xml PyObject* module = PyImport_ImportModule(shiboken.c_str()); if (!module) { - std::string error = "Cannot load "; - error += shiboken; - error += " module"; - throw Py::Exception(PyExc_ImportError, error); + throw Py::Exception(PyExc_ImportError, formatModuleError(shiboken)); } Py::Module mainmod(module, true); @@ -452,9 +431,10 @@ PyTypeObject *getPyTypeObjectForTypeName() } template -qttype* qt_getCppType(PyObject* pyobj) +qttype* qt_getCppType(PyObject* pyobj, const std::string& shiboken) { #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + Q_UNUSED(shiboken) PyTypeObject * type = getPyTypeObjectForTypeName(); if (type) { if (Shiboken::Object::checkType(pyobj)) { @@ -464,7 +444,9 @@ qttype* qt_getCppType(PyObject* pyobj) } } #else - Q_UNUSED(pyobj) + void* ptr = qt_getCppPointer(Py::asObject(pyobj), shiboken, "getCppPointer"); + if (ptr) + return reinterpret_cast(ptr); #endif return nullptr; @@ -536,30 +518,12 @@ bool PythonWrapper::toCString(const Py::Object& pyobject, std::string& str) QObject* PythonWrapper::toQObject(const Py::Object& pyobject) { - // http://pastebin.com/JByDAF5Z -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - return qt_getCppType(pyobject.ptr()); -#else - // Access shiboken/PySide via Python - // - void* ptr = qt_getCppPointer(pyobject, shiboken, "getCppPointer"); - return static_cast(ptr); -#endif - - return nullptr; + return qt_getCppType(pyobject.ptr(), shiboken); } QGraphicsItem* PythonWrapper::toQGraphicsItem(PyObject* pyPtr) { -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - return qt_getCppType(pyPtr); -#else - // Access shiboken/PySide via Python - // - void* ptr = qt_getCppPointer(Py::asObject(pyPtr), shiboken, "getCppPointer"); - return static_cast(ptr); -#endif - return nullptr; + return qt_getCppType(pyPtr, shiboken); } QGraphicsItem* PythonWrapper::toQGraphicsItem(const Py::Object& pyobject) @@ -569,15 +533,7 @@ QGraphicsItem* PythonWrapper::toQGraphicsItem(const Py::Object& pyobject) QGraphicsObject* PythonWrapper::toQGraphicsObject(PyObject* pyPtr) { -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - return qt_getCppType(pyPtr); -#else - // Access shiboken/PySide via Python - // - void* ptr = qt_getCppPointer(Py::asObject(pyPtr), shiboken, "getCppPointer"); - return reinterpret_cast(ptr); -#endif - return nullptr; + return qt_getCppType(pyPtr, shiboken); } QGraphicsObject* PythonWrapper::toQGraphicsObject(const Py::Object& pyobject) @@ -608,12 +564,7 @@ Py::Object PythonWrapper::fromQImage(const QImage& img) QImage *PythonWrapper::toQImage(PyObject *pyobj) { -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - return qt_getCppType(pyobj); -#else - Q_UNUSED(pyobj); -#endif - return nullptr; + return qt_getCppType(pyobj, shiboken); } Py::Object PythonWrapper::fromQIcon(const QIcon* icon) @@ -639,12 +590,7 @@ Py::Object PythonWrapper::fromQIcon(const QIcon* icon) QIcon *PythonWrapper::toQIcon(PyObject *pyobj) { -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - return qt_getCppType(pyobj); -#else - Q_UNUSED(pyobj); -#endif - return nullptr; + return qt_getCppType(pyobj, shiboken); } Py::Object PythonWrapper::fromQDir(const QDir& dir) @@ -668,12 +614,7 @@ Py::Object PythonWrapper::fromQDir(const QDir& dir) QDir* PythonWrapper::toQDir(PyObject* pyobj) { -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - return qt_getCppType(pyobj); -#else - Q_UNUSED(pyobj); -#endif - return nullptr; + return qt_getCppType(pyobj, shiboken); } Py::Object PythonWrapper::fromQPrinter(QPrinter* printer) diff --git a/src/Gui/SoDatumLabel.cpp b/src/Gui/SoDatumLabel.cpp index 2e2b44b3c4..52f1823317 100644 --- a/src/Gui/SoDatumLabel.cpp +++ b/src/Gui/SoDatumLabel.cpp @@ -93,6 +93,9 @@ SoDatumLabel::SoDatumLabel() SO_NODE_ADD_FIELD(param2, (0.f)); SO_NODE_ADD_FIELD(param4, (0.f)); SO_NODE_ADD_FIELD(param5, (0.f)); + SO_NODE_ADD_FIELD(param6, (0.f)); + SO_NODE_ADD_FIELD(param7, (0.f)); + SO_NODE_ADD_FIELD(param8, (0.f)); useAntialiasing = true; @@ -989,6 +992,43 @@ void SoDatumLabel::GLRender(SoGLRenderAction * action) glVertex2f(ar3[0], ar3[1]); glVertex2f(ar4[0], ar4[1]); glEnd(); + + + if (this->datumtype.getValue() == DISTANCE) { + // Draw arc helpers if needed + float range1 = this->param4.getValue(); + if (range1 != 0.0) { + float startangle1 = this->param3.getValue(); + float radius1 = this->param5.getValue(); + SbVec3f center = points[2]; + int countSegments = std::max(6, abs(int(50.0 * range1 / (2 * M_PI)))); + double segment = range1 / (countSegments - 1); + + glBegin(GL_LINE_STRIP); + for (int i = 0; i < countSegments; i++) { + double theta = startangle1 + segment * i; + SbVec3f v1 = center + SbVec3f(radius1 * cos(theta), radius1 * sin(theta), 0); + glVertex2f(v1[0], v1[1]); + } + glEnd(); + } + float range2 = this->param7.getValue(); + if (range2 != 0.0) { + float startangle2 = this->param6.getValue(); + float radius2 = this->param8.getValue(); + SbVec3f center = points[3]; + int countSegments = std::max(6, abs(int(50.0 * range2 / (2 * M_PI)))); + double segment = range2 / (countSegments - 1); + + glBegin(GL_LINE_STRIP); + for (int i = 0; i < countSegments; i++) { + double theta = startangle2 + segment * i; + SbVec3f v1 = center + SbVec3f(radius2 * cos(theta), radius2 * sin(theta), 0); + glVertex2f(v1[0], v1[1]); + } + glEnd(); + } + } } else if (this->datumtype.getValue() == RADIUS || this->datumtype.getValue() == DIAMETER) { // Get the Points diff --git a/src/Gui/SoDatumLabel.h b/src/Gui/SoDatumLabel.h index 077069236a..2e55ffeb8e 100644 --- a/src/Gui/SoDatumLabel.h +++ b/src/Gui/SoDatumLabel.h @@ -77,6 +77,9 @@ public: SoSFFloat param3; SoSFFloat param4; SoSFFloat param5; + SoSFFloat param6; + SoSFFloat param7; + SoSFFloat param8; SoMFVec3f pnts; SoSFVec3f norm; SoSFImage image; diff --git a/src/Gui/Stylesheets/images_dark-light/check_dark_disabled.svg b/src/Gui/Stylesheets/images_dark-light/check_dark_disabled.svg new file mode 100644 index 0000000000..5355029ad4 --- /dev/null +++ b/src/Gui/Stylesheets/images_dark-light/check_dark_disabled.svg @@ -0,0 +1,51 @@ + + + + + + + + + image/svg+xml + + + + Pablo Gil + + + + + SVG + template + + + + + + + + + + diff --git a/src/Gui/Stylesheets/images_dark-light/check_light_disabled.svg b/src/Gui/Stylesheets/images_dark-light/check_light_disabled.svg new file mode 100644 index 0000000000..71445d475f --- /dev/null +++ b/src/Gui/Stylesheets/images_dark-light/check_light_disabled.svg @@ -0,0 +1,51 @@ + + + + + + + + + image/svg+xml + + + + Pablo Gil + + + + + SVG + template + + + + + + + + + + diff --git a/src/Gui/Tree.cpp b/src/Gui/Tree.cpp index 4a657680d4..30148e512b 100644 --- a/src/Gui/Tree.cpp +++ b/src/Gui/Tree.cpp @@ -881,7 +881,7 @@ void TreeWidget::contextMenuEvent(QContextMenuEvent* e) MenuItem view; Gui::Application::Instance->setupContextMenu("Tree", &view); - view << "Std_Expressions"; + view << "Std_Properties" << "Separator" << "Std_Expressions"; Workbench::createLinkMenu(&view); QMenu contextMenu; @@ -1747,7 +1747,8 @@ void TreeWidget::dragMoveEvent(QDragMoveEvent* event) // let the view provider decide to accept the object or ignore it if (da != Qt::LinkAction && !vp->canDropObjectEx(obj, owner, subname.c_str(), item->mySubs)) { - if (event->possibleActions() & Qt::LinkAction) { + // Cause unexpected bugs : https://github.com/FreeCAD/FreeCAD/issues/11676 + /*if (event->possibleActions() & Qt::LinkAction) { if (items.size() > 1) { TREE_TRACE("Cannot replace with more than one object"); event->ignore(); @@ -1760,7 +1761,7 @@ void TreeWidget::dragMoveEvent(QDragMoveEvent* event) } event->setDropAction(Qt::LinkAction); return; - } + }*/ TREE_TRACE("cannot drop " << obj->getFullName() << ' ' << (owner ? owner->getFullName() : "") << '.' << subname); diff --git a/src/Gui/ViewProviderLink.cpp b/src/Gui/ViewProviderLink.cpp index c121fe91b2..61a68d6465 100644 --- a/src/Gui/ViewProviderLink.cpp +++ b/src/Gui/ViewProviderLink.cpp @@ -225,7 +225,7 @@ public: } const char *getLinkedName() const { - return pcLinked->getObject()->getNameInDocument(); + return pcLinked->getObject()->getDagKey(); } const char *getLinkedLabel() const { diff --git a/src/Gui/ViewProviderOriginFeature.cpp b/src/Gui/ViewProviderOriginFeature.cpp index 6e360c06dd..df57b9b044 100644 --- a/src/Gui/ViewProviderOriginFeature.cpp +++ b/src/Gui/ViewProviderOriginFeature.cpp @@ -51,6 +51,7 @@ ViewProviderOriginFeature::ViewProviderOriginFeature () { QT_TRANSLATE_NOOP("App::Property", "Visual size of the feature")); ShapeColor.setValue ( ViewProviderOrigin::defaultColor ); // Set default color for origin (light-blue) + Transparency.setValue(0); BoundingBox.setStatus(App::Property::Hidden, true); // Hide Boundingbox from the user due to it doesn't make sense // Create node for scaling the origin diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index 3f03d97a57..131e938f98 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -647,7 +647,7 @@ MenuItem* StdWorkbench::setupMenuBar() const << "Std_Refresh" << "Std_BoxSelection" << "Std_BoxElementSelection" << "Std_SelectAll" << "Std_Delete" << "Std_SendToPythonConsole" << "Separator" << "Std_Placement" << "Std_TransformManip" << "Std_Alignment" - << "Std_Edit" << "Separator" << "Std_UserEditMode" << "Separator" << "Std_DlgPreferences"; + << "Std_Edit" << "Std_Properties" << "Separator" << "Std_UserEditMode" << "Separator" << "Std_DlgPreferences"; auto axoviews = new MenuItem; axoviews->setCommand("Axonometric"); diff --git a/src/Gui/propertyeditor/PropertyEditor.cpp b/src/Gui/propertyeditor/PropertyEditor.cpp index b0b0e3489b..e9aa88ca2d 100644 --- a/src/Gui/propertyeditor/PropertyEditor.cpp +++ b/src/Gui/propertyeditor/PropertyEditor.cpp @@ -99,6 +99,12 @@ PropertyEditor::PropertyEditor(QWidget *parent) setHeaderHidden(true); viewport()->installEventFilter(this); viewport()->setMouseTracking(true); + + auto hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/DockWindows/PropertyView"); + int firstColumnSize = hGrp->GetInt("FirstColumnSize", 0); + if (firstColumnSize != 0) { + header()->resizeSection(0, firstColumnSize); + } } PropertyEditor::~PropertyEditor() @@ -868,6 +874,9 @@ bool PropertyEditor::eventFilter(QObject* object, QEvent* event) { else if (mouse_event->type() == QEvent::MouseButtonRelease && mouse_event->button() == Qt::LeftButton && dragInProgress) { dragInProgress = false; + + auto hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/DockWindows/PropertyView"); + hGrp->SetInt("FirstColumnSize", header()->sectionSize(0)); return true; } } diff --git a/src/Gui/propertyeditor/PropertyEditor.h b/src/Gui/propertyeditor/PropertyEditor.h index bd01c2f99e..c50fedf0d9 100644 --- a/src/Gui/propertyeditor/PropertyEditor.h +++ b/src/Gui/propertyeditor/PropertyEditor.h @@ -100,7 +100,7 @@ protected Q_SLOTS: void onRowsRemoved(const QModelIndex &parent, int start, int end); protected: - bool eventFilter(QObject* object, QEvent* event); + bool eventFilter(QObject* object, QEvent* event) override; void closeEditor (QWidget * editor, QAbstractItemDelegate::EndEditHint hint) override; void commitData (QWidget * editor) override; void editorDestroyed (QObject * editor) override; diff --git a/src/Mod/AddonManager/CMakeLists.txt b/src/Mod/AddonManager/CMakeLists.txt index dc9e527c49..bedb988620 100644 --- a/src/Mod/AddonManager/CMakeLists.txt +++ b/src/Mod/AddonManager/CMakeLists.txt @@ -28,6 +28,7 @@ SET(AddonManager_SRCS addonmanager_macro_parser.py addonmanager_metadata.py addonmanager_pyside_interface.py + addonmanager_readme_viewer.py addonmanager_update_all_gui.py addonmanager_uninstaller.py addonmanager_uninstaller_gui.py diff --git a/src/Mod/AddonManager/NetworkManager.py b/src/Mod/AddonManager/NetworkManager.py index 8c7a475512..8b1c1e6e1d 100644 --- a/src/Mod/AddonManager/NetworkManager.py +++ b/src/Mod/AddonManager/NetworkManager.py @@ -531,7 +531,7 @@ if HAVE_QTNETWORK: any notifications have been called.""" reply = self.sender() if not reply: - print("Network Manager Error: __reply_finished not called by a Qt signal") + # This can happen during a cancellation operation: silently do nothing return if reply.error() == QtNetwork.QNetworkReply.NetworkError.OperationCanceledError: diff --git a/src/Mod/AddonManager/addonmanager_git.py b/src/Mod/AddonManager/addonmanager_git.py index 9d44388948..e3a51b1ae1 100644 --- a/src/Mod/AddonManager/addonmanager_git.py +++ b/src/Mod/AddonManager/addonmanager_git.py @@ -322,6 +322,9 @@ class GitManager: if "Windows" in platform.system(): git_exe += ".exe" + if platform.system() == "Darwin" and not self._xcode_command_line_tools_are_installed(): + return + if not git_exe or not os.path.exists(git_exe): git_exe = shutil.which("git") @@ -331,6 +334,18 @@ class GitManager: prefs.SetString("GitExecutable", git_exe) self.git_exe = git_exe + def _xcode_command_line_tools_are_installed(self) -> bool: + """On Macs, there is *always* an executable called "git", but sometimes it's just a + script that tells the user to install XCode's Command Line tools. So the existence of git + on the Mac actually requires us to check for that installation.""" + try: + subprocess.check_output(["xcode-select", "-p"]) + fci.Console.PrintMessage("XCode command line tools are installed: git is available\n") + return True + except subprocess.CalledProcessError: + fci.Console.PrintMessage("XCode command line tools are not installed: not using git\n") + return False + def _synchronous_call_git(self, args: List[str]) -> str: """Calls git and returns its output.""" final_args = [self.git_exe] diff --git a/src/Mod/AddonManager/addonmanager_installer.py b/src/Mod/AddonManager/addonmanager_installer.py index a8a9e888e2..d35a3bc4b9 100644 --- a/src/Mod/AddonManager/addonmanager_installer.py +++ b/src/Mod/AddonManager/addonmanager_installer.py @@ -24,7 +24,7 @@ """ Contains the classes to manage Addon installation: intended as a stable API, safe for external code to call and to rely upon existing. See classes AddonInstaller and MacroInstaller for details. """ - +import json from datetime import datetime, timezone from enum import IntEnum, auto import os @@ -503,16 +503,34 @@ class MacroInstaller(QtCore.QObject): self.finished.emit() return False - # If it succeeded, move all of the files to the macro install location + # If it succeeded, move all the files to the macro install location, + # keeping a list of all the files we installed, so they can be removed later + # if this macro is uninstalled. + manifest = [] for item in os.listdir(temp_dir): src = os.path.join(temp_dir, item) dst = os.path.join(self.installation_path, item) shutil.move(src, dst) + manifest.append(dst) + self._write_installation_manifest(manifest) self.success.emit(self.addon_to_install) self.addon_to_install.set_status(Addon.Status.NO_UPDATE_AVAILABLE) self.finished.emit() return True + def _write_installation_manifest(self, manifest): + manifest_file = os.path.join( + self.installation_path, self.addon_to_install.macro.filename + ".manifest" + ) + try: + with open(manifest_file, "w", encoding="utf-8") as f: + f.write(json.dumps(manifest, indent=" ")) + except OSError as e: + FreeCAD.Console.PrintWarning( + translate("AddonsInstaller", "Failed to create installation manifest " "file:\n") + ) + FreeCAD.Console.PrintWarning(manifest_file) + @classmethod def _validate_object(cls, addon: object): """Make sure this object provides an attribute called "macro" with a method called diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py index c133a0a9b3..b01a7fde78 100644 --- a/src/Mod/AddonManager/addonmanager_macro.py +++ b/src/Mod/AddonManager/addonmanager_macro.py @@ -69,6 +69,7 @@ class Macro: self.version = "" self.date = "" self.src_filename = "" + self.filename_from_url = "" self.author = "" self.icon = "" self.icon_source = None @@ -104,6 +105,8 @@ class Macro: """The filename of this macro""" if self.on_git: return os.path.basename(self.src_filename) + elif self.filename_from_url: + return self.filename_from_url return (self.name + ".FCMacro").replace(" ", "_") def is_installed(self): @@ -211,8 +214,14 @@ class Macro: ) return None code = u2.decode("utf8") + self._set_filename_from_url(self.raw_code_url) return code + def _set_filename_from_url(self, url: str): + lhs, slash, rhs = url.rpartition("/") + if rhs.endswith(".py") or rhs.lower().endswith(".fcmacro"): + self.filename_from_url = rhs + @staticmethod def _read_code_from_wiki(p: str) -> Optional[str]: code = re.findall(r"
(.*?)
", p.replace("\n", "--endl--")) diff --git a/src/Mod/AddonManager/addonmanager_readme_viewer.py b/src/Mod/AddonManager/addonmanager_readme_viewer.py new file mode 100644 index 0000000000..fc3e9b19f6 --- /dev/null +++ b/src/Mod/AddonManager/addonmanager_readme_viewer.py @@ -0,0 +1,258 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2024 The FreeCAD Project Association AISBL * +# * * +# * 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 * +# * . * +# * * +# *************************************************************************** + +""" A Qt Widget for displaying Addon README information """ + +import Addon +from PySide import QtCore, QtGui, QtWidgets +from enum import Enum, auto +from html.parser import HTMLParser + +import addonmanager_freecad_interface as fci +import addonmanager_utilities as utils +import NetworkManager + +translate = fci.translate + + +class ReadmeViewer(QtWidgets.QTextBrowser): + + """A QTextBrowser widget that, when given an Addon, downloads the README data as appropriate + and renders it with whatever technology is available (usually Qt's Markdown renderer for + workbenches and its HTML renderer for Macros).""" + + def __init__(self, parent=None): + super().__init__(parent) + NetworkManager.InitializeNetworkManager() + NetworkManager.AM_NETWORK_MANAGER.completed.connect(self._download_completed) + self.readme_request_index = 0 + self.resource_requests = {} + self.url = "" + self.repo: Addon.Addon = None + self.setOpenExternalLinks(True) + self.setOpenLinks(True) + self.image_map = {} + self.stop = True + + def set_addon(self, repo: Addon): + """Set which Addon's information is displayed""" + + self.setPlainText(translate("AddonsInstaller", "Loading README data...")) + self.repo = repo + self.stop = False + if self.repo.repo_type == Addon.Addon.Kind.MACRO: + self.url = self.repo.macro.wiki + if not self.url: + self.url = self.repo.macro.url + else: + self.url = utils.get_readme_url(repo) + + self.readme_request_index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get( + self.url + ) + + def _download_completed(self, index: int, code: int, data: QtCore.QByteArray) -> None: + """Callback for handling a completed README file download.""" + if index == self.readme_request_index: + if code == 200: # HTTP success + self._process_package_download(data.data().decode("utf-8")) + else: + self.setPlainText( + translate( + "AddonsInstaller", + "Failed to download data from {} -- received response code {}.", + ).format(self.url, code) + ) + elif index in self.resource_requests: + if code == 200: + self._process_resource_download(self.resource_requests[index], data.data()) + else: + self.image_map[self.resource_requests[index]] = None + del self.resource_requests[index] + if not self.resource_requests: + self.set_addon(self.repo) # Trigger a reload of the page now with resources + + def _process_package_download(self, data: str): + if self.repo.repo_type == Addon.Addon.Kind.MACRO: + parser = WikiCleaner() + parser.feed(data) + self.setHtml(parser.final_html) + else: + # Check for recent Qt (e.g. Qt5.15 or later). Check can be removed when + # we no longer support Ubuntu 20.04LTS for compiling. + if hasattr(self, "setMarkdown"): + self.setMarkdown(data) + else: + self.setPlainText(data) + + def _process_resource_download(self, resource_name: str, resource_data: bytes): + image = QtGui.QImage.fromData(resource_data) + if image: + self.image_map[resource_name] = self._ensure_appropriate_width(image) + else: + self.image_map[resource_name] = None + + def loadResource(self, resource_type: int, name: QtCore.QUrl) -> object: + """Callback for resource loading. Called automatically by underlying Qt + code when external resources are needed for rendering. In particular, + here it is used to download and cache (in RAM) the images needed for the + README and Wiki pages.""" + if resource_type == QtGui.QTextDocument.ImageResource and not self.stop: + full_url = self._create_full_url(name.toString()) + if full_url not in self.image_map: + self.image_map[full_url] = None + fci.Console.PrintMessage(f"Downloading image from {full_url}...\n") + index = NetworkManager.AM_NETWORK_MANAGER.submit_unmonitored_get(full_url) + self.resource_requests[index] = full_url + return self.image_map[full_url] + return super().loadResource(resource_type, name) + + def hideEvent(self, event: QtGui.QHideEvent): + self.stop = True + for request in self.resource_requests: + NetworkManager.AM_NETWORK_MANAGER.abort(request) + self.resource_requests.clear() + + def _create_full_url(self, url: str) -> str: + if url.startswith("http"): + return url + if not self.url: + return url + lhs, slash, _ = self.url.rpartition("/") + return lhs + slash + url + + def _ensure_appropriate_width(self, image: QtGui.QImage) -> QtGui.QImage: + ninety_seven_percent = self.width() * 0.97 + if image.width() < ninety_seven_percent: + return image + return image.scaledToWidth(ninety_seven_percent) + + +class WikiCleaner(HTMLParser): + """This HTML parser cleans up FreeCAD Macro Wiki Page for display in a + QTextBrowser widget (which does not deal will with tables used as formatting, + etc.) It strips out any tables, and extracts the mw-parser-output div as the only + thing that actually gets displayed. It also discards anything inside the [edit] + spans that litter wiki output.""" + + class State(Enum): + BeforeMacroContent = auto() + InMacroContent = auto() + InTable = auto() + InEditSpan = auto() + AfterMacroContent = auto() + + def __init__(self): + super().__init__() + self.depth_in_div = 0 + self.depth_in_span = 0 + self.depth_in_table = 0 + self.final_html = "" + self.previous_state = WikiCleaner.State.BeforeMacroContent + self.state = WikiCleaner.State.BeforeMacroContent + + def handle_starttag(self, tag: str, attrs): + if tag == "div": + self.handle_div_start(attrs) + elif tag == "span": + self.handle_span_start(attrs) + elif tag == "table": + self.handle_table_start(attrs) + else: + if self.state == WikiCleaner.State.InMacroContent: + self.add_tag_to_html(tag, attrs) + + def handle_div_start(self, attrs): + for name, value in attrs: + if name == "class" and value == "mw-parser-output": + self.previous_state = self.state + self.state = WikiCleaner.State.InMacroContent + if self.state == WikiCleaner.State.InMacroContent: + self.depth_in_div += 1 + self.add_tag_to_html("div", attrs) + + def handle_span_start(self, attrs): + for name, value in attrs: + if name == "class" and value == "mw-editsection": + self.previous_state = self.state + self.state = WikiCleaner.State.InEditSpan + break + if self.state == WikiCleaner.State.InEditSpan: + self.depth_in_span += 1 + elif WikiCleaner.State.InMacroContent: + self.add_tag_to_html("span", attrs) + + def handle_table_start(self, unused): + if self.state != WikiCleaner.State.InTable: + self.previous_state = self.state + self.state = WikiCleaner.State.InTable + self.depth_in_table += 1 + + def add_tag_to_html(self, tag, attrs=None): + self.final_html += f"<{tag}" + if attrs: + self.final_html += " " + for attr, value in attrs: + self.final_html += f"{attr}='{value}'" + self.final_html += ">\n" + + def handle_endtag(self, tag): + if tag == "table": + self.handle_table_end() + elif tag == "span": + self.handle_span_end() + elif tag == "div": + self.handle_div_end() + else: + if self.state == WikiCleaner.State.InMacroContent: + self.add_tag_to_html(f"/{tag}") + + def handle_span_end(self): + if self.state == WikiCleaner.State.InEditSpan: + self.depth_in_span -= 1 + if self.depth_in_span <= 0: + self.depth_in_span = 0 + self.state = self.previous_state + else: + self.add_tag_to_html(f"/span") + + def handle_div_end(self): + if self.state == WikiCleaner.State.InMacroContent: + self.depth_in_div -= 1 + if self.depth_in_div <= 0: + self.depth_in_div = 0 + self.state = WikiCleaner.State.AfterMacroContent + self.final_html += "" + else: + self.add_tag_to_html(f"/div") + + def handle_table_end(self): + if self.state == WikiCleaner.State.InTable: + self.depth_in_table -= 1 + if self.depth_in_table <= 0: + self.depth_in_table = 0 + self.state = self.previous_state + + def handle_data(self, data): + if self.state == WikiCleaner.State.InMacroContent: + self.final_html += data diff --git a/src/Mod/AddonManager/addonmanager_uninstaller.py b/src/Mod/AddonManager/addonmanager_uninstaller.py index b83c8085f2..0c42f591dd 100644 --- a/src/Mod/AddonManager/addonmanager_uninstaller.py +++ b/src/Mod/AddonManager/addonmanager_uninstaller.py @@ -24,7 +24,7 @@ """ Contains the classes to manage Addon removal: intended as a stable API, safe for external code to call and to rely upon existing. See classes AddonUninstaller and MacroUninstaller for details.""" - +import json import os from typing import List @@ -228,7 +228,10 @@ class MacroUninstaller(QObject): directories = set() for f in self._get_files_to_remove(): normed = os.path.normpath(f) - full_path = os.path.join(self.installation_location, normed) + if os.path.isabs(normed): + full_path = normed + else: + full_path = os.path.join(self.installation_location, normed) if "/" in f: directories.add(os.path.dirname(full_path)) try: @@ -246,6 +249,10 @@ class MacroUninstaller(QObject): + str(e) ) success = False + except Exception: + # Generic catch-all, just in case (because failure to catch an exception + # here can break things pretty badly) + success = False self._cleanup_directories(directories) @@ -256,8 +263,17 @@ class MacroUninstaller(QObject): self.addon_to_remove.set_status(Addon.Status.NOT_INSTALLED) self.finished.emit() - def _get_files_to_remove(self) -> List[os.PathLike]: + def _get_files_to_remove(self) -> List[str]: """Get the list of files that should be removed""" + manifest_file = os.path.join( + self.installation_location, self.addon_to_remove.macro.filename + ".manifest" + ) + if os.path.exists(manifest_file): + with open(manifest_file, "r", encoding="utf-8") as f: + manifest_data = f.read() + manifest = json.loads(manifest_data) + manifest.append(manifest_file) # Remove the manifest itself as well + return manifest files_to_remove = [self.addon_to_remove.macro.filename] if self.addon_to_remove.macro.icon: files_to_remove.append(self.addon_to_remove.macro.icon) diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index 9ceabc2e9a..183a2047b0 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -36,7 +36,7 @@ from typing import Optional, Any from urllib.parse import urlparse try: - from PySide import QtCore, QtWidgets + from PySide import QtCore, QtGui, QtWidgets except ImportError: QtCore = None QtWidgets = None @@ -245,7 +245,7 @@ def get_readme_html_url(repo): def is_darkmode() -> bool: """Heuristics to determine if we are in a darkmode stylesheet""" pl = fci.FreeCADGui.getMainWindow().palette() - return pl.color(pl.Background).lightness() < 128 + return pl.color(QtGui.QPalette.Window).lightness() < 128 def warning_color_string() -> str: diff --git a/src/Mod/AddonManager/change_branch.py b/src/Mod/AddonManager/change_branch.py index 332795a22a..ebe1819bc7 100644 --- a/src/Mod/AddonManager/change_branch.py +++ b/src/Mod/AddonManager/change_branch.py @@ -216,7 +216,7 @@ class ChangeBranchDialogModel(QtCore.QAbstractTableModel): dd = self.display_data[row] if column == 3 or column == 4: if dd[column] is not None: - qdate = QtCore.QDateTime.fromTime_t(dd[column]) + qdate = QtCore.QDateTime.fromSecsSinceEpoch(dd[column]) return QtCore.QLocale().toString(qdate, QtCore.QLocale.ShortFormat) elif column < len(dd): return dd[column] diff --git a/src/Mod/AddonManager/package_details.py b/src/Mod/AddonManager/package_details.py index 36952b7bd3..a9e73a42c7 100644 --- a/src/Mod/AddonManager/package_details.py +++ b/src/Mod/AddonManager/package_details.py @@ -33,6 +33,7 @@ import addonmanager_freecad_interface as fci import addonmanager_utilities as utils from addonmanager_metadata import Version, UrlType, get_first_supported_freecad_version from addonmanager_workers_startup import GetMacroDetailsWorker, CheckSingleUpdateWorker +from addonmanager_readme_viewer import ReadmeViewer from Addon import Addon from change_branch import ChangeBranchDialog @@ -48,22 +49,6 @@ except ImportError: translate = fci.translate -show_javascript_console_output = False - -try: - from PySide import QtWebEngineWidgets - - HAS_QTWEBENGINE = True -except ImportError: - fci.Console.PrintWarning( - translate( - "AddonsInstaller", - "Addon Manager Warning: Could not import QtWebEngineWidgets -- README data will display as text-only", - ) - + "\n" - ) - HAS_QTWEBENGINE = False - class PackageDetails(QtWidgets.QWidget): """The PackageDetails QWidget shows package README information and provides @@ -95,17 +80,6 @@ class PackageDetails(QtWidgets.QWidget): self.ui.buttonChangeBranch.clicked.connect(self.change_branch_clicked) self.ui.buttonEnable.clicked.connect(self.enable_clicked) self.ui.buttonDisable.clicked.connect(self.disable_clicked) - if HAS_QTWEBENGINE: - self.ui.webView.loadStarted.connect(self.load_started) - self.ui.webView.loadProgress.connect(self.load_progress) - self.ui.webView.loadFinished.connect(self.load_finished) - - loading_html_file = os.path.join(os.path.dirname(__file__), "loading.html") - with open(loading_html_file, "r", errors="ignore", encoding="utf-8") as f: - html = f.read() - self.ui.loadingLabel.setHtml(html) - self.ui.loadingLabel.show() - self.ui.webView.hide() def show_repo(self, repo: Addon, reload: bool = False) -> None: """The main entry point for this class, shows the package details and related buttons @@ -114,19 +88,9 @@ class PackageDetails(QtWidgets.QWidget): # If this is the same repo we were already showing, we do not have to do the # expensive refetch unless reload is true - if self.repo != repo or reload: + if True or self.repo != repo or reload: self.repo = repo - if HAS_QTWEBENGINE: - self.ui.loadingLabel.show() - self.ui.slowLoadLabel.hide() - self.ui.webView.setHtml("Loading...") - self.ui.webView.hide() - self.ui.progressBar.show() - self.timeout = QtCore.QTimer.singleShot(6000, self.long_load_running) # Six seconds - else: - self.ui.missingWebViewLabel.setStyleSheet("color:" + utils.warning_color_string()) - if self.worker is not None: if not self.worker.isFinished(): self.worker.requestInterruption() @@ -167,10 +131,9 @@ class PackageDetails(QtWidgets.QWidget): date = "" installed_version_string = "

" if repo.updated_timestamp: - date = ( - QtCore.QDateTime.fromTime_t(repo.updated_timestamp) - .date() - .toString(QtCore.Qt.SystemLocaleShortDate) + date = QtCore.QLocale().toString( + QtCore.QDateTime.fromSecsSinceEpoch(int(round(repo.updated_timestamp, 0))), + QtCore.QLocale.ShortFormat, ) if version and date: installed_version_string += ( @@ -420,33 +383,13 @@ class PackageDetails(QtWidgets.QWidget): def show_workbench(self, repo: Addon) -> None: """loads information of a given workbench""" - url = utils.get_readme_html_url(repo) - if HAS_QTWEBENGINE: - self.ui.webView.load(QtCore.QUrl(url)) - self.ui.urlBar.setText(url) - else: - readme_data = utils.blocking_get(url) - text = readme_data.decode("utf8") - self.ui.textBrowserReadMe.setHtml(text) + + self.ui.textBrowserReadMe.set_addon(repo) def show_package(self, repo: Addon) -> None: """Show the details for a package (a repo with a package.xml metadata file)""" - readme_url = None - if repo.metadata: - for url in repo.metadata.url: - if url.type == UrlType.readme: - readme_url = url.location - break - if not readme_url: - readme_url = utils.get_readme_html_url(repo) - if HAS_QTWEBENGINE: - self.ui.webView.load(QtCore.QUrl(readme_url)) - self.ui.urlBar.setText(readme_url) - else: - readme_data = utils.blocking_get(readme_url) - text = readme_data.decode("utf8") - self.ui.textBrowserReadMe.setHtml(text) + self.ui.textBrowserReadMe.set_addon(repo) def show_macro(self, repo: Addon) -> None: """loads information of a given macro""" @@ -461,136 +404,8 @@ class PackageDetails(QtWidgets.QWidget): def macro_readme_updated(self): """Update the display of a Macro's README data.""" - url = self.repo.macro.wiki - if not url: - url = self.repo.macro.url - if HAS_QTWEBENGINE: - if url: - self.ui.webView.load(QtCore.QUrl(url)) - self.ui.urlBar.setText(url) - else: - self.ui.urlBar.setText( - "(" - + translate("AddonsInstaller", "No URL or wiki page provided by this macro") - + ")" - ) - else: - if url: - readme_data = utils.blocking_get(url) - text = readme_data.decode("utf8") - self.ui.textBrowserReadMe.setHtml(text) - else: - self.ui.textBrowserReadMe.setHtml( - "(" - + translate("AddonsInstaller", "No URL or wiki page provided by this macro") - + ")" - ) - - def run_javascript(self): - """Modify the page for a README to optimize for viewing in a smaller window""" - - s = """ -( function() { - const url = new URL (window.location); - const body = document.getElementsByTagName("body")[0]; - if (url.hostname === "github.com") { - const articles = document.getElementsByTagName("article"); - if (articles.length > 0) { - const article = articles[0]; - body.appendChild (article); - body.style.padding = "1em"; - let sibling = article.previousSibling; - while (sibling) { - sibling.remove(); - sibling = article.previousSibling; - } - } - } else if (url.hostname === "gitlab.com" || - url.hostname === "framagit.org" || - url.hostname === "salsa.debian.org") { - // These all use the GitLab page display... - const articles = document.getElementsByTagName("article"); - if (articles.length > 0) { - const article = articles[0]; - body.appendChild (article); - body.style.padding = "1em"; - let sibling = article.previousSibling; - while (sibling) { - sibling.remove(); - sibling = article.previousSibling; - } - } - } else if (url.hostname === "wiki.freecad.org" || - url.hostname === "wiki.freecad.org") { - const first_heading = document.getElementById('firstHeading'); - const body_content = document.getElementById('bodyContent'); - const new_node = document.createElement("div"); - new_node.appendChild(first_heading); - new_node.appendChild(body_content); - body.appendChild(new_node); - let sibling = new_node.previousSibling; - while (sibling) { - sibling.remove(); - sibling = new_node.previousSibling; - } - } -} -) () -""" - self.ui.webView.page().runJavaScript(s) - - def load_started(self): - """Called when loading is started: sets up the progress bar""" - self.ui.progressBar.show() - self.ui.progressBar.setValue(0) - - def load_progress(self, progress: int): - """Called during load to update the progress bar""" - self.ui.progressBar.setValue(progress) - - def load_finished(self, load_succeeded: bool): - """Once loading is complete, update the display of the progress bar and loading widget.""" - self.ui.loadingLabel.hide() - self.ui.slowLoadLabel.hide() - self.ui.webView.show() - self.ui.progressBar.hide() - url = self.ui.webView.url() - if ( - hasattr(self, "timeout") - and hasattr(self.timeout, "isActive") - and self.timeout.isActive() - ): - self.timeout.stop() - if load_succeeded: - # It says it succeeded, but it might have only succeeded in loading a - # "Page not found" page! - title = self.ui.webView.title() - path_components = url.path().split("/") - expected_content = path_components[-1] - if url.host() == "github.com" and expected_content not in title: - self.show_error_for(url) - elif title == "": - self.show_error_for(url) - else: - self.run_javascript() - else: - self.show_error_for(url) - - def long_load_running(self): - """Displays a message about loading taking a long time.""" - if hasattr(self.ui, "webView") and self.ui.webView.isHidden(): - self.ui.slowLoadLabel.show() - self.ui.loadingLabel.hide() - self.ui.webView.show() - - def show_error_for(self, url: QtCore.QUrl) -> None: - """Displays error information.""" - m = translate("AddonsInstaller", "Could not load README data from URL {}").format( - url.toString() - ) - html = f"

{m}

" - self.ui.webView.setHtml(html) + self.ui.textBrowserReadMe.set_addon(self.repo) def change_branch_clicked(self) -> None: """Loads the branch-switching dialog""" @@ -671,57 +486,6 @@ class PackageDetails(QtWidgets.QWidget): self.update_status.emit(self.repo) -if HAS_QTWEBENGINE: - - class RestrictedWebPage(QtWebEngineWidgets.QWebEnginePage): - """A class that follows links to FreeCAD wiki pages, but opens all other - clicked links in the system web browser""" - - def __init__(self, parent): - super().__init__(parent) - self.settings().setAttribute( - QtWebEngineWidgets.QWebEngineSettings.ErrorPageEnabled, False - ) - self.stored_url = None - - def acceptNavigationRequest(self, requested_url, _type, isMainFrame): - """A callback for navigation requests: this widget will only display - navigation requests to the FreeCAD Wiki (for translation purposes) -- - anything else will open in a new window. - """ - if _type == QtWebEngineWidgets.QWebEnginePage.NavigationTypeLinkClicked: - # See if the link is to a FreeCAD Wiki page -- if so, follow it, - # otherwise ask the OS to open it - if ( - requested_url.host() == "wiki.freecad.org" - or requested_url.host() == "wiki.freecad.org" - ): - return super().acceptNavigationRequest(requested_url, _type, isMainFrame) - QtGui.QDesktopServices.openUrl(requested_url) - self.stored_url = self.url() - QtCore.QTimer.singleShot(0, self._reload_stored_url) - return False - return super().acceptNavigationRequest(requested_url, _type, isMainFrame) - - def javaScriptConsoleMessage(self, level, message, lineNumber, _): - """Handle JavaScript console messages by optionally outputting them to - the FreeCAD Console. This must be manually enabled in this Python file by - setting the global show_javascript_console_output to true.""" - global show_javascript_console_output - if show_javascript_console_output: - tag = translate("AddonsInstaller", "Page JavaScript reported") - if level == QtWebEngineWidgets.QWebEnginePage.InfoMessageLevel: - fci.Console.PrintMessage(f"{tag} {lineNumber}: {message}\n") - elif level == QtWebEngineWidgets.QWebEnginePage.WarningMessageLevel: - fci.Console.PrintWarning(f"{tag} {lineNumber}: {message}\n") - elif level == QtWebEngineWidgets.QWebEnginePage.ErrorMessageLevel: - fci.Console.PrintError(f"{tag} {lineNumber}: {message}\n") - - def _reload_stored_url(self): - if self.stored_url: - self.load(self.stored_url) - - class Ui_PackageDetails(object): """The generated UI from the Qt Designer UI file""" @@ -810,48 +574,10 @@ class Ui_PackageDetails(object): sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) - if HAS_QTWEBENGINE: - self.webView = QtWebEngineWidgets.QWebEngineView(PackageDetails) - self.webView.setObjectName("webView") - self.webView.setSizePolicy(sizePolicy1) - self.webView.setPage(RestrictedWebPage(PackageDetails)) + self.textBrowserReadMe = ReadmeViewer(PackageDetails) + self.textBrowserReadMe.setObjectName("textBrowserReadMe") - self.verticalLayout_2.addWidget(self.webView) - - self.loadingLabel = QtWebEngineWidgets.QWebEngineView(PackageDetails) - self.loadingLabel.setObjectName("loadingLabel") - self.loadingLabel.setSizePolicy(sizePolicy1) - - self.verticalLayout_2.addWidget(self.loadingLabel) - - self.slowLoadLabel = QtWidgets.QLabel(PackageDetails) - self.slowLoadLabel.setObjectName("slowLoadLabel") - - self.verticalLayout_2.addWidget(self.slowLoadLabel) - - self.progressBar = QtWidgets.QProgressBar(PackageDetails) - self.progressBar.setObjectName("progressBar") - self.progressBar.setTextVisible(False) - - self.verticalLayout_2.addWidget(self.progressBar) - - self.urlBar = QtWidgets.QLineEdit(PackageDetails) - self.urlBar.setObjectName("urlBar") - self.urlBar.setReadOnly(True) - - self.verticalLayout_2.addWidget(self.urlBar) - else: - self.missingWebViewLabel = QtWidgets.QLabel(PackageDetails) - self.missingWebViewLabel.setObjectName("missingWebViewLabel") - self.missingWebViewLabel.setWordWrap(True) - self.verticalLayout_2.addWidget(self.missingWebViewLabel) - - self.textBrowserReadMe = QtWidgets.QTextBrowser(PackageDetails) - self.textBrowserReadMe.setObjectName("textBrowserReadMe") - self.textBrowserReadMe.setOpenExternalLinks(True) - self.textBrowserReadMe.setOpenLinks(True) - - self.verticalLayout_2.addWidget(self.textBrowserReadMe) + self.verticalLayout_2.addWidget(self.textBrowserReadMe) self.retranslateUi(PackageDetails) @@ -888,22 +614,5 @@ class Ui_PackageDetails(object): self.buttonBack.setToolTip( QtCore.QCoreApplication.translate("AddonsInstaller", "Return to package list", None) ) - if not HAS_QTWEBENGINE: - self.missingWebViewLabel.setText( - "

" - + QtCore.QCoreApplication.translate( - "AddonsInstaller", - "QtWebEngine Python bindings not installed -- using fallback README display.", - None, - ) - + "

" - ) - else: - self.slowLoadLabel.setText( - QtCore.QCoreApplication.translate( - "AddonsInstaller", - "The page is taking a long time to load... showing the data we have so far...", - ) - ) # retranslateUi diff --git a/src/Mod/AddonManager/package_list.py b/src/Mod/AddonManager/package_list.py index a9dd3dc598..d26b4f5489 100644 --- a/src/Mod/AddonManager/package_list.py +++ b/src/Mod/AddonManager/package_list.py @@ -465,10 +465,9 @@ class PackageListItemDelegate(QtWidgets.QStyledItemDelegate): installed_date_string = "" if repo.updated_timestamp: installed_date_string = "
" + translate("AddonsInstaller", "Installed on") + ": " - installed_date_string += ( - QtCore.QDateTime.fromTime_t(repo.updated_timestamp) - .date() - .toString(QtCore.Qt.SystemLocaleShortDate) + installed_date_string += QtCore.QLocale().toString( + QtCore.QDateTime.fromSecsSinceEpoch(int(round(repo.updated_timestamp, 0))), + QtCore.QLocale.ShortFormat, ) available_version_string = "" diff --git a/src/Mod/Arch/ArchAxis.py b/src/Mod/Arch/ArchAxis.py index ecda27a768..7384fef8ed 100644 --- a/src/Mod/Arch/ArchAxis.py +++ b/src/Mod/Arch/ArchAxis.py @@ -26,6 +26,8 @@ import ArchCommands import Draft import Part from FreeCAD import Vector +from draftutils import params + if FreeCAD.GuiUp: import FreeCADGui, re from PySide import QtCore, QtGui @@ -224,11 +226,11 @@ class _ViewProviderAxis: def setProperties(self,vobj): - ts = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetFloat("textheight",350) + ts = params.get_param("textheight") * params.get_param("DefaultAnnoScaleMultiplier") pl = vobj.PropertiesList if not "BubbleSize" in pl: vobj.addProperty("App::PropertyLength","BubbleSize","Axis", QT_TRANSLATE_NOOP("App::Property","The size of the axis bubbles")) - vobj.BubbleSize = ts*1.42 + vobj.BubbleSize = ts * 1.42 if not "NumberingStyle" in pl: vobj.addProperty("App::PropertyEnumeration","NumberingStyle","Axis", QT_TRANSLATE_NOOP("App::Property","The numbering style")) vobj.NumberingStyle = ["1,2,3","01,02,03","001,002,003","A,B,C","a,b,c","I,II,III","L0,L1,L2"] @@ -251,7 +253,7 @@ class _ViewProviderAxis: vobj.StartNumber = 1 if not "FontName" in pl: vobj.addProperty("App::PropertyFont","FontName","Axis",QT_TRANSLATE_NOOP("App::Property","The font to use for texts")) - vobj.FontName = Draft.getParam("textfont","Arial,Sans") + vobj.FontName = params.get_param("textfont") if not "FontSize" in pl: vobj.addProperty("App::PropertyLength","FontSize","Axis",QT_TRANSLATE_NOOP("App::Property","The font size")) vobj.FontSize = ts @@ -476,7 +478,7 @@ class _ViewProviderAxis: txpos = FreeCAD.Vector(center.x,center.y-fs/2.5,center.z) tr.translation.setValue(tuple(txpos)) fo = coin.SoFont() - fn = Draft.getParam("textfont","Arial,Sans") + fn = params.get_param("textfont") if hasattr(vobj,"FontName"): if vobj.FontName: try: @@ -545,7 +547,7 @@ class _ViewProviderAxis: fs = vobj.BubbleSize*0.75 tr.translation.setValue(tuple(pl.Base)) tr.rotation.setValue(pl.Rotation.Q) - fn = Draft.getParam("textfont","Arial,Sans") + fn = params.get_param("textfont") if hasattr(vobj,"FontName"): if vobj.FontName: try: diff --git a/src/Mod/Arch/ArchBuilding.py b/src/Mod/Arch/ArchBuilding.py index 68e5a9352a..8231f81a34 100644 --- a/src/Mod/Arch/ArchBuilding.py +++ b/src/Mod/Arch/ArchBuilding.py @@ -24,6 +24,8 @@ import FreeCAD import ArchCommands import ArchFloor import Draft +from draftutils import params + if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore @@ -227,8 +229,7 @@ class _CommandBuilding: def Activated(self): sel = FreeCADGui.Selection.getSelection() - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - link = p.GetBool("FreeLinking",False) + link = params.get_param_arch("FreeLinking") buildingobj = [] warning = False for obj in sel : diff --git a/src/Mod/Arch/ArchBuildingPart.py b/src/Mod/Arch/ArchBuildingPart.py index f21b7ae935..7a1cf4a71d 100644 --- a/src/Mod/Arch/ArchBuildingPart.py +++ b/src/Mod/Arch/ArchBuildingPart.py @@ -27,6 +27,8 @@ import DraftVecUtils import ArchIFC import tempfile import os +from draftutils import params + if FreeCAD.GuiUp: import FreeCADGui from draftutils.translate import translate @@ -567,10 +569,10 @@ class ViewProviderBuildingPart: vobj.ShowLabel = True if not "FontName" in pl: vobj.addProperty("App::PropertyFont","FontName","BuildingPart",QT_TRANSLATE_NOOP("App::Property","The font to be used for texts")) - vobj.FontName = Draft.getParam("textfont","Arial") + vobj.FontName = params.get_param("textfont") if not "FontSize" in pl: vobj.addProperty("App::PropertyLength","FontSize","BuildingPart",QT_TRANSLATE_NOOP("App::Property","The font size of texts")) - vobj.FontSize = Draft.getParam("textheight",2.0) + vobj.FontSize = params.get_param("textheight") * params.get_param("DefaultAnnoScaleMultiplier") if not "DiffuseColor" in pl: vobj.addProperty("App::PropertyColorList","DiffuseColor","BuildingPart",QT_TRANSLATE_NOOP("App::Property","The individual face colors")) @@ -600,17 +602,16 @@ class ViewProviderBuildingPart: vobj.addProperty("App::PropertyBool","ChildrenOverride","Children",QT_TRANSLATE_NOOP("App::Property","If true, show the objects contained in this Building Part will adopt these line, color and transparency settings")) if not "ChildrenLineWidth" in pl: vobj.addProperty("App::PropertyFloat","ChildrenLineWidth","Children",QT_TRANSLATE_NOOP("App::Property","The line width of child objects")) - vobj.LineWidth = 1 + vobj.ChildrenLineWidth = params.get_param_view("DefaultShapeLineWidth") if not "ChildrenLineColor" in pl: vobj.addProperty("App::PropertyColor","ChildrenLineColor","Children",QT_TRANSLATE_NOOP("App::Property","The line color of child objects")) - c = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/View").GetUnsigned("DefaultShapeLineColor",255) - vobj.ChildrenLineColor = (float((c>>24)&0xFF)/255.0,float((c>>16)&0xFF)/255.0,float((c>>8)&0xFF)/255.0,0.0) + vobj.ChildrenLineColor = params.get_param_view("DefaultShapeLineColor") & 0xFFFFFF00 if not "ChildrenShapeColor" in pl: vobj.addProperty("App::PropertyColor","ChildrenShapeColor","Children",QT_TRANSLATE_NOOP("App::Property","The shape color of child objects")) - c = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/View").GetUnsigned("DefaultShapeColor",4294967295) - vobj.ChildrenLineColor = (float((c>>24)&0xFF)/255.0,float((c>>16)&0xFF)/255.0,float((c>>8)&0xFF)/255.0,0.0) + vobj.ChildrenShapeColor = params.get_param_view("DefaultShapeColor") & 0xFFFFFF00 if not "ChildrenTransparency" in pl: vobj.addProperty("App::PropertyPercent","ChildrenTransparency","Children",QT_TRANSLATE_NOOP("App::Property","The transparency of child objects")) + vobj.ChildrenTransparency = params.get_param_view("DefaultShapeTransparency") # clip properties if not "CutView" in pl: @@ -790,7 +791,7 @@ class ViewProviderBuildingPart: txt += units.display_external(float(q),None,'Length',vobj.ShowUnit,u) except Exception: q = q.getValueAs(q.getUserPreferred()[2]) - d = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units").GetInt("Decimals",0) + d = params.get_param("Decimals",path="Units") fmt = "{0:."+ str(d) + "f}" if not vobj.ShowUnit: u = "" @@ -882,8 +883,12 @@ class ViewProviderBuildingPart: return True def setEdit(self, vobj, mode): - # For some reason mode is always 0. - # Using FreeCADGui.getUserEditMode() as a workaround. + # mode == 1 if Transform is selected in the Tree view contex menu. + # mode == 2 has been added for consistency. + if mode == 1 or mode == 2: + return None + # For some reason mode is always 0 if the object is double-clicked in + # the Tree view. Using FreeCADGui.getUserEditMode() as a workaround. if FreeCADGui.getUserEditMode() in ("Transform", "Cutting"): return None @@ -891,8 +896,8 @@ class ViewProviderBuildingPart: return False # Return `False` as we don't want to enter edit mode. def unsetEdit(self, vobj, mode): - # For some reason mode is always 0. - # Using FreeCADGui.getUserEditMode() as a workaround. + if mode == 1 or mode == 2: + return None if FreeCADGui.getUserEditMode() in ("Transform", "Cutting"): return None diff --git a/src/Mod/Arch/ArchCommands.py b/src/Mod/Arch/ArchCommands.py index 61635d21ab..712d55ed5d 100644 --- a/src/Mod/Arch/ArchCommands.py +++ b/src/Mod/Arch/ArchCommands.py @@ -25,6 +25,8 @@ import ArchComponent import Draft import DraftVecUtils from FreeCAD import Vector +from draftutils import params + if FreeCAD.GuiUp: import FreeCADGui from PySide import QtGui,QtCore @@ -62,33 +64,29 @@ def getStringList(objects): def getDefaultColor(objectType): '''getDefaultColor(string): returns a color value for the given object type (Wall, Structure, Window, WindowGlass)''' - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") transparency = 0.0 if objectType == "Wall": - c = p.GetUnsigned("WallColor",4294967295) + c = params.get_param_arch("WallColor") elif objectType == "Structure": - c = p.GetUnsigned("StructureColor",2847259391) + c = params.get_param_arch("StructureColor") elif objectType == "WindowGlass": - c = p.GetUnsigned("WindowGlassColor",1772731135) - transparency = p.GetInt("WindowTransparency",85)/100.0 + c = params.get_param_arch("WindowGlassColor") + transparency = params.get_param_arch("WindowTransparency") / 100.0 elif objectType == "Rebar": - c = p.GetUnsigned("RebarColor",3111475967) + c = params.get_param_arch("RebarColor") elif objectType == "Panel": - c = p.GetUnsigned("PanelColor",3416289279) + c = params.get_param_arch("PanelColor") elif objectType == "Space": - c = p.GetUnsigned("defaultSpaceColor",4278190080) + c = params.get_param_arch("defaultSpaceColor") elif objectType == "Helpers": - c = p.GetUnsigned("ColorHelpers",674321151) + c = params.get_param_arch("ColorHelpers") elif objectType == "Construction": - c = Draft.getParam("constructioncolor",746455039) + c = params.get_param("constructioncolor") transparency = 0.80 else: - c = p.GetUnsigned("WindowsColor",810781695) - r = float((c>>24)&0xFF)/255.0 - g = float((c>>16)&0xFF)/255.0 - b = float((c>>8)&0xFF)/255.0 - result = (r,g,b,transparency) - return result + c = params.get_param_arch("WindowColor") + r, g, b, _ = Draft.get_rgba_tuple(c) + return (r, g, b, transparency) def addComponents(objectsList,host): '''addComponents(objectsList,hostObject): adds the given object or the objects @@ -251,7 +249,7 @@ def setAsSubcomponent(obj): '''Sets the given object properly to become a subcomponent (addition, subtraction) of an Arch component''' Draft.ungroup(obj) - if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("applyConstructionStyle",True): + if params.get_param_arch("applyConstructionStyle"): if FreeCAD.GuiUp: color = getDefaultColor("Construction") if hasattr(obj.ViewObject,"LineColor"): @@ -662,10 +660,7 @@ def download(url,force=False): from urllib2 import urlopen import os name = url.split('/')[-1] - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro") - macropath = p.GetString("MacroPath","") - if not macropath: - macropath = FreeCAD.ConfigGet("UserAppData") + macropath = FreeCAD.getUserMacroDir(True) filepath = os.path.join(macropath,name) if os.path.exists(filepath) and not(force): return filepath @@ -845,7 +840,7 @@ def survey(callback=False): for o in newsels: if hasattr(o.Object, 'Shape'): n = o.Object.Label - showUnit = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("surveyUnits",True) + showUnit = params.get_param_arch("surveyUnits") t = "" u = FreeCAD.Units.Quantity() if not o.HasSubObjects: @@ -1040,7 +1035,7 @@ class SurveyTaskPanel: if hasattr(FreeCAD,"SurveyObserver"): u = FreeCAD.Units.Quantity(FreeCAD.SurveyObserver.totalLength,FreeCAD.Units.Length) t = u.getUserPreferred()[0] - if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("surveyUnits",True): + if params.get_param_arch("surveyUnits"): QtGui.QApplication.clipboard().setText(t) else: QtGui.QApplication.clipboard().setText(str(u.Value/u.getUserPreferred()[1])) @@ -1050,7 +1045,7 @@ class SurveyTaskPanel: u = FreeCAD.Units.Quantity(FreeCAD.SurveyObserver.totalArea,FreeCAD.Units.Area) t = u.getUserPreferred()[0] t = t.replace("^2","²") - if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("surveyUnits",True): + if params.get_param_arch("surveyUnits"): QtGui.QApplication.clipboard().setText(t) else: QtGui.QApplication.clipboard().setText(str(u.Value/u.getUserPreferred()[1])) @@ -1448,11 +1443,10 @@ class _CommandMeshToShape: if f.InList: if f.InList[0].isDerivedFrom("App::DocumentObjectGroup"): g = f.InList[0] - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - fast = p.GetBool("ConversionFast",True) - tol = p.GetFloat("ConversionTolerance",0.001) - flat = p.GetBool("ConversionFlat",False) - cut = p.GetBool("ConversionCut",False) + fast = params.get_param_arch("ConversionFast") + tol = params.get_param_arch("ConversionTolerance") + flat = params.get_param_arch("ConversionFlat") + cut = params.get_param_arch("ConversionCut") FreeCAD.ActiveDocument.openTransaction(translate("Arch","Mesh to Shape")) for obj in FreeCADGui.Selection.getSelection(): newobj = meshToShape(obj,True,fast,tol,flat,cut) diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py index d6d4e06379..1faaa34111 100644 --- a/src/Mod/Arch/ArchComponent.py +++ b/src/Mod/Arch/ArchComponent.py @@ -35,6 +35,8 @@ import FreeCAD import ArchCommands import ArchIFC import Draft +from draftutils import params + if FreeCAD.GuiUp: import FreeCADGui from PySide import QtGui,QtCore @@ -213,10 +215,10 @@ class Component(ArchIFC.IfcProduct): FreeCAD.Console.PrintMessage("Upgrading "+obj.Label+" BaseMaterial property to Material\n") if not "MoveBase" in pl: obj.addProperty("App::PropertyBool","MoveBase","Component",QT_TRANSLATE_NOOP("App::Property","Specifies if moving this object moves its base instead")) - obj.MoveBase = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("MoveBase",False) + obj.MoveBase = params.get_param_arch("MoveBase") if not "MoveWithHost" in pl: obj.addProperty("App::PropertyBool","MoveWithHost","Component",QT_TRANSLATE_NOOP("App::Property","Specifies if this object must move together when its host is moved")) - obj.MoveWithHost = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("MoveWithHost",False) + obj.MoveWithHost = params.get_param_arch("MoveWithHost") if not "VerticalArea" in pl: obj.addProperty("App::PropertyArea","VerticalArea","Component",QT_TRANSLATE_NOOP("App::Property","The area of all vertical faces of this object")) obj.setEditorMode("VerticalArea",1) @@ -992,7 +994,7 @@ class Component(ArchIFC.IfcProduct): import Part import TechDraw - fmax = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("MaxComputeAreas",20) + fmax = params.get_param_arch("MaxComputeAreas") if len(obj.Shape.Faces) > fmax: obj.VerticalArea = 0 obj.HorizontalArea = 0 @@ -1174,7 +1176,7 @@ class ViewProviderComponent: if not "UseMaterialColor" in vobj.PropertiesList: vobj.addProperty("App::PropertyBool","UseMaterialColor","Component",QT_TRANSLATE_NOOP("App::Property","Use the material color as this object's shape color, if available")) - vobj.UseMaterialColor = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("UseMaterialColor",True) + vobj.UseMaterialColor = params.get_param_arch("UseMaterialColor") def updateData(self,obj,prop): """Method called when the host object has a property changed. @@ -1456,7 +1458,7 @@ class ViewProviderComponent: objlink = getattr(self.Object,link) if objlink: c.append(objlink) - if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ClaimHosted",True): + if params.get_param_arch("ClaimHosted"): for link in self.Object.Proxy.getHosts(self.Object): c.append(link) @@ -2294,7 +2296,7 @@ if FreeCAD.GuiUp: editor = QtGui.QSpinBox(parent) elif "Real" in ptype: editor = QtGui.QDoubleSpinBox(parent) - editor.setDecimals(FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units").GetInt("Decimals",2)) + editor.setDecimals(params.get_param("Decimals",path="Units")) elif ("Boolean" in ptype) or ("Logical" in ptype): editor = QtGui.QComboBox(parent) editor.addItems(["True","False"]) diff --git a/src/Mod/Arch/ArchCurtainWall.py b/src/Mod/Arch/ArchCurtainWall.py index 64ce7ea311..6b67f3f929 100644 --- a/src/Mod/Arch/ArchCurtainWall.py +++ b/src/Mod/Arch/ArchCurtainWall.py @@ -29,6 +29,7 @@ import FreeCAD import ArchComponent import ArchCommands import DraftVecUtils +from draftutils import params if FreeCAD.GuiUp: import FreeCADGui @@ -167,13 +168,12 @@ class CurtainWall(ArchComponent.Component): pl = obj.PropertiesList vsize = 50 hsize = 50 - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") if not "Host" in pl: obj.addProperty("App::PropertyLink","Host","CurtainWall",QT_TRANSLATE_NOOP("App::Property","An optional host object for this curtain wall")) if not "Height" in pl: obj.addProperty("App::PropertyLength","Height","CurtainWall", QT_TRANSLATE_NOOP("App::Property","The height of the curtain wall, if based on an edge")) - obj.Height = p.GetFloat("WallHeight",3000) + obj.Height = params.get_param_arch("WallHeight") if not "VerticalMullionNumber" in pl: obj.addProperty("App::PropertyInteger","VerticalMullionNumber","CurtainWall", QT_TRANSLATE_NOOP("App::Property","The number of vertical mullions")) diff --git a/src/Mod/Arch/ArchFloor.py b/src/Mod/Arch/ArchFloor.py index 601abd661b..85c66daf6f 100644 --- a/src/Mod/Arch/ArchFloor.py +++ b/src/Mod/Arch/ArchFloor.py @@ -33,6 +33,8 @@ import ArchCommands import ArchIFC import Draft import DraftVecUtils +from draftutils import params + if FreeCAD.GuiUp: import FreeCADGui from draftutils.translate import translate @@ -135,8 +137,7 @@ class _CommandFloor: """ sel = FreeCADGui.Selection.getSelection() - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - link = p.GetBool("FreeLinking",False) + link = params.get_param_arch("FreeLinking") floorobj = [] warning = False for obj in sel : diff --git a/src/Mod/Arch/ArchIFCSchema.py b/src/Mod/Arch/ArchIFCSchema.py index 917b0a2c3a..f196545068 100644 --- a/src/Mod/Arch/ArchIFCSchema.py +++ b/src/Mod/Arch/ArchIFCSchema.py @@ -30,9 +30,10 @@ import os import json import FreeCAD +from draftutils import params ifcVersions = ["IFC4", "IFC2X3"] -IfcVersion = ifcVersions[FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("IfcVersion",0)] +IfcVersion = ifcVersions[params.get_param_arch("IfcVersion")] with open(os.path.join(FreeCAD.getResourceDir(), "Mod", "Arch", "Presets", "ifc_contexts_" + IfcVersion + ".json")) as f: diff --git a/src/Mod/Arch/ArchMaterial.py b/src/Mod/Arch/ArchMaterial.py index a9809fb44d..f5d39abcb1 100644 --- a/src/Mod/Arch/ArchMaterial.py +++ b/src/Mod/Arch/ArchMaterial.py @@ -20,6 +20,8 @@ #*************************************************************************** import FreeCAD +from draftutils import params + if FreeCAD.GuiUp: import FreeCADGui, os import Arch_rc # Needed for access to icons # lgtm [py/unused_import] @@ -887,9 +889,8 @@ class _ArchMultiMaterialTaskPanel: self.model.clear() self.model.setHorizontalHeaderLabels([translate("Arch","Name"),translate("Arch","Material"),translate("Arch","Thickness")]) # restore widths - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - self.form.tree.setColumnWidth(0,p.GetInt("MultiMaterialColumnWidth0",60)) - self.form.tree.setColumnWidth(1,p.GetInt("MultiMaterialColumnWidth1",60)) + self.form.tree.setColumnWidth(0,params.get_param_arch("MultiMaterialColumnWidth0")) + self.form.tree.setColumnWidth(1,params.get_param_arch("MultiMaterialColumnWidth1")) for i in range(len(obj.Names)): item1 = QtGui.QStandardItem(obj.Names[i]) item2 = QtGui.QStandardItem(obj.Materials[i].Label) @@ -974,9 +975,8 @@ class _ArchMultiMaterialTaskPanel: def accept(self): # store widths - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - p.SetInt("MultiMaterialColumnWidth0",self.form.tree.columnWidth(0)) - p.SetInt("MultiMaterialColumnWidth1",self.form.tree.columnWidth(1)) + params.set_param_arch("MultiMaterialColumnWidth0",self.form.tree.columnWidth(0)) + params.set_param_arch("MultiMaterialColumnWidth1",self.form.tree.columnWidth(1)) if self.obj: mats = [] for m in FreeCAD.ActiveDocument.Objects: diff --git a/src/Mod/Arch/ArchPanel.py b/src/Mod/Arch/ArchPanel.py index 20fb4f4083..36bd428be7 100644 --- a/src/Mod/Arch/ArchPanel.py +++ b/src/Mod/Arch/ArchPanel.py @@ -28,6 +28,8 @@ import Draft import DraftVecUtils import Part from FreeCAD import Vector +from draftutils import params + if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore, QtGui @@ -142,10 +144,9 @@ class CommandPanel: def Activated(self): - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - self.Length = p.GetFloat("PanelLength",1000) - self.Width = p.GetFloat("PanelWidth",1000) - self.Thickness = p.GetFloat("PanelThickness",10) + self.Length = params.get_param_arch("PanelLength") + self.Width = params.get_param_arch("PanelWidth") + self.Thickness = params.get_param_arch("PanelThickness") self.Profile = None self.continueCmd = False self.rotated = False @@ -167,7 +168,7 @@ class CommandPanel: # interactive mode import WorkingPlane WorkingPlane.get_working_plane() - + self.points = [] self.tracker = DraftTrackers.boxTracker() self.tracker.width(self.Width) @@ -282,17 +283,17 @@ class CommandPanel: def setWidth(self,d): self.Width = d - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PanelWidth",d) + params.set_param_arch("PanelWidth",d) def setThickness(self,d): self.Thickness = d - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PanelThickness",d) + params.set_param_arch("PanelThickness",d) def setLength(self,d): self.Length = d - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PanelLength",d) + params.set_param_arch("PanelLength",d) def setContinue(self,i): @@ -832,7 +833,7 @@ class PanelCut(Draft.DraftObject): obj.addProperty("App::PropertyAngle","TagRotation","PanelCut",QT_TRANSLATE_NOOP("App::Property","The rotation of the tag text")) if not "FontFile" in pl: obj.addProperty("App::PropertyFile","FontFile","PanelCut",QT_TRANSLATE_NOOP("App::Property","The font of the tag text")) - obj.FontFile = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetString("FontFile","") + obj.FontFile = params.get_param("FontFile") if not "MakeFace" in pl: obj.addProperty("App::PropertyBool","MakeFace","PanelCut",QT_TRANSLATE_NOOP("App::Property","If True, the object is rendered as a face, if possible.")) if not "AllowedAngles" in pl: @@ -1099,7 +1100,6 @@ class PanelSheet(Draft.DraftObject): def setProperties(self,obj): pl = obj.PropertiesList - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") if not "Group" in pl: obj.addProperty("App::PropertyLinkList","Group","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The linked Panel cuts")) if not "TagText" in pl: @@ -1113,13 +1113,13 @@ class PanelSheet(Draft.DraftObject): obj.addProperty("App::PropertyAngle","TagRotation","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The rotation of the tag text")) if not "FontFile" in pl: obj.addProperty("App::PropertyFile","FontFile","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The font of the tag text")) - obj.FontFile = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetString("FontFile",QT_TRANSLATE_NOOP("App::Property","The font file")) + obj.FontFile = params.get_param("FontFile") if not "Width" in pl: obj.addProperty("App::PropertyLength","Width","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The width of the sheet")) - obj.Width = p.GetFloat("PanelLength",1000) + obj.Width = params.get_param_arch("PanelLength") if not "Height" in pl: obj.addProperty("App::PropertyLength","Height","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The height of the sheet")) - obj.Height = p.GetFloat("PanelWidth",1000) + obj.Height = params.get_param_arch("PanelWidth") if not "FillRatio" in pl: obj.addProperty("App::PropertyPercent","FillRatio","PanelSheet",QT_TRANSLATE_NOOP("App::Property","The fill ratio of this sheet")) obj.setEditorMode("FillRatio",2) diff --git a/src/Mod/Arch/ArchPipe.py b/src/Mod/Arch/ArchPipe.py index bdcdfac1ab..596fac7cb0 100644 --- a/src/Mod/Arch/ArchPipe.py +++ b/src/Mod/Arch/ArchPipe.py @@ -22,6 +22,8 @@ import FreeCAD import ArchComponent +from draftutils import params + if FreeCAD.GuiUp: import FreeCADGui import Arch_rc @@ -71,8 +73,7 @@ def makePipe(baseobj=None,diameter=0,length=0,placement=None,name=None): if diameter: obj.Diameter = diameter else: - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - obj.Diameter = p.GetFloat("PipeDiameter",50) + obj.Diameter = params.get_param_arch("PipeDiameter") if placement: obj.Placement = placement return obj diff --git a/src/Mod/Arch/ArchPrecast.py b/src/Mod/Arch/ArchPrecast.py index 1b7543da25..7bb929b10e 100644 --- a/src/Mod/Arch/ArchPrecast.py +++ b/src/Mod/Arch/ArchPrecast.py @@ -28,6 +28,8 @@ Beams, pillars, slabs and panels""" import ArchCommands,ArchComponent,FreeCAD from FreeCAD import Vector +from draftutils import params + if FreeCAD.GuiUp: from draftutils.translate import translate from PySide.QtCore import QT_TRANSLATE_NOOP @@ -897,21 +899,20 @@ class _PrecastTaskPanel: QtCore.QObject.connect(self.valueTread,QtCore.SIGNAL("valueChanged(double)"),self.setTread) # restore presets - param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - self.restoreValue(self.valueChamfer, param.GetFloat("PrecastChamfer", 0.0)) - self.restoreValue(self.valueDentLength, param.GetFloat("PrecastDentLength", 0.0)) - self.restoreValue(self.valueDentWidth, param.GetFloat("PrecastDentWidth", 0.0)) - self.restoreValue(self.valueDentHeight, param.GetFloat("PrecastDentHeight", 0.0)) - self.restoreValue(self.valueBase, param.GetFloat("PrecastBase", 0.0)) - self.restoreValue(self.valueHoleMajor, param.GetFloat("PrecastHoleMajor", 0.0)) - self.restoreValue(self.valueHoleMinor, param.GetFloat("PrecastHoleMinor", 0.0)) - self.restoreValue(self.valueHoleSpacing, param.GetFloat("PrecastHoleSpacing", 0.0)) - self.restoreValue(self.valueGrooveDepth, param.GetFloat("PrecastGrooveDepth", 0.0)) - self.restoreValue(self.valueGrooveHeight, param.GetFloat("PrecastGrooveHeight", 0.0)) - self.restoreValue(self.valueGrooveSpacing, param.GetFloat("PrecastGrooveSpacing", 0.0)) - self.restoreValue(self.valueDownLength, param.GetFloat("PrecastDownLength", 0.0)) - self.restoreValue(self.valueRiser, param.GetFloat("PrecastRiser", 0.0)) - self.restoreValue(self.valueTread, param.GetFloat("PrecastTread", 0.0)) + self.restoreValue(self.valueChamfer, params.get_param_arch("PrecastChamfer")) + self.restoreValue(self.valueDentLength, params.get_param_arch("PrecastDentLength")) + self.restoreValue(self.valueDentWidth, params.get_param_arch("PrecastDentWidth")) + self.restoreValue(self.valueDentHeight, params.get_param_arch("PrecastDentHeight")) + self.restoreValue(self.valueBase, params.get_param_arch("PrecastBase")) + self.restoreValue(self.valueHoleMajor, params.get_param_arch("PrecastHoleMajor")) + self.restoreValue(self.valueHoleMinor, params.get_param_arch("PrecastHoleMinor")) + self.restoreValue(self.valueHoleSpacing, params.get_param_arch("PrecastHoleSpacing")) + self.restoreValue(self.valueGrooveDepth, params.get_param_arch("PrecastGrooveDepth")) + self.restoreValue(self.valueGrooveHeight, params.get_param_arch("PrecastGrooveHeight")) + self.restoreValue(self.valueGrooveSpacing, params.get_param_arch("PrecastGrooveSpacing")) + self.restoreValue(self.valueDownLength, params.get_param_arch("PrecastDownLength")) + self.restoreValue(self.valueRiser, params.get_param_arch("PrecastRiser")) + self.restoreValue(self.valueTread, params.get_param_arch("PrecastTread")) self.retranslateUi(self.form) self.form.hide() @@ -945,59 +946,59 @@ class _PrecastTaskPanel: def setChamfer(self,value): self.Chamfer = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastChamfer",value) + params.set_param_arch("PrecastChamfer",value) def setDentLength(self,value): self.DentLength = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastDentLength",value) + params.set_param_arch("PrecastDentLength",value) def setDentWidth(self,value): self.DentWidth = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastDentWidth",value) + params.set_param_arch("PrecastDentWidth",value) def setDentHeight(self,value): self.DentHeight = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastDentHeight",value) + params.set_param_arch("PrecastDentHeight",value) def setBase(self,value): self.Base = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastBase",value) + params.set_param_arch("PrecastBase",value) def setHoleMajor(self,value): self.HoleMajor = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastHoleMajor",value) + params.set_param_arch("PrecastHoleMajor",value) def setHoleMinor(self,value): self.HoleMinor = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastHoleMinor",value) + params.set_param_arch("PrecastHoleMinor",value) def setHoleSpacing(self,value): self.HoleSpacing = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastHoleSpacing",value) + params.set_param_arch("PrecastHoleSpacing",value) def setGrooveDepth(self,value): self.GrooveDepth = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastGrooveDepth",value) + params.set_param_arch("PrecastGrooveDepth",value) def setGrooveHeight(self,value): self.GrooveHeight = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastGrooveHeight",value) + params.set_param_arch("PrecastGrooveHeight",value) def setGrooveSpacing(self,value): self.GrooveSpacing = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastGrooveSpacing",value) + params.set_param_arch("PrecastGrooveSpacing",value) def setDownLength(self,value): self.DownLength = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastDownLength",value) + params.set_param_arch("PrecastDownLength",value) def setRiser(self,value): self.Riser = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastRiser",value) + params.set_param_arch("PrecastRiser",value) def setTread(self,value): self.Tread = value - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetFloat("PrecastTread",value) + params.set_param_arch("PrecastTread",value) def retranslateUi(self, dialog): from PySide import QtGui diff --git a/src/Mod/Arch/ArchProfile.py b/src/Mod/Arch/ArchProfile.py index 923a7c78d2..a8d88689ab 100644 --- a/src/Mod/Arch/ArchProfile.py +++ b/src/Mod/Arch/ArchProfile.py @@ -36,6 +36,7 @@ import os import FreeCAD import Draft from FreeCAD import Vector +from draftutils import params if FreeCAD.GuiUp: import FreeCADGui @@ -169,7 +170,7 @@ class Arch_Profile: self.vPresets.currentIndexChanged.connect(self.setPreset) # restore preset - stored = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetString("StructurePreset","") + stored = params.get_param_arch("StructurePreset") if stored: if ";" in stored: stored = stored.split(";") @@ -206,13 +207,13 @@ class Arch_Profile: if i == 0: self.pSelect = [None] self.vPresets.addItems([" "]) - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetString("StructurePreset","") + params.set_param_arch("StructurePreset","") else: self.pSelect = [p for p in self.Presets if p[1] == self.Categories[i-1]] fpresets = [] for p in self.pSelect: f = FreeCAD.Units.Quantity(p[4],FreeCAD.Units.Length).getUserPreferred() - d = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units").GetInt("Decimals",2) + d = params.get_param("Decimals",path="Units") s1 = str(round(p[4]/f[1],d)) s2 = str(round(p[5]/f[1],d)) s3 = str(f[2]) @@ -227,7 +228,7 @@ class Arch_Profile: if elt: p=elt[0]-1 # Presets indexes are 1-based self.Profile = self.Presets[p] - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").SetString("StructurePreset",";".join([str(i) for i in self.Profile])) + params.set_param_arch("StructurePreset",";".join([str(i) for i in self.Profile])) class _Profile(Draft._DraftObject): @@ -565,7 +566,7 @@ class ProfileTaskPanel: if pre[1] == text: self.currentpresets.append(pre) f = FreeCAD.Units.Quantity(pre[4],FreeCAD.Units.Length).getUserPreferred() - d = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units").GetInt("Decimals",2) + d = params.get_param("Decimals",path="Units") s1 = str(round(pre[4]/f[1],d)) s2 = str(round(pre[5]/f[1],d)) s3 = str(f[2]) diff --git a/src/Mod/Arch/ArchRebar.py b/src/Mod/Arch/ArchRebar.py index abe3221a72..d60585a60a 100644 --- a/src/Mod/Arch/ArchRebar.py +++ b/src/Mod/Arch/ArchRebar.py @@ -25,6 +25,8 @@ import Draft import ArchComponent import DraftVecUtils import ArchCommands +from draftutils import params + if FreeCAD.GuiUp: import FreeCADGui from draftutils.translate import translate @@ -58,7 +60,6 @@ def makeRebar(baseobj=None,sketch=None,diameter=None,amount=1,offset=None,name=N if not FreeCAD.ActiveDocument: FreeCAD.Console.PrintError("No active document. Aborting\n") return - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Rebar") obj.Label = name if name else translate("Arch","Rebar") _Rebar(obj) @@ -87,15 +88,15 @@ def makeRebar(baseobj=None,sketch=None,diameter=None,amount=1,offset=None,name=N if diameter: obj.Diameter = diameter else: - obj.Diameter = p.GetFloat("RebarDiameter",6) + obj.Diameter = params.get_param_arch("RebarDiameter") obj.Amount = amount obj.Document.recompute() if offset is not None: obj.OffsetStart = offset obj.OffsetEnd = offset else: - obj.OffsetStart = p.GetFloat("RebarOffset",30) - obj.OffsetEnd = p.GetFloat("RebarOffset",30) + obj.OffsetStart = params.get_param_arch("RebarOffset") + obj.OffsetEnd = params.get_param_arch("RebarOffset") obj.Mark = obj.Label return obj diff --git a/src/Mod/Arch/ArchReference.py b/src/Mod/Arch/ArchReference.py index ae2e209d5f..2c474f42f8 100644 --- a/src/Mod/Arch/ArchReference.py +++ b/src/Mod/Arch/ArchReference.py @@ -28,6 +28,8 @@ import FreeCAD import os import zipfile import re +from draftutils import params + if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore, QtGui @@ -404,7 +406,7 @@ class ViewProviderArchReference: # Check for file change every minute self.timer = QtCore.QTimer() self.timer.timeout.connect(self.checkChanges) - s = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("ReferenceCheckInterval",60) + s = params.get_param_arch("ReferenceCheckInterval") self.timer.start(1000*s) def dumps(self): diff --git a/src/Mod/Arch/ArchSchedule.py b/src/Mod/Arch/ArchSchedule.py index 5e6e2b0209..7ee88f0b75 100644 --- a/src/Mod/Arch/ArchSchedule.py +++ b/src/Mod/Arch/ArchSchedule.py @@ -21,6 +21,8 @@ #*************************************************************************** import FreeCAD +from draftutils import params + if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore, QtGui @@ -344,7 +346,7 @@ class _ArchSchedule: tp = FreeCAD.Units.Length # format value - dv = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units").GetInt("Decimals",2) + dv = params.get_param("Decimals",path="Units") fs = "{:."+str(dv)+"f}" # format string for o in objs: if verbose: @@ -514,13 +516,12 @@ class ArchScheduleTaskPanel: self.form.buttonSelect.setIcon(QtGui.QIcon(":/icons/edit-select-all.svg")) # restore widths - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - self.form.list.setColumnWidth(0,p.GetInt("ScheduleColumnWidth0",100)) - self.form.list.setColumnWidth(1,p.GetInt("ScheduleColumnWidth1",100)) - self.form.list.setColumnWidth(2,p.GetInt("ScheduleColumnWidth2",50)) - self.form.list.setColumnWidth(3,p.GetInt("ScheduleColumnWidth3",100)) - w = p.GetInt("ScheduleDialogWidth",300) - h = p.GetInt("ScheduleDialogHeight",200) + self.form.list.setColumnWidth(0,params.get_param_arch("ScheduleColumnWidth0")) + self.form.list.setColumnWidth(1,params.get_param_arch("ScheduleColumnWidth1")) + self.form.list.setColumnWidth(2,params.get_param_arch("ScheduleColumnWidth2")) + self.form.list.setColumnWidth(3,params.get_param_arch("ScheduleColumnWidth3")) + w = params.get_param_arch("ScheduleDialogWidth") + h = params.get_param_arch("ScheduleDialogHeight") self.form.resize(w,h) # set delegate - Not using custom delegates for now... @@ -712,13 +713,12 @@ class ArchScheduleTaskPanel: """Saves the changes and closes the dialog""" # store widths - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - p.SetInt("ScheduleColumnWidth0",self.form.list.columnWidth(0)) - p.SetInt("ScheduleColumnWidth1",self.form.list.columnWidth(1)) - p.SetInt("ScheduleColumnWidth2",self.form.list.columnWidth(2)) - p.SetInt("ScheduleColumnWidth3",self.form.list.columnWidth(3)) - p.SetInt("ScheduleDialogWidth",self.form.width()) - p.SetInt("ScheduleDialogHeight",self.form.height()) + params.set_param_arch("ScheduleColumnWidth0",self.form.list.columnWidth(0)) + params.set_param_arch("ScheduleColumnWidth1",self.form.list.columnWidth(1)) + params.set_param_arch("ScheduleColumnWidth2",self.form.list.columnWidth(2)) + params.set_param_arch("ScheduleColumnWidth3",self.form.list.columnWidth(3)) + params.set_param_arch("ScheduleDialogWidth",self.form.width()) + params.set_param_arch("ScheduleDialogHeight",self.form.height()) # commit values self.writeValues() diff --git a/src/Mod/Arch/ArchSectionPlane.py b/src/Mod/Arch/ArchSectionPlane.py index 281c525425..33596ee4ec 100644 --- a/src/Mod/Arch/ArchSectionPlane.py +++ b/src/Mod/Arch/ArchSectionPlane.py @@ -31,6 +31,8 @@ import uuid import time from FreeCAD import Vector +from draftutils import params + if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore, QtGui @@ -346,7 +348,6 @@ def getSVG(source, windows.append(o) objs = nonspaces - archUserParameters = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") scaledLineWidth = linewidth/scale if renderMode in ["Coin",2,"Coin mono",3]: # don't scale linewidths in coin mode @@ -357,11 +358,11 @@ def getSVG(source, scaledCutLineWidth = cutlinewidth/scale svgCutLineWidth = str(scaledCutLineWidth) + 'px' else: - st = archUserParameters.GetFloat("CutLineThickness",2) + st = params.get_param_arch("CutLineThickness") svgCutLineWidth = str(scaledLineWidth * st) + 'px' - yt = archUserParameters.GetFloat("SymbolLineThickness",0.6) + yt = params.get_param_arch("SymbolLineThickness") svgSymbolLineWidth = str(linewidth * yt) - hiddenPattern = archUserParameters.GetString("archHiddenPattern","30,10") + hiddenPattern = params.get_param_arch("archHiddenPattern") svgHiddenPattern = hiddenPattern.replace(" ","") #fillpattern = '= 0: FreeCADGui.Snapper.setSelectMode(False) self.tracker.length(self.Width) - self.tracker.width(self.Thickness) self.tracker.height(self.Height) + self.tracker.width(self.W1) self.tracker.on() self.pic.hide() self.im.show() @@ -1180,7 +1174,9 @@ class _ViewProviderWindow(ArchComponent.ViewProviderComponent): typeidx = (i*5)+1 if typeidx < len(obj.WindowParts): typ = obj.WindowParts[typeidx] - if typ == WindowPartTypes[2]: # transparent parts + if typ == WindowPartTypes[0]: # "Frame" + ccol = ArchCommands.getDefaultColor("") + elif typ == WindowPartTypes[2]: # "Glass panel" ccol = ArchCommands.getDefaultColor("WindowGlass") if not ccol: ccol = base diff --git a/src/Mod/Arch/Resources/ui/preferences-archdefaults.ui b/src/Mod/Arch/Resources/ui/preferences-archdefaults.ui index 09c08af4e3..7ce6a3b397 100644 --- a/src/Mod/Arch/Resources/ui/preferences-archdefaults.ui +++ b/src/Mod/Arch/Resources/ui/preferences-archdefaults.ui @@ -25,135 +25,62 @@ Walls - + - - - - - Width: - - - - - - - mm - - - 9999.989999999999782 - - - 200.000000000000000 - - - WallWidth - - - Mod/Arch - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Height: - - - - - - - mm - - - 9999.989999999999782 - - - 3000.000000000000000 - - - WallHeight - - - Mod/Arch - - - - + + + Use sketches + + + true + + + WallSketches + + + Mod/Arch + + - - - - - Use sketches - - - true - - - WallSketches - - - Mod/Arch - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Color: - - - - - - - This is the default color for new Wall objects - - - - 214 - 214 - 214 - - - - WallColor - - - Mod/Arch - - - - + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Color: + + + + + + + This is the default color for new Wall objects + + + + 214 + 214 + 214 + + + + WallColor + + + Mod/Arch + + @@ -163,158 +90,46 @@ Structures - + - - - - - Length: - - - - - - - mm - - - 9999.989999999999782 - - - 100.000000000000000 - - - StructureLength - - - Mod/Arch - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Width: - - - - - - - mm - - - 9999.989999999999782 - - - 100.000000000000000 - - - StructureWidth - - - Mod/Arch - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Height: - - - - - - - mm - - - 9999.989999999999782 - - - 1000.000000000000000 - - - StructureHeight - - - Mod/Arch - - - - + + + Qt::Horizontal + + + + 40 + 20 + + + - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Color: - - - - - - - This is the default color for new Structure objects - - - - 150 - 169 - 186 - - - - StructureColor - - - Mod/Arch - - - - + + + Color: + + + + + + + This is the default color for new Structure objects + + + + 150 + 169 + 186 + + + + StructureColor + + + Mod/Arch + + @@ -443,208 +258,87 @@ Windows - + - - - - - Width: - - - - - - - The default width for new windows - - - mm - - - 99999.990000000005239 - - - 1000.000000000000000 - - - WindowWidth - - - Mod/Arch - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Height: - - - - - - - The default height for new windows - - - mm - - - 99999.990000000005239 - - - 1000.000000000000000 - - - WindowHeight - - - Mod/Arch - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Thickness: - - - - - - - The default thickness for new windows - - - mm - - - 99999.990000000005239 - - - 100.000000000000000 - - - WindowThickness - - - Mod/Arch - - - - + + + Qt::Horizontal + + + + 40 + 20 + + + - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Transparency: - - - - - - - 85 - - - WindowTransparency - - - Mod/Arch - - - - - - - Frame color: - - - - - - - - 33 - 45 - 66 - - - - WindowColor - - - Mod/Arch - - - - - - - Glass color: - - - - - - - - 93 - 183 - 203 - - - - WindowGlassColor - - - Mod/Arch - - - - + + + Transparency: + + + + + + + 85 + + + WindowTransparency + + + Mod/Arch + + + + + + + Frame color: + + + + + + + + 33 + 45 + 66 + + + + WindowColor + + + Mod/Arch + + + + + + + Glass color: + + + + + + + + 93 + 183 + 203 + + + + WindowGlassColor + + + Mod/Arch + + @@ -811,155 +505,43 @@ Panels - + - - - - - Length: - - - - - - - mm - - - 99999.990000000005239 - - - 1000.000000000000000 - - - PanelLength - - - Mod/Arch - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Width: - - - - - - - mm - - - 9999.989999999999782 - - - 1000.000000000000000 - - - PanelWidth - - - Mod/Arch - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Thickness - - - - - - - mm - - - 9999.989999999999782 - - - 10.000000000000000 - - - PanelThickness - - - Mod/Arch - - - - + + + Qt::Horizontal + + + + 40 + 20 + + + - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Color: - - - - - - - - 203 - 160 - 111 - - - - PanelColor - - - Mod/Arch - - - - + + + Color: + + + + + + + + 203 + 160 + 111 + + + + PanelColor + + + Mod/Arch + + @@ -1181,6 +763,13 @@ + + + + Qt::Vertical + + + diff --git a/src/Mod/Arch/exportIFC.py b/src/Mod/Arch/exportIFC.py index c10938cd90..e4e8dec5f6 100644 --- a/src/Mod/Arch/exportIFC.py +++ b/src/Mod/Arch/exportIFC.py @@ -44,6 +44,7 @@ import exportIFCStructuralTools from DraftGeomUtils import vec from importIFCHelper import dd2dms +from draftutils import params from draftutils.messages import _msg, _err if FreeCAD.GuiUp: @@ -107,12 +108,10 @@ END-ISO-10303-21; def getPreferences(): """Retrieve the IFC preferences available in import and export.""" - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - - if FreeCAD.GuiUp and p.GetBool("ifcShowDialog", False): + if FreeCAD.GuiUp and params.get_param_arch("ifcShowDialog"): FreeCADGui.showPreferences("Import-Export", 1) - ifcunit = p.GetInt("ifcUnit", 0) + ifcunit = params.get_param_arch("ifcUnit") # Factor to multiply the dimension in millimeters # mm x 0.001 = metre @@ -142,20 +141,20 @@ def getPreferences(): # some objects may be "unreferenced" and won't belong to the `IfcProject`. # Some applications may fail at importing these unreferenced objects. preferences = { - 'DEBUG': p.GetBool("ifcDebug", False), - 'CREATE_CLONES': p.GetBool("ifcCreateClones", True), - 'FORCE_BREP': p.GetBool("ifcExportAsBrep", False), - 'STORE_UID': p.GetBool("ifcStoreUid", True), - 'SERIALIZE': p.GetBool("ifcSerialize", False), - 'EXPORT_2D': p.GetBool("ifcExport2D", True), - 'FULL_PARAMETRIC': p.GetBool("IfcExportFreeCADProperties", False), - 'ADD_DEFAULT_SITE': p.GetBool("IfcAddDefaultSite", True), - 'ADD_DEFAULT_BUILDING': p.GetBool("IfcAddDefaultBuilding", True), - 'ADD_DEFAULT_STOREY': p.GetBool("IfcAddDefaultStorey", True), + 'DEBUG': params.get_param_arch("ifcDebug"), + 'CREATE_CLONES': params.get_param_arch("ifcCreateClones"), + 'FORCE_BREP': params.get_param_arch("ifcExportAsBrep"), + 'STORE_UID': params.get_param_arch("ifcStoreUid"), + 'SERIALIZE': params.get_param_arch("ifcSerialize"), + 'EXPORT_2D': params.get_param_arch("ifcExport2D"), + 'FULL_PARAMETRIC': params.get_param_arch("IfcExportFreeCADProperties"), + 'ADD_DEFAULT_SITE': params.get_param_arch("IfcAddDefaultSite"), + 'ADD_DEFAULT_BUILDING': params.get_param_arch("IfcAddDefaultBuilding"), + 'ADD_DEFAULT_STOREY': params.get_param_arch("IfcAddDefaultStorey"), 'IFC_UNIT': u, 'SCALE_FACTOR': f, - 'GET_STANDARD': p.GetBool("getStandardType", False), - 'EXPORT_MODEL': ['arch', 'struct', 'hybrid'][p.GetInt("ifcExportModel", 0)] + 'GET_STANDARD': params.get_param_arch("getStandardType"), + 'EXPORT_MODEL': ['arch', 'struct', 'hybrid'][params.get_param_arch("ifcExportModel")] } # get ifcopenshell version @@ -175,7 +174,7 @@ def getPreferences(): schema = ifcopenshell.schema_identifier elif ifcos_version >= 0.6: # v0.6 onwards allows to set our own schema - schema = ["IFC4", "IFC2X3"][p.GetInt("IfcVersion", 0)] + schema = ["IFC4", "IFC2X3"][params.get_param_arch("IfcVersion")] else: schema = "IFC2X3" @@ -1869,7 +1868,7 @@ def checkRectangle(edges): or not. It will return True when edges form a rectangular shape or return False when edges do not form a rectangular shape.""" - if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("DisableIfcRectangleProfileDef",False): + if params.get_param_arch("DisableIfcRectangleProfileDef"): return False if len(edges) != 4: return False @@ -2226,8 +2225,8 @@ def getRepresentation( except Base.FreeCADError: pass if curves: - joinfacets = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcJoinCoplanarFacets",False) - usedae = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcUseDaeOptions",False) + joinfacets = params.get_param_arch("ifcJoinCoplanarFacets") + usedae = params.get_param_arch("ifcUseDaeOptions") if joinfacets: result = Arch.removeCurves(fcsolid,dae=usedae) if result: diff --git a/src/Mod/Arch/exportIFCHelper.py b/src/Mod/Arch/exportIFCHelper.py index 926aecf553..cd2f6c0304 100644 --- a/src/Mod/Arch/exportIFCHelper.py +++ b/src/Mod/Arch/exportIFCHelper.py @@ -25,6 +25,7 @@ import math import FreeCAD # import Draft import ifcopenshell +from draftutils import params def getObjectsOfIfcType(objects, ifcType): results = [] @@ -204,8 +205,8 @@ class recycler: def __init__(self,ifcfile,template=True): self.ifcfile = ifcfile - self.compress = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcCompress",True) - self.mergeProfiles = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcMergeProfiles",False) + self.compress = params.get_param_arch("ifcCompress") + self.mergeProfiles = params.get_param_arch("ifcMergeProfiles") self.cartesianpoints = {} self.directions = {} self.axis2placement3ds = {} diff --git a/src/Mod/Arch/importDAE.py b/src/Mod/Arch/importDAE.py index 2dfaad11b7..8cd5464680 100644 --- a/src/Mod/Arch/importDAE.py +++ b/src/Mod/Arch/importDAE.py @@ -20,6 +20,8 @@ #*************************************************************************** import FreeCAD, Mesh, os, numpy, MeshPart, Arch, Draft +from draftutils import params + if FreeCAD.GuiUp: from draftutils.translate import translate else: @@ -65,15 +67,14 @@ def triangulate(shape): "triangulates the given face" - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - mesher = p.GetInt("ColladaMesher",0) - tessellation = p.GetFloat("ColladaTessellation",1.0) - grading = p.GetFloat("ColladaGrading",0.3) - segsperedge = p.GetInt("ColladaSegsPerEdge",1) - segsperradius = p.GetInt("ColladaSegsPerRadius",2) - secondorder = p.GetBool("ColladaSecondOrder",False) - optimize = p.GetBool("ColladaOptimize",True) - allowquads = p.GetBool("ColladaAllowQuads",False) + mesher = params.get_param_arch("ColladaMesher") + tessellation = params.get_param_arch("ColladaTessellation") + grading = params.get_param_arch("ColladaGrading") + segsperedge = params.get_param_arch("ColladaSegsPerEdge") + segsperradius = params.get_param_arch("ColladaSegsPerRadius") + secondorder = params.get_param_arch("ColladaSecondOrder") + optimize = params.get_param_arch("ColladaOptimize") + allowquads = params.get_param_arch("ColladaAllowQuads") if mesher == 0: return shape.tessellate(tessellation) elif mesher == 1: @@ -118,7 +119,7 @@ def read(filename): "reads a DAE file" global col - col = collada.Collada(filename, ignore=[collada.DaeUnsupportedError]) + col = collada.Collada(filename, ignore=[collada.common.DaeUnsupportedError]) # Read the unitmeter info from dae file and compute unit to convert to mm unitmeter = col.assetInfo.unitmeter or 1 unit = unitmeter / 0.001 @@ -178,12 +179,9 @@ def export(exportList,filename,tessellation=1,colors=None): curved surfaces into triangles.""" if not checkCollada(): return - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - scale = p.GetFloat("ColladaScalingFactor",1.0) + scale = params.get_param_arch("ColladaScalingFactor") scale = scale * 0.001 # from millimeters (FreeCAD) to meters (Collada) - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/View") - c = p.GetUnsigned("DefaultShapeColor",4294967295) - defaultcolor = (float((c>>24)&0xFF)/255.0,float((c>>16)&0xFF)/255.0,float((c>>8)&0xFF)/255.0) + defaultcolor = Draft.get_rgba_tuple(params.get_param_view("DefaultShapeColor"))[:3] colmesh = collada.Collada() colmesh.assetInfo.upaxis = collada.asset.UP_AXIS.Z_UP # authoring info diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index d05aac8e8d..bf4d65ad70 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -42,6 +42,7 @@ import ArchIFCSchema import importIFCHelper import importIFCmulticore +from draftutils import params from draftutils.messages import _msg, _err if FreeCAD.GuiUp: @@ -379,7 +380,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if preferences['DEBUG']: print(" no layer found", ptype,end="") # checking for full FreeCAD parametric definition, overriding everything else - if psets and FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("IfcImportFreeCADProperties",False): + if psets and params.get_param_arch("IfcImportFreeCADProperties"): if "FreeCADPropertySet" in [ifcfile[pset].Name for pset in psets.keys()]: if preferences['DEBUG']: print(" restoring from parametric definition...",end="") obj,parametrics = importIFCHelper.createFromProperties(psets,ifcfile,parametrics) diff --git a/src/Mod/Arch/importIFCHelper.py b/src/Mod/Arch/importIFCHelper.py index 3e86fd522e..0939e5fdcb 100644 --- a/src/Mod/Arch/importIFCHelper.py +++ b/src/Mod/Arch/importIFCHelper.py @@ -26,11 +26,12 @@ import FreeCAD import Arch import ArchIFC +from draftutils import params +from draftutils.messages import _msg, _wrn + if FreeCAD.GuiUp: import FreeCADGui as Gui -from draftutils.messages import _msg, _wrn - PREDEFINED_RGB = {"black": (0, 0, 0), "red": (1.0, 0, 0), @@ -82,29 +83,27 @@ def getPreferences(): 2 = Part shapes 3 = One compound per storey """ - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - - if FreeCAD.GuiUp and p.GetBool("ifcShowDialog", False): + if FreeCAD.GuiUp and params.get_param_arch("ifcShowDialog"): Gui.showPreferences("Import-Export", 0) preferences = { - 'DEBUG': p.GetBool("ifcDebug", False), - 'PREFIX_NUMBERS': p.GetBool("ifcPrefixNumbers", False), - 'SKIP': p.GetString("ifcSkip", "").split(","), - 'SEPARATE_OPENINGS': p.GetBool("ifcSeparateOpenings", False), - 'ROOT_ELEMENT': p.GetString("ifcRootElement", "IfcProduct"), - 'GET_EXTRUSIONS': p.GetBool("ifcGetExtrusions", False), - 'MERGE_MATERIALS': p.GetBool("ifcMergeMaterials", False), - 'MERGE_MODE_ARCH': p.GetInt("ifcImportModeArch", 0), - 'MERGE_MODE_STRUCT': p.GetInt("ifcImportModeStruct", 1), - 'CREATE_CLONES': p.GetBool("ifcCreateClones", True), - 'IMPORT_PROPERTIES': p.GetBool("ifcImportProperties", False), - 'SPLIT_LAYERS': p.GetBool("ifcSplitLayers", False), # wall layer, not layer for visual props - 'FITVIEW_ONIMPORT': p.GetBool("ifcFitViewOnImport", False), - 'ALLOW_INVALID': p.GetBool("ifcAllowInvalid", False), - 'REPLACE_PROJECT': p.GetBool("ifcReplaceProject", False), - 'MULTICORE': p.GetInt("ifcMulticore", 0), - 'IMPORT_LAYER': p.GetBool("ifcImportLayer", True) + 'DEBUG': params.get_param_arch("ifcDebug"), + 'PREFIX_NUMBERS': params.get_param_arch("ifcPrefixNumbers"), + 'SKIP': params.get_param_arch("ifcSkip").split(","), + 'SEPARATE_OPENINGS': params.get_param_arch("ifcSeparateOpenings"), + 'ROOT_ELEMENT': params.get_param_arch("ifcRootElement"), + 'GET_EXTRUSIONS': params.get_param_arch("ifcGetExtrusions"), + 'MERGE_MATERIALS': params.get_param_arch("ifcMergeMaterials"), + 'MERGE_MODE_ARCH': params.get_param_arch("ifcImportModeArch"), + 'MERGE_MODE_STRUCT': params.get_param_arch("ifcImportModeStruct"), + 'CREATE_CLONES': params.get_param_arch("ifcCreateClones"), + 'IMPORT_PROPERTIES': params.get_param_arch("ifcImportProperties"), + 'SPLIT_LAYERS': params.get_param_arch("ifcSplitLayers"), # wall layer, not layer for visual props + 'FITVIEW_ONIMPORT': params.get_param_arch("ifcFitViewOnImport"), + 'ALLOW_INVALID': params.get_param_arch("ifcAllowInvalid"), + 'REPLACE_PROJECT': params.get_param_arch("ifcReplaceProject"), + 'MULTICORE': params.get_param_arch("ifcMulticore"), + 'IMPORT_LAYER': params.get_param_arch("ifcImportLayer") } if preferences['MERGE_MODE_ARCH'] > 0: diff --git a/src/Mod/Arch/importIFClegacy.py b/src/Mod/Arch/importIFClegacy.py index bf2e1a36aa..037e889ce5 100644 --- a/src/Mod/Arch/importIFClegacy.py +++ b/src/Mod/Arch/importIFClegacy.py @@ -27,6 +27,7 @@ import FreeCAD, Arch, Draft, os, sys, time, Part, DraftVecUtils, uuid, math, re +from draftutils import params from draftutils.translate import translate __title__="FreeCAD IFC importer" @@ -82,19 +83,18 @@ def getConfig(): global SKIP, CREATE_IFC_GROUPS, ASMESH, PREFIX_NUMBERS, FORCE_PYTHON_PARSER, SEPARATE_OPENINGS, SEPARATE_PLACEMENTS, JOINSOLIDS, AGGREGATE_WINDOWS IMPORT_IFC_FURNITURE = False ASMESH = ["IfcFurnishingElement"] - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - CREATE_IFC_GROUPS = p.GetBool("createIfcGroups",False) - FORCE_PYTHON_PARSER = p.GetBool("forceIfcPythonParser",False) - DEBUG = p.GetBool("ifcDebug",False) - SEPARATE_OPENINGS = p.GetBool("ifcSeparateOpenings",False) - SEPARATE_PLACEMENTS = p.GetBool("ifcSeparatePlacements",False) - PREFIX_NUMBERS = p.GetBool("ifcPrefixNumbers",False) - JOINSOLIDS = p.GetBool("ifcJoinSolids",False) - AGGREGATE_WINDOWS = p.GetBool("ifcAggregateWindows",False) - skiplist = p.GetString("ifcSkip","") + CREATE_IFC_GROUPS = params.get_param_arch("createIfcGroups") + FORCE_PYTHON_PARSER = params.get_param_arch("forceIfcPythonParser") + DEBUG = params.get_param_arch("ifcDebug") + SEPARATE_OPENINGS = params.get_param_arch("ifcSeparateOpenings") + SEPARATE_PLACEMENTS = params.get_param_arch("ifcSeparatePlacements") + PREFIX_NUMBERS = params.get_param_arch("ifcPrefixNumbers") + JOINSOLIDS = params.get_param_arch("ifcJoinSolids") + AGGREGATE_WINDOWS = params.get_param_arch("ifcAggregateWindows") + skiplist = params.get_param_arch("ifcSkip") if skiplist: SKIP = skiplist.split(",") - asmeshlist = p.GetString("ifcAsMesh","") + asmeshlist = params.get_param_arch("ifcAsMesh") if asmeshlist: ASMESH = asmeshlist.split(",") @@ -811,7 +811,7 @@ def getVector(entity): def getSchema(): "retrieves the express schema" - custom = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetString("CustomIfcSchema","") + custom = params.get_param_arch("CustomIfcSchema") if custom: if os.path.exists(custom): if DEBUG: print("Using custom schema: ",custom.split(os.sep)[-1]) @@ -943,10 +943,9 @@ def export(exportList,filename): # creating base IFC project getConfig() - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch") - scaling = p.GetFloat("IfcScalingFactor",1.0) - exporttxt = p.GetBool("IfcExportList",False) - forcebrep = p.GetBool("ifcExportAsBrep",False) + scaling = params.get_param_arch("IfcScalingFactor") + exporttxt = params.get_param_arch("IfcExportList") + forcebrep = params.get_param_arch("ifcExportAsBrep") application = "FreeCAD" ver = FreeCAD.Version() version = ver[0]+"."+ver[1]+" build"+ver[2] @@ -1394,9 +1393,7 @@ class IfcSchema: def __init__(self, filename): self.filename = filename if not os.path.exists(filename): - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro") - p = p.GetString("MacroPath","") - filename = p + os.sep + filename + filename = FreeCAD.getUserMacroDir(True) + os.sep + filename if not os.path.exists(filename): raise ImportError("no IFCSchema file found!") diff --git a/src/Mod/Arch/importOBJ.py b/src/Mod/Arch/importOBJ.py index 6fbfe04eb0..b69d6d92a7 100644 --- a/src/Mod/Arch/importOBJ.py +++ b/src/Mod/Arch/importOBJ.py @@ -31,6 +31,7 @@ import DraftGeomUtils import Mesh import MeshPart import Part +from draftutils import params if FreeCAD.GuiUp: from draftutils.translate import translate @@ -80,8 +81,7 @@ def getIndices(obj,shape,offsetv,offsetvn): except Exception: # unimplemented curve type hascurve = True if hascurve: - param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Mesh") - tol = param.GetFloat("MaxDeviationExport",0.1) + tol = params.get_param("MaxDeviationExport",path="Mod/Mesh") mesh = Mesh.Mesh() mesh.addFacets(shape.getFaces(tol)) FreeCAD.Console.PrintWarning(translate("Arch","Found a shape containing curves, triangulating")+"\n") diff --git a/src/Mod/Arch/importSHP.py b/src/Mod/Arch/importSHP.py index 6878f50682..4fe6c88eb3 100644 --- a/src/Mod/Arch/importSHP.py +++ b/src/Mod/Arch/importSHP.py @@ -141,9 +141,7 @@ def checkShapeFileLibrary(): FreeCAD.Console.PrintError(translate("Arch","Error: Unable to download from:")+" "+url+"\n") return False b = u.read() - p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro") - fp = p.GetString("MacroPath",os.path.join(FreeCAD.getUserAppDataDir(),"Macros")) - fp = os.path.join(fp,"shapefile.py") + fp = os.path.join(FreeCAD.getUserMacroDir(True),"shapefile.py") f = pythonopen(fp,"wb") f.write(b) f.close() diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 50f32c4bc6..d54960f138 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -379,8 +379,7 @@ if App.GuiUp: from draftobjects.layer import (Layer, _VisGroup) -from draftmake.make_layer import (make_layer, - makeLayer) +from draftmake.make_layer import make_layer if App.GuiUp: from draftviewproviders.view_layer import (ViewProviderLayer, diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py index c41b38ae30..326b7a5a1c 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -163,10 +163,8 @@ class DraftToolBar: self.pointcallback = None # OBSOLETE BUT STILL USED BY SOME ADDONS AND MACROS - self.paramcolor = utils.rgba_to_argb(params.get_param_view("DefaultShapeLineColor")) - self.color = QtGui.QColor(self.paramcolor) - # ToDo: in setStyleButton() self.facecolor is assigned a QColor - self.facecolor = utils.rgba_to_argb(params.get_param_view("DefaultShapeColor")) + self.color = QtGui.QColor(utils.rgba_to_argb(params.get_param_view("DefaultShapeLineColor"))) + self.facecolor = QtGui.QColor(utils.rgba_to_argb(params.get_param_view("DefaultShapeColor"))) self.linewidth = params.get_param_view("DefaultShapeLineWidth") self.fontsize = params.get_param("textheight") @@ -399,16 +397,22 @@ class DraftToolBar: self.selectButton = self._pushbutton("selectButton", bl, icon='view-select') # update modes from parameters: - self.continueMode = params.get_param("ContinueMode") self.relativeMode = params.get_param("RelativeMode") self.globalMode = params.get_param("GlobalMode") self.fillmode = params.get_param("fillmode") + self.continueMode = params.get_param("ContinueMode") + + # Note: The order of the calls to self._checkbox() below controls + # the position of the checkboxes in the task panel. # update checkboxes with parameters and internal modes: - self.continueCmd = self._checkbox("continueCmd", self.layout, checked=self.continueMode) self.isRelative = self._checkbox("isRelative", self.layout, checked=self.relativeMode) self.isGlobal = self._checkbox("isGlobal", self.layout, checked=self.globalMode) self.hasFill = self._checkbox("hasFill", self.layout, checked=self.fillmode) + self.continueCmd = self._checkbox("continueCmd", self.layout, checked=self.continueMode) + + # update checkboxes without parameters and without internal modes: + self.occOffset = self._checkbox("occOffset", self.layout, checked=False) # update checkboxes with parameters but without internal modes: # self.isCopy is also updated in modUi ("CopyMode") and offsetUi ("OffsetCopyMode") @@ -419,9 +423,6 @@ class DraftToolBar: self.layout, checked=params.get_param("SubelementMode")) - # update checkboxes without parameters and without internal modes: - self.occOffset = self._checkbox("occOffset", self.layout, checked=False) - # spacer spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) diff --git a/src/Mod/Draft/draftguitools/gui_clone.py b/src/Mod/Draft/draftguitools/gui_clone.py index b50262c6f7..262948dfb7 100644 --- a/src/Mod/Draft/draftguitools/gui_clone.py +++ b/src/Mod/Draft/draftguitools/gui_clone.py @@ -1,7 +1,8 @@ # *************************************************************************** -# * (c) 2009, 2010 Yorik van Havre * -# * (c) 2009, 2010 Ken Cline * -# * (c) 2020 Eliud Cabrera Castillo * +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2020 Eliud Cabrera Castillo * +# * Copyright (c) 2023 FreeCAD Project Association * # * * # * This file is part of the FreeCAD CAx development system. * # * * @@ -48,7 +49,7 @@ import Draft_rc import draftguitools.gui_base_original as gui_base_original import draftguitools.gui_tool_utils as gui_tool_utils import draftutils.todo as todo -from draftutils.messages import _msg +from draftutils.messages import _msg, _wrn from draftutils.translate import translate # The module is used to prevent complaints from code checkers (flake8) @@ -65,10 +66,10 @@ class Clone(gui_base_original.Modifier): def GetResources(self): """Set icon, menu and tooltip.""" - return {'Pixmap': 'Draft_Clone', - 'Accel': "C,L", - 'MenuText': QT_TRANSLATE_NOOP("Draft_Clone", "Clone"), - 'ToolTip': QT_TRANSLATE_NOOP("Draft_Clone", "Creates a clone of the selected objects.\nThe resulting clone can be scaled in each of its three directions.")} + return {"Pixmap": "Draft_Clone", + "Accel": "C, L", + "MenuText": QT_TRANSLATE_NOOP("Draft_Clone", "Clone"), + "ToolTip": QT_TRANSLATE_NOOP("Draft_Clone", "Creates a clone of the selected objects.\nThe resulting clone can be scaled in each of its three directions.")} def Activated(self): """Execute when the command is called.""" @@ -84,30 +85,31 @@ class Clone(gui_base_original.Modifier): self.proceed() def proceed(self): - """Proceed with the command if one object was selected.""" - if Gui.Selection.getSelection(): - sels = len(Gui.Selection.getSelection()) - Gui.addModule("Draft") - App.ActiveDocument.openTransaction(translate("Draft", "Clone")) - nonRepeatList = [] - n = 0 - for obj in Gui.Selection.getSelection(): - if obj not in nonRepeatList: - _cmd = "Draft.make_clone" - _cmd += "(" - _cmd += "FreeCAD.ActiveDocument." - _cmd += 'getObject("' + obj.Name + '")' - _cmd += ")" - Gui.doCommand("c" + str(n) + " = " + _cmd) - nonRepeatList.append(obj) - n += 1 - App.ActiveDocument.commitTransaction() - App.ActiveDocument.recompute() - Gui.Selection.clearSelection() + """Proceed with the command if objects were selected.""" + objs = Gui.Selection.getSelection() + if not objs: + self.finish() + return + objs_shape = [obj for obj in objs if hasattr(obj, "Shape")] + if not objs_shape: + _wrn(translate("draft", "Cannot clone object(s) without a Shape, aborting")) + self.finish() + return + elif len(objs_shape) < len(objs): + _wrn(translate("draft", "Cannot clone object(s) without a Shape, skipping them")) - objects = App.ActiveDocument.Objects - for i in range(sels): - Gui.Selection.addSelection(objects[-(1 + i)]) + Gui.addModule("Draft") + App.ActiveDocument.openTransaction(translate("Draft", "Clone")) + for idx, obj in enumerate(objs_shape): + cmd = "Draft.make_clone(FreeCAD.ActiveDocument." + obj.Name + ")" + Gui.doCommand("clone" + str(idx) + " = " + cmd) + App.ActiveDocument.commitTransaction() + App.ActiveDocument.recompute() + + Gui.Selection.clearSelection() + objs = App.ActiveDocument.Objects + for i in range(len(objs_shape)): + Gui.Selection.addSelection(objs[-(1 + i)]) self.finish() def finish(self): diff --git a/src/Mod/Draft/draftguitools/gui_layers.py b/src/Mod/Draft/draftguitools/gui_layers.py index 21b709e33d..800c6c6091 100644 --- a/src/Mod/Draft/draftguitools/gui_layers.py +++ b/src/Mod/Draft/draftguitools/gui_layers.py @@ -37,6 +37,7 @@ import Draft import Draft_rc from draftguitools import gui_base from draftutils import params +from draftutils import utils from draftutils.translate import translate # The module is used to prevent complaints from code checkers (flake8) @@ -76,8 +77,8 @@ class Layer(gui_base.GuiCommandSimplest): self.doc.openTransaction("Create Layer") Gui.addModule("Draft") - Gui.doCommand('_layer_ = Draft.make_layer()') - Gui.doCommand('FreeCAD.ActiveDocument.recompute()') + Gui.doCommand("_layer_ = Draft.make_layer(name=None, line_color=None, shape_color=None, line_width=None, draw_style=None, transparency=None)") + Gui.doCommand("FreeCAD.ActiveDocument.recompute()") self.doc.commitTransaction() @@ -170,7 +171,8 @@ class LayerManager: if not changed: FreeCAD.ActiveDocument.openTransaction("Layers change") changed = True - obj = Draft.make_layer() + obj = Draft.make_layer(name=None, line_color=None, shape_color=None, + line_width=None, draw_style=None, transparency=None) # visibility checked = True if self.model.item(row,0).checkState() == QtCore.Qt.Checked else False @@ -303,7 +305,7 @@ class LayerManager: nameItem = QtGui.QStandardItem(translate("Draft", "New Layer")) widthItem = QtGui.QStandardItem() widthItem.setData(params.get_param_view("DefaultShapeLineWidth"), QtCore.Qt.DisplayRole) - styleItem = QtGui.QStandardItem("Solid") + styleItem = QtGui.QStandardItem(utils.DRAW_STYLES[params.get_param("DefaultDrawStyle")]) lineColorItem = QtGui.QStandardItem() lineColorItem.setData( utils.get_rgba_tuple(params.get_param_view("DefaultShapeLineColor"))[:3], @@ -315,7 +317,10 @@ class LayerManager: QtCore.Qt.UserRole ) transparencyItem = QtGui.QStandardItem() - transparencyItem.setData(0, QtCore.Qt.DisplayRole) + transparencyItem.setData( + params.get_param_view("DefaultShapeTransparency"), + QtCore.Qt.DisplayRole + ) linePrintColorItem = QtGui.QStandardItem() linePrintColorItem.setData( utils.get_rgba_tuple(params.get_param("DefaultPrintColor"))[:3], @@ -428,7 +433,7 @@ if FreeCAD.GuiUp: editor.setMaximum(99) elif index.column() == 3: # Line style editor = QtGui.QComboBox(parent) - editor.addItems(["Solid","Dashed","Dotted","Dashdot"]) + editor.addItems(utils.DRAW_STYLES) elif index.column() == 4: # Line color editor = QtGui.QLineEdit(parent) self.first = True @@ -452,7 +457,7 @@ if FreeCAD.GuiUp: elif index.column() == 2: # Line width editor.setValue(index.data()) elif index.column() == 3: # Line style - editor.setCurrentIndex(["Solid","Dashed","Dotted","Dashdot"].index(index.data())) + editor.setCurrentIndex(utils.DRAW_STYLES.index(index.data())) elif index.column() == 4: # Line color editor.setText(str(index.data(QtCore.Qt.UserRole))) if self.first: @@ -486,7 +491,7 @@ if FreeCAD.GuiUp: elif index.column() == 2: # Line width model.setData(index,editor.value()) elif index.column() == 3: # Line style - model.setData(index,["Solid","Dashed","Dotted","Dashdot"][editor.currentIndex()]) + model.setData(index,utils.DRAW_STYLES[editor.currentIndex()]) elif index.column() == 4: # Line color model.setData(index,eval(editor.text()),QtCore.Qt.UserRole) model.itemFromIndex(index).setIcon(getColorIcon(eval(editor.text()))) diff --git a/src/Mod/Draft/draftmake/make_layer.py b/src/Mod/Draft/draftmake/make_layer.py index d1287d11ed..59020fd912 100644 --- a/src/Mod/Draft/draftmake/make_layer.py +++ b/src/Mod/Draft/draftmake/make_layer.py @@ -30,7 +30,6 @@ # @{ import FreeCAD as App from draftobjects.layer import Layer, LayerContainer -from draftutils import params from draftutils import utils from draftutils.messages import _msg, _err from draftutils.translate import translate @@ -79,53 +78,52 @@ def getLayerContainer(): def make_layer(name=None, - line_color=None, shape_color=None, + line_color=(0.0, 0.0, 0.0), # does not match default DefaultShapeLineColor + shape_color=(0.8, 0.8, 0.8), # matches default DefaultShapeColor line_width=2.0, - draw_style="Solid", transparency=0): + draw_style="Solid", + transparency=0): """Create a Layer object in the active document. - If a layer container named `'LayerContainer'` does not exist, - it is created with this name. + If a layer container named `'LayerContainer'` does not exist, it is created. - A layer controls the view properties of the objects inside the layer, - so all parameters except for `name` only apply if the graphical interface + A layer controls the view properties of the objects inside the layer. + All parameters except for `name` only apply if the graphical interface is up. + All parameters that control view properties can be set to `None`. Their + value, as set by the view provider (matching the current preferences), is + then not changed. + Parameters ---------- - name: str, optional - It is used to set the layer's `Label` (user editable). - It defaults to `None`, in which case the `Label` - is set to `'Layer'` or to its translation in the current language. + name: str or `None`, optional + It defaults to `None`. + It is used to set the layer's `Label`. If it is `None` the `Label` is + set to `'Layer'` or to its translation in the current language. - line_color: tuple, optional - It defaults to `None`, in which case it uses the value of the parameter - `User parameter:BaseApp/Preferences/View/DefaultShapeLineColor`. - If it is given, it should be a tuple of three - floating point values from 0.0 to 1.0. + line_color: tuple or `None`, optional + It defaults to `(0.0, 0.0, 0.0)`. + If it is given, it should be a tuple of three floating point values + from 0.0 to 1.0. - shape_color: tuple, optional - It defaults to `None`, in which case it uses the value of the parameter - `User parameter:BaseApp/Preferences/View/DefaultShapeColor`. - If it is given, it should be a tuple of three - floating point values from 0.0 to 1.0. + shape_color: tuple or `None`, optional + It defaults to `(0.8, 0.8, 0.8)`. + If it is given, it should be a tuple of three floating point values + from 0.0 to 1.0. - line_width: float, optional + line_width: float or `None`, optional It defaults to 2.0. - It determines the width of the edges of the objects contained - in the layer. + It determines the width of the edges of the objects contained in the layer. - draw_style: str, optional + draw_style: str or `None`, optional It defaults to `'Solid'`. - It determines the style of the edges of the objects contained - in the layer. - If it is given, it should be 'Solid', 'Dashed', 'Dotted', - or 'Dashdot'. + It determines the style of the edges of the objects contained in the layer. + If it is given, it should be 'Solid', 'Dashed', 'Dotted' or 'Dashdot'. - transparency: int, optional + transparency: int or `None`, optional It defaults to 0. - It should be an integer value from 0 (completely opaque) - to 100 (completely transparent). + It should be an integer from 0 to 100. Return ------ @@ -139,15 +137,13 @@ def make_layer(name=None, If there is a problem it will return `None`. """ _name = "make_layer" - utils.print_header(_name, translate("draft","Layer")) found, doc = utils.find_doc(App.activeDocument()) if not found: _err(translate("draft","No active document. Aborting.")) return None - if name: - _msg("name: {}".format(name)) + if name is not None: try: utils.type_check([(name, str)], name=_name) except TypeError: @@ -156,8 +152,7 @@ def make_layer(name=None, else: name = translate("draft", "Layer") - if line_color: - _msg("line_color: {}".format(line_color)) + if line_color is not None: try: utils.type_check([(line_color, tuple)], name=_name) except TypeError: @@ -167,14 +162,8 @@ def make_layer(name=None, if not all(isinstance(color, (int, float)) for color in line_color): _err(translate("draft","Wrong input: must be a tuple of three floats 0.0 to 1.0.")) return None - else: - c = params.get_param_view("DefaultShapeLineColor") - line_color = (((c >> 24) & 0xFF) / 255, - ((c >> 16) & 0xFF) / 255, - ((c >> 8) & 0xFF) / 255) - if shape_color: - _msg("shape_color: {}".format(shape_color)) + if shape_color is not None: try: utils.type_check([(shape_color, tuple)], name=_name) except TypeError: @@ -184,38 +173,33 @@ def make_layer(name=None, if not all(isinstance(color, (int, float)) for color in shape_color): _err(translate("draft","Wrong input: must be a tuple of three floats 0.0 to 1.0.")) return None - else: - c = params.get_param_view("DefaultShapeColor") - shape_color = (((c >> 24) & 0xFF) / 255, - ((c >> 16) & 0xFF) / 255, - ((c >> 8) & 0xFF) / 255) - _msg("line_width: {}".format(line_width)) - try: - utils.type_check([(line_width, (int, float))], name=_name) - line_width = float(abs(line_width)) - except TypeError: - _err(translate("draft","Wrong input: must be a number.")) - return None + if line_width is not None: + try: + utils.type_check([(line_width, (int, float))], name=_name) + line_width = float(abs(line_width)) + except TypeError: + _err(translate("draft","Wrong input: must be a number.")) + return None - _msg("draw_style: {}".format(draw_style)) - try: - utils.type_check([(draw_style, str)], name=_name) - except TypeError: - _err(translate("draft","Wrong input: must be 'Solid', 'Dashed', 'Dotted', or 'Dashdot'.")) - return None + if draw_style is not None: + try: + utils.type_check([(draw_style, str)], name=_name) + except TypeError: + _err(translate("draft","Wrong input: must be 'Solid', 'Dashed', 'Dotted', or 'Dashdot'.")) + return None - if draw_style not in ('Solid', 'Dashed', 'Dotted', 'Dashdot'): - _err(translate("draft","Wrong input: must be 'Solid', 'Dashed', 'Dotted', or 'Dashdot'.")) - return None + if draw_style not in ('Solid', 'Dashed', 'Dotted', 'Dashdot'): + _err(translate("draft","Wrong input: must be 'Solid', 'Dashed', 'Dotted', or 'Dashdot'.")) + return None - _msg("transparency: {}".format(transparency)) - try: - utils.type_check([(transparency, (int, float))], name=_name) - transparency = int(abs(transparency)) - except TypeError: - _err(translate("draft","Wrong input: must be a number between 0 and 100.")) - return None + if transparency is not None: + try: + utils.type_check([(transparency, (int, float))], name=_name) + transparency = int(abs(transparency)) + except TypeError: + _err(translate("draft","Wrong input: must be a number between 0 and 100.")) + return None new_obj = doc.addObject("App::FeaturePython", "Layer") Layer(new_obj) @@ -224,32 +208,20 @@ def make_layer(name=None, if App.GuiUp: ViewProviderLayer(new_obj.ViewObject) - - new_obj.ViewObject.LineColor = line_color - new_obj.ViewObject.ShapeColor = shape_color - new_obj.ViewObject.LineWidth = line_width - new_obj.ViewObject.DrawStyle = draw_style - new_obj.ViewObject.Transparency = transparency + if line_color is not None: + new_obj.ViewObject.LineColor = line_color + if shape_color is not None: + new_obj.ViewObject.ShapeColor = shape_color + if line_width is not None: + new_obj.ViewObject.LineWidth = line_width + if draw_style is not None: + new_obj.ViewObject.DrawStyle = draw_style + if transparency is not None: + new_obj.ViewObject.Transparency = transparency container = get_layer_container() container.addObject(new_obj) return new_obj - -def makeLayer(name=None, linecolor=None, drawstyle=None, - shapecolor=None, transparency=None): - """Create a Layer. DEPRECATED. Use 'make_layer'.""" - utils.use_instead("make_layer") - - if not drawstyle: - drawstyle = "Solid" - - if not transparency: - transparency = 0 - - return make_layer(name, - line_color=linecolor, shape_color=shapecolor, - draw_style=drawstyle, transparency=transparency) - ## @} diff --git a/src/Mod/Draft/drafttaskpanels/task_scale.py b/src/Mod/Draft/drafttaskpanels/task_scale.py index e23ec7a105..7afa3723d0 100644 --- a/src/Mod/Draft/drafttaskpanels/task_scale.py +++ b/src/Mod/Draft/drafttaskpanels/task_scale.py @@ -46,7 +46,7 @@ class ScaleTaskPanel: """The task panel for the Draft Scale tool.""" def __init__(self): - decimals = params.get_param("Decimals", path="Units") + decimals = max(6, params.get_param("Decimals", path="Units")) self.sourceCmd = None self.form = QtGui.QWidget() self.form.setWindowIcon(QtGui.QIcon(":/icons/Draft_Scale.svg")) diff --git a/src/Mod/Draft/drafttaskpanels/task_shapestring.py b/src/Mod/Draft/drafttaskpanels/task_shapestring.py index 2a18768b62..b98e3846b3 100644 --- a/src/Mod/Draft/drafttaskpanels/task_shapestring.py +++ b/src/Mod/Draft/drafttaskpanels/task_shapestring.py @@ -149,10 +149,9 @@ class ShapeStringTaskPanelCmd(ShapeStringTaskPanel): def createObject(self): """Create object in the current document.""" - dquote = '"' - String = self.form.leString.text() - String = dquote + String.replace(dquote, '\\"') + dquote - FFile = dquote + str(self.fileSpec) + dquote + String = self.form.leString.text().replace('\\', '\\\\').replace('"', '\\"') + String = '"' + String + '"' + FFile = '"' + str(self.fileSpec) + '"' Size = str(App.Units.Quantity(self.form.sbHeight.text()).Value) Tracking = str(0.0) @@ -163,13 +162,13 @@ class ShapeStringTaskPanelCmd(ShapeStringTaskPanel): try: qr, sup, points, fil = self.sourceCmd.getStrings() Gui.addModule("Draft") - self.sourceCmd.commit(translate("draft", "Create ShapeString"), - ['ss=Draft.make_shapestring(String=' + String + ', FontFile=' + FFile + ', Size=' + Size + ', Tracking=' + Tracking + ')', - 'plm=FreeCAD.Placement()', - 'plm.Base=' + toString(ssBase), - 'plm.Rotation.Q=' + qr, - 'ss.Placement=plm', - 'ss.Support=' + sup, + self.sourceCmd.commit(translate('draft', 'Create ShapeString'), + ['ss = Draft.make_shapestring(String=' + String + ', FontFile=' + FFile + ', Size=' + Size + ', Tracking=' + Tracking + ')', + 'plm = FreeCAD.Placement()', + 'plm.Base = ' + toString(ssBase), + 'plm.Rotation.Q = ' + qr, + 'ss.Placement = plm', + 'ss.Support = ' + sup, 'Draft.autogroup(ss)', 'FreeCAD.ActiveDocument.recompute()']) except Exception: diff --git a/src/Mod/Draft/drafttests/draft_test_objects.py b/src/Mod/Draft/drafttests/draft_test_objects.py index 1544b7089a..e81c7730e1 100644 --- a/src/Mod/Draft/drafttests/draft_test_objects.py +++ b/src/Mod/Draft/drafttests/draft_test_objects.py @@ -586,6 +586,7 @@ def _create_objects(doc=None, line_color=(0.33, 0.0, 0.49), shape_color=(0.56, 0.89, 0.56), line_width=4, + draw_style="Solid", transparency=50) box = doc.addObject("Part::Box", "Box") box.Length = 200 diff --git a/src/Mod/Draft/draftutils/init_draft_statusbar.py b/src/Mod/Draft/draftutils/init_draft_statusbar.py index 7b5f4b3453..7f1148c87b 100644 --- a/src/Mod/Draft/draftutils/init_draft_statusbar.py +++ b/src/Mod/Draft/draftutils/init_draft_statusbar.py @@ -78,12 +78,12 @@ def get_scales(unit_system = 0): if unit_system == 0: scale_units_system = params.get_param("UserSchema", path="Units") - if scale_units_system in [0, 1, 4, 6]: - return draft_scales_metrics - elif scale_units_system in [2, 3, 5]: + if scale_units_system in [2, 3, 5]: return draft_scales_arch_imperial elif scale_units_system in [7]: return draft_scales_eng_imperial + else: + return draft_scales_metrics elif unit_system == 1: return draft_scales_metrics elif unit_system == 2: diff --git a/src/Mod/Draft/draftutils/params.py b/src/Mod/Draft/draftutils/params.py index fce3a67fc9..09c47218a6 100644 --- a/src/Mod/Draft/draftutils/params.py +++ b/src/Mod/Draft/draftutils/params.py @@ -393,8 +393,91 @@ def _get_param_dictionary(): # Arch parameters that are not in the preferences: param_dict["Mod/Arch"] = { + "ClaimHosted": ("bool", True), + "CustomIfcSchema": ("string", ""), # importIFClegacy.py + "createIfcGroups": ("bool", False), # importIFClegacy.py + "DoorHeight": ("float", 2100.0), + "DoorPreset": ("int", 5), + "DoorSill": ("float", 0.0), + "DoorWidth": ("float", 1000.0), + "FreeLinking": ("bool", False), + "forceIfcPythonParser": ("bool", False), # importIFClegacy.py + "getStandardType": ("bool", False), + "ifcAggregateWindows": ("bool", False), # importIFClegacy.py + "ifcAsMesh": ("string", ""), # importIFClegacy.py + "IfcExportList": ("bool", False), # importIFClegacy.py + "ifcImportLayer": ("bool", True), + "ifcJoinSolids": ("bool", False), # importIFClegacy.py + "ifcMergeProfiles": ("bool", False), + "IfcScalingFactor": ("float", 1.0), # importIFClegacy.py + "ifcSeparatePlacements": ("bool", False), # importIFClegacy.py + "MultiMaterialColumnWidth0": ("int", 60), + "MultiMaterialColumnWidth1": ("int", 60), + "PanelLength": ("float", 1000.0), + "PanelThickness": ("float", 10.0), + "PanelWidth": ("float", 1000.0), + "PrecastBase": ("float", 0.0), + "PrecastChamfer": ("float", 0.0), + "PrecastDentHeight": ("float", 0.0), + "PrecastDentLength": ("float", 0.0), + "PrecastDentWidth": ("float", 0.0), + "PrecastDownLength": ("float", 0.0), + "PrecastGrooveDepth": ("float", 0.0), + "PrecastGrooveHeight": ("float", 0.0), + "PrecastGrooveSpacing": ("float", 0.0), + "PrecastHoleMajor": ("float", 0.0), + "PrecastHoleMinor": ("float", 0.0), + "PrecastHoleSpacing": ("float", 0.0), + "PrecastRiser": ("float", 0.0), + "PrecastTread": ("float", 0.0), + "ScheduleColumnWidth0": ("int", 100), + "ScheduleColumnWidth1": ("int", 100), + "ScheduleColumnWidth2": ("int", 50), + "ScheduleColumnWidth3": ("int", 100), + "ScheduleDialogHeight": ("int", 200), + "ScheduleDialogWidth": ("int", 300), + "StructureHeight": ("float", 1000.0), + "StructureLength": ("float", 100.0), + "StructurePreset": ("string", ""), + "StructureWidth": ("float", 100.0), + "swallowAdditions": ("bool", True), + "swallowSubtractions": ("bool", True), + "WallAlignment": ("int", 0), + "WallHeight": ("float", 3000.0), + "WallWidth": ("float", 200.0), + "WindowH1": ("float", 50.0), + "WindowH2": ("float", 50.0), + "WindowH3": ("float", 50.0), + "WindowHeight": ("float", 1000.0), + "WindowO1": ("float", 0.0), + "WindowO2": ("float", 50.0), + "WindowPreset": ("int", 0), + "WindowSill": ("float", 0.0), + "WindowW1": ("float", 100.0), + "WindowW2": ("float", 50.0), + "WindowWidth": ("float", 1000.0), + } + # For the Mod/Mesh parameters we do not check the preferences: + param_dict["Mod/Mesh"] = { + "MaxDeviationExport": ("float", 0.1), + } + # For the Mod/TechDraw/PAT parameters we do not check the preferences: + param_dict["Mod/TechDraw/PAT"] = { + "FilePattern": ("string", ""), + "NamePattern": ("string", "Diamant"), + } + + # For the General parameters we do not check the preferences: + param_dict["General"] = { + "ToolbarIconSize": ("int", 24), + } + + # For the Units parameters we do not check the preferences: + param_dict["Units"] = { + "Decimals": ("int", 2), + "UserSchema": ("int", 0), } # For the View parameters we do not check the preferences: @@ -414,23 +497,6 @@ def _get_param_dictionary(): "NewDocumentCameraScale": ("float", 100.0), } - # For the General parameters we do not check the preferences: - param_dict["General"] = { - "ToolbarIconSize": ("int", 24), - } - - # For the Units parameters we do not check the preferences: - param_dict["Units"] = { - "Decimals": ("int", 2), - "UserSchema": ("int", 0), - } - - # For the Mod/TechDraw/PAT parameters we do not check the preferences: - param_dict["Mod/TechDraw/PAT"] = { - "FilePattern": ("string", ""), - "NamePattern": ("string", "Diamant"), - } - # Preferences ui files are stored in resource files. # For the Draft Workbench: /Mod/Draft/Draft_rc.py diff --git a/src/Mod/Draft/draftviewproviders/view_layer.py b/src/Mod/Draft/draftviewproviders/view_layer.py index 2160d02475..65aa23f661 100644 --- a/src/Mod/Draft/draftviewproviders/view_layer.py +++ b/src/Mod/Draft/draftviewproviders/view_layer.py @@ -557,7 +557,8 @@ class ViewProviderLayerContainer: doc = App.ActiveDocument doc.openTransaction(translate("draft", "Add new layer")) - Draft.make_layer() + Draft.make_layer(name=None, line_color=None, shape_color=None, + line_width=None, draw_style=None, transparency=None) doc.recompute() doc.commitTransaction() diff --git a/src/Mod/Fem/App/FemConstraint.cpp b/src/Mod/Fem/App/FemConstraint.cpp index 16a61d26fa..fddf496146 100644 --- a/src/Mod/Fem/App/FemConstraint.cpp +++ b/src/Mod/Fem/App/FemConstraint.cpp @@ -25,12 +25,14 @@ #ifndef _PreComp_ #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -39,6 +41,7 @@ #include #include #include +#include #include #include #include //OvG: Required for log10 @@ -109,8 +112,8 @@ App::DocumentObjectExecReturn* Constraint::execute() // OvG: Provide the ability to determine how big to draw constraint arrows etc. int Constraint::calcDrawScaleFactor(double lparam) const { - return ((int)round(log(lparam) * log(lparam) * log(lparam) / 10) > 1) - ? ((int)round(log(lparam) * log(lparam) * log(lparam) / 10)) + return (static_cast(round(log(lparam) * log(lparam) * log(lparam) / 10)) > 1) + ? (static_cast(round(log(lparam) * log(lparam) * log(lparam) / 10))) : 1; } @@ -123,7 +126,8 @@ int Constraint::calcDrawScaleFactor() const { return 1; } -#define CONSTRAINTSTEPLIMIT 50 + +constexpr int CONSTRAINTSTEPLIMIT = 50; void Constraint::onChanged(const App::Property* prop) { @@ -221,11 +225,11 @@ bool Constraint::getPoints(std::vector& points, // OvG: Increase 10 units distance proportionately to l for larger objects. if (l >= 30) { *scale = this->calcDrawScaleFactor(l); // OvG: setup draw scale for constraint - steps = (int)round(l / (10 * (*scale))); + steps = static_cast(round(l / (10 * (*scale)))); steps = steps < 3 ? 3 : steps; } else if (l >= 20) { - steps = (int)round(l / 10); + steps = static_cast(round(l / 10)); *scale = this->calcDrawScaleFactor(); // OvG: setup draw scale for constraint } else { @@ -309,11 +313,11 @@ bool Constraint::getPoints(std::vector& points, int stepsv; if (lv >= 30) { *scale = this->calcDrawScaleFactor(lv, lu); // OvG: setup draw scale for constraint - stepsv = (int)round(lv / (10 * (*scale))); + stepsv = static_cast(round(lv / (10 * (*scale)))); stepsv = stepsv < 3 ? 3 : stepsv; } else if (lv >= 20.0) { - stepsv = (int)round(lv / 10); + stepsv = static_cast(round(lv / 10)); *scale = this->calcDrawScaleFactor(); // OvG: setup draw scale for constraint } else { @@ -329,11 +333,11 @@ bool Constraint::getPoints(std::vector& points, // OvG: Increase 10 units distance proportionately to lu for larger objects. if (lu >= 30) { *scale = this->calcDrawScaleFactor(lv, lu); // OvG: setup draw scale for constraint - stepsu = (int)round(lu / (10 * (*scale))); + stepsu = static_cast(round(lu / (10 * (*scale)))); stepsu = stepsu < 3 ? 3 : stepsu; } else if (lu >= 20.0) { - stepsu = (int)round(lu / 10); + stepsu = static_cast(round(lu / 10)); *scale = this->calcDrawScaleFactor(); // OvG: setup draw scale for constraint } else { @@ -345,19 +349,43 @@ bool Constraint::getPoints(std::vector& points, stepsu = stepsu > CONSTRAINTSTEPLIMIT ? CONSTRAINTSTEPLIMIT : stepsu; double stepv = (vlp - vfp) / stepsv; double stepu = (ulp - ufp) / stepsu; + // Create points and normals + auto fillPointsAndNormals = [&](Standard_Real u, Standard_Real v) { + gp_Pnt p = surface.Value(u, v); + BRepClass_FaceClassifier classifier(face, p, Precision::Confusion()); + if (classifier.State() != TopAbs_OUT) { + points.emplace_back(p.X(), p.Y(), p.Z()); + props.Normal(u, v, center, normal); + normal.Normalize(); + normals.emplace_back(normal.X(), normal.Y(), normal.Z()); + } + }; + + size_t prevSize = points.size(); for (int i = 0; i < stepsv + 1; i++) { for (int j = 0; j < stepsu + 1; j++) { double v = vfp + i * stepv; double u = ufp + j * stepu; - gp_Pnt p = surface.Value(u, v); - BRepClass_FaceClassifier classifier(face, p, Precision::Confusion()); - if (classifier.State() != TopAbs_OUT) { - points.emplace_back(p.X(), p.Y(), p.Z()); - props.Normal(u, v, center, normal); - normal.Normalize(); - normals.emplace_back(normal.X(), normal.Y(), normal.Z()); - } + fillPointsAndNormals(u, v); + } + } + + // it could happen that on a trimmed surface the steps on the iso-curves + // are outside the surface, so no points are added. + // In that case use points on the outer wire. + // https://github.com/FreeCAD/FreeCAD/issues/6073 + if (prevSize == points.size()) { + BRepAdaptor_CompCurve compCurve(BRepTools::OuterWire(face), Standard_True); + GProp_GProps linProps; + BRepGProp::LinearProperties(compCurve.Wire(), linProps); + double outWireLength = linProps.Mass(); + int stepWire = stepsu + stepsv; + ShapeAnalysis_Surface surfAnalysis(surface.Surface().Surface()); + for (int i = 0; i < stepWire; ++i) { + gp_Pnt p = compCurve.Value(outWireLength * i / stepWire); + gp_Pnt2d pUV = surfAnalysis.ValueOfUV(p, Precision::Confusion()); + fillPointsAndNormals(pUV.X(), pUV.Y()); } } } diff --git a/src/Mod/Fem/App/FemConstraintContact.cpp b/src/Mod/Fem/App/FemConstraintContact.cpp index d49c67c262..f4d4fa5268 100644 --- a/src/Mod/Fem/App/FemConstraintContact.cpp +++ b/src/Mod/Fem/App/FemConstraintContact.cpp @@ -33,9 +33,31 @@ PROPERTY_SOURCE(Fem::ConstraintContact, Fem::Constraint) ConstraintContact::ConstraintContact() { /*Note: Initialise parameters here*/ - ADD_PROPERTY(Slope, (0.0)); - ADD_PROPERTY(Friction, (0.0)); - /* */ + ADD_PROPERTY_TYPE(Slope, + (0.0), + "ConstraintContact", + App::PropertyType(App::Prop_None), + "Contact stiffness"); + ADD_PROPERTY_TYPE(Adjust, + (0.0), + "ConstraintContact", + App::PropertyType(App::Prop_None), + "Node clearance adjustment limit"); + ADD_PROPERTY_TYPE(Friction, + (false), + "ConstraintContact", + App::PropertyType(App::Prop_None), + "Enable friction interaction"); + ADD_PROPERTY_TYPE(FrictionCoefficient, + (0.0), + "ConstraintContact", + App::PropertyType(App::Prop_None), + "Friction coefficient"); + ADD_PROPERTY_TYPE(StickSlope, + (0.0), + "ConstraintContact", + App::PropertyType(App::Prop_None), + "Stick slope"); ADD_PROPERTY_TYPE(Points, (Base::Vector3d()), @@ -47,6 +69,7 @@ ConstraintContact::ConstraintContact() "ConstraintContact", App::PropertyType(App::Prop_ReadOnly | App::Prop_Output), "Normals where symbols are drawn"); + /* */ Points.setValues(std::vector()); Normals.setValues(std::vector()); } @@ -77,3 +100,28 @@ void ConstraintContact::onChanged(const App::Property* prop) } } } + +void ConstraintContact::handleChangedPropertyType(Base::XMLReader& reader, + const char* typeName, + App::Property* prop) +{ + if (prop == &Slope && strcmp(typeName, "App::PropertyFloat") == 0) { + App::PropertyFloat oldSlope; + oldSlope.Restore(reader); + // old slope value stored as MPa/mm equivalent to 1e3 kg/(mm^2*s^2) + Slope.setValue(oldSlope.getValue() * 1000); + + // stick slope internally generated as slope/10 + StickSlope.setValue(Slope.getValue() / 10); + } + else if (prop == &Friction && strcmp(typeName, "App::PropertyFloat") == 0) { + App::PropertyFloat oldFriction; + oldFriction.Restore(reader); + FrictionCoefficient.setValue(oldFriction.getValue()); + + Friction.setValue(oldFriction.getValue() > 0 ? true : false); + } + else { + Constraint::handleChangedPropertyType(reader, typeName, prop); + } +} diff --git a/src/Mod/Fem/App/FemConstraintContact.h b/src/Mod/Fem/App/FemConstraintContact.h index 60c82cfbf9..68b17eef8b 100644 --- a/src/Mod/Fem/App/FemConstraintContact.h +++ b/src/Mod/Fem/App/FemConstraintContact.h @@ -50,8 +50,11 @@ public: * This is only the definitions of the variables ******/ // ex. - App::PropertyFloat Slope; - App::PropertyFloat Friction; + App::PropertyStiffnessDensity Slope; + App::PropertyLength Adjust; + App::PropertyBool Friction; + App::PropertyFloat FrictionCoefficient; + App::PropertyStiffnessDensity StickSlope; // etc /* */ @@ -64,6 +67,9 @@ public: protected: void onChanged(const App::Property* prop) override; + void handleChangedPropertyType(Base::XMLReader& reader, + const char* typeName, + App::Property* prop) override; }; } // namespace Fem diff --git a/src/Mod/Fem/App/FemConstraintDisplacement.cpp b/src/Mod/Fem/App/FemConstraintDisplacement.cpp index 4fcb5f4f01..4ef6497c70 100644 --- a/src/Mod/Fem/App/FemConstraintDisplacement.cpp +++ b/src/Mod/Fem/App/FemConstraintDisplacement.cpp @@ -141,6 +141,9 @@ void ConstraintDisplacement::handleChangedPropertyType(Base::XMLReader& reader, zRotationProperty.Restore(reader); zRotation.setValue(zRotationProperty.getValue()); } + else { + Constraint::handleChangedPropertyType(reader, TypeName, prop); + } } void ConstraintDisplacement::onChanged(const App::Property* prop) diff --git a/src/Mod/Fem/App/FemConstraintForce.cpp b/src/Mod/Fem/App/FemConstraintForce.cpp index 1a54f680aa..79b9d8c746 100644 --- a/src/Mod/Fem/App/FemConstraintForce.cpp +++ b/src/Mod/Fem/App/FemConstraintForce.cpp @@ -80,6 +80,9 @@ void ConstraintForce::handleChangedPropertyType(Base::XMLReader& reader, // e.g. "2.5" must become 2500 to result in 2.5 N Force.setValue(ForceProperty.getValue() * 1000); } + else { + Constraint::handleChangedPropertyType(reader, TypeName, prop); + } } void ConstraintForce::onChanged(const App::Property* prop) diff --git a/src/Mod/Fem/App/FemConstraintInitialTemperature.cpp b/src/Mod/Fem/App/FemConstraintInitialTemperature.cpp index 32b3203c10..cb2af6aa78 100644 --- a/src/Mod/Fem/App/FemConstraintInitialTemperature.cpp +++ b/src/Mod/Fem/App/FemConstraintInitialTemperature.cpp @@ -74,6 +74,9 @@ void ConstraintInitialTemperature::handleChangedPropertyType(Base::XMLReader& re initialTemperatureProperty.Restore(reader); initialTemperature.setValue(initialTemperatureProperty.getValue()); } + else { + Constraint::handleChangedPropertyType(reader, TypeName, prop); + } } void ConstraintInitialTemperature::onChanged(const App::Property* prop) diff --git a/src/Mod/Fem/App/FemConstraintPressure.cpp b/src/Mod/Fem/App/FemConstraintPressure.cpp index e6a06c4efb..911d555056 100644 --- a/src/Mod/Fem/App/FemConstraintPressure.cpp +++ b/src/Mod/Fem/App/FemConstraintPressure.cpp @@ -71,6 +71,9 @@ void ConstraintPressure::handleChangedPropertyType(Base::XMLReader& reader, // therefore we must convert the value with a factor 1000 Pressure.setValue(PressureProperty.getValue() * 1000.0); } + else { + Constraint::handleChangedPropertyType(reader, TypeName, prop); + } } void ConstraintPressure::onChanged(const App::Property* prop) diff --git a/src/Mod/Fem/App/FemConstraintTemperature.cpp b/src/Mod/Fem/App/FemConstraintTemperature.cpp index dc4037482f..6c906333c8 100644 --- a/src/Mod/Fem/App/FemConstraintTemperature.cpp +++ b/src/Mod/Fem/App/FemConstraintTemperature.cpp @@ -86,6 +86,9 @@ void ConstraintTemperature::handleChangedPropertyType(Base::XMLReader& reader, CFluxProperty.Restore(reader); CFlux.setValue(CFluxProperty.getValue()); } + else { + Constraint::handleChangedPropertyType(reader, TypeName, prop); + } } void ConstraintTemperature::onChanged(const App::Property* prop) diff --git a/src/Mod/Fem/App/FemConstraintTransform.cpp b/src/Mod/Fem/App/FemConstraintTransform.cpp index 6ee538c62f..53947104b2 100644 --- a/src/Mod/Fem/App/FemConstraintTransform.cpp +++ b/src/Mod/Fem/App/FemConstraintTransform.cpp @@ -110,6 +110,9 @@ void ConstraintTransform::handleChangedPropertyType(Base::XMLReader& reader, Z_rotProperty.Restore(reader); Z_rot.setValue(Z_rotProperty.getValue()); } + else { + Constraint::handleChangedPropertyType(reader, TypeName, prop); + } } void ConstraintTransform::onChanged(const App::Property* prop) diff --git a/src/Mod/Fem/App/FemPostFilter.cpp b/src/Mod/Fem/App/FemPostFilter.cpp index 3b5c600680..1e3a1ca27c 100644 --- a/src/Mod/Fem/App/FemPostFilter.cpp +++ b/src/Mod/Fem/App/FemPostFilter.cpp @@ -217,6 +217,9 @@ void FemPostDataAlongLineFilter::handleChangedPropertyType(Base::XMLReader& read Point2Property.Restore(reader); Point2.setValue(Point2Property.getValue()); } + else { + FemPostFilter::handleChangedPropertyType(reader, TypeName, prop); + } } void FemPostDataAlongLineFilter::onChanged(const Property* prop) diff --git a/src/Mod/Fem/App/PreCompiled.h b/src/Mod/Fem/App/PreCompiled.h index cd51febaf4..240207ea65 100644 --- a/src/Mod/Fem/App/PreCompiled.h +++ b/src/Mod/Fem/App/PreCompiled.h @@ -109,6 +109,7 @@ // Opencascade #include +#include #include #include #include @@ -136,6 +137,7 @@ #include #include #include +#include #include #include #include diff --git a/src/Mod/Fem/Gui/Command.cpp b/src/Mod/Fem/Gui/Command.cpp index 82f82e1a3f..0f5f1d8d1c 100644 --- a/src/Mod/Fem/Gui/Command.cpp +++ b/src/Mod/Fem/Gui/Command.cpp @@ -250,10 +250,19 @@ void CmdFemConstraintContact::activated(int) "App.activeDocument().addObject(\"Fem::ConstraintContact\",\"%s\")", FeatName.c_str()); doCommand(Doc, - "App.activeDocument().%s.Slope = 1000000.00", + "App.activeDocument().%s.Slope = \"1e6 GPa/m\"", FeatName.c_str()); // OvG: set default not equal to 0 doCommand(Doc, - "App.activeDocument().%s.Friction = 0.0", + "App.activeDocument().%s.Adjust = 0.0", + FeatName.c_str()); // OvG: set default equal to 0 + doCommand(Doc, + "App.activeDocument().%s.Friction = False", + FeatName.c_str()); // OvG: set default equal to 0 + doCommand(Doc, + "App.activeDocument().%s.FrictionCoefficient = 0.0", + FeatName.c_str()); // OvG: set default equal to 0 + doCommand(Doc, + "App.activeDocument().%s.StickSlope = \"1e4 GPa/m\"", FeatName.c_str()); // OvG: set default not equal to 0 doCommand(Doc, "App.activeDocument().%s.Scale = 1", diff --git a/src/Mod/Fem/Gui/Resources/ui/ConstraintTie.ui b/src/Mod/Fem/Gui/Resources/ui/ConstraintTie.ui index 309fe91eca..c33c39b6eb 100644 --- a/src/Mod/Fem/Gui/Resources/ui/ConstraintTie.ui +++ b/src/Mod/Fem/Gui/Resources/ui/ConstraintTie.ui @@ -28,31 +28,13 @@ - - QFormLayout::AllNonFixedFieldsGrow - - - - - 0 - 0 - + + + mm - - - 80 - 20 - - - - Qt::LeftToRight - - - 0 mm - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 0.000000000000000 1.000000000000000 @@ -60,9 +42,6 @@ 1000000000.000000000000000 - - mm - 2 @@ -74,7 +53,14 @@ - Tolerance: + Tolerance + + + + + + + Enable Adjust @@ -87,9 +73,9 @@ - Gui::InputField - QLineEdit -
Gui/InputField.h
+ Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
diff --git a/src/Mod/Fem/Gui/TaskFemConstraintContact.cpp b/src/Mod/Fem/Gui/TaskFemConstraintContact.cpp index cc15da6dbb..d515c44438 100644 --- a/src/Mod/Fem/Gui/TaskFemConstraintContact.cpp +++ b/src/Mod/Fem/Gui/TaskFemConstraintContact.cpp @@ -91,14 +91,36 @@ TaskFemConstraintContact::TaskFemConstraintContact(ViewProviderFemConstraintCont std::vector Objects = pcConstraint->References.getValues(); std::vector SubElements = pcConstraint->References.getSubValues(); - double slope = pcConstraint->Slope.getValue(); - double friction = pcConstraint->Friction.getValue(); + + bool friction = pcConstraint->Friction.getValue(); // Fill data into dialog elements - ui->spSlope->setMinimum(1.0); - ui->spSlope->setValue(slope); - ui->spFriction->setValue(friction); + ui->spbSlope->setUnit(pcConstraint->Slope.getUnit()); + ui->spbSlope->setMinimum(0); + ui->spbSlope->setMaximum(FLOAT_MAX); + ui->spbSlope->setValue(pcConstraint->Slope.getQuantityValue()); + ui->spbSlope->bind(pcConstraint->Slope); + ui->spbAdjust->setUnit(pcConstraint->Adjust.getUnit()); + ui->spbAdjust->setMinimum(0); + ui->spbAdjust->setMaximum(FLOAT_MAX); + ui->spbAdjust->setValue(pcConstraint->Adjust.getQuantityValue()); + ui->spbAdjust->bind(pcConstraint->Adjust); + + ui->ckbFriction->setChecked(friction); + + ui->spbFrictionCoeff->setMinimum(0); + ui->spbFrictionCoeff->setMaximum(FLOAT_MAX); + ui->spbFrictionCoeff->setValue(pcConstraint->FrictionCoefficient.getValue()); + ui->spbFrictionCoeff->setEnabled(friction); + ui->spbFrictionCoeff->bind(pcConstraint->FrictionCoefficient); + + ui->spbStickSlope->setUnit(pcConstraint->StickSlope.getUnit()); + ui->spbStickSlope->setMinimum(0); + ui->spbStickSlope->setMaximum(FLOAT_MAX); + ui->spbStickSlope->setValue(pcConstraint->StickSlope.getQuantityValue()); + ui->spbStickSlope->setEnabled(friction); + ui->spbStickSlope->bind(pcConstraint->StickSlope); /* */ ui->lw_referencesMaster->clear(); @@ -136,6 +158,11 @@ TaskFemConstraintContact::TaskFemConstraintContact(ViewProviderFemConstraintCont this, &TaskFemConstraintContact::removeFromSelectionMaster); + connect(ui->ckbFriction, + &QCheckBox::toggled, + this, + &TaskFemConstraintContact::onFrictionChanged); + updateUI(); } @@ -428,6 +455,12 @@ void TaskFemConstraintContact::onReferenceDeletedMaster() TaskFemConstraintContact::removeFromSelectionMaster(); } +void TaskFemConstraintContact::onFrictionChanged(bool state) +{ + ui->spbFrictionCoeff->setEnabled(state); + ui->spbStickSlope->setEnabled(state); +} + const std::string TaskFemConstraintContact::getReferences() const { int rowsSlave = ui->lw_referencesSlave->model()->rowCount(); @@ -443,15 +476,29 @@ const std::string TaskFemConstraintContact::getReferences() const return TaskFemConstraint::getReferences(items); } -/* Note: */ -double TaskFemConstraintContact::get_Slope() const +const std::string TaskFemConstraintContact::getSlope() const { - return ui->spSlope->rawValue(); + return ui->spbSlope->value().getSafeUserString().toStdString(); } -double TaskFemConstraintContact::get_Friction() const +const std::string TaskFemConstraintContact::getAdjust() const { - return ui->spFriction->value(); + return ui->spbAdjust->value().getSafeUserString().toStdString(); +} + +bool TaskFemConstraintContact::getFriction() const +{ + return ui->ckbFriction->isChecked(); +} + +double TaskFemConstraintContact::getFrictionCoeff() const +{ + return ui->spbFrictionCoeff->value(); +} + +const std::string TaskFemConstraintContact::getStickSlope() const +{ + return ui->spbStickSlope->value().getSafeUserString().toStdString(); } void TaskFemConstraintContact::changeEvent(QEvent*) @@ -478,7 +525,7 @@ void TaskDlgFemConstraintContact::open() // a transaction is already open at creation time of the panel if (!Gui::Command::hasPendingCommand()) { QString msg = QObject::tr("Contact constraint"); - Gui::Command::openCommand((const char*)msg.toUtf8()); + Gui::Command::openCommand(static_cast(msg.toUtf8())); ConstraintView->setVisible(true); Gui::Command::runCommand( Gui::Command::Doc, @@ -497,13 +544,25 @@ bool TaskDlgFemConstraintContact::accept() try { Gui::Command::doCommand(Gui::Command::Doc, - "App.ActiveDocument.%s.Slope = %f", + "App.ActiveDocument.%s.Slope = \"%s\"", name.c_str(), - parameterContact->get_Slope()); + parameterContact->getSlope().c_str()); Gui::Command::doCommand(Gui::Command::Doc, - "App.ActiveDocument.%s.Friction = %f", + "App.ActiveDocument.%s.Adjust = \"%s\"", name.c_str(), - parameterContact->get_Friction()); + parameterContact->getAdjust().c_str()); + Gui::Command::doCommand(Gui::Command::Doc, + "App.ActiveDocument.%s.Friction = %s", + name.c_str(), + parameterContact->getFriction() ? "True" : "False"); + Gui::Command::doCommand(Gui::Command::Doc, + "App.ActiveDocument.%s.FrictionCoefficient = %f", + name.c_str(), + parameterContact->getFrictionCoeff()); + Gui::Command::doCommand(Gui::Command::Doc, + "App.ActiveDocument.%s.StickSlope = \"%s\"", + name.c_str(), + parameterContact->getStickSlope().c_str()); std::string scale = parameterContact->getScale(); // OvG: determine modified scale Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.%s.Scale = %s", diff --git a/src/Mod/Fem/Gui/TaskFemConstraintContact.h b/src/Mod/Fem/Gui/TaskFemConstraintContact.h index cc7891a667..b5f78a6ec2 100644 --- a/src/Mod/Fem/Gui/TaskFemConstraintContact.h +++ b/src/Mod/Fem/Gui/TaskFemConstraintContact.h @@ -46,8 +46,11 @@ public: QWidget* parent = nullptr); ~TaskFemConstraintContact() override; const std::string getReferences() const override; - double get_Slope() const; - double get_Friction() const; + const std::string getAdjust() const; + const std::string getSlope() const; + bool getFriction() const; + const std::string getStickSlope() const; + double getFrictionCoeff() const; private Q_SLOTS: void onReferenceDeletedSlave(); @@ -56,12 +59,12 @@ private Q_SLOTS: void removeFromSelectionSlave(); void addToSelectionMaster(); void removeFromSelectionMaster(); + void onFrictionChanged(bool); protected: void changeEvent(QEvent* e) override; private: - // void onSelectionChanged(const Gui::SelectionChanges& msg); void updateUI(); std::unique_ptr ui; }; diff --git a/src/Mod/Fem/Gui/TaskFemConstraintContact.ui b/src/Mod/Fem/Gui/TaskFemConstraintContact.ui index 0d86b655fe..3fca07dde2 100644 --- a/src/Mod/Fem/Gui/TaskFemConstraintContact.ui +++ b/src/Mod/Fem/Gui/TaskFemConstraintContact.ui @@ -135,56 +135,123 @@
+ - - - - - Contact Stiffness - - - - - - - 1.000000000000000 - - - 1000000000.000000000000000 - - - Pa - - - 1000000.000000000000000 - - - - - - - - - - - Friction coefficient - - - - - - - 2 - - - 1.000000000000000 - - - 0.100000000000000 - - - - + + + + 0 + 0 + + + + + + + Parameters + + + + + + Contact Stiffness + + + + + + + 1.000000000000000 + + + 1000000000.000000000000000 + + + GPa/m + + + 100.000000000000000 + + + + + + + Clearance Adjustment + + + + + + + 0.000000000000000 + + + mm + + + 0.000000000000000 + + + 0.100000000000000 + + + + + + + Enable Friction + + + + + + + Friction Coefficient + + + + + + + 2 + + + 0.000000000000000 + + + 0.000000000000000 + + + 0.100000000000000 + + + + + + + Stick Slope + + + + + + + 1.000000000000000 + + + 1000000000.000000000000000 + + + GPa/m + + + 1.000000000000000 + + + + +
lbl_info_2 @@ -194,9 +261,14 @@ - Gui::InputField - QLineEdit -
Gui/InputField.h
+ Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+ + Gui::DoubleSpinBox + QWidget +
Gui/SpinBox.h
diff --git a/src/Mod/Fem/Gui/ViewProviderFemMesh.cpp b/src/Mod/Fem/Gui/ViewProviderFemMesh.cpp index cfc8bf172d..b7829b9521 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemMesh.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemMesh.cpp @@ -197,6 +197,7 @@ ViewProviderFemMesh::ViewProviderFemMesh() LineWidth.setConstraints(&floatRange); ShapeColor.setValue(App::Color(1.0f, 0.7f, 0.0f)); + Transparency.setValue(0); ADD_PROPERTY(BackfaceCulling, (true)); ADD_PROPERTY(ShowInner, (false)); ADD_PROPERTY(MaxFacesShowInner, (50000)); diff --git a/src/Mod/Fem/femexamples/constraint_contact_shell_shell.py b/src/Mod/Fem/femexamples/constraint_contact_shell_shell.py index 3e6464a041..2d2b42af80 100644 --- a/src/Mod/Fem/femexamples/constraint_contact_shell_shell.py +++ b/src/Mod/Fem/femexamples/constraint_contact_shell_shell.py @@ -193,9 +193,8 @@ def setup(doc=None, solvertype="ccxtools"): (lower_tube, "Face1"), (upper_tube, "Face1"), ] - con_contact.Friction = 0.0 - # con_contact.Slope = "1000000.0 kg/(mm*s^2)" # contact stiffness - con_contact.Slope = 1000000.0 # should be 1000000.0 kg/(mm*s^2) + con_contact.Friction = False + con_contact.Slope = "1000000.0 GPa/m" analysis.addObject(con_contact) # mesh diff --git a/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py b/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py index c3efe9b189..d2392d1d2a 100644 --- a/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py +++ b/src/Mod/Fem/femexamples/constraint_contact_solid_solid.py @@ -177,8 +177,8 @@ def setup(doc=None, solvertype="ccxtools"): (geom_obj, "Face7"), # first seams slave face, TODO proof in writer code! (geom_obj, "Face3"), # second seams master face, TODO proof in writer code! ] - con_contact.Friction = 0.0 - con_contact.Slope = 1000000.0 # contact stiffness 1000000.0 kg/(mm*s^2) + con_contact.Friction = False + con_contact.Slope = "1000000.0 GPa/m" analysis.addObject(con_contact) # mesh diff --git a/src/Mod/Fem/femexamples/constraint_selfweight_cantilever.py b/src/Mod/Fem/femexamples/constraint_selfweight_cantilever.py index ee6e3c572a..6473c0b959 100644 --- a/src/Mod/Fem/femexamples/constraint_selfweight_cantilever.py +++ b/src/Mod/Fem/femexamples/constraint_selfweight_cantilever.py @@ -124,7 +124,7 @@ def setup(doc=None, solvertype="ccxtools"): # constraint selfweight con_selfweight = ObjectsFem.makeConstraintSelfWeight(doc, "ConstraintSelfWeight") - con_selfweight.Gravity_z = -1.00 + con_selfweight.GravityDirection = (0, 0, -1) analysis.addObject(con_selfweight) # mesh diff --git a/src/Mod/Fem/femexamples/equation_electrostatics_capacitance_two_balls.py b/src/Mod/Fem/femexamples/equation_electrostatics_capacitance_two_balls.py index 94ae02167f..ed4624da90 100644 --- a/src/Mod/Fem/femexamples/equation_electrostatics_capacitance_two_balls.py +++ b/src/Mod/Fem/femexamples/equation_electrostatics_capacitance_two_balls.py @@ -138,6 +138,7 @@ def setup(doc=None, solvertype="elmer"): con_elect_pot1 = ObjectsFem.makeConstraintElectrostaticPotential(doc, name_pot1) con_elect_pot1.References = [(geom_obj, "Face1")] con_elect_pot1.ElectricInfinity = True + con_elect_pot1.PotentialEnabled = False analysis.addObject(con_elect_pot1) # constraint potential 2nd @@ -146,6 +147,7 @@ def setup(doc=None, solvertype="elmer"): con_elect_pot2.References = [(geom_obj, "Face2")] con_elect_pot2.CapacitanceBody = 1 con_elect_pot2.CapacitanceBodyEnabled = True + con_elect_pot2.PotentialEnabled = False analysis.addObject(con_elect_pot2) # constraint potential 3rd @@ -154,6 +156,7 @@ def setup(doc=None, solvertype="elmer"): con_elect_pot3.References = [(geom_obj, "Face3")] con_elect_pot3.CapacitanceBody = 2 con_elect_pot3.CapacitanceBodyEnabled = True + con_elect_pot3.PotentialEnabled = False analysis.addObject(con_elect_pot3) # mesh diff --git a/src/Mod/Fem/femobjects/constraint_selfweight.py b/src/Mod/Fem/femobjects/constraint_selfweight.py index 843457bd38..6972ad2d9d 100644 --- a/src/Mod/Fem/femobjects/constraint_selfweight.py +++ b/src/Mod/Fem/femobjects/constraint_selfweight.py @@ -29,7 +29,9 @@ __url__ = "https://www.freecad.org" # \ingroup FEM # \brief constraint self weight object +import FreeCAD from . import base_fempythonobject +from femtools import constants class ConstraintSelfWeight(base_fempythonobject.BaseFemPythonObject): @@ -42,33 +44,52 @@ class ConstraintSelfWeight(base_fempythonobject.BaseFemPythonObject): def __init__(self, obj): super(ConstraintSelfWeight, self).__init__(obj) - obj.addProperty( - "App::PropertyFloat", - "Gravity_x", - "Gravity", - "Gravity direction: set the x-component of the normalized gravity vector" - ) - - obj.addProperty( - "App::PropertyFloat", - "Gravity_y", - "Gravity", - "Gravity direction: set the y-component of the normalized gravity vector" - ) - - obj.addProperty( - "App::PropertyFloat", - "Gravity_z", - "Gravity", - "Gravity direction: set the z-component of the normalized gravity vector" - ) - - obj.Gravity_x = 0.0 - obj.Gravity_y = 0.0 - obj.Gravity_z = -1.0 + self.addProperty(obj) # https://wiki.freecad.org/Scripted_objects#Property_Type # https://forum.freecad.org/viewtopic.php?f=18&t=13460&start=20#p109709 # https://forum.freecad.org/viewtopic.php?t=25524 # obj.setEditorMode("References", 1) # read only in PropertyEditor, but writeable by Python obj.setEditorMode("References", 2) # do not show in Editor + + def addProperty(self, obj): + obj.addProperty("App::PropertyAcceleration", + "GravityAcceleration", + "Gravity", + "Gravity acceleration") + obj.GravityAcceleration = constants.gravity() + + obj.addProperty("App::PropertyVector", + "GravityDirection", + "Gravity", + "Normalized gravity direction") + obj.GravityDirection = FreeCAD.Vector(0, 0, -1) + + obj.setPropertyStatus("NormalDirection", "Hidden") + + def onDocumentRestored(self, obj): + # migrate old App::PropertyFloat "Gravity_*" if exists + try: + grav_x = obj.getPropertyByName("Gravity_x") + grav_y = obj.getPropertyByName("Gravity_y") + grav_z = obj.getPropertyByName("Gravity_z") + grav = FreeCAD.Vector(grav_x, grav_y, grav_z) + + self.addProperty(obj) + obj.GravityAcceleration = constants.gravity() + obj.GravityAcceleration *= grav.Length + obj.GravityDirection = grav.normalize() + + obj.removeProperty("Gravity_x") + obj.removeProperty("Gravity_y") + obj.removeProperty("Gravity_z") + + return + + except: + return + + def execute(self, obj): + obj.GravityDirection.normalize() + + return False diff --git a/src/Mod/Fem/femobjects/constraint_tie.py b/src/Mod/Fem/femobjects/constraint_tie.py index ad36031163..c983656706 100644 --- a/src/Mod/Fem/femobjects/constraint_tie.py +++ b/src/Mod/Fem/femobjects/constraint_tie.py @@ -46,5 +46,14 @@ class ConstraintTie(base_fempythonobject.BaseFemPythonObject): "App::PropertyLength", "Tolerance", "Geometry", - "set max gap between tied faces" + "Set max gap between tied faces" ) + obj.Tolerance = "0.0 mm" + + obj.addProperty( + "App::PropertyBool", + "Adjust", + "Geometry", + "Adjust connected nodes" + ) + obj.Adjust = False diff --git a/src/Mod/Fem/femsolver/calculix/write_constraint_contact.py b/src/Mod/Fem/femsolver/calculix/write_constraint_contact.py index c8527975f6..a9c0bcc9dd 100644 --- a/src/Mod/Fem/femsolver/calculix/write_constraint_contact.py +++ b/src/Mod/Fem/femsolver/calculix/write_constraint_contact.py @@ -68,20 +68,24 @@ def write_meshdata_constraint(f, femobj, contact_obj, ccxwriter): def write_constraint(f, femobj, contact_obj, ccxwriter): # floats read from ccx should use {:.13G}, see comment in writer module + adjust = "" + if contact_obj.Adjust.Value > 0: + adjust = ", ADJUST={:.13G}".format( + contact_obj.Adjust.getValueAs("mm").Value) f.write( - "*CONTACT PAIR, INTERACTION=INT{},TYPE=SURFACE TO SURFACE\n" - .format(contact_obj.Name) + "*CONTACT PAIR, INTERACTION=INT{}, TYPE=SURFACE TO SURFACE{}\n" + .format(contact_obj.Name, adjust) ) ind_surf = "IND" + contact_obj.Name dep_surf = "DEP" + contact_obj.Name - f.write("{},{}\n".format(dep_surf, ind_surf)) + f.write("{}, {}\n".format(dep_surf, ind_surf)) f.write("*SURFACE INTERACTION, NAME=INT{}\n".format(contact_obj.Name)) - f.write("*SURFACE BEHAVIOR,PRESSURE-OVERCLOSURE=LINEAR\n") - slope = contact_obj.Slope + f.write("*SURFACE BEHAVIOR, PRESSURE-OVERCLOSURE=LINEAR\n") + slope = contact_obj.Slope.getValueAs("MPa/mm").Value f.write("{:.13G}\n".format(slope)) - friction = contact_obj.Friction - if friction > 0: - f.write("*FRICTION \n") - stick = (slope / 10.0) + if contact_obj.Friction: + f.write("*FRICTION\n") + friction = contact_obj.FrictionCoefficient + stick = contact_obj.StickSlope.getValueAs("MPa/mm").Value f.write("{:.13G}, {:.13G}\n".format(friction, stick)) diff --git a/src/Mod/Fem/femsolver/calculix/write_constraint_selfweight.py b/src/Mod/Fem/femsolver/calculix/write_constraint_selfweight.py index a4eeeb495a..ee366df41c 100644 --- a/src/Mod/Fem/femsolver/calculix/write_constraint_selfweight.py +++ b/src/Mod/Fem/femsolver/calculix/write_constraint_selfweight.py @@ -52,10 +52,10 @@ def write_constraint(f, femobj, selwei_obj, ccxwriter): "{},GRAV,{:.13G},{:.13G},{:.13G},{:.13G}\n" .format( ccxwriter.ccx_eall, - ccxwriter.gravity, # actual magnitude of gravity vector - selwei_obj.Gravity_x, # coordinate x of normalized gravity vector - selwei_obj.Gravity_y, # y - selwei_obj.Gravity_z # z + selwei_obj.GravityAcceleration.getValueAs("mm/s^2").Value, # actual magnitude of gravity vector + selwei_obj.GravityDirection.x, # coordinate x of normalized gravity vector + selwei_obj.GravityDirection.y, # y + selwei_obj.GravityDirection.z # z ) ) f.write("\n") diff --git a/src/Mod/Fem/femsolver/calculix/write_constraint_tie.py b/src/Mod/Fem/femsolver/calculix/write_constraint_tie.py index 8817fa9954..c75ab90be3 100644 --- a/src/Mod/Fem/femsolver/calculix/write_constraint_tie.py +++ b/src/Mod/Fem/femsolver/calculix/write_constraint_tie.py @@ -70,10 +70,13 @@ def write_constraint(f, femobj, tie_obj, ccxwriter): # floats read from ccx should use {:.13G}, see comment in writer module tolerance = tie_obj.Tolerance.getValueAs("mm").Value + adjust = "" + if not tie_obj.Adjust: + adjust = ", ADJUST=NO" f.write( - "*TIE, POSITION TOLERANCE={:.13G}, ADJUST=NO, NAME=TIE{}\n" - .format(tolerance, tie_obj.Name) + "*TIE, POSITION TOLERANCE={:.13G}{}, NAME=TIE{}\n" + .format(tolerance, adjust, tie_obj.Name) ) ind_surf = "TIE_IND{}".format(tie_obj.Name) dep_surf = "TIE_DEP{}".format(tie_obj.Name) - f.write("{},{}\n".format(dep_surf, ind_surf)) + f.write("{}, {}\n".format(dep_surf, ind_surf)) diff --git a/src/Mod/Fem/femsolver/elmer/equations/deformation_writer.py b/src/Mod/Fem/femsolver/elmer/equations/deformation_writer.py index 080976218d..f76dd39fe0 100644 --- a/src/Mod/Fem/femsolver/elmer/equations/deformation_writer.py +++ b/src/Mod/Fem/femsolver/elmer/equations/deformation_writer.py @@ -148,7 +148,7 @@ class DeformationWriter: obj = self.write.getSingleMember("Fem::ConstraintSelfWeight") if obj is not None: for name in bodies: - gravity = self.write.convert(self.write.constsdef["Gravity"], "L/T^2") + gravity = self.write.convert(obj.GravityAcceleration.toStr(), "L/T^2") if self.write.getBodyMaterial(name) is None: raise general_writer.WriteError( "The body {} is not referenced in any material.\n\n".format(name) @@ -164,9 +164,9 @@ class DeformationWriter: dimension = "M/L^2" density = self.write.convert(densityQuantity, dimension) - force1 = gravity * obj.Gravity_x * density - force2 = gravity * obj.Gravity_y * density - force3 = gravity * obj.Gravity_z * density + force1 = gravity * obj.GravityDirection.x * density + force2 = gravity * obj.GravityDirection.y * density + force3 = gravity * obj.GravityDirection.z * density self.write.bodyForce(name, "Stress Bodyforce 1", force1) self.write.bodyForce(name, "Stress Bodyforce 2", force2) self.write.bodyForce(name, "Stress Bodyforce 3", force3) diff --git a/src/Mod/Fem/femsolver/elmer/equations/elasticity_writer.py b/src/Mod/Fem/femsolver/elmer/equations/elasticity_writer.py index bc71a67368..4424e903e7 100644 --- a/src/Mod/Fem/femsolver/elmer/equations/elasticity_writer.py +++ b/src/Mod/Fem/femsolver/elmer/equations/elasticity_writer.py @@ -364,7 +364,7 @@ class ElasticityWriter: obj = self.write.getSingleMember("Fem::ConstraintSelfWeight") if obj is not None: for name in bodies: - gravity = self.write.convert(self.write.constsdef["Gravity"], "L/T^2") + gravity = self.write.convert(obj.GravityAcceleration.toStr(), "L/T^2") if self.write.getBodyMaterial(name) is None: raise general_writer.WriteError( "The body {} is not referenced in any material.\n\n".format(name) @@ -380,9 +380,9 @@ class ElasticityWriter: dimension = "M/L^2" density = self.write.convert(densityQuantity, dimension) - force1 = gravity * obj.Gravity_x * density - force2 = gravity * obj.Gravity_y * density - force3 = gravity * obj.Gravity_z * density + force1 = gravity * obj.GravityDirection.x * density + force2 = gravity * obj.GravityDirection.y * density + force3 = gravity * obj.GravityDirection.z * density self.write.bodyForce(name, "Stress Bodyforce 1", force1) self.write.bodyForce(name, "Stress Bodyforce 2", force2) self.write.bodyForce(name, "Stress Bodyforce 3", force3) diff --git a/src/Mod/Fem/femtaskpanels/task_constraint_tie.py b/src/Mod/Fem/femtaskpanels/task_constraint_tie.py index 05b1369c13..f2e97ede3f 100644 --- a/src/Mod/Fem/femtaskpanels/task_constraint_tie.py +++ b/src/Mod/Fem/femtaskpanels/task_constraint_tie.py @@ -52,10 +52,15 @@ class _TaskPanel: FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/ConstraintTie.ui" ) QtCore.QObject.connect( - self.parameterWidget.if_tolerance, + self.parameterWidget.spb_tolerance, QtCore.SIGNAL("valueChanged(Base::Quantity)"), self.tolerance_changed ) + QtCore.QObject.connect( + self.parameterWidget.ckb_adjust, + QtCore.SIGNAL("toggled(bool)"), + self.adjust_changed + ) self.init_parameter_widget() # geometry selection widget @@ -67,7 +72,7 @@ class _TaskPanel: ) # form made from param and selection widget - self.form = [self.parameterWidget, self.selectionWidget] + self.form = [self.selectionWidget, self.parameterWidget] def accept(self): # check values @@ -94,6 +99,7 @@ class _TaskPanel: elif msgBox.clickedButton() == ignoreButton: pass self.obj.Tolerance = self.tolerance + self.obj.Adjust = self.adjust self.obj.References = self.selectionWidget.references self.recompute_and_set_back_all() return True @@ -112,7 +118,12 @@ class _TaskPanel: def init_parameter_widget(self): self.tolerance = self.obj.Tolerance - self.parameterWidget.if_tolerance.setText(self.tolerance.UserString) + self.adjust = self.obj.Adjust + self.parameterWidget.spb_tolerance.setProperty("value", self.tolerance) + self.parameterWidget.ckb_adjust.setChecked(self.adjust) def tolerance_changed(self, base_quantity_value): self.tolerance = base_quantity_value + + def adjust_changed(self, bool_value): + self.adjust = bool_value diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_contact_shell_shell.inp b/src/Mod/Fem/femtest/data/calculix/constraint_contact_shell_shell.inp index 437d5c7feb..4f696b5b7d 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_contact_shell_shell.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_contact_shell_shell.inp @@ -38365,10 +38365,10 @@ Efaces *********************************************************** ** Contact Constraints ** ConstraintContact -*CONTACT PAIR, INTERACTION=INTConstraintContact,TYPE=SURFACE TO SURFACE -DEPConstraintContact,INDConstraintContact +*CONTACT PAIR, INTERACTION=INTConstraintContact, TYPE=SURFACE TO SURFACE +DEPConstraintContact, INDConstraintContact *SURFACE INTERACTION, NAME=INTConstraintContact -*SURFACE BEHAVIOR,PRESSURE-OVERCLOSURE=LINEAR +*SURFACE BEHAVIOR, PRESSURE-OVERCLOSURE=LINEAR 1000000 *********************************************************** diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_contact_solid_solid.inp b/src/Mod/Fem/femtest/data/calculix/constraint_contact_solid_solid.inp index 883a37b530..5d315d93ad 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_contact_solid_solid.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_contact_solid_solid.inp @@ -5572,10 +5572,10 @@ Evolumes *********************************************************** ** Contact Constraints ** ConstraintContact -*CONTACT PAIR, INTERACTION=INTConstraintContact,TYPE=SURFACE TO SURFACE +*CONTACT PAIR, INTERACTION=INTConstraintContact, TYPE=SURFACE TO SURFACE DEPConstraintContact,INDConstraintContact *SURFACE INTERACTION, NAME=INTConstraintContact -*SURFACE BEHAVIOR,PRESSURE-OVERCLOSURE=LINEAR +*SURFACE BEHAVIOR, PRESSURE-OVERCLOSURE=LINEAR 1000000 *********************************************************** diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_selfweight_cantilever.inp b/src/Mod/Fem/femtest/data/calculix/constraint_selfweight_cantilever.inp index 4fda9cf903..1721a498ea 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_selfweight_cantilever.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_selfweight_cantilever.inp @@ -2170,7 +2170,7 @@ ConstraintFixed,3 ** Self weight Constraint ** ConstraintSelfWeight *DLOAD -Eall,GRAV,9806,0,0,-1 +Eall,GRAV,9806.65,0,0,-1 *********************************************************** diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp b/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp index d028a0ba94..a201c5415f 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp @@ -18608,7 +18608,7 @@ Evolumes ** Tie Constraints ** ConstraintTie *TIE, POSITION TOLERANCE=25, ADJUST=NO, NAME=TIEConstraintTie -TIE_DEPConstraintTie,TIE_INDConstraintTie +TIE_DEPConstraintTie, TIE_INDConstraintTie *********************************************************** ** At least one step is needed to run an CalculiX analysis of FreeCAD diff --git a/src/Mod/Import/App/dxf/dxf.cpp b/src/Mod/Import/App/dxf/dxf.cpp index cbde2326a9..5d9920620a 100644 --- a/src/Mod/Import/App/dxf/dxf.cpp +++ b/src/Mod/Import/App/dxf/dxf.cpp @@ -63,6 +63,10 @@ CDxfWrite::CDxfWrite(const char* filepath) return; } m_ofs->imbue(std::locale("C")); + + // use lots of digits to avoid rounding errors + m_ssEntity->setf(std::ios::fixed); + m_ssEntity->precision(9); } CDxfWrite::~CDxfWrite() diff --git a/src/Mod/Import/App/dxf/dxf.h b/src/Mod/Import/App/dxf/dxf.h index d82119df08..45ac21fdec 100644 --- a/src/Mod/Import/App/dxf/dxf.h +++ b/src/Mod/Import/App/dxf/dxf.h @@ -173,7 +173,12 @@ struct LWPolyDataOut std::vector Bulge; point3D Extr; }; -using eDXFGroupCode_t = enum { + + +// "using" for enums is not supported by all platforms +// https://stackoverflow.com/questions/41167119/how-to-fix-a-wsubobject-linkage-warning +enum eDXFGroupCode_t +{ eObjectType = 0, ePrimaryText = 1, eName = 2, @@ -211,7 +216,8 @@ using eDXFGroupCode_t = enum { eYOffset = 10, eZOffset = 20 }; -using eDXFVersion_t = enum { +enum eDXFVersion_t +{ RUnknown, ROlder, R10, @@ -226,7 +232,8 @@ using eDXFVersion_t = enum { R2018, RNewer, }; -using eDimensionType_t = enum { +enum eDimensionType_t +{ eLinear = 0, // Rotated, Horizontal, or Vertical eAligned = 1, eAngular = 2, @@ -431,6 +438,7 @@ class ImportExport CDxfRead private: // Low-level reader members std::ifstream* m_ifs; // TODO: gsl::owner + // https://stackoverflow.com/questions/41167119/how-to-fix-a-wsubobject-linkage-warning eDXFGroupCode_t m_record_type = eObjectType; std::string m_record_data; bool m_not_eof = true; diff --git a/src/Mod/Import/Gui/dxf/ImpExpDxfGui.cpp b/src/Mod/Import/Gui/dxf/ImpExpDxfGui.cpp index a9888d115d..89e04b819b 100644 --- a/src/Mod/Import/Gui/dxf/ImpExpDxfGui.cpp +++ b/src/Mod/Import/Gui/dxf/ImpExpDxfGui.cpp @@ -82,6 +82,7 @@ void ImpExpDxfReadGui::ApplyGuiStyles(Part::Feature* object) view->LineColor.setValue(color); view->PointColor.setValue(color); view->ShapeColor.setValue(color); + view->Transparency.setValue(0); } void ImpExpDxfReadGui::ApplyGuiStyles(App::FeaturePython* object) diff --git a/src/Mod/Material/App/AppMaterial.cpp b/src/Mod/Material/App/AppMaterial.cpp index 9d2471c657..b63547925d 100644 --- a/src/Mod/Material/App/AppMaterial.cpp +++ b/src/Mod/Material/App/AppMaterial.cpp @@ -28,6 +28,8 @@ #include // #include "Model.h" +#include "MaterialFilter.h" + #include "MaterialManagerPy.h" #include "MaterialPy.h" #include "ModelManagerPy.h" @@ -71,5 +73,27 @@ PyMOD_INIT_FUNC(Material) Base::Interpreter().addType(&Materials::ModelPy ::Type, module, "Model"); Base::Interpreter().addType(&Materials::UUIDsPy ::Type, module, "UUIDs"); + + // Initialize types + + Materials::Material ::init(); + Materials::MaterialFilter ::init(); + Materials::MaterialManager ::init(); + Materials::Model ::init(); + Materials::ModelManager ::init(); + Materials::ModelUUIDs ::init(); + + Materials::LibraryBase ::init(); + Materials::MaterialLibrary ::init(); + Materials::ModelLibrary ::init(); + Materials::MaterialExternalLibrary ::init(); + + Materials::ModelProperty ::init(); + Materials::MaterialProperty ::init(); + + Materials::MaterialValue ::init(); + Materials::Material2DArray ::init(); + Materials::Material3DArray ::init(); + PyMOD_Return(module); } diff --git a/src/Mod/Material/App/MaterialConfigLoader.cpp b/src/Mod/Material/App/MaterialConfigLoader.cpp index fe2090f009..b16b14daa8 100644 --- a/src/Mod/Material/App/MaterialConfigLoader.cpp +++ b/src/Mod/Material/App/MaterialConfigLoader.cpp @@ -24,18 +24,17 @@ #include #include #include -#endif - -#include - #include #include #include #include +#include +#include +#endif + #include #include -#include #include "Exceptions.h" diff --git a/src/Mod/Material/App/MaterialLibrary.cpp b/src/Mod/Material/App/MaterialLibrary.cpp index d1c30730f8..87de1c2104 100644 --- a/src/Mod/Material/App/MaterialLibrary.cpp +++ b/src/Mod/Material/App/MaterialLibrary.cpp @@ -22,10 +22,10 @@ #include "PreCompiled.h" #ifndef _PreComp_ #include -#endif - #include #include +#endif + #include @@ -41,7 +41,7 @@ using namespace Materials; /* TRANSLATOR Material::Materials */ -TYPESYSTEM_SOURCE(Materials::MaterialLibrary, LibraryBase) +TYPESYSTEM_SOURCE(Materials::MaterialLibrary, Materials::LibraryBase) MaterialLibrary::MaterialLibrary(const QString& libraryName, const QString& dir, @@ -352,7 +352,7 @@ MaterialLibrary::getMaterialTree(const MaterialFilter* filter) const return materialTree; } -TYPESYSTEM_SOURCE(Materials::MaterialExternalLibrary, MaterialLibrary::MaterialLibrary) +TYPESYSTEM_SOURCE(Materials::MaterialExternalLibrary, Materials::MaterialLibrary) MaterialExternalLibrary::MaterialExternalLibrary(const QString& libraryName, const QString& dir, diff --git a/src/Mod/Material/App/MaterialLoader.cpp b/src/Mod/Material/App/MaterialLoader.cpp index a6ab7a5a8a..fc0e6f14c7 100644 --- a/src/Mod/Material/App/MaterialLoader.cpp +++ b/src/Mod/Material/App/MaterialLoader.cpp @@ -21,16 +21,12 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#include -#endif - #include #include #include #include - -#if QT_VERSION >= QT_VERSION_CHECK(6,0,0) -#include +#include +#include #endif #include @@ -85,7 +81,7 @@ std::shared_ptr> MaterialYamlEntry::readList(const YAML::Node& n QVariant nodeValue; if (isImageList) { nodeValue = QString::fromStdString(it->as()) - .remove(QRegExp(QString::fromStdString("[\r\n]"))); + .remove(QRegularExpression(QString::fromStdString("[\r\n]"))); } else { nodeValue = QString::fromStdString(it->as()); @@ -247,7 +243,7 @@ void MaterialYamlEntry::addToTree( QString::fromStdString((itp->second).as()); if (type == MaterialValue::Image) { propertyValue = - propertyValue.remove(QRegExp(QString::fromStdString("[\r\n]"))); + propertyValue.remove(QRegularExpression(QString::fromStdString("[\r\n]"))); } finalModel->setPhysicalValue(QString::fromStdString(propertyName), propertyValue); @@ -314,7 +310,7 @@ void MaterialYamlEntry::addToTree( QString::fromStdString((itp->second).as()); if (type == MaterialValue::Image) { propertyValue = - propertyValue.remove(QRegExp(QString::fromStdString("[\r\n]"))); + propertyValue.remove(QRegularExpression(QString::fromStdString("[\r\n]"))); } finalModel->setAppearanceValue(QString::fromStdString(propertyName), propertyValue); diff --git a/src/Mod/Material/App/MaterialValue.cpp b/src/Mod/Material/App/MaterialValue.cpp index a9ccc6513e..f27bfed838 100644 --- a/src/Mod/Material/App/MaterialValue.cpp +++ b/src/Mod/Material/App/MaterialValue.cpp @@ -21,11 +21,10 @@ #include "PreCompiled.h" #ifndef _PreComp_ +#include #include #endif -#include - #include #include #include @@ -264,7 +263,7 @@ QString MaterialValue::getYAMLStringMultiLine() const QString yaml; yaml = QString::fromStdString(" |2"); auto list = - getValue().toString().split(QRegExp(QString::fromStdString("[\r\n]")), Qt::SkipEmptyParts); + getValue().toString().split(QRegularExpression(QString::fromStdString("[\r\n]")), Qt::SkipEmptyParts); for (auto& it : list) { yaml += QString::fromStdString("\n ") + it; } diff --git a/src/Mod/Material/App/Materials.cpp b/src/Mod/Material/App/Materials.cpp index a1d7592125..106bf43bdf 100644 --- a/src/Mod/Material/App/Materials.cpp +++ b/src/Mod/Material/App/Materials.cpp @@ -21,10 +21,10 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#endif - #include #include +#endif + #include #include diff --git a/src/Mod/Material/App/Model.cpp b/src/Mod/Material/App/Model.cpp index 2d30bdadc3..19a4ebb832 100644 --- a/src/Mod/Material/App/Model.cpp +++ b/src/Mod/Material/App/Model.cpp @@ -21,9 +21,8 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#endif - #include +#endif #include diff --git a/src/Mod/Material/App/ModelLibrary.cpp b/src/Mod/Material/App/ModelLibrary.cpp index baa244ecdd..40df9b3366 100644 --- a/src/Mod/Material/App/ModelLibrary.cpp +++ b/src/Mod/Material/App/ModelLibrary.cpp @@ -21,9 +21,8 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#endif - #include +#endif #include @@ -55,7 +54,7 @@ QString LibraryBase::getLocalPath(const QString& path) const QString prefix = QString::fromStdString("/") + getName(); if (cleanPath.startsWith(prefix)) { // Remove the library name from the path - filePath += cleanPath.rightRef(cleanPath.length() - prefix.length()); + filePath += cleanPath.right(cleanPath.length() - prefix.length()); } else { filePath += cleanPath; @@ -100,7 +99,7 @@ QString LibraryBase::getRelativePath(const QString& path) const return filePath; } -TYPESYSTEM_SOURCE(Materials::ModelLibrary, LibraryBase) +TYPESYSTEM_SOURCE(Materials::ModelLibrary, Materials::LibraryBase) ModelLibrary::ModelLibrary(const QString& libraryName, const QString& dir, const QString& icon) : LibraryBase(libraryName, dir, icon) diff --git a/src/Mod/Material/App/ModelLoader.cpp b/src/Mod/Material/App/ModelLoader.cpp index 3976f2a28c..a24bea113a 100644 --- a/src/Mod/Material/App/ModelLoader.cpp +++ b/src/Mod/Material/App/ModelLoader.cpp @@ -22,13 +22,13 @@ #include "PreCompiled.h" #ifndef _PreComp_ #include +#include +#include #endif #include #include -#include -#include #include "Model.h" #include "ModelLoader.h" diff --git a/src/Mod/Material/App/PreCompiled.h b/src/Mod/Material/App/PreCompiled.h index c2ca3cd36e..e991f90cda 100644 --- a/src/Mod/Material/App/PreCompiled.h +++ b/src/Mod/Material/App/PreCompiled.h @@ -47,14 +47,26 @@ // STL #include +#include #include +#include #include #include #include // Qt #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #endif //_PreComp_ diff --git a/src/Mod/Material/Gui/Array2D.cpp b/src/Mod/Material/Gui/Array2D.cpp index 359160ac13..234c608dfc 100644 --- a/src/Mod/Material/Gui/Array2D.cpp +++ b/src/Mod/Material/Gui/Array2D.cpp @@ -21,11 +21,10 @@ #include "PreCompiled.h" #ifndef _PreComp_ +#include #include #endif -#include - #include #include diff --git a/src/Mod/Material/Gui/Array3D.cpp b/src/Mod/Material/Gui/Array3D.cpp index 37677a5f62..16cebf5931 100644 --- a/src/Mod/Material/Gui/Array3D.cpp +++ b/src/Mod/Material/Gui/Array3D.cpp @@ -21,12 +21,11 @@ #include "PreCompiled.h" #ifndef _PreComp_ +#include #include #include #endif -#include - #include #include diff --git a/src/Mod/Material/Gui/ArrayDelegate.cpp b/src/Mod/Material/Gui/ArrayDelegate.cpp index 4ad6bffe93..fa06605f19 100644 --- a/src/Mod/Material/Gui/ArrayDelegate.cpp +++ b/src/Mod/Material/Gui/ArrayDelegate.cpp @@ -30,9 +30,8 @@ #include #include #include -#endif - #include +#endif #include #include diff --git a/src/Mod/Material/Gui/ArrayModel.cpp b/src/Mod/Material/Gui/ArrayModel.cpp index c7f581316e..5d1dbdd57d 100644 --- a/src/Mod/Material/Gui/ArrayModel.cpp +++ b/src/Mod/Material/Gui/ArrayModel.cpp @@ -21,10 +21,9 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#endif - #include #include +#endif #include #include diff --git a/src/Mod/Material/Gui/BaseDelegate.cpp b/src/Mod/Material/Gui/BaseDelegate.cpp index d018a80e68..3f75d52fb8 100644 --- a/src/Mod/Material/Gui/BaseDelegate.cpp +++ b/src/Mod/Material/Gui/BaseDelegate.cpp @@ -31,9 +31,8 @@ #include #include #include -#endif - #include +#endif #include #include diff --git a/src/Mod/Material/Gui/ImageEdit.cpp b/src/Mod/Material/Gui/ImageEdit.cpp index 7ed319554c..b0c21b1258 100644 --- a/src/Mod/Material/Gui/ImageEdit.cpp +++ b/src/Mod/Material/Gui/ImageEdit.cpp @@ -21,17 +21,16 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#include -#endif - #include #include #include +#include #include #include #include #include #include +#endif #include #include diff --git a/src/Mod/Material/Gui/ListDelegate.cpp b/src/Mod/Material/Gui/ListDelegate.cpp index 1932cb396f..52f0ca6d2c 100644 --- a/src/Mod/Material/Gui/ListDelegate.cpp +++ b/src/Mod/Material/Gui/ListDelegate.cpp @@ -30,9 +30,8 @@ #include #include #include -#endif - #include +#endif #include #include diff --git a/src/Mod/Material/Gui/ListEdit.cpp b/src/Mod/Material/Gui/ListEdit.cpp index cc1a59bad8..b8df1722a7 100644 --- a/src/Mod/Material/Gui/ListEdit.cpp +++ b/src/Mod/Material/Gui/ListEdit.cpp @@ -21,11 +21,10 @@ #include "PreCompiled.h" #ifndef _PreComp_ +#include #include #endif -#include - #include #include diff --git a/src/Mod/Material/Gui/ListModel.cpp b/src/Mod/Material/Gui/ListModel.cpp index edf56e2b68..e89a6dcadc 100644 --- a/src/Mod/Material/Gui/ListModel.cpp +++ b/src/Mod/Material/Gui/ListModel.cpp @@ -21,9 +21,8 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#endif - #include +#endif #include #include diff --git a/src/Mod/Material/Gui/MaterialDelegate.cpp b/src/Mod/Material/Gui/MaterialDelegate.cpp index 38e67cc5e1..d3787e6d46 100644 --- a/src/Mod/Material/Gui/MaterialDelegate.cpp +++ b/src/Mod/Material/Gui/MaterialDelegate.cpp @@ -31,9 +31,8 @@ #include #include #include -#endif - #include +#endif #include #include diff --git a/src/Mod/Material/Gui/MaterialsEditor.cpp b/src/Mod/Material/Gui/MaterialsEditor.cpp index 3c23ec20f8..d2c3cc2508 100644 --- a/src/Mod/Material/Gui/MaterialsEditor.cpp +++ b/src/Mod/Material/Gui/MaterialsEditor.cpp @@ -31,9 +31,8 @@ #include #include #include -#endif - #include +#endif #include #include diff --git a/src/Mod/Material/Gui/PreCompiled.h b/src/Mod/Material/Gui/PreCompiled.h index 36cb312a6f..9d0d6c1884 100644 --- a/src/Mod/Material/Gui/PreCompiled.h +++ b/src/Mod/Material/Gui/PreCompiled.h @@ -44,6 +44,7 @@ // standard #include #include +#include // STL #include diff --git a/src/Mod/Material/Gui/TextEdit.cpp b/src/Mod/Material/Gui/TextEdit.cpp index 93f46b77c3..95c1558855 100644 --- a/src/Mod/Material/Gui/TextEdit.cpp +++ b/src/Mod/Material/Gui/TextEdit.cpp @@ -21,11 +21,10 @@ #include "PreCompiled.h" #ifndef _PreComp_ +#include #include #endif -#include - #include #include diff --git a/src/Mod/Mesh/Gui/PreCompiled.h b/src/Mod/Mesh/Gui/PreCompiled.h index e492cbd9aa..8e9b804a4e 100644 --- a/src/Mod/Mesh/Gui/PreCompiled.h +++ b/src/Mod/Mesh/Gui/PreCompiled.h @@ -44,6 +44,7 @@ // standard #include +#include // STL #include diff --git a/src/Mod/Part/App/CMakeLists.txt b/src/Mod/Part/App/CMakeLists.txt index bf0ce716eb..1a85f3b620 100644 --- a/src/Mod/Part/App/CMakeLists.txt +++ b/src/Mod/Part/App/CMakeLists.txt @@ -533,6 +533,8 @@ SET(Part_SRCS TopoShapeCache.cpp TopoShapeCache.h TopoShapeExpansion.cpp + TopoShapeMapper.h + TopoShapeMapper.cpp TopoShapeOpCode.h edgecluster.cpp edgecluster.h diff --git a/src/Mod/Part/App/FaceMaker.cpp b/src/Mod/Part/App/FaceMaker.cpp index 31c022ab6c..44fa2ce2cd 100644 --- a/src/Mod/Part/App/FaceMaker.cpp +++ b/src/Mod/Part/App/FaceMaker.cpp @@ -33,7 +33,9 @@ #include #include "FaceMaker.h" +#include #include "TopoShape.h" +#include "TopoShapeOpCode.h" TYPESYSTEM_SOURCE_ABSTRACT(Part::FaceMaker, Base::BaseClass) @@ -46,6 +48,11 @@ void Part::FaceMaker::addWire(const TopoDS_Wire& w) void Part::FaceMaker::addShape(const TopoDS_Shape& sh) { + addTopoShape(sh); +} + +void Part::FaceMaker::addTopoShape(const TopoShape& shape) { + const TopoDS_Shape &sh = shape.getShape(); if(sh.IsNull()) throw Base::ValueError("Input shape is null."); switch(sh.ShapeType()){ @@ -58,11 +65,14 @@ void Part::FaceMaker::addShape(const TopoDS_Shape& sh) case TopAbs_EDGE: this->myWires.push_back(BRepBuilderAPI_MakeWire(TopoDS::Edge(sh)).Wire()); break; + case TopAbs_FACE: + this->myInputFaces.push_back(sh); + break; default: throw Base::TypeError("Shape must be a wire, edge or compound. Something else was supplied."); break; } - this->mySourceShapes.push_back(sh); + this->mySourceShapes.push_back(shape); } void Part::FaceMaker::useCompound(const TopoDS_Compound& comp) @@ -73,14 +83,29 @@ void Part::FaceMaker::useCompound(const TopoDS_Compound& comp) } } +void Part::FaceMaker::useTopoCompound(const TopoShape& comp) +{ + for(auto &s : comp.getSubTopoShapes()) + this->addTopoShape(s); +} + const TopoDS_Face& Part::FaceMaker::Face() { - const TopoDS_Shape &sh = this->Shape(); - if(sh.IsNull()) + return TopoDS::Face(TopoFace().getShape()); +} + +const Part::TopoShape &Part::FaceMaker::TopoFace() const{ + if(this->myTopoShape.isNull()) throw NullShapeException("Part::FaceMaker: result shape is null."); - if (sh.ShapeType() != TopAbs_FACE) + if (this->myTopoShape.getShape().ShapeType() != TopAbs_FACE) throw Base::TypeError("Part::FaceMaker: return shape is not a single face."); - return TopoDS::Face(sh); + return this->myTopoShape; +} + +const Part::TopoShape &Part::FaceMaker::getTopoShape() const{ + if(this->myTopoShape.isNull()) + throw NullShapeException("Part::FaceMaker: result shape is null."); + return this->myTopoShape; } #if OCC_VERSION_HEX >= 0x070600 @@ -90,7 +115,7 @@ void Part::FaceMaker::Build() #endif { this->NotDone(); - this->myShapesToReturn.clear(); + this->myShapesToReturn = this->myInputFaces; this->myGenerated.Clear(); this->Build_Essence();//adds stuff to myShapesToReturn @@ -118,6 +143,7 @@ void Part::FaceMaker::Build() if(this->myShapesToReturn.empty()){ //nothing to do, null shape will be returned. + this->myShape = TopoDS_Shape(); } else if (this->myShapesToReturn.size() == 1){ this->myShape = this->myShapesToReturn[0]; } else { @@ -129,6 +155,67 @@ void Part::FaceMaker::Build() } this->myShape = cmp_res; } + + postBuild(); +} + +struct ElementName { + long tag; + Data::MappedName name; + Data::ElementIDRefs sids; + + ElementName(long t, const Data::MappedName &n, const Data::ElementIDRefs &sids) + :tag(t),name(n), sids(sids) + {} + + inline bool operator<(const ElementName &other) const { + if(tagother.tag) + return false; + return Data::ElementNameComparator()(name,other.name); + } +}; + +void Part::FaceMaker::postBuild() { + this->myTopoShape.setShape(this->myShape); + this->myTopoShape.Hasher = this->MyHasher; + this->myTopoShape.mapSubElement(this->mySourceShapes); + int index = 0; + const char *op = this->MyOp; + if(!op) + op = Part::OpCodes::Face; + const auto &faces = this->myTopoShape.getSubTopoShapes(TopAbs_FACE); + // name the face using the edges of its outer wire + for(auto &face : faces) { + ++index; + TopoShape wire = face.splitWires(); + wire.mapSubElement(face); + std::set edgeNames; + int count = wire.countSubShapes(TopAbs_EDGE); + for (int index2 = 1; index2 <= count; ++index2) { + Data::ElementIDRefs sids; + Data::MappedName name = + face.getMappedName(Data::IndexedName::fromConst("Edge", index2), false, &sids); + if (!name) { + continue; + } + edgeNames.emplace(wire.getElementHistory(name), name, sids); + } + if (edgeNames.empty()) { + continue; + } + + std::vector names; + Data::ElementIDRefs sids; + // We just use the first source element name to make the face name more + // stable + names.push_back(edgeNames.begin()->name); + sids = edgeNames.begin()->sids; + this->myTopoShape.setElementComboName( + Data::IndexedName::fromConst("Face",index),names,op,nullptr,&sids); + } + this->myTopoShape.initCache(true); this->Done(); } @@ -172,12 +259,12 @@ TYPESYSTEM_SOURCE(Part::FaceMakerSimple, Part::FaceMakerPublic) std::string Part::FaceMakerSimple::getUserFriendlyName() const { - return {QT_TRANSLATE_NOOP("Part_FaceMaker","Simple")}; + return std::string(QT_TRANSLATE_NOOP("Part_FaceMaker","Simple")); } std::string Part::FaceMakerSimple::getBriefExplanation() const { - return {QT_TRANSLATE_NOOP("Part_FaceMaker","Makes separate plane face from every wire independently. No support for holes; wires can be on different planes.")}; + return std::string(QT_TRANSLATE_NOOP("Part_FaceMaker","Makes separate plane face from every wire independently. No support for holes; wires can be on different planes.")); } void Part::FaceMakerSimple::Build_Essence() diff --git a/src/Mod/Part/App/FaceMaker.h b/src/Mod/Part/App/FaceMaker.h index aaf289c2a0..cd25ada21b 100644 --- a/src/Mod/Part/App/FaceMaker.h +++ b/src/Mod/Part/App/FaceMaker.h @@ -33,6 +33,8 @@ #include #include +#include +#include "TopoShape.h" namespace Part { @@ -47,11 +49,16 @@ namespace Part */ class PartExport FaceMaker: public BRepBuilderAPI_MakeShape, public Base::BaseClass { - TYPESYSTEM_HEADER_WITH_OVERRIDE(); + TYPESYSTEM_HEADER(); public: - FaceMaker() = default; - ~FaceMaker() override = default; + FaceMaker() {} + virtual ~FaceMaker() {} + + void addTopoShape(const TopoShape &s); + void useTopoCompound(const TopoShape &comp); + const TopoShape &getTopoShape() const; + const TopoShape &TopoFace() const; virtual void addWire(const TopoDS_Wire& w); /** @@ -69,6 +76,8 @@ public: */ virtual void useCompound(const TopoDS_Compound &comp); + virtual void setPlane(const gp_Pln &) {} + /** * @brief Face: returns the face (result). If result is not a single face, * throws Base::TypeError. (hint: use .Shape() instead) @@ -79,7 +88,7 @@ public: #if OCC_VERSION_HEX >= 0x070600 void Build(const Message_ProgressRange& theRange = Message_ProgressRange()) override; #else - void Build() override; + virtual void Build(); #endif //fails to compile, huh! @@ -90,11 +99,16 @@ public: static std::unique_ptr ConstructFromType(const char* className); static std::unique_ptr ConstructFromType(Base::Type type); + const char *MyOp = 0; + App::StringHasherRef MyHasher; + protected: - std::vector mySourceShapes; //wire or compound + std::vector mySourceShapes; //wire or compound std::vector myWires; //wires from mySourceShapes std::vector myCompounds; //compounds, for recursive processing std::vector myShapesToReturn; + std::vector myInputFaces; + TopoShape myTopoShape; /** * @brief Build_Essence: build routine that can assume there is no nesting. @@ -106,6 +120,7 @@ protected: * whole Build(). */ virtual void Build_Essence() = 0; + void postBuild(); static void throwNotImplemented(); }; @@ -115,7 +130,7 @@ protected: */ class PartExport FaceMakerPublic : public FaceMaker { - TYPESYSTEM_HEADER_WITH_OVERRIDE(); + TYPESYSTEM_HEADER(); public: virtual std::string getUserFriendlyName() const = 0; virtual std::string getBriefExplanation() const = 0; diff --git a/src/Mod/Part/App/FeatureExtrusion.cpp b/src/Mod/Part/App/FeatureExtrusion.cpp index c3fb5ba21f..bca055c84a 100644 --- a/src/Mod/Part/App/FeatureExtrusion.cpp +++ b/src/Mod/Part/App/FeatureExtrusion.cpp @@ -356,14 +356,14 @@ void FaceMakerExtrusion::Build() if (mySourceShapes.empty()) throw Base::ValueError("No input shapes!"); if (mySourceShapes.size() == 1) { - inputShape = mySourceShapes[0]; + inputShape = mySourceShapes[0].getShape(); } else { TopoDS_Builder builder; TopoDS_Compound cmp; builder.MakeCompound(cmp); - for (const TopoDS_Shape& sh : mySourceShapes) { - builder.Add(cmp, sh); + for (const auto &sh : mySourceShapes) { + builder.Add(cmp, sh.getShape()); } inputShape = cmp; } diff --git a/src/Mod/Part/App/FeaturePartCommon.cpp b/src/Mod/Part/App/FeaturePartCommon.cpp index 9832b8925c..2a4fbb9778 100644 --- a/src/Mod/Part/App/FeaturePartCommon.cpp +++ b/src/Mod/Part/App/FeaturePartCommon.cpp @@ -80,6 +80,7 @@ short MultiCommon::mustExecute() const App::DocumentObjectExecReturn *MultiCommon::execute() { +#ifndef FC_USE_TNP_FIX std::vector s; std::vector obj = Shapes.getValues(); @@ -194,4 +195,39 @@ App::DocumentObjectExecReturn *MultiCommon::execute() } return App::DocumentObject::StdReturn; +#else + std::vector shapes; + for (auto obj : Shapes.getValues()) { + TopoShape sh = Feature::getTopoShape(obj); + if (sh.isNull()) { + return new App::DocumentObjectExecReturn("Input shape is null"); + } + shapes.push_back(sh); + } + + TopoShape res {}; + res.makeElementBoolean(Part::OpCodes::Common, shapes); + if (res.isNull()) { + throw Base::RuntimeError("Resulting shape is null"); + } + + Base::Reference hGrp = App::GetApplication() + .GetUserParameter() + .GetGroup("BaseApp") + ->GetGroup("Preferences") + ->GetGroup("Mod/Part/Boolean"); + if (hGrp->GetBool("CheckModel", false)) { + BRepCheck_Analyzer aChecker(res.getShape()); + if (!aChecker.IsValid()) { + return new App::DocumentObjectExecReturn("Resulting shape is invalid"); + } + } + + if (this->Refine.getValue()) { + res = res.makeElementRefine(); + } + this->Shape.setValue(res); + + return Part::Feature::execute(); +#endif } diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index 4046c879b7..8bbfbc9017 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -3188,9 +3188,9 @@ bool TopoShape::fix() // through makEWires(), and it will remove those edges. Without // remapping, there will be invalid index jumpping in reference in // Sketch002.ExternalEdge5. - makESHAPE(fixThis.Shape(), MapperHistory(fixThis), {*this}); + makeShapeWithElementMap(fixThis.Shape(), MapperHistory(fixThis), {*this}); } else - makESHAPE(fix.Shape(), MapperHistory(fix), {copy}); + makeShapeWithElementMap(fix.Shape(), MapperHistory(fix), {copy}); return true; } diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index abcd8157e8..4525581a67 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -30,6 +30,10 @@ #include #include +#include +#include +#include +#include #include #include #include @@ -84,7 +88,7 @@ class PartExport ShapeSegment: public Data::Segment TYPESYSTEM_HEADER_WITH_OVERRIDE(); public: - ShapeSegment(const TopoDS_Shape& ShapeIn) + explicit ShapeSegment(const TopoDS_Shape& ShapeIn) : Shape(ShapeIn) {} ShapeSegment() = default; @@ -260,7 +264,7 @@ public: bool analyze(bool runBopCheck, std::ostream&) const; bool isClosed() const; bool isCoplanar(const TopoShape& other, double tol = -1) const; - bool findPlane(gp_Pln& pln, double tol = -1) const; + bool findPlane(gp_Pln& plane, double tol = -1) const; /// Returns true if the expansion of the shape is infinite, false otherwise bool isInfinite() const; /// Checks whether the shape is a planar face @@ -391,6 +395,27 @@ public: TopoDS_Shape defeaturing(const std::vector& s) const; TopoDS_Shape makeShell(const TopoDS_Shape&) const; //@} + + /// Wire re-orientation when calling splitWires() + enum SplitWireReorient { + /// Keep original reorientation + NoReorient, + /// Make outer wire forward, and inner wires reversed + Reorient, + /// Make both outer and inner wires forward + ReorientForward, + /// Make both outer and inner wires reversed + ReorientReversed, + }; + /** Return the outer and inner wires of a face + * + * @param inner: optional output of inner wires + * @param reorient: wire reorientation, see SplitWireReorient + * + * @return Return the outer wire + */ + TopoShape splitWires(std::vector *inner = nullptr, + SplitWireReorient reorient = Reorient) const; /** @name Element name mapping aware shape maker * @@ -575,6 +600,11 @@ public: const std::string& shapeName(bool silent = false) const; static std::pair shapeTypeAndIndex(const char* name); + Data::MappedName setElementComboName(const Data::IndexedName & element, + const std::vector &names, + const char *marker=nullptr, + const char *op=nullptr, + const Data::ElementIDRefs *sids=nullptr); /** @name sub shape cached functions * @@ -613,10 +643,48 @@ public: void copyElementMap(const TopoShape & topoShape, const char *op=nullptr); bool canMapElement(const TopoShape &other) const; void mapSubElement(const TopoShape &other,const char *op=nullptr, bool forceHasher=false); - void mapSubElement(const std::vector &shapes, const char *op); + void mapSubElement(const std::vector &shapes, const char *op=nullptr); void mapSubElementsTo(std::vector &shapes, const char *op=nullptr) const; bool hasPendingElementMap() const; + /** Helper class to return the generated and modified shape given an input shape + * + * Shape history information is extracted using OCCT APIs + * BRepBuilderAPI_MakeShape::Generated/Modified(). However, there is often + * some glitches in various derived class. So we use this class as an + * abstraction, and create various derived classes to deal with the glitches. + */ + struct PartExport Mapper { + /// Helper vector for temporary storage of both generated and modified shapes + mutable std::vector _res; + virtual ~Mapper() {} + /// Return a list of shape generated from the given input shape + virtual const std::vector &generated(const TopoDS_Shape &) const { + return _res; + } + /// Return a list of shape modified from the given input shape + virtual const std::vector &modified(const TopoDS_Shape &) const { + return _res; + } + }; + + /** Core function to generate mapped element names from shape history + * + * @param shape: the new shape + * @param mapper: for mapping input shapes to generated/modified shapes + * @param sources: list of source shapes. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return The original content of this TopoShape is discarded and replaced + * with the given new shape. The function returns the TopoShape + * itself as a self reference so that multiple operations can be + * carried out for the same shape in the same line of code. + */ + TopoShape &makeShapeWithElementMap(const TopoDS_Shape &shape, + const Mapper &mapper, + const std::vector &sources, + const char *op=nullptr); /** * When given a single shape to create a compound, two results are possible: either to simply * return the shape as given, or to force it to be placed in a Compound. @@ -787,6 +855,135 @@ public: return TopoShape(Tag,Hasher).makeElementCopy(*this,op,copyGeom,copyMesh); } + /* Make a shell using this shape + * @param silent: whether to throw exception on failure + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return The original content of this TopoShape is discarded and replaced + * with the new shape. The function returns the TopoShape itself as + * a self reference so that multiple operations can be carried out + * for the same shape in the same line of code. + */ + TopoShape& makeElementShell(bool silent = true, const char* op = nullptr); + + /* Make a shell with input wires + * + * @param wires: input wires + * @param silent: whether to throw exception on failure + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return The original content of this TopoShape is discarded and replaced + * with the new shape. The function returns the TopoShape itself as + * a self reference so that multiple operations can be carried out + * for the same shape in the same line of code. + */ + // TopoShape& makeElementShellFromWires(const std::vector& wires, + // bool silent = true, + // const char* op = nullptr); + /* Make a shell with input wires + * + * @param wires: input wires + * @param silent: whether to throw exception on failure + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the new shape. The TopoShape itself is not modified. + */ + // TopoShape& makeElementShellFromWires(bool silent = true, const char* op = nullptr) + // { + // return makeElementShellFromWires(getSubTopoShapes(TopAbs_WIRE), silent, op); + // } + + TopoShape& makeElementFace(const std::vector& shapes, + const char* op = nullptr, + const char* maker = nullptr, + const gp_Pln* plane = nullptr); + /** Make a planar face with the input wire or edge + * + * @param shape: input shape. Can be either edge, wire, or compound of + * those two types + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param maker: optional type name of the face maker. If not given, + * default to "Part::FaceMakerBullseye" + * @param plane: optional plane of the face. + * + * @return The function creates a planar face. The original content of this + * TopoShape is discarded and replaced with the new shape. The + * function returns the TopoShape itself as a reference so that + * multiple operations can be carried out for the same shape in the + * same line of code. + */ + TopoShape& makeElementFace(const TopoShape& shape, + const char* op = nullptr, + const char* maker = nullptr, + const gp_Pln* plane = nullptr); + /** Make a planar face using this shape + * + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param maker: optional type name of the face maker. If not given, + * default to "Part::FaceMakerBullseye" + * @param plane: optional plane of the face. + * + * @return The function returns a new planar face made using the wire or edge + * inside this shape. The shape itself is not modified. + */ + TopoShape makeElementFace(const char* op = nullptr, + const char* maker = nullptr, + const gp_Pln* plane = nullptr) const + { + return TopoShape(0, Hasher).makeElementFace(*this, op, maker, plane); + } + + /// Filling style when making a BSpline face + enum class FillingStyle + { + /// The style with the flattest patches + Stretch, + /// A rounded style of patch with less depth than those of Curved + Coons, + /// The style with the most rounded patches + Curved, + }; + + struct BRepFillingParams; + + /** Provides information about the continuity of a curve. + * Corresponds to OCCT type GeomAbs_Shape + */ + enum class Continuity { + /// Only geometric continuity + C0, + /** for each point on the curve, the tangent vectors 'on the right' and 'on + * the left' are collinear with the same orientation. + */ + G1, + /** Continuity of the first derivative. The 'C1' curve is also 'G1' but, in + * addition, the tangent vectors 'on the right' and 'on the left' are equal. + */ + C1, + + /** For each point on the curve, the normalized normal vectors 'on the + * right' and 'on the left' are equal. + */ + G2, + + /// Continuity of the second derivative. + C2, + + /// Continuity of the third derivative. + C3, + + /** Continuity of the N-th derivative, whatever is the value given for N + * (infinite order of continuity). Also provides information about the + * continuity of a surface. + */ + CN, + }; + friend class TopoShapeCache; private: @@ -1019,7 +1216,20 @@ struct PartExport MapperMaker: TopoShape::Mapper { virtual const std::vector &generated(const TopoDS_Shape &s) const override; }; +/** Shape mapper for BRepTools_History + * + * Uses BRepTools_History::Modified/Generated() function to extract + * shape history for generating mapped element names + */ +struct PartExport MapperHistory: TopoShape::Mapper { + Handle(BRepTools_History) history; + MapperHistory(const Handle(BRepTools_History) &history); + MapperHistory(const Handle(BRepTools_ReShape) &reshape); + MapperHistory(ShapeFix_Root &fix); + virtual const std::vector &modified(const TopoDS_Shape &s) const override; + virtual const std::vector &generated(const TopoDS_Shape &s) const override; +}; + } // namespace Part - #endif // PART_TOPOSHAPE_H diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 57709a489b..6d4db3e461 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -27,8 +27,20 @@ #ifndef _PreComp_ #include + +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include + +#include #include #include #include @@ -40,6 +52,9 @@ #include "TopoShape.h" #include "TopoShapeCache.h" #include "TopoShapeOpCode.h" +#include "FaceMaker.h" + +#include FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT @@ -337,6 +352,8 @@ void checkAndMatchHasher(TopoShape& topoShape1, const TopoShape& topoShape2) } } // namespace + +// TODO: Refactor mapSubElementTypeForShape to reduce complexity void TopoShape::mapSubElementTypeForShape(const TopoShape& other, TopAbs_ShapeEnum type, const char* op, @@ -386,6 +403,9 @@ void TopoShape::mapSubElementTypeForShape(const TopoShape& other, } std::ostringstream ss; char elementType {shapeName(type)[0]}; + if (!elementMap()) { + FC_THROWM(NullShapeException, "No element map"); // NOLINT + } elementMap()->encodeElementName(elementType, name, ss, &sids, Tag, op, other.Tag); elementMap()->setElementName(element, name, Tag, &sids); } @@ -512,6 +532,823 @@ void TopoShape::mapSubElement(const std::vector& shapes, const char* } } +const std::vector & +MapperMaker::modified(const TopoDS_Shape &s) const +{ + _res.clear(); + try { + TopTools_ListIteratorOfListOfShape it; + for (it.Initialize(maker.Modified(s)); it.More(); it.Next()) + _res.push_back(it.Value()); + } catch (const Standard_Failure & e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + return _res; +} + +const std::vector & +MapperMaker::generated(const TopoDS_Shape &s) const +{ + _res.clear(); + try { + TopTools_ListIteratorOfListOfShape it; + for (it.Initialize(maker.Generated(s)); it.More(); it.Next()) + _res.push_back(it.Value()); + } catch (const Standard_Failure & e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + return _res; +} + +MapperHistory::MapperHistory(const Handle(BRepTools_History) &history) + :history(history) +{} + +MapperHistory::MapperHistory(const Handle(BRepTools_ReShape) &reshape) +{ + if (reshape) + history = reshape->History(); +} + +MapperHistory::MapperHistory(ShapeFix_Root &fix) +{ + if (fix.Context()) + history = fix.Context()->History(); +} + +const std::vector & +MapperHistory::modified(const TopoDS_Shape &s) const +{ + _res.clear(); + try { + if (history) { + TopTools_ListIteratorOfListOfShape it; + for (it.Initialize(history->Modified(s)); it.More(); it.Next()) + _res.push_back(it.Value()); + } + } catch (const Standard_Failure & e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + return _res; +} + +const std::vector & +MapperHistory::generated(const TopoDS_Shape &s) const +{ + _res.clear(); + try { + if (history) { + TopTools_ListIteratorOfListOfShape it; + for (it.Initialize(history->Generated(s)); it.More(); it.Next()) + _res.push_back(it.Value()); + } + } catch (const Standard_Failure & e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + return _res; +} + +struct ShapeInfo +{ + const TopoDS_Shape& shape; + TopoShapeCache::Ancestry& cache; + TopAbs_ShapeEnum type; + const char* shapetype; + + ShapeInfo(const TopoDS_Shape& shape, TopAbs_ShapeEnum type, TopoShapeCache::Ancestry& cache) + : shape(shape) + , cache(cache) + , type(type) + , shapetype(TopoShape::shapeName(type).c_str()) + {} + + int count() const + { + return cache.count(); + } + + TopoDS_Shape find(int index) + { + return cache.find(shape, index); + } + + int find(const TopoDS_Shape& subshape) + { + return cache.find(shape, subshape); + } +}; + +//////////////////////////////////////// +// makESHAPE -> makeShapeWithElementMap +/////////////////////////////////////// + +struct NameKey +{ + Data::MappedName name; + long tag = 0; + int shapetype = 0; + + NameKey() + = default; + explicit NameKey(Data::MappedName n) + : name(std::move(n)) + {} + NameKey(int type, Data::MappedName n) + : name(std::move(n)) + { + // Order the shape type from vertex < edge < face < other. We'll rely + // on this for sorting when we name the geometry element. + switch (type) { + case TopAbs_VERTEX: + shapetype = 0; + break; + case TopAbs_EDGE: + shapetype = 1; + break; + case TopAbs_FACE: + shapetype = 2; + break; + default: + shapetype = 3; + } + } + bool operator<(const NameKey& other) const + { + if (shapetype < other.shapetype) { + return true; + } + if (shapetype > other.shapetype) { + return false; + } + if (tag < other.tag) { + return true; + } + if (tag > other.tag) { + return false; + } + return name < other.name; + } +}; + +struct NameInfo +{ + int index {}; + Data::ElementIDRefs sids; + const char* shapetype {}; +}; + + +const std::string& modPostfix() +{ + static std::string postfix(Data::POSTFIX_MOD); + return postfix; +} + +const std::string& modgenPostfix() +{ + static std::string postfix(Data::POSTFIX_MODGEN); + return postfix; +} + +const std::string& genPostfix() +{ + static std::string postfix(Data::POSTFIX_GEN); + return postfix; +} + +const std::string& upperPostfix() +{ + static std::string postfix(Data::POSTFIX_UPPER); + return postfix; +} + +const std::string& lowerPostfix() +{ + static std::string postfix(Data::POSTFIX_LOWER); + return postfix; +} + +// TODO: Refactor checkForParallelOrCoplanar to reduce complexity +void checkForParallelOrCoplanar(const TopoDS_Shape& newShape, + const ShapeInfo& newInfo, + std::vector& newShapes, + const gp_Pln& pln, + int parallelFace, + int& coplanarFace, + int& checkParallel) +{ + for (TopExp_Explorer xp(newShape, newInfo.type); xp.More(); xp.Next()) { + newShapes.push_back(xp.Current()); + + if ((parallelFace < 0 || coplanarFace < 0) && checkParallel > 0) { + // Specialized checking for high level mapped + // face that are either coplanar or parallel + // with the source face, which are common in + // operations like extrusion. Once found, the + // first coplanar face will assign an index of + // INT_MIN+1, and the first parallel face + // INT_MIN. The purpose of these special + // indexing is to make the name more stable for + // those generated faces. + // + // For example, the top or bottom face of an + // extrusion will be named using the extruding + // face. With a fixed index, the name is no + // longer affected by adding/removing of holes + // inside the extruding face/sketch. + gp_Pln plnOther; + if (TopoShape(newShapes.back()).findPlane(plnOther)) { + if (pln.Axis().IsParallel(plnOther.Axis(), Precision::Angular())) { + if (coplanarFace < 0) { + gp_Vec vec(pln.Axis().Location(), plnOther.Axis().Location()); + Standard_Real D1 = gp_Vec(pln.Axis().Direction()).Dot(vec); + if (D1 < 0) { + D1 = -D1; + } + Standard_Real D2 = gp_Vec(plnOther.Axis().Direction()).Dot(vec); + if (D2 < 0) { + D2 = -D2; + } + if (D1 <= Precision::Confusion() && D2 <= Precision::Confusion()) { + coplanarFace = (int)newShapes.size(); + continue; + } + } + if (parallelFace < 0) { + parallelFace = (int)newShapes.size(); + } + } + } + } + } +} + +// TODO: Refactor makeShapeWithElementMap to reduce complexity +TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, + const Mapper& mapper, + const std::vector& shapes, + const char* op) +{ + setShape(shape); + if (shape.IsNull()) { + FC_THROWM(NullShapeException, "Null shape"); + } + + if (shapes.empty()) { + return *this; + } + + size_t canMap = 0; + for (auto& incomingShape : shapes) { + if (canMapElement(incomingShape)) { + ++canMap; + } + } + if (canMap == 0U) { + return *this; + } + if (canMap != shapes.size() && FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Not all input shapes are mappable"); // NOLINT + } + + if (!op) { + op = Part::OpCodes::Maker; + } + std::string _op = op; + _op += '_'; + + initCache(); + ShapeInfo vertexInfo(_Shape, TopAbs_VERTEX, _cache->getAncestry(TopAbs_VERTEX)); + ShapeInfo edgeInfo(_Shape, TopAbs_EDGE, _cache->getAncestry(TopAbs_EDGE)); + ShapeInfo faceInfo(_Shape, TopAbs_FACE, _cache->getAncestry(TopAbs_FACE)); + mapSubElement(shapes, op); + + std::array infos = {&vertexInfo, &edgeInfo, &faceInfo}; + + std::array infoMap {}; + infoMap[TopAbs_VERTEX] = &vertexInfo; + infoMap[TopAbs_EDGE] = &edgeInfo; + infoMap[TopAbs_WIRE] = &edgeInfo; + infoMap[TopAbs_FACE] = &faceInfo; + infoMap[TopAbs_SHELL] = &faceInfo; + infoMap[TopAbs_SOLID] = &faceInfo; + infoMap[TopAbs_COMPOUND] = &faceInfo; + infoMap[TopAbs_COMPSOLID] = &faceInfo; + + std::ostringstream ss; + std::string postfix; + Data::MappedName newName; + + std::map> newNames; + + // First, collect names from other shapes that generates or modifies the + // new shape + for (auto& pinfo : infos) { + auto& info = *pinfo; + for (const auto & incomingShape : shapes) { + if (!canMapElement(incomingShape)) { + continue; + } + auto& otherMap = incomingShape._cache->getAncestry(info.type); + if (otherMap.count() == 0) { + continue; + } + + for (int i = 1; i <= otherMap.count(); i++) { + const auto& otherElement = otherMap.find(incomingShape._Shape, i); + // Find all new objects that are a modification of the old object + Data::ElementIDRefs sids; + NameKey key(info.type, + incomingShape.getMappedName(Data::IndexedName::fromConst(info.shapetype, i), + true, + &sids)); + + int newShapeCounter = 0; + for (auto& newShape : mapper.modified(otherElement)) { + ++newShapeCounter; + if (newShape.ShapeType() >= TopAbs_SHAPE) { + // NOLINTNEXTLINE + FC_ERR("unknown modified shape type " << newShape.ShapeType() << " from " + << info.shapetype << i); + continue; + } + auto& newInfo = *infoMap.at(newShape.ShapeType()); + if (newInfo.type != newShape.ShapeType()) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + // TODO: it seems modified shape may report higher + // level shape type just like generated shape below. + // Maybe we shall do the same for name construction. + // NOLINTNEXTLINE + FC_WARN("modified shape type " << shapeName(newShape.ShapeType()) + << " mismatch with " << info.shapetype + << i); + } + continue; + } + int newShapeIndex = newInfo.find(newShape); + if (newShapeIndex == 0) { + // This warning occurs in makERevolve. It generates + // some shape from a vertex that never made into the + // final shape. There may be incomingShape cases there. + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + // NOLINTNEXTLINE + FC_WARN("Cannot find " << op << " modified " << newInfo.shapetype + << " from " << info.shapetype << i); + } + continue; + } + + Data::IndexedName element = Data::IndexedName::fromConst(newInfo.shapetype, newShapeIndex); + if (getMappedName(element)) { + continue; + } + + key.tag = incomingShape.Tag; + auto& name_info = newNames[element][key]; + name_info.sids = sids; + name_info.index = newShapeCounter; + name_info.shapetype = info.shapetype; + } + + int checkParallel = -1; + gp_Pln pln; + + // Find all new objects that were generated from an old object + // (e.g. a face generated from an edge) + newShapeCounter = 0; + for (auto& newShape : mapper.generated(otherElement)) { + if (newShape.ShapeType() >= TopAbs_SHAPE) { + // NOLINTNEXTLINE + FC_ERR("unknown generated shape type " << newShape.ShapeType() << " from " + << info.shapetype << i); + continue; + } + + int parallelFace = -1; + int coplanarFace = -1; + auto& newInfo = *infoMap.at(newShape.ShapeType()); + std::vector newShapes; + int shapeOffset = 0; + if (newInfo.type == newShape.ShapeType()) { + newShapes.push_back(newShape); + } + else { + // It is possible for the maker to report generating a + // higher level shape, such as shell or solid. For + // example, when extruding, OCC will report the + // extruding face generating the entire solid. However, + // it will also report the edges of the extruding face + // generating the side faces. In this case, too much + // information is bad for us. We don't want the name of + // the side face (and its edges) to be coupled with + // incomingShape (unrelated) edges in the extruding face. + // + // shapeOffset below is used to make sure the higher + // level mapped names comes late after sorting. We'll + // ignore those names if there are more precise mapping + // available. + shapeOffset = 3; + + if (info.type == TopAbs_FACE && checkParallel < 0) { + if (!TopoShape(otherElement).findPlane(pln)) { + checkParallel = 0; + } + else { + checkParallel = 1; + } + } + checkForParallelOrCoplanar(newShape, + newInfo, + newShapes, + pln, + parallelFace, + coplanarFace, + checkParallel); + } + key.shapetype += shapeOffset; + for (auto& workingShape : newShapes) { + ++newShapeCounter; + int workingShapeIndex = newInfo.find(workingShape); + if (workingShapeIndex == 0) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + // NOLINTNEXTLINE + FC_WARN("Cannot find " << op << " generated " << newInfo.shapetype + << " from " << info.shapetype << i); + } + continue; + } + + Data::IndexedName element = + Data::IndexedName::fromConst(newInfo.shapetype, workingShapeIndex); + auto mapped = getMappedName(element); + if (mapped) { + continue; + } + + key.tag = incomingShape.Tag; + auto& name_info = newNames[element][key]; + name_info.sids = sids; + if (newShapeCounter == parallelFace) { + name_info.index = std::numeric_limits::min(); + } + else if (newShapeCounter == coplanarFace) { + name_info.index = std::numeric_limits::min() + 1; + } + else { + name_info.index = -newShapeCounter; + } + name_info.shapetype = info.shapetype; + } + key.shapetype -= shapeOffset; + } + } + } + } + + // We shall first exclude those names generated from high level mapping. If + // there are still any unnamed elements left after we go through the process + // below, we set delayed=true, and start using those excluded names. + bool delayed = false; + + while (true) { + + // Construct the names for modification/generation info collected in + // the previous step + for (auto itName = newNames.begin(), itNext = itName; itNext != newNames.end(); + itName = itNext) { + // We treat the first modified/generated source shape name specially. + // If case there are more than one source shape. We hash the first + // source name separately, and then obtain the second string id by + // hashing all the source names together. We then use the second + // string id as the postfix for our name. + // + // In this way, we can associate the same source that are modified by + // multiple other shapes. + + ++itNext; + + auto& element = itName->first; + auto& names = itName->second; + const auto& first_key = names.begin()->first; + auto& first_info = names.begin()->second; + + if (!delayed && first_key.shapetype >= 3 && first_info.index > INT_MIN + 1) { + // This name is mapped from high level (shell, solid, etc.) + // Delay till next round. + // + // index>INT_MAX+1 is for checking generated coplanar and + // parallel face mapping, which has special fixed index to make + // name stable. These names are not delayed. + continue; + } + if (!delayed && getMappedName(element)) { + newNames.erase(itName); + continue; + } + + int name_type = + first_info.index > 0 ? 1 : 2; // index>0 means modified, or else generated + Data::MappedName first_name = first_key.name; + + Data::ElementIDRefs sids(first_info.sids); + + postfix.clear(); + if (names.size() > 1) { + ss.str(""); + ss << '('; + bool first = true; + auto it = names.begin(); + int count = 0; + for (++it; it != names.end(); ++it) { + auto& other_key = it->first; + if (other_key.shapetype >= 3 && first_key.shapetype < 3) { + // shapetype>=3 means it's a high level mapping (e.g. a face + // generates a solid). We don't want that if there are more + // precise low level mapping available. See comments above + // for more details. + break; + } + if (first) { + first = false; + } + else { + ss << '|'; + } + auto& other_info = it->second; + std::ostringstream ss2; + if (other_info.index != 1) { + // 'K' marks the additional source shape of this + // generate (or modified) shape. + ss2 << elementMapPrefix() << 'K'; + if (other_info.index == INT_MIN) { + ss2 << '0'; + } + else if (other_info.index == INT_MIN + 1) { + ss2 << "00"; + } + else { + // The same source shape may generate or modify + // more than one shape. The index here marks the + // position it is reported by OCC. Including the + // index here is likely to degrade name stablilty, + // but is unfortunately a necessity to avoid + // duplicate names. + ss2 << other_info.index; + } + } + Data::MappedName other_name = other_key.name; + elementMap()->encodeElementName(*other_info.shapetype, + other_name, + ss2, + &sids, + Tag, + nullptr, + other_key.tag); + ss << other_name; + if ((name_type == 1 && other_info.index < 0) + || (name_type == 2 && other_info.index > 0)) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("element is both generated and modified"); // NOLINT + } + name_type = 0; + } + sids += other_info.sids; + // To avoid the name becoming to long, just put some limit here + if (++count == 4) { + break; + } + } + if (!first) { + ss << ')'; + if (Hasher) { + sids.push_back(Hasher->getID(ss.str().c_str())); + ss.str(""); + ss << sids.back().toString(); + } + postfix = ss.str(); + } + } + + ss.str(""); + if (name_type == 2) { + ss << genPostfix(); + } + else if (name_type == 1) { + ss << modPostfix(); + } + else { + ss << modgenPostfix(); + } + if (first_info.index == INT_MIN) { + ss << '0'; + } + else if (first_info.index == INT_MIN + 1) { + ss << "00"; + } + else if (abs(first_info.index) > 1) { + ss << abs(first_info.index); + } + ss << postfix; + elementMap() + ->encodeElementName(element[0], first_name, ss, &sids, Tag, op, first_key.tag); + elementMap()->setElementName(element, first_name, Tag, &sids); + + if (!delayed && first_key.shapetype < 3) { + newNames.erase(itName); + } + } + + // The reverse pass. Starting from the highest level element, i.e. + // Face, for any element that are named, assign names for its lower unnamed + // elements. For example, if Edge1 is named E1, and its vertexes are not + // named, then name them as E1;U1, E1;U2, etc. + // + // In order to make the name as stable as possible, we may assign multiple + // names (which must be sorted, because we may use the first one to name + // upper element in the final pass) to lower element if it appears in + // multiple higher elements, e.g. same edge in multiple faces. + + for (size_t infoIndex = infos.size() - 1; infoIndex != 0; --infoIndex) { + std::map> + names; + auto& info = *infos.at(infoIndex); + auto& next = *infos.at(infoIndex - 1); + int elementCounter = 1; + auto it = newNames.end(); + if (delayed) { + it = newNames.upper_bound(Data::IndexedName::fromConst(info.shapetype, 0)); + } + for (;; ++elementCounter) { + Data::IndexedName element; + if (!delayed) { + if (elementCounter > info.count()) { + break; + } + element = Data::IndexedName::fromConst(info.shapetype, elementCounter); + if (newNames.count(element) != 0U) { + continue; + } + } + else if (it == newNames.end() + || !boost::starts_with(it->first.getType(), info.shapetype)) { + break; + } + else { + element = it->first; + ++it; + elementCounter = element.getIndex(); + if (elementCounter == 0 || elementCounter > info.count()) { + continue; + } + } + Data::ElementIDRefs sids; + Data::MappedName mapped = getMappedName(element, false, &sids); + if (!mapped) { + continue; + } + + TopTools_IndexedMapOfShape submap; + TopExp::MapShapes(info.find(elementCounter), next.type, submap); + for (int submapIndex = 1, infoCounter = 1; submapIndex <= submap.Extent(); ++submapIndex) { + ss.str(""); + int elementIndex = next.find(submap(submapIndex)); + assert(elementIndex); + Data::IndexedName indexedName = Data::IndexedName::fromConst(next.shapetype, elementIndex); + if (getMappedName(indexedName)) { + continue; + } + auto& infoRef = names[indexedName][mapped]; + infoRef.index = infoCounter++; + infoRef.sids = sids; + } + } + // Assign the actual names + for (auto& [indexedName, nameInfoMap] : names) { + // Do we really want multiple names for an element in this case? + // If not, we just pick the name in the first sorting order here. + auto& nameInfoMapEntry = *nameInfoMap.begin(); + { + auto& nameInfo = nameInfoMapEntry.second; + auto& sids = nameInfo.sids; + newName = nameInfoMapEntry.first; + ss.str(""); + ss << upperPostfix(); + if (nameInfo.index > 1) { + ss << nameInfo.index; + } + elementMap()->encodeElementName(indexedName[0], newName, ss, &sids, Tag, op); + elementMap()->setElementName(indexedName, newName, Tag, &sids); + } + } + } + + // The forward pass. For any elements that are not named, try construct its + // name from the lower elements + bool hasUnnamed = false; + for (size_t ifo = 1; ifo < infos.size(); ++ifo) { + auto& info = *infos.at(ifo); + auto& prev = *infos.at(ifo-1); + for (int i = 1; i <= info.count(); ++i) { + Data::IndexedName element = Data::IndexedName::fromConst(info.shapetype, i); + if (getMappedName(element)) { + continue; + } + + Data::ElementIDRefs sids; + std::map names; + TopExp_Explorer xp; + if (info.type == TopAbs_FACE) { + xp.Init(BRepTools::OuterWire(TopoDS::Face(info.find(i))), TopAbs_EDGE); + } + else { + xp.Init(info.find(i), prev.type); + } + for (; xp.More(); xp.Next()) { + int previousElementIndex = prev.find(xp.Current()); + assert(previousElementIndex); + Data::IndexedName prevElement = Data::IndexedName::fromConst(prev.shapetype, previousElementIndex); + if (!delayed && (newNames.count(prevElement) != 0U)) { + names.clear(); + break; + } + Data::ElementIDRefs sid; + Data::MappedName name = getMappedName(prevElement, false, &sid); + if (!name) { + // only assign name if all lower elements are named + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("unnamed lower element " << prevElement); // NOLINT + } + names.clear(); + break; + } + auto res = names.emplace(name, prevElement); + if (res.second) { + sids += sid; + } + else if (prevElement != res.first->second) { + // The seam edge will appear twice, which is normal. We + // only warn if the mapped element names are different. + // NOLINTNEXTLINE + FC_WARN("lower element " << prevElement << " and " << res.first->second + << " has duplicated name " << name << " for " + << info.shapetype << i); + } + } + if (names.empty()) { + hasUnnamed = true; + continue; + } + auto it = names.begin(); + newName = it->first; + if (names.size() == 1) { + ss << lowerPostfix(); + } + else { + bool first = true; + ss.str(""); + if (!Hasher) { + ss << lowerPostfix(); + } + ss << '('; + int count = 0; + for (++it; it != names.end(); ++it) { + if (first) { + first = false; + } + else { + ss << '|'; + } + ss << it->first; + + // To avoid the name becoming to long, just put some limit here + if (++count == 4) { + break; + } + } + ss << ')'; + if (Hasher) { + sids.push_back(Hasher->getID(ss.str().c_str())); + ss.str(""); + ss << lowerPostfix() << sids.back().toString(); + } + } + elementMap()->encodeElementName(element[0], newName, ss, &sids, Tag, op); + elementMap()->setElementName(element, newName, Tag, &sids); + } + } + if (!hasUnnamed || delayed || newNames.empty()) { + break; + } + delayed = true; + } + return *this; +} + namespace { void addShapesToBuilder(const std::vector& shapes, @@ -884,4 +1721,389 @@ TopoShape &TopoShape::makeElementCopy(const TopoShape &shape, const char *op, bo return *this; } + +TopoShape& TopoShape::makeElementFace(const TopoShape& shape, + const char* op, + const char* maker, + const gp_Pln* plane) +{ + std::vector shapes; + if (shape.isNull()) { + FC_THROWM(NullShapeException, "Null shape"); + } + if (shape.getShape().ShapeType() == TopAbs_COMPOUND) { + shapes = shape.getSubTopoShapes(); + } + else { + shapes.push_back(shape); + } + return makeElementFace(shapes, op, maker, plane); +} + +TopoShape& TopoShape::makeElementFace(const std::vector& shapes, + const char* op, + const char* maker, + const gp_Pln* plane) +{ + if (!maker || !maker[0]) { + maker = "Part::FaceMakerBullseye"; + } + std::unique_ptr mkFace = FaceMaker::ConstructFromType(maker); + mkFace->MyHasher = Hasher; + mkFace->MyOp = op; + if (plane) { + mkFace->setPlane(*plane); + } + + for (auto& shape : shapes) { + if (shape.getShape().ShapeType() == TopAbs_COMPOUND) { + mkFace->useTopoCompound(shape); + } + else { + mkFace->addTopoShape(shape); + } + } + mkFace->Build(); + + const auto& ret = mkFace->getTopoShape(); + setShape(ret._Shape); + Hasher = ret.Hasher; + resetElementMap(ret.elementMap()); + if (!isValid()) { + ShapeFix_ShapeTolerance aSFT; + aSFT.LimitTolerance(getShape(), + Precision::Confusion(), + Precision::Confusion(), + TopAbs_SHAPE); + + // In some cases, the OCC reports the returned shape having invalid + // tolerance. Not sure about the real cause. + // + // Update: one of the cause is related to OCC bug in + // BRepBuilder_FindPlane, A possible call sequence is, + // + // makEOffset2D() -> TopoShape::findPlane() -> BRepLib_FindSurface + // + // See code comments in findPlane() for the description of the bug and + // work around. + + ShapeFix_Shape fixer(getShape()); + fixer.Perform(); + setShape(fixer.Shape(), false); + + if (!isValid()) { + FC_WARN("makeElementFace: resulting face is invalid"); + } + } + return *this; +} + +/** + * Encode and set an element name in the elementMap. If a hasher is defined, apply it to the name. + * + * @param element The element name(type) that provides 1 one character suffix to the name IF + * . + * @param names The subnames to build the name from. If empty, return the TopoShape MappedName. + * @param marker The elementMap name or suffix to start the name with. If null, use the + * elementMapPrefix. + * @param op The op text passed to the element name encoder along with the TopoShape Tag + * @param _sids If defined, records the sub ids processed. + * + * @return The encoded, possibly hashed name. + */ +Data::MappedName TopoShape::setElementComboName(const Data::IndexedName& element, + const std::vector& names, + const char* marker, + const char* op, + const Data::ElementIDRefs* _sids) +{ + if (names.empty()) { + return Data::MappedName(); + } + std::string _marker; + if (!marker) { + marker = elementMapPrefix().c_str(); + } + else if (!boost::starts_with(marker, elementMapPrefix())) { + _marker = elementMapPrefix() + marker; + marker = _marker.c_str(); + } + auto it = names.begin(); + Data::MappedName newName = *it; + std::ostringstream ss; + Data::ElementIDRefs sids; + if (_sids) { + sids = *_sids; + } + if (names.size() == 1) { + ss << marker; + } + else { + bool first = true; + ss.str(""); + if (!Hasher) { + ss << marker; + } + ss << '('; + for (++it; it != names.end(); ++it) { + if (first) { + first = false; + } + else { + ss << '|'; + } + ss << *it; + } + ss << ')'; + if (Hasher) { + sids.push_back(Hasher->getID(ss.str().c_str())); + ss.str(""); + ss << marker << sids.back().toString(); + } + } + elementMap()->encodeElementName(element[0], newName, ss, &sids, Tag, op); + return elementMap()->setElementName(element, newName, Tag, &sids); +} + +/** + * Reorient the outer and inner wires of the TopoShape + * + * @param inner If this is not a nullptr, then any inner wires processed will be returned in this + * vector. + * @param reorient One of NoReorient, Reorient ( Outer forward, inner reversed ), + * ReorientForward ( all forward ), or ReorientReversed ( all reversed ) + * @return The outer wire, or an empty TopoShape if this isn't a Face, has no Face subShapes, or the + * outer wire isn't found. + */ +TopoShape TopoShape::splitWires(std::vector* inner, SplitWireReorient reorient) const +{ + // ShapeAnalysis::OuterWire() is un-reliable for some reason. OCC source + // code shows it works by creating face using each wire, and then test using + // BRepTopAdaptor_FClass2d::PerformInfinitePoint() to check if it is an out + // bound wire. And practice shows it sometimes returns the incorrect + // result. Need more investigation. Note that this may be related to + // unreliable solid face orientation + // (https://forum.freecadweb.org/viewtopic.php?p=446006#p445674) + // + // Use BrepTools::OuterWire() instead. OCC source code shows it is + // implemented using simple bound box checking. This should be a + // reliable method, especially so for a planar face. + + TopoDS_Shape tmp; + if (shapeType(true) == TopAbs_FACE) { + tmp = BRepTools::OuterWire(TopoDS::Face(_Shape)); + } + else if (countSubShapes(TopAbs_FACE) == 1) { + tmp = BRepTools::OuterWire(TopoDS::Face(getSubShape(TopAbs_FACE, 1))); + } + if (tmp.IsNull()) { + return TopoShape(); + } + const auto& wires = getSubTopoShapes(TopAbs_WIRE); + auto it = wires.begin(); + + TopAbs_Orientation orientOuter, orientInner; + switch (reorient) { + case ReorientReversed: + orientOuter = orientInner = TopAbs_REVERSED; + break; + case ReorientForward: + orientOuter = orientInner = TopAbs_FORWARD; + break; + default: + orientOuter = TopAbs_FORWARD; + orientInner = TopAbs_REVERSED; + break; + } + + auto doReorient = [](TopoShape& s, TopAbs_Orientation orient) { + // Special case of single edge wire. Make sure the edge is in the + // required orientation. This is necessary because BRepFill_OffsetWire + // has special handling of circular edge offset, which seem to only + // respect the edge orientation and disregard the wire orientation. The + // orientation is used to determine whether to shrink or expand. + if (s.countSubShapes(TopAbs_EDGE) == 1) { + TopoDS_Shape e = s.getSubShape(TopAbs_EDGE, 1); + if (e.Orientation() == orient) { + if (s._Shape.Orientation() == orient) { + return; + } + } + else { + e = e.Oriented(orient); + } + BRepBuilderAPI_MakeWire mkWire(TopoDS::Edge(e)); + s.setShape(mkWire.Shape(), false); + } + else if (s._Shape.Orientation() != orient) { + s.setShape(s._Shape.Oriented(orient), false); + } + }; + + for (; it != wires.end(); ++it) { + auto& wire = *it; + if (wire.getShape().IsSame(tmp)) { + if (inner) { + for (++it; it != wires.end(); ++it) { + inner->push_back(*it); + if (reorient) { + doReorient(inner->back(), orientInner); + } + } + } + auto res = wire; + if (reorient) { + doReorient(res, orientOuter); + } + return res; + } + if (inner) { + inner->push_back(wire); + if (reorient) { + doReorient(inner->back(), orientInner); + } + } + } + return TopoShape(); +} + +struct MapperFill: Part::TopoShape::Mapper +{ + BRepFill_Generator& maker; + explicit MapperFill(BRepFill_Generator& maker) + : maker(maker) + {} + const std::vector& generated(const TopoDS_Shape& s) const override + { + _res.clear(); + try { + TopTools_ListIteratorOfListOfShape it; + for (it.Initialize(maker.GeneratedShapes(s)); it.More(); it.Next()) { + _res.push_back(it.Value()); + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + } + return _res; + } +}; + +// topo naming counterpart of TopoShape::makeShell() +TopoShape& TopoShape::makeElementShell(bool silent, const char* op) +{ + if (silent) { + if (isNull()) { + return *this; + } + + if (shapeType(true) != TopAbs_COMPOUND) { + return *this; + } + + // we need a compound that consists of only faces + TopExp_Explorer it; + // no shells + if (hasSubShape(TopAbs_SHELL)) { + return *this; + } + + // no wires outside a face + it.Init(_Shape, TopAbs_WIRE, TopAbs_FACE); + if (it.More()) { + return *this; + } + + // no edges outside a wire + it.Init(_Shape, TopAbs_EDGE, TopAbs_WIRE); + if (it.More()) { + return *this; + } + + // no vertexes outside an edge + it.Init(_Shape, TopAbs_VERTEX, TopAbs_EDGE); + if (it.More()) { + return *this; + } + } + else if (!hasSubShape(TopAbs_FACE)) { + FC_THROWM(Base::CADKernelError, "Cannot make shell without face"); + } + + BRep_Builder builder; + TopoDS_Shape shape; + TopoDS_Shell shell; + builder.MakeShell(shell); + + try { + for (const auto& face : getSubShapes(TopAbs_FACE)) { + builder.Add(shell, face); + } + + TopoShape tmp(Tag, Hasher, shell); + tmp.resetElementMap(); + tmp.mapSubElement(*this, op); + + shape = shell; + BRepCheck_Analyzer check(shell); + if (!check.IsValid()) { + ShapeUpgrade_ShellSewing sewShell; + shape = sewShell.ApplySewing(shell); + // TODO confirm the above won't change OCCT topological naming + } + + if (shape.IsNull()) { + if (silent) { + return *this; + } + FC_THROWM(NullShapeException, "Failed to make shell"); + } + + if (shape.ShapeType() != TopAbs_SHELL) { + if (silent) { + return *this; + } + FC_THROWM(Base::CADKernelError, + "Failed to make shell: unexpected output shape type " + << shapeType(shape.ShapeType(), true)); + } + + setShape(shape); + resetElementMap(tmp.elementMap()); + } + catch (Standard_Failure& e) { + if (!silent) { + FC_THROWM(Base::CADKernelError, "Failed to make shell: " << e.GetMessageString()); + } + } + + return *this; +} + +// TopoShape& TopoShape::makeElementShellFromWires(const std::vector& wires, +// bool silent, +// const char* op) +// { +// BRepFill_Generator maker; +// for (auto& w : wires) { +// if (w.shapeType(silent) == TopAbs_WIRE) { +// maker.AddWire(TopoDS::Wire(w.getShape())); +// } +// } +// if (wires.empty()) { +// if (silent) { +// _Shape.Nullify(); +// return *this; +// } +// FC_THROWM(NullShapeException, "No input shapes"); +// } +// maker.Perform(); +// this->makeShapeWithElementMap(maker.Shell(), MapperFill(maker), wires, op); +// return *this; +// } + + + } // namespace Part diff --git a/src/Mod/Part/App/TopoShapeMapper.cpp b/src/Mod/Part/App/TopoShapeMapper.cpp new file mode 100644 index 0000000000..4268a433d2 --- /dev/null +++ b/src/Mod/Part/App/TopoShapeMapper.cpp @@ -0,0 +1,99 @@ +#include "PreCompiled.h" + +#include "TopoShapeMapper.h" + +namespace Part +{ + +void ShapeMapper::expand(const TopoDS_Shape& d, std::vector& shapes) +{ + if (d.IsNull()) { + return; + } + for (TopExp_Explorer xp(d, TopAbs_FACE); xp.More(); xp.Next()) { + shapes.push_back(xp.Current()); + } + for (TopExp_Explorer xp(d, TopAbs_EDGE, TopAbs_FACE); xp.More(); xp.Next()) { + shapes.push_back(xp.Current()); + } + for (TopExp_Explorer xp(d, TopAbs_VERTEX, TopAbs_EDGE); xp.More(); xp.Next()) { + shapes.push_back(xp.Current()); + } +} + +void ShapeMapper::populate(MappingStatus status, + const TopTools_ListOfShape& src, + const TopTools_ListOfShape& dst) +{ + for (TopTools_ListIteratorOfListOfShape it(src); it.More(); it.Next()) { + populate(status, it.Value(), dst); + } +} + +void ShapeMapper::populate(MappingStatus status, + const TopoShape& src, + const TopTools_ListOfShape& dst) +{ + if (src.isNull()) { + return; + } + std::vector dstShapes; + for (TopTools_ListIteratorOfListOfShape it(dst); it.More(); it.Next()) { + expand(it.Value(), dstShapes); + } + insert(status, src.getShape(), dstShapes); +} + +void ShapeMapper::insert(MappingStatus status, const TopoDS_Shape& s, const TopoDS_Shape& d) +{ + if (s.IsNull() || d.IsNull()) { + return; + } + // Prevent an element shape from being both generated and modified + if (status == MappingStatus::Generated) { + if (_modifiedShapes.count(d)) { + return; + } + _generatedShapes.insert(d); + } + else { + if (_generatedShapes.count(d)) { + return; + } + _modifiedShapes.insert(d); + } + auto& entry = (status == MappingStatus::Generated) ? _generated[s] : _modified[s]; + if (entry.shapeSet.insert(d).second) { + entry.shapes.push_back(d); + } +}; + +void ShapeMapper::insert(MappingStatus status, + const TopoDS_Shape& s, + const std::vector& d) +{ + if (s.IsNull() || d.empty()) { + return; + } + auto& entry = (status == MappingStatus::Generated) ? _generated[s] : _modified[s]; + for (auto& shape : d) { + // Prevent an element shape from being both generated and modified + if (status == MappingStatus::Generated) { + if (_modifiedShapes.count(shape)) { + continue; + } + _generatedShapes.insert(shape); + } + else { + if (_generatedShapes.count(shape)) { + continue; + } + _modifiedShapes.insert(shape); + } + if (entry.shapeSet.insert(shape).second) { + entry.shapes.push_back(shape); + } + } +}; + +} // namespace Part diff --git a/src/Mod/Part/App/TopoShapeMapper.h b/src/Mod/Part/App/TopoShapeMapper.h new file mode 100644 index 0000000000..a37d596798 --- /dev/null +++ b/src/Mod/Part/App/TopoShapeMapper.h @@ -0,0 +1,205 @@ +#include +#include +#include + +#include +#include +#include +#include +#include "TopoShape.h" + +class BRepBuilderAPI_MakeShape; +class BRepTools_History; +class BRepTools_ReShape; +class ShapeFix_Root; + +namespace Part +{ + +enum class MappingStatus +{ + Generated, + Modified +}; +/** Shape mapper for user defined shape mapping + */ +struct PartExport ShapeMapper: TopoShape::Mapper +{ + virtual ~ShapeMapper() noexcept = default; + + /** Populate mapping from a source shape to a list of shape + * + * @param status: whether the shape is generated + * @param src: source shape + * @param dst: a list of sub shapes in the new shape + * + * The source will be expanded into sub shapes of faces, edges and vertices + * before being inserted into the map. + */ + void populate(MappingStatus status, const TopoShape& src, const TopTools_ListOfShape& dst); + /** Populate mapping from a source sub shape to a list of shape + * + * @param status: whether the shape is generated + * @param src: a list of sub shapes in the source shape + * @param dst: a list of sub shapes in the new shape + * + * The source will be expanded into sub shapes of faces, edges and vertices + * before being inserted into the map. + */ + void populate(MappingStatus status, + const TopTools_ListOfShape& src, + const TopTools_ListOfShape& dst); + + /** Populate mapping from a source sub shape to a list of shape + * + * @param status: whether the shape is generated + * @param src: a list of sub shapes in the source shape + * @param dst: a list of sub shapes in the new shape + * + * The source will be expanded into sub shapes of faces, edges and vertices + * before being inserted into the map. + */ + void populate(MappingStatus status, + const std::vector& src, + const std::vector& dst) + { + for (auto& s : src) { + populate(status, s, dst); + } + } + + /** Populate mapping from a source sub shape to a list of shape + * + * @param status: whether the shape is generated + * @param src: a sub shape of the source shape + * @param dst: a list of sub shapes in the new shape + * + * The source will be expanded into sub shapes of faces, edges and vertices + * before being inserted into the map. + */ + void populate(MappingStatus status, const TopoShape& src, const std::vector& dst) + { + if (src.isNull()) { + return; + } + std::vector dstShapes; + for (auto& d : dst) { + expand(d.getShape(), dstShapes); + } + insert(status, src.getShape(), dstShapes); + } + + /** Expand a shape into faces, edges and vertices + * @params d: shape to expand + * @param shapes: output sub shapes of faces, edges and vertices + */ + void expand(const TopoDS_Shape& d, std::vector& shapes); + + /** Insert a map entry from a sub shape in the source to a list of sub shapes in the new shape + * + * @params status: whether the sub shapes are generated or modified + * @param s: a sub shape in the source + * @param d: a list of sub shapes in the new shape + */ + void insert(MappingStatus status, const TopoDS_Shape& s, const std::vector& d); + + /** Insert a map entry from a sub shape in the source to a sub shape in the new shape + * + * @params status: whether the sub shapes are generated or modified + * @param s: a sub shape in the source + * @param d: a list of sub shapes in the new shape + */ + void insert(MappingStatus status, const TopoDS_Shape& s, const TopoDS_Shape& d); + + const std::vector& generated(const TopoDS_Shape& s) const override + { + auto iter = _generated.find(s); + if (iter != _generated.end()) { + return iter->second.shapes; + } + return _res; + } + + const std::vector& modified(const TopoDS_Shape& s) const override + { + auto iter = _modified.find(s); + if (iter != _modified.end()) { + return iter->second.shapes; + } + return _res; + } + + std::vector shapes; + std::unordered_set shapeSet; + + struct ShapeValue + { + std::vector shapes; + std::unordered_set shapeSet; + }; + typedef std::unordered_map ShapeMap; + ShapeMap _generated; + std::unordered_set _generatedShapes; + ShapeMap _modified; + std::unordered_set _modifiedShapes; +}; + +/// Parameters for TopoShape::makeElementFilledFace() +struct PartExport TopoShape::BRepFillingParams +{ + /** Optional initial surface to begin the construction of the surface for the filled face. + * + * It is useful if the surface resulting from construction for the + * algorithm is likely to be complex. The support surface of the face + * under construction is computed by a deformation of Surf which satisfies + * the given constraints. The set of bounding edges defines the wire of + * the face. If no initial surface is given, the algorithm computes it + * automatically. If the set of edges is not connected (Free constraint), + * missing edges are automatically computed. Important: the initial + * surface must have orthogonal local coordinates, i.e. partial + * derivatives dS/du and dS/dv must be orthogonal at each point of + * surface. If this condition breaks, distortions of resulting surface are + * possible + */ + TopoShape surface; + /** Optional map from input edge to continutity order. The default + * continuity order is TopoShape::Continuity::C0. + */ + std::unordered_map orders; + /// Optional map from input shape to face used as support + std::unordered_map supports; + /// Optional begin index to the input shapes to be used as the boundary of the filled face. + int boundary_begin = -1; + /// Optional end index (last index + 1) to the input shapes to be used as the boundary of the + /// filled face. + int boundary_end = -1; + /// The energe minimizing criterion degree; + unsigned int degree = 3; + /// The number of points on the curve NbPntsOnCur + unsigned int ptsoncurve = 15; + /// The number of iterations NbIter + unsigned int numiter = 2; + /// The Boolean Anisotropie + bool anisotropy = false; + /// The 2D tolerance Tol2d + double tol2d = 1e-5; + /// The 3D tolerance Tol3d + double tol3d = 1e-4; + /// The angular tolerance TolAng + double tolG1 = 0.01; + /// The tolerance for curvature TolCur + double tolG2 = 0.1; + /// The highest polynomial degree MaxDeg + unsigned int maxdeg = 8; + /** The greatest number of segments MaxSeg. + * + * If the Boolean Anistropie is true, the algorithm's performance is better + * in cases where the ratio of the length U and the length V indicate a + * great difference between the two. In other words, when the surface is, + * for example, extremely long. + */ + unsigned int maxseg = 9; +}; + + +} // namespace Part diff --git a/src/Mod/Part/CMakeLists.txt b/src/Mod/Part/CMakeLists.txt index b7502d6e77..767b1a43d9 100644 --- a/src/Mod/Part/CMakeLists.txt +++ b/src/Mod/Part/CMakeLists.txt @@ -69,6 +69,7 @@ set(Part_tests parttests/regression_tests.py parttests/TopoShapeListTest.py parttests/ColorPerFaceTest.py + parttests/ColorTransparencyTest.py ) add_custom_target(PartScripts ALL SOURCES diff --git a/src/Mod/Part/Gui/DlgProjectionOnSurface.cpp b/src/Mod/Part/Gui/DlgProjectionOnSurface.cpp index 4d0704f981..c36b7b5ef3 100644 --- a/src/Mod/Part/Gui/DlgProjectionOnSurface.cpp +++ b/src/Mod/Part/Gui/DlgProjectionOnSurface.cpp @@ -638,6 +638,7 @@ void PartGui::DlgProjectionOnSurface::show_projected_shapes(const std::vectorLineColor.setValue(0x8ae23400); vp->ShapeColor.setValue(0x8ae23400); vp->PointColor.setValue(0x8ae23400); + vp->Transparency.setValue(0); } } diff --git a/src/Mod/Part/Gui/PreCompiled.h b/src/Mod/Part/Gui/PreCompiled.h index f88b6d206d..4758d16e94 100644 --- a/src/Mod/Part/Gui/PreCompiled.h +++ b/src/Mod/Part/Gui/PreCompiled.h @@ -62,13 +62,8 @@ #include #include -// Qt Toolkit -#ifndef __QtAll__ -# include -#endif - // GL -// Include glext before InventorAll +// Include glext before QtAll/InventorAll #ifdef FC_OS_WIN32 # include # include @@ -87,6 +82,11 @@ // Should come after glext.h to avoid warnings #include +// Qt Toolkit +#ifndef __QtAll__ +# include +#endif + // Inventor includes OpenGL #ifndef __InventorAll__ # include diff --git a/src/Mod/Part/TestPartGui.py b/src/Mod/Part/TestPartGui.py index 9527da2fbe..dc207cc831 100644 --- a/src/Mod/Part/TestPartGui.py +++ b/src/Mod/Part/TestPartGui.py @@ -44,6 +44,7 @@ def findDockWidget(name): #--------------------------------------------------------------------------- """ from parttests.ColorPerFaceTest import ColorPerFaceTest +from parttests.ColorTransparencyTest import ColorTransparencyTest #class PartGuiTestCases(unittest.TestCase): diff --git a/src/Mod/Part/parttests/ColorTransparencyTest.py b/src/Mod/Part/parttests/ColorTransparencyTest.py new file mode 100644 index 0000000000..f948bdac8a --- /dev/null +++ b/src/Mod/Part/parttests/ColorTransparencyTest.py @@ -0,0 +1,58 @@ +import unittest + +import FreeCAD as App + + +class ColorTransparencyTest(unittest.TestCase): + + def setUp(self): + self._doc = App.newDocument() + self._pg = App.ParamGet('User parameter:BaseApp/Preferences/View') + self._backup_default_transparency = self._pg.GetInt('DefaultShapeTransparency') + self._backup_default_shapecolor = self._pg.GetUnsigned('DefaultShapeColor') + + + def tearDown(self): + App.closeDocument(self._doc.Name) + self._pg.SetInt('DefaultShapeTransparency', self._backup_default_transparency) + self._pg.SetUnsigned('DefaultShapeColor', self._backup_default_shapecolor) + + + def test_default_shape_transparency(self): + """ + related: https://github.com/FreeCAD/FreeCAD/pull/11866 + related: https://github.com/FreeCAD/FreeCAD/pull/11586 + """ + transparency = 70 + self._pg.SetInt('DefaultShapeTransparency', transparency) + obj = self._doc.addObject('Part::Box') + assert obj.ViewObject.Transparency == transparency + obj.ViewObject.ShapeColor = (0.5, 0.0, 0.0) + + self.assertEqual(obj.ViewObject.Transparency, transparency, + 'transparency was unexpectedly changed to {} when changing the color.'.format( + obj.ViewObject.Transparency)) + + + def test_default_shape_color(self): + """ + related: https://github.com/FreeCAD/FreeCAD/pull/11866 + """ + self._pg.SetUnsigned('DefaultShapeColor', 0xff000000) # red + obj = self._doc.addObject('Part::Box') + + self.assertEqual(obj.ViewObject.ShapeColor, (1.0, 0.0, 0.0, 0.0), + 'default shape color was not set correctly') + + + def test_app_plane_transparency(self): + """ + related: https://github.com/FreeCAD/FreeCAD/pull/12064 + """ + self._pg.SetInt('DefaultShapeTransparency', 70) + obj = self._doc.addObject('App::Origin') + t = self._doc.findObjects('App::Plane')[0].ViewObject.Transparency + + self.assertEqual(t, 0, + 'transparency of App::Plane object is {} instead of 0'.format(t)) + diff --git a/src/Mod/PartDesign/App/FeatureLinearPattern.cpp b/src/Mod/PartDesign/App/FeatureLinearPattern.cpp index 256ea7d29b..2b95e1aca1 100644 --- a/src/Mod/PartDesign/App/FeatureLinearPattern.cpp +++ b/src/Mod/PartDesign/App/FeatureLinearPattern.cpp @@ -221,7 +221,7 @@ const std::list LinearPattern::getTransformations(const std::vector #include #include +#include #include #include "Area.h" @@ -496,47 +497,169 @@ void Area::add(const TopoDS_Shape& shape, short op) { myShapes.emplace_back(op, shape); } -std::shared_ptr Area::getClearedArea(double tipDiameter, double diameter) { +class ClearedAreaSegmentVisitor : public PathSegmentVisitor +{ +private: + CArea pathSegments; + CArea holes; + double maxZ; + double radius; + Base::BoundBox3d bbox; + + void point(const Base::Vector3d &p) + { + if (p.z <= maxZ) { + if (bbox.MinX <= p.x && p.x <= bbox.MaxX && bbox.MinY <= p.y && p.y <= bbox.MaxY) { + CCurve curve; + curve.append(CVertex{{p.x + radius, p.y}}); + curve.append(CVertex{1, {p.x - radius, p.y}, {p.x, p.y}}); + curve.append(CVertex{1, {p.x + radius, p.y}, {p.x, p.y}}); + holes.append(curve); + } + } + } + + void line(const Base::Vector3d &last, const Base::Vector3d &next) + { + if (last.z <= maxZ && next.z <= maxZ) { + Base::BoundBox2d segBox = {}; + segBox.Add({last.x, last.y}); + segBox.Add({next.x, next.y}); + if (bbox.Intersect(segBox)) { + CCurve curve; + curve.append(CVertex{{last.x, last.y}}); + curve.append(CVertex{{next.x, next.y}}); + pathSegments.append(curve); + } + } + } +public: + ClearedAreaSegmentVisitor(double maxZ, double radius, Base::BoundBox3d bbox) : maxZ(maxZ), radius(radius), bbox(bbox) + { + bbox.Enlarge(radius); + } + + CArea getClearedArea() + { + CArea result{pathSegments}; + result.Thicken(radius); + result.Union(holes); + return result; + } + + void g0(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts) override + { + (void)id; + (void)pts; + line(last, next); + } + + void g1(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts) override + { + (void)id; + (void)pts; + line(last, next); + } + + void g23(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts, const Base::Vector3d ¢er) override + { + (void)id; + (void)center; + + // Compute cw vs ccw + const Base::Vector3d vdirect = next - last; + const Base::Vector3d vstep = pts[0] - last; + const bool ccw = vstep.x * vdirect.y - vstep.y * vdirect.x > 0; + + // Add an arc + CCurve curve; + curve.append(CVertex{{last.x, last.y}}); + curve.append(CVertex{ccw ? 1 : -1, {next.x, next.y}, {center.x, center.y}}); + pathSegments.append(curve); + } + + void g8x(int id, const Base::Vector3d &last, const Base::Vector3d &next, const std::deque &pts, + const std::deque &plist, const std::deque &qlist) override + { + // (peck) drilling + (void)id; + (void)qlist; // pecks are always within the bounds of plist + + point(last); + for (const auto p : pts) { + point(p); + } + for (const auto p : plist) { + point(p); + } + point(next); + } + void g38(int id, const Base::Vector3d &last, const Base::Vector3d &next) override + { + // probe operation; clears nothing + (void)id; + (void)last; + (void)next; + } +}; + +std::shared_ptr Area::getClearedArea(const Toolpath *path, double diameter, double zmax, Base::BoundBox3d bbox) { build(); -#define AREA_MY(_param) myParams.PARAM_FNAME(_param) - PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_OFFSET_CONF); - PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_CLIPPER_FILL); - (void)SubjectFill; - (void)ClipFill; + + // Precision losses in arc/segment conversions (multiples of Accuracy): + // 2.3 in generation of gcode (see documentation in the implementation of CCurve::CheckForArc (libarea/Curve.cpp) + // 1 in gcode arc to segment + // 1 in Thicken() cleared area + // 2 in getRestArea target area offset in and back out + // Oversize cleared areas by buffer to smooth out imprecision in arc/segment conversion. getRestArea() will compensate for this + AreaParams params = myParams; + params.Accuracy = myParams.Accuracy * .7/4; // 2.3 already encoded in gcode; 4 * .7/4 = 3 total + params.SubjectFill = ClipperLib::pftNonZero; + params.ClipFill = ClipperLib::pftNonZero; + const double buffer = myParams.Accuracy * 3; // Do not fit arcs after these offsets; it introduces unnecessary approximation error, and all off // those arcs will be converted back to segments again for clipper differencing in getRestArea anyway - CAreaConfig conf(myParams, /*no_fit_arcs*/ true); + CAreaConfig conf(params, /*no_fit_arcs*/ true); - const double roundPrecision = myParams.Accuracy; - const double buffer = 2 * roundPrecision; + ClearedAreaSegmentVisitor visitor(zmax, diameter/2 + buffer, bbox); + PathSegmentWalker walker(*path); + walker.walk(visitor, Base::Vector3d(0, 0, zmax + 1)); - // A = myArea - // prevCenters = offset(A, -rTip) - const double rTip = tipDiameter / 2.; - CArea prevCenter(*myArea); - prevCenter.OffsetWithClipper(-rTip, JoinType, EndType, myParams.MiterLimit, roundPrecision); - - // prevCleared = offset(prevCenter, r). - CArea prevCleared(prevCenter); - prevCleared.OffsetWithClipper(diameter / 2. + buffer, JoinType, EndType, myParams.MiterLimit, roundPrecision); - - std::shared_ptr clearedArea = make_shared(*this); - clearedArea->myArea.reset(new CArea(prevCleared)); + std::shared_ptr clearedArea = make_shared(¶ms); + clearedArea->myTrsf = {}; + const CArea ca = visitor.getClearedArea(); + if (ca.m_curves.size() > 0) { + TopoDS_Shape clearedAreaShape = Area::toShape(ca, false); + clearedArea->add(clearedAreaShape, OperationCompound); + clearedArea->build(); + } else { + clearedArea->myArea = std::make_unique(); + clearedArea->myAreaOpen = std::make_unique(); + } return clearedArea; } std::shared_ptr Area::getRestArea(std::vector> clearedAreas, double diameter) { build(); +#define AREA_MY(_param) myParams.PARAM_FNAME(_param) PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_OFFSET_CONF); PARAM_ENUM_CONVERT(AREA_MY, PARAM_FNAME, PARAM_ENUM_EXCEPT, AREA_PARAMS_CLIPPER_FILL); - const double roundPrecision = myParams.Accuracy; - const double buffer = 2 * roundPrecision; + // Precision losses in arc/segment conversions (multiples of Accuracy): + // 2.3 in generation of gcode (see documentation in the implementation of CCurve::CheckForArc (libarea/Curve.cpp) + // 1 in gcode arc to segment + // 1 in Thicken() cleared area + // 2 in getRestArea target area offset in and back out + // Cleared area representations are oversized by buffer to smooth out imprecision in arc/segment conversion. getRestArea() will compensate for this + AreaParams params = myParams; + params.Accuracy = myParams.Accuracy * .7/4; // 2.3 already encoded in gcode; 4 * .7/4 = 3 total + const double buffer = myParams.Accuracy * 3; + const double roundPrecision = params.Accuracy; // transform all clearedAreas into our workplane - Area clearedAreasInPlane(&myParams); + Area clearedAreasInPlane(¶ms); clearedAreasInPlane.myArea.reset(new CArea()); for (std::shared_ptr clearedArea : clearedAreas) { gp_Trsf trsf = clearedArea->myTrsf; @@ -547,18 +670,28 @@ std::shared_ptr Area::getRestArea(std::vector> clear &myWorkPlane); } - // remaining = A - prevCleared - CArea remaining(*myArea); + // clearable = offset(offset(A, -dTool/2), dTool/2) + CArea clearable(*myArea); + clearable.OffsetWithClipper(-diameter/2, JoinType, EndType, params.MiterLimit, roundPrecision); + clearable.OffsetWithClipper(diameter/2, JoinType, EndType, params.MiterLimit, roundPrecision); + + // remaining = clearable - prevCleared + CArea remaining(clearable); remaining.Clip(toClipperOp(Area::OperationDifference), &*(clearedAreasInPlane.myArea), SubjectFill, ClipFill); - // rest = intersect(A, offset(remaining, dTool)) + // rest = intersect(clearable, offset(remaining, dTool)) + // add buffer to dTool to compensate for oversizing in getClearedArea CArea restCArea(remaining); - restCArea.OffsetWithClipper(diameter + buffer, JoinType, EndType, myParams.MiterLimit, roundPrecision); - restCArea.Clip(toClipperOp(Area::OperationIntersection), &*myArea, SubjectFill, ClipFill); + restCArea.OffsetWithClipper(diameter + buffer, JoinType, EndType, params.MiterLimit, roundPrecision); + restCArea.Clip(toClipperOp(Area::OperationIntersection), &clearable, SubjectFill, ClipFill); + if(restCArea.m_curves.size() == 0) { + return {}; + } + + std::shared_ptr restArea = make_shared(¶ms); gp_Trsf trsf(myTrsf.Inverted()); TopoDS_Shape restShape = Area::toShape(restCArea, false, &trsf); - std::shared_ptr restArea = make_shared(&myParams); restArea->add(restShape, OperationCompound); return restArea; diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 5b192ae08a..e79a62577d 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -242,7 +242,7 @@ public: const std::vector& heights = std::vector(), const TopoDS_Shape& plane = TopoDS_Shape()); - std::shared_ptr getClearedArea(double tipDiameter, double diameter); + std::shared_ptr getClearedArea(const Toolpath *path, double diameter, double zmax, Base::BoundBox3d bbox); std::shared_ptr getRestArea(std::vector> clearedAreas, double diameter); TopoDS_Shape toTopoShape(); diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index 817aed0e37..bbfb1b5088 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -22,11 +22,13 @@ #include "PreCompiled.h" +#include #include #include #include // inclusion of the generated files (generated out of AreaPy.xml) +#include "PathPy.h" #include "AreaPy.h" #include "AreaPy.cpp" @@ -149,8 +151,8 @@ static const PyMethodDef areaOverrides[] = { }, { "getClearedArea",nullptr,0, - "getClearedArea(tipDiameter, diameter):\n" - "Gets the area cleared when a tool maximally clears this area. This method assumes a tool tip diameter 'tipDiameter' traces the full area, and that (perhaps at a different height on the tool) this clears a different region with tool diameter 'diameter'.\n", + "getClearedArea(path, diameter, zmax, bbox):\n" + "Gets the area cleared when a tool of the specified diameter follows the gcode represented in the path, ignoring cleared space above zmax and path segments that don't affect space within the x/y space of bbox.\n", }, { "getRestArea",nullptr,0, @@ -406,10 +408,21 @@ PyObject* AreaPy::makeSections(PyObject *args, PyObject *keywds) PyObject* AreaPy::getClearedArea(PyObject *args) { PY_TRY { - double tipDiameter, diameter; - if (!PyArg_ParseTuple(args, "dd", &tipDiameter, &diameter)) + PyObject *pyPath, *pyBbox; + double diameter, zmax; + if (!PyArg_ParseTuple(args, "OddO", &pyPath, &diameter, &zmax, &pyBbox)) return nullptr; - std::shared_ptr clearedArea = getAreaPtr()->getClearedArea(tipDiameter, diameter); + if (!PyObject_TypeCheck(pyPath, &(PathPy::Type))) { + PyErr_SetString(PyExc_TypeError, "path must be of type PathPy"); + return nullptr; + } + if (!PyObject_TypeCheck(pyBbox, &(Base::BoundBoxPy::Type))) { + PyErr_SetString(PyExc_TypeError, "bbox must be of type BoundBoxPy"); + return nullptr; + } + const PathPy *path = static_cast(pyPath); + const Py::BoundingBox bbox(pyBbox, false); + std::shared_ptr clearedArea = getAreaPtr()->getClearedArea(path->getToolpathPtr(), diameter, zmax, bbox.getValue()); auto pyClearedArea = Py::asObject(new AreaPy(new Area(*clearedArea, true))); return Py::new_reference_to(pyClearedArea); } PY_CATCH_OCC @@ -440,6 +453,9 @@ PyObject* AreaPy::getRestArea(PyObject *args) } std::shared_ptr restArea = getAreaPtr()->getRestArea(clearedAreas, diameter); + if (!restArea) { + return Py_None; + } auto pyRestArea = Py::asObject(new AreaPy(new Area(*restArea, true))); return Py::new_reference_to(pyRestArea); } PY_CATCH_OCC diff --git a/src/Mod/Path/Path/Main/Gui/Job.py b/src/Mod/Path/Path/Main/Gui/Job.py index aec6fe875f..faa6b1c2e9 100644 --- a/src/Mod/Path/Path/Main/Gui/Job.py +++ b/src/Mod/Path/Path/Main/Gui/Job.py @@ -621,25 +621,29 @@ class TaskPanel: vUnit = FreeCAD.Units.Quantity(1, FreeCAD.Units.Velocity).getUserPreferred()[2] self.form.toolControllerList.horizontalHeaderItem(1).setText("#") self.form.toolControllerList.horizontalHeaderItem(2).setText( - translate("Path", "H","H is horizontal feed rate. Must be as short as possible") + translate( + "Path", "H", "H is horizontal feed rate. Must be as short as possible" + ) ) self.form.toolControllerList.horizontalHeaderItem(3).setText( - translate("Path", "V","V is vertical feed rate. Must be as short as possible") + translate( + "Path", "V", "V is vertical feed rate. Must be as short as possible" + ) ) self.form.toolControllerList.horizontalHeader().setResizeMode( 0, QtGui.QHeaderView.Stretch ) self.form.toolControllerList.horizontalHeaderItem(1).setToolTip( - translate("Path", "Tool number") + ' ' + translate("Path", "Tool number") + " " ) self.form.toolControllerList.horizontalHeaderItem(2).setToolTip( - translate("Path", "Horizontal feedrate")+ ' ' + vUnit + translate("Path", "Horizontal feedrate") + " " + vUnit ) self.form.toolControllerList.horizontalHeaderItem(3).setToolTip( - translate("Path", "Vertical feedrate")+ ' ' + vUnit + translate("Path", "Vertical feedrate") + " " + vUnit ) self.form.toolControllerList.horizontalHeaderItem(4).setToolTip( - translate("Path", "Spindle RPM")+ ' ' + translate("Path", "Spindle RPM") + " " ) # ensure correct ellisis behaviour on tool controller names. @@ -804,11 +808,31 @@ class TaskPanel: self.setupOps.getFields() def selectComboBoxText(self, widget, text): - index = widget.findText(text, QtCore.Qt.MatchFixedString) - if index >= 0: + """selectInComboBox(name, combo) ... + helper function to select a specific value in a combo box.""" + index = widget.currentIndex() # Save initial index + + # Search using currentData and return if found + newindex = widget.findData(text) + if newindex >= 0: + widget.blockSignals(True) - widget.setCurrentIndex(index) + widget.setCurrentIndex(newindex) widget.blockSignals(False) + return + + # if not found, search using current text + newindex = widget.findText(text, QtCore.Qt.MatchFixedString) + if newindex >= 0: + widget.blockSignals(True) + widget.setCurrentIndex(newindex) + widget.blockSignals(False) + return + + widget.blockSignals(True) + widget.setCurrentIndex(index) + widget.blockSignals(False) + return def updateToolController(self): tcRow = self.form.toolControllerList.currentRow() @@ -900,10 +924,9 @@ class TaskPanel: self.form.operationsList.addItem(item) self.form.jobModel.clear() - for name, count in Counter([ - self.obj.Proxy.baseObject(self.obj, o).Label - for o in self.obj.Model.Group - ]).items(): + for name, count in Counter( + [self.obj.Proxy.baseObject(self.obj, o).Label for o in self.obj.Model.Group] + ).items(): if count == 1: self.form.jobModel.addItem(name) else: diff --git a/src/Mod/Path/Path/Op/Area.py b/src/Mod/Path/Path/Op/Area.py index 8e329ae4b5..5a3756cacd 100644 --- a/src/Mod/Path/Path/Op/Area.py +++ b/src/Mod/Path/Path/Op/Area.py @@ -240,41 +240,22 @@ class ObjectOp(PathOp.ObjectOp): # Rest machining self.sectionShapes = self.sectionShapes + [section.toTopoShape() for section in sections] if hasattr(obj, "UseRestMachining") and obj.UseRestMachining: - # Loop through prior operations - clearedAreas = [] - foundSelf = False - for op in self.job.Operations.Group: - if foundSelf: - break - oplist = [op] + op.OutListRecursive - oplist = list(filter(lambda op: hasattr(op, "Active"), oplist)) - for op in oplist: - if op.Proxy == self: - # Ignore self, and all later operations - foundSelf = True - break - if hasattr(op, "RestMachiningRegions") and op.Active: - if hasattr(op, "RestMachiningRegionsNeedRecompute") and op.RestMachiningRegionsNeedRecompute: - Path.Log.warning( - translate("PathAreaOp", "Previous operation %s is required for rest machining, but it has no stored rest machining metadata. Recomputing to generate this metadata...") % op.Label - ) - op.recompute() - - tool = op.Proxy.tool if hasattr(op.Proxy, "tool") else op.ToolController.Proxy.getTool(op.ToolController) - diameter = tool.Diameter.getValueAs("mm") - def shapeToArea(shape): - area = Path.Area() - area.setPlane(PathUtils.makeWorkplane(shape)) - area.add(shape) - return area - opClearedAreas = [shapeToArea(pa).getClearedArea(diameter, diameter) for pa in op.RestMachiningRegions.SubShapes] - clearedAreas.extend(opClearedAreas) restSections = [] for section in sections: - z = section.getShape().BoundBox.ZMin - sectionClearedAreas = [a for a in clearedAreas if a.getShape().BoundBox.ZMax <= z] + bbox = section.getShape().BoundBox + z = bbox.ZMin + sectionClearedAreas = [] + for op in self.job.Operations.Group: + if self in [x.Proxy for x in [op] + op.OutListRecursive if hasattr(x, "Proxy")]: + break + if hasattr(op, "Active") and op.Active and op.Path: + tool = op.Proxy.tool if hasattr(op.Proxy, "tool") else op.ToolController.Proxy.getTool(op.ToolController) + diameter = tool.Diameter.getValueAs("mm") + dz = 0 if not hasattr(tool, "TipAngle") else -PathUtils.drillTipLength(tool) # for drills, dz translates to the full width part of the tool + sectionClearedAreas.append(section.getClearedArea(op.Path, diameter, z+dz+self.job.GeometryTolerance.getValueAs("mm"), bbox)) restSection = section.getRestArea(sectionClearedAreas, self.tool.Diameter.getValueAs("mm")) - restSections.append(restSection) + if (restSection is not None): + restSections.append(restSection) sections = restSections shapelist = [sec.getShape() for sec in sections] @@ -481,10 +462,6 @@ class ObjectOp(PathOp.ObjectOp): ) ) - if hasattr(obj, "RestMachiningRegions"): - obj.RestMachiningRegions = Part.makeCompound(self.sectionShapes) - if hasattr(obj, "RestMachiningRegionsNeedRecompute"): - obj.RestMachiningRegionsNeedRecompute = False Path.Log.debug("obj.Name: " + str(obj.Name) + "\n\n") return sims diff --git a/src/Mod/Path/Path/Op/PocketBase.py b/src/Mod/Path/Path/Op/PocketBase.py index 829dd865c3..9dc30836de 100644 --- a/src/Mod/Path/Path/Op/PocketBase.py +++ b/src/Mod/Path/Path/Op/PocketBase.py @@ -194,16 +194,6 @@ class ObjectPocket(PathAreaOp.ObjectOp): "Skips machining regions that have already been cleared by previous operations.", ), ) - obj.addProperty( - "Part::PropertyPartShape", - "RestMachiningRegions", - "Pocket", - QT_TRANSLATE_NOOP( - "App::Property", - "The areas cleared by this operation, one area per height, stored as a compound part. Used internally for rest machining.", - ), - ) - obj.setEditorMode("RestMachiningRegions", 2) # hide for n in self.pocketPropertyEnumerations(): setattr(obj, n[0], n[1]) @@ -277,29 +267,10 @@ class ObjectPocket(PathAreaOp.ObjectOp): ), ) - if not hasattr(obj, "RestMachiningRegions"): - obj.addProperty( - "Part::PropertyPartShape", - "RestMachiningRegions", - "Pocket", - QT_TRANSLATE_NOOP( - "App::Property", - "The areas cleared by this operation, one area per height, stored as a compound part. Used internally for rest machining.", - ), - ) - obj.setEditorMode("RestMachiningRegions", 2) # hide - - obj.addProperty( - "App::PropertyBool", - "RestMachiningRegionsNeedRecompute", - "Pocket", - QT_TRANSLATE_NOOP( - "App::Property", - "Flag to indicate that the rest machining regions have never been computed, and must be recomputed before being used.", - ), - ) - obj.setEditorMode("RestMachiningRegionsNeedRecompute", 2) # hide - obj.RestMachiningRegionsNeedRecompute = True + if hasattr(obj, "RestMachiningRegions"): + obj.removeProperty("RestMachiningRegions") + if hasattr(obj, "RestMachiningRegionsNeedRecompute"): + obj.removeProperty("RestMachiningRegionsNeedRecompute") Path.Log.track() diff --git a/src/Mod/Path/libarea/AreaClipper.cpp b/src/Mod/Path/libarea/AreaClipper.cpp index d3e9ff8397..1ee608bbba 100644 --- a/src/Mod/Path/libarea/AreaClipper.cpp +++ b/src/Mod/Path/libarea/AreaClipper.cpp @@ -252,12 +252,16 @@ static void MakeObround(const Point &pt0, const CVertex &vt1, double radius) static void OffsetSpansWithObrounds(const CArea& area, TPolyPolygon &pp_new, double radius) { Clipper c; - c.StrictlySimple(CArea::m_clipper_simple); - + c.StrictlySimple(CArea::m_clipper_simple); + pp_new.clear(); for(std::list::const_iterator It = area.m_curves.begin(); It != area.m_curves.end(); It++) { + c.Clear(); + c.AddPaths(pp_new, ptSubject, true); + pp_new.clear(); pts_for_AddVertex.clear(); + const CCurve& curve = *It; const CVertex* prev_vertex = NULL; for(std::list::const_iterator It2 = curve.m_vertices.begin(); It2 != curve.m_vertices.end(); It2++) @@ -278,10 +282,9 @@ static void OffsetSpansWithObrounds(const CArea& area, TPolyPolygon &pp_new, dou } prev_vertex = &vertex; } + c.Execute(ctUnion, pp_new, pftNonZero, pftNonZero); } - pp_new.clear(); - c.Execute(ctUnion, pp_new, pftNonZero, pftNonZero); // reverse all the resulting polygons TPolyPolygon copy = pp_new; diff --git a/src/Mod/Sketcher/App/PythonConverter.cpp b/src/Mod/Sketcher/App/PythonConverter.cpp index d063378161..506463ae62 100644 --- a/src/Mod/Sketcher/App/PythonConverter.cpp +++ b/src/Mod/Sketcher/App/PythonConverter.cpp @@ -59,11 +59,11 @@ std::string PythonConverter::convert(const Part::Geometry* geo, Mode mode) return command; } -std::string PythonConverter::convert(const Sketcher::Constraint* constraint) +std::string PythonConverter::convert(const Sketcher::Constraint* constraint, GeoIdMode geoIdMode) { // addConstraint(Sketcher.Constraint('Distance',%d,%f)) std::string command; - auto cg = process(constraint); + auto cg = process(constraint, geoIdMode); command = boost::str(boost::format("addConstraint(%s)\n") % cg); @@ -85,13 +85,13 @@ std::string PythonConverter::convert(const std::string& doc, if (ngeos > 0) { if (construction) { command = boost::str( - boost::format("constrGeoList = []\n%s\n%s.addGeometry(constrGeoList,%s)\ndel " - "constrGeoList") + boost::format("constrGeoList = []\n%s%s.addGeometry(constrGeoList,%s)\n" + "del constrGeoList\n") % geolist % doc % "True"); } else { command = boost::str( - boost::format("geoList = []\n%s\n%s.addGeometry(geoList,%s)\ndel geoList") + boost::format("geoList = []\n%s%s.addGeometry(geoList,%s)\ndel geoList\n") % geolist % doc % "False"); } } @@ -134,10 +134,10 @@ std::string PythonConverter::convert(const std::string& doc, if (sg.construction) { geolist = - boost::str(boost::format("%s\nconstrGeoList.append(%s)\n") % geolist % sg.creation); + boost::str(boost::format("%sconstrGeoList.append(%s)\n") % geolist % sg.creation); } else { - geolist = boost::str(boost::format("%s\ngeoList.append(%s)\n") % geolist % sg.creation); + geolist = boost::str(boost::format("%sgeoList.append(%s)\n") % geolist % sg.creation); } ngeos++; @@ -165,7 +165,8 @@ std::string PythonConverter::convert(const std::string& doc, } std::string PythonConverter::convert(const std::string& doc, - const std::vector& constraints) + const std::vector& constraints, + GeoIdMode geoIdMode) { if (constraints.size() == 1) { auto cg = convert(constraints[0]); @@ -176,7 +177,7 @@ std::string PythonConverter::convert(const std::string& doc, std::string constraintlist = "constraintList = []"; for (auto constraint : constraints) { - auto cg = process(constraint); + auto cg = process(constraint, geoIdMode); constraintlist = boost::str(boost::format("%s\nconstraintList.append(%s)") % constraintlist % cg); @@ -200,7 +201,8 @@ PythonConverter::SingleGeometry PythonConverter::process(const Part::Geometry* g auto sgeo = static_cast(geo); SingleGeometry sg; sg.creation = boost::str( - boost::format("Part.LineSegment(App.Vector(%f,%f,%f),App.Vector(%f,%f,%f))") + boost::format( + "Part.LineSegment(App.Vector(%f, %f, %f),App.Vector(%f, %f, %f))") % sgeo->getStartPoint().x % sgeo->getStartPoint().y % sgeo->getStartPoint().z % sgeo->getEndPoint().x % sgeo->getEndPoint().y % sgeo->getEndPoint().z); sg.construction = Sketcher::GeometryFacade::getConstruction(geo); @@ -225,7 +227,7 @@ PythonConverter::SingleGeometry PythonConverter::process(const Part::Geometry* g auto sgeo = static_cast(geo); SingleGeometry sg; sg.creation = - boost::str(boost::format("Part.Point(App.Vector(%f,%f,%f))") + boost::str(boost::format("Part.Point(App.Vector(%f, %f, %f))") % sgeo->getPoint().x % sgeo->getPoint().y % sgeo->getPoint().z); sg.construction = Sketcher::GeometryFacade::getConstruction(geo); return sg; @@ -309,10 +311,10 @@ PythonConverter::SingleGeometry PythonConverter::process(const Part::Geometry* g controlpoints.append(1, ']'); SingleGeometry sg; - sg.creation = - boost::str(boost::format("Part.BSplineCurve (%s,None,None,%s,%d,None,False)") - % controlpoints.c_str() % (bSpline->isPeriodic() ? "True" : "False") - % bSpline->getDegree()); + sg.creation = boost::str( + boost::format("Part.BSplineCurve (%s, None, None, %s, %d, None, False)") + % controlpoints.c_str() % (bSpline->isPeriodic() ? "True" : "False") + % bSpline->getDegree()); sg.construction = Sketcher::GeometryFacade::getConstruction(geo); return sg; }}, @@ -342,128 +344,159 @@ PythonConverter::SingleGeometry PythonConverter::process(const Part::Geometry* g return creator(geo); } -std::string PythonConverter::process(const Sketcher::Constraint* constraint) +std::string PythonConverter::process(const Sketcher::Constraint* constraint, GeoIdMode geoIdMode) { - static std::map> + bool addLastIdVar = geoIdMode == GeoIdMode::AddLastGeoIdToGeoIds; + std::string geoId1 = (addLastIdVar ? "lastGeoId + " : "") + std::to_string(constraint->First); + std::string geoId2 = (addLastIdVar ? "lastGeoId + " : "") + std::to_string(constraint->Second); + std::string geoId3 = (addLastIdVar ? "lastGeoId + " : "") + std::to_string(constraint->Third); + + static std::map< + const Sketcher::ConstraintType, + std::function< + std::string(const Sketcher::Constraint*, std::string&, std::string&, std::string&)>> converterMap = { {Sketcher::Coincident, - [](const Sketcher::Constraint* constr) { + [](const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { return boost::str( - boost::format("Sketcher.Constraint('Coincident', %i, %i, %i, %i)") - % constr->First % static_cast(constr->FirstPos) % constr->Second + boost::format("Sketcher.Constraint('Coincident', %s, %i, %s, %i)") % geoId1 + % static_cast(constr->FirstPos) % geoId2 % static_cast(constr->SecondPos)); }}, {Sketcher::Horizontal, - [](const Sketcher::Constraint* constr) { + [](const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { if (constr->Second == GeoEnum::GeoUndef) { - return boost::str(boost::format("Sketcher.Constraint('Horizontal', %i)") - % constr->First); + return boost::str(boost::format("Sketcher.Constraint('Horizontal', %s)") + % geoId1); } else { return boost::str( - boost::format("Sketcher.Constraint('Horizontal', %i, %i, %i, %i)") - % constr->First % static_cast(constr->FirstPos) % constr->Second + boost::format("Sketcher.Constraint('Horizontal', %s, %i, %s, %i)") % geoId1 + % static_cast(constr->FirstPos) % geoId2 % static_cast(constr->SecondPos)); } }}, {Sketcher::Vertical, - [](const Sketcher::Constraint* constr) { + [](const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { if (constr->Second == GeoEnum::GeoUndef) { - return boost::str(boost::format("Sketcher.Constraint('Vertical', %i)") - % constr->First); + return boost::str(boost::format("Sketcher.Constraint('Vertical', %s)") + % geoId1); } else { return boost::str( - boost::format("Sketcher.Constraint('Vertical', %i, %i, %i, %i)") - % constr->First % static_cast(constr->FirstPos) % constr->Second + boost::format("Sketcher.Constraint('Vertical', %s, %i, %s, %i)") % geoId1 + % static_cast(constr->FirstPos) % geoId2 % static_cast(constr->SecondPos)); } }}, {Sketcher::Block, - [](const Sketcher::Constraint* constr) { - return boost::str(boost::format("Sketcher.Constraint('Block', %i)") - % constr->First); + []([[maybe_unused]] const Sketcher::Constraint* constr, + std::string& geoId1, + [[maybe_unused]] std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { + return boost::str(boost::format("Sketcher.Constraint('Block', %s)") % geoId1); }}, {Sketcher::Tangent, - [](const Sketcher::Constraint* constr) { + [](const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { if (constr->FirstPos == Sketcher::PointPos::none) { - return boost::str(boost::format("Sketcher.Constraint('Tangent', %i, %i)") - % constr->First % constr->Second); + return boost::str(boost::format("Sketcher.Constraint('Tangent', %s, %s)") + % geoId1 % geoId2); } else if (constr->SecondPos == Sketcher::PointPos::none) { - return boost::str(boost::format("Sketcher.Constraint('Tangent', %i, %i, %i)") - % constr->First % static_cast(constr->FirstPos) - % constr->Second); + return boost::str(boost::format("Sketcher.Constraint('Tangent', %s, %i, %s)") + % geoId1 % static_cast(constr->FirstPos) % geoId2); } else { return boost::str( - boost::format("Sketcher.Constraint('Tangent', %i, %i, %i, %i)") - % constr->First % static_cast(constr->FirstPos) % constr->Second + boost::format("Sketcher.Constraint('Tangent', %s, %i, %s, %i)") % geoId1 + % static_cast(constr->FirstPos) % geoId2 % static_cast(constr->SecondPos)); } }}, {Sketcher::Parallel, - [](const Sketcher::Constraint* constr) { - return boost::str(boost::format("Sketcher.Constraint('Parallel', %i, %i)") - % constr->First % constr->Second); + []([[maybe_unused]] const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { + return boost::str(boost::format("Sketcher.Constraint('Parallel', %s, %s)") % geoId1 + % geoId2); }}, {Sketcher::Perpendicular, - [](const Sketcher::Constraint* constr) { + []([[maybe_unused]] const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { if (constr->FirstPos == Sketcher::PointPos::none) { - return boost::str(boost::format("Sketcher.Constraint('Perpendicular', %i, %i)") - % constr->First % constr->Second); + return boost::str(boost::format("Sketcher.Constraint('Perpendicular', %s, %s)") + % geoId1 % geoId2); } else if (constr->SecondPos == Sketcher::PointPos::none) { return boost::str( - boost::format("Sketcher.Constraint('Perpendicular', %i, %i, %i)") - % constr->First % static_cast(constr->FirstPos) % constr->Second); + boost::format("Sketcher.Constraint('Perpendicular', %s, %i, %s)") % geoId1 + % static_cast(constr->FirstPos) % geoId2); } else { return boost::str( - boost::format("Sketcher.Constraint('Perpendicular', %i, %i, %i, %i)") - % constr->First % static_cast(constr->FirstPos) % constr->Second + boost::format("Sketcher.Constraint('Perpendicular', %s, %i, %s, %i)") + % geoId1 % static_cast(constr->FirstPos) % geoId2 % static_cast(constr->SecondPos)); } }}, {Sketcher::Equal, - [](const Sketcher::Constraint* constr) { - return boost::str(boost::format("Sketcher.Constraint('Equal', %i, %i)") - % constr->First % constr->Second); + []([[maybe_unused]] const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { + return boost::str(boost::format("Sketcher.Constraint('Equal', %s, %s)") % geoId1 + % geoId2); }}, {Sketcher::InternalAlignment, - [](const Sketcher::Constraint* constr) { + [](const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { if (constr->AlignmentType == EllipseMajorDiameter || constr->AlignmentType == EllipseMinorDiameter || constr->AlignmentType == HyperbolaMajor || constr->AlignmentType == HyperbolaMinor || constr->AlignmentType == ParabolaFocalAxis) { return boost::str( - boost::format("Sketcher.Constraint('InternalAlignment:%s', %i, %i)") - % constr->internalAlignmentTypeToString() % constr->First - % constr->Second); + boost::format("Sketcher.Constraint('InternalAlignment:%s', %s, %s)") + % constr->internalAlignmentTypeToString() % geoId1 % geoId2); } else if (constr->AlignmentType == EllipseFocus1 || constr->AlignmentType == EllipseFocus2 || constr->AlignmentType == HyperbolaFocus || constr->AlignmentType == ParabolaFocus) { return boost::str( - boost::format("Sketcher.Constraint('InternalAlignment:%s', %i, %i, %i)") - % constr->internalAlignmentTypeToString() % constr->First - % static_cast(constr->FirstPos) % constr->Second); + boost::format("Sketcher.Constraint('InternalAlignment:%s', %s, %i, %s)") + % constr->internalAlignmentTypeToString() % geoId1 + % static_cast(constr->FirstPos) % geoId2); } else if (constr->AlignmentType == BSplineControlPoint) { return boost::str( boost::format( - "Sketcher.Constraint('InternalAlignment:%s', %i, %i, %i, %i)") - % constr->internalAlignmentTypeToString() % constr->First - % static_cast(constr->FirstPos) % constr->Second + "Sketcher.Constraint('InternalAlignment:%s', %s, %i, %s, %i)") + % constr->internalAlignmentTypeToString() % geoId1 + % static_cast(constr->FirstPos) % geoId2 % constr->InternalAlignmentIndex); } else if (constr->AlignmentType == BSplineKnotPoint) { return boost::str( - boost::format("Sketcher.Constraint('InternalAlignment:%s', %i, 1, %i, %i)") - % constr->internalAlignmentTypeToString() % constr->First % constr->Second + boost::format("Sketcher.Constraint('InternalAlignment:%s', %s, 1, %s, %i)") + % constr->internalAlignmentTypeToString() % geoId1 % geoId2 % constr->InternalAlignmentIndex); } @@ -471,124 +504,160 @@ std::string PythonConverter::process(const Sketcher::Constraint* constraint) "PythonConverter: Constraint Alignment Type not supported") }}, {Sketcher::Distance, - [](const Sketcher::Constraint* constr) { + [](const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { if (constr->Second == GeoEnum::GeoUndef) { - return boost::str(boost::format("Sketcher.Constraint('Distance', %i, %f)") - % constr->First % constr->getValue()); + return boost::str(boost::format("Sketcher.Constraint('Distance', %s, %f)") + % geoId1 % 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()); + return boost::str(boost::format("Sketcher.Constraint('Distance', %s, %s, %f)") + % geoId1 % geoId2 % constr->getValue()); } else if (constr->SecondPos == Sketcher::PointPos::none) { return boost::str( - boost::format("Sketcher.Constraint('Distance', %i, %i, %i, %f)") - % constr->First % static_cast(constr->FirstPos) % constr->Second - % constr->getValue()); + boost::format("Sketcher.Constraint('Distance', %s, %i, %s, %f)") % geoId1 + % static_cast(constr->FirstPos) % geoId2 % constr->getValue()); } else { return boost::str( - boost::format("Sketcher.Constraint('Distance', %i, %i, %i, %i, %f)") - % constr->First % static_cast(constr->FirstPos) % constr->Second + boost::format("Sketcher.Constraint('Distance', %s, %i, %s, %i, %f)") + % geoId1 % static_cast(constr->FirstPos) % geoId2 % static_cast(constr->SecondPos) % constr->getValue()); } }}, {Sketcher::Angle, - [](const Sketcher::Constraint* constr) { + [](const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + std::string& geoId3) { if (constr->Second == GeoEnum::GeoUndef) { - return boost::str(boost::format("Sketcher.Constraint('Angle', %i, %f)") - % constr->First % constr->getValue()); + return boost::str(boost::format("Sketcher.Constraint('Angle', %s, %f)") + % geoId1 % constr->getValue()); } - else if (constr->SecondPos == Sketcher::PointPos::none) { - return boost::str(boost::format("Sketcher.Constraint('Angle', %i, %i, %f)") - % constr->First % constr->Second % constr->getValue()); + else if (constr->Third == GeoEnum::GeoUndef) { + if (constr->SecondPos == Sketcher::PointPos::none) { + return boost::str(boost::format("Sketcher.Constraint('Angle', %s, %s, %f)") + % geoId1 % geoId2 % constr->getValue()); + } + else { + return boost::str( + boost::format("Sketcher.Constraint('Angle', %s, %i, %s, %i, %f)") + % geoId1 % static_cast(constr->FirstPos) % geoId2 + % static_cast(constr->SecondPos) % constr->getValue()); + } } else { return boost::str( - boost::format("Sketcher.Constraint('Angle', %i, %i, %i, %i, %f)") - % constr->First % static_cast(constr->FirstPos) % constr->Second - % static_cast(constr->SecondPos) % constr->getValue()); + boost::format("Sketcher.Constraint('AngleViaPoint', %s, %s, %s, %i, %f)") + % geoId1 % geoId2 % geoId3 % static_cast(constr->ThirdPos) + % constr->getValue()); } }}, {Sketcher::DistanceX, - [](const Sketcher::Constraint* constr) { + [](const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + std::string& geoId3) { if (constr->Second == GeoEnum::GeoUndef) { - return boost::str(boost::format("Sketcher.Constraint('DistanceX', %i, %f)") - % constr->First % constr->getValue()); + return boost::str(boost::format("Sketcher.Constraint('DistanceX', %s, %f)") + % geoId1 % constr->getValue()); } else if (constr->SecondPos == Sketcher::PointPos::none) { - return boost::str(boost::format("Sketcher.Constraint('DistanceX', %i, %i, %f)") - % constr->First % static_cast(constr->FirstPos) + return boost::str(boost::format("Sketcher.Constraint('DistanceX', %s, %i, %f)") + % geoId1 % static_cast(constr->FirstPos) % constr->getValue()); } else { return boost::str( - boost::format("Sketcher.Constraint('DistanceX', %i, %i, %i, %i, %f)") - % constr->First % static_cast(constr->FirstPos) % constr->Second + boost::format("Sketcher.Constraint('DistanceX', %s, %i, %s, %i, %f)") + % geoId1 % static_cast(constr->FirstPos) % geoId2 % static_cast(constr->SecondPos) % constr->getValue()); } }}, {Sketcher::DistanceY, - [](const Sketcher::Constraint* constr) { + [](const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { if (constr->Second == GeoEnum::GeoUndef) { - return boost::str(boost::format("Sketcher.Constraint('DistanceY', %i, %f)") - % constr->First % constr->getValue()); + return boost::str(boost::format("Sketcher.Constraint('DistanceY', %s, %f)") + % geoId1 % constr->getValue()); } else if (constr->SecondPos == Sketcher::PointPos::none) { - return boost::str(boost::format("Sketcher.Constraint('DistanceY', %i, %i, %f)") - % constr->First % static_cast(constr->FirstPos) + return boost::str(boost::format("Sketcher.Constraint('DistanceY', %s, %i, %f)") + % geoId1 % static_cast(constr->FirstPos) % constr->getValue()); } else { return boost::str( - boost::format("Sketcher.Constraint('DistanceY', %i, %i, %i, %i, %f)") - % constr->First % static_cast(constr->FirstPos) % constr->Second + boost::format("Sketcher.Constraint('DistanceY', %s, %i, %s, %i, %f)") + % geoId1 % static_cast(constr->FirstPos) % geoId2 % static_cast(constr->SecondPos) % constr->getValue()); } }}, {Sketcher::Radius, - [](const Sketcher::Constraint* constr) { - return boost::str(boost::format("Sketcher.Constraint('Radius', %i, %f)") - % constr->First % constr->getValue()); + [](const Sketcher::Constraint* constr, + std::string& geoId1, + [[maybe_unused]] std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { + return boost::str(boost::format("Sketcher.Constraint('Radius', %s, %f)") % geoId1 + % constr->getValue()); }}, {Sketcher::Diameter, - [](const Sketcher::Constraint* constr) { - return boost::str(boost::format("Sketcher.Constraint('Diameter', %i, %f)") - % constr->First % constr->getValue()); + [](const Sketcher::Constraint* constr, + std::string& geoId1, + [[maybe_unused]] std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { + return boost::str(boost::format("Sketcher.Constraint('Diameter', %s, %f)") % geoId1 + % constr->getValue()); }}, {Sketcher::Weight, - [](const Sketcher::Constraint* constr) { - return boost::str(boost::format("Sketcher.Constraint('Weight', %i, %f)") - % constr->First % constr->getValue()); + [](const Sketcher::Constraint* constr, + std::string& geoId1, + [[maybe_unused]] std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { + return boost::str(boost::format("Sketcher.Constraint('Weight', %s, %f)") % geoId1 + % constr->getValue()); }}, {Sketcher::PointOnObject, - [](const Sketcher::Constraint* constr) { - return boost::str(boost::format("Sketcher.Constraint('PointOnObject', %i, %i, %i)") - % constr->First % static_cast(constr->FirstPos) - % constr->Second); + [](const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + [[maybe_unused]] std::string& geoId3) { + return boost::str(boost::format("Sketcher.Constraint('PointOnObject', %s, %i, %s)") + % geoId1 % static_cast(constr->FirstPos) % geoId2); }}, {Sketcher::Symmetric, - [](const Sketcher::Constraint* constr) { + [](const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + std::string& geoId3) { if (constr->ThirdPos == Sketcher::PointPos::none) { return boost::str( - boost::format("Sketcher.Constraint('Symmetric', %i, %i, %i, %i, %i)") - % constr->First % static_cast(constr->FirstPos) % constr->Second - % static_cast(constr->SecondPos) % constr->Third); + boost::format("Sketcher.Constraint('Symmetric', %s, %i, %s, %i, %s)") + % geoId1 % static_cast(constr->FirstPos) % geoId2 + % static_cast(constr->SecondPos) % geoId3); } else { return boost::str( - boost::format("Sketcher.Constraint('Symmetric', %i, %i, %i, %i, %i, %i)") - % constr->First % static_cast(constr->FirstPos) % constr->Second - % static_cast(constr->SecondPos) % constr->Third + boost::format("Sketcher.Constraint('Symmetric', %s, %i, %s, %i, %s, %i)") + % geoId1 % static_cast(constr->FirstPos) % geoId2 + % static_cast(constr->SecondPos) % geoId3 % static_cast(constr->ThirdPos)); } }}, {Sketcher::SnellsLaw, - [](const Sketcher::Constraint* constr) { + [](const Sketcher::Constraint* constr, + std::string& geoId1, + std::string& geoId2, + std::string& geoId3) { return boost::str( - boost::format("Sketcher.Constraint('SnellsLaw', %i, %i, %i, %i, %i, %f)") - % constr->First % static_cast(constr->FirstPos) % constr->Second - % static_cast(constr->SecondPos) % constr->Third % constr->getValue()); + boost::format("Sketcher.Constraint('SnellsLaw', %s, %i, %s, %i, %s, %f)") + % geoId1 % static_cast(constr->FirstPos) % geoId2 + % static_cast(constr->SecondPos) % geoId3 % constr->getValue()); }}, }; @@ -600,7 +669,7 @@ std::string PythonConverter::process(const Sketcher::Constraint* constraint) auto creator = result->second; - return creator(constraint); + return creator(constraint, geoId1, geoId2, geoId3); } std::vector PythonConverter::multiLine(std::string&& singlestring) diff --git a/src/Mod/Sketcher/App/PythonConverter.h b/src/Mod/Sketcher/App/PythonConverter.h index 98535583b1..8062064589 100644 --- a/src/Mod/Sketcher/App/PythonConverter.h +++ b/src/Mod/Sketcher/App/PythonConverter.h @@ -24,6 +24,9 @@ #ifndef SKETCHER_PythonConverter_H #define SKETCHER_PythonConverter_H +#include +#include + #include namespace Part @@ -57,7 +60,13 @@ public: OmitInternalGeometry }; - explicit PythonConverter() = delete; + enum class GeoIdMode + { + DoNotChangeGeoIds, + AddLastGeoIdToGeoIds, + }; + + PythonConverter() = delete; ~PythonConverter() = delete; /// Convert a geometry into the string representing the command creating it @@ -68,17 +77,20 @@ public: const std::vector& geos, Mode mode = Mode::CreateInternalGeometry); - static std::string convert(const Sketcher::Constraint* constraint); + static std::string convert(const Sketcher::Constraint* constraint, + GeoIdMode geoIdMode = GeoIdMode::DoNotChangeGeoIds); static std::string convert(const std::string& doc, - const std::vector& constraints); + const std::vector& constraints, + GeoIdMode geoIdMode = GeoIdMode::DoNotChangeGeoIds); static std::vector multiLine(std::string&& singlestring); private: static SingleGeometry process(const Part::Geometry* geo); - static std::string process(const Sketcher::Constraint* constraint); + static std::string process(const Sketcher::Constraint* constraint, + GeoIdMode geoIdMode = GeoIdMode::DoNotChangeGeoIds); }; } // namespace Sketcher diff --git a/src/Mod/Sketcher/Gui/AppSketcherGui.cpp b/src/Mod/Sketcher/Gui/AppSketcherGui.cpp index 2d416d37dc..c45f9b91d9 100644 --- a/src/Mod/Sketcher/Gui/AppSketcherGui.cpp +++ b/src/Mod/Sketcher/Gui/AppSketcherGui.cpp @@ -143,7 +143,7 @@ PyMOD_INIT_FUNC(SketcherGui) QT_TRANSLATE_NOOP("QObject", "Sketcher")); (void)new Gui::PrefPageProducer( QT_TRANSLATE_NOOP("QObject", "Sketcher")); - (void)new Gui::PrefPageProducer( + (void)new Gui::PrefPageProducer( QT_TRANSLATE_NOOP("QObject", "Sketcher")); // add resources and reloads the translators diff --git a/src/Mod/Sketcher/Gui/CMakeLists.txt b/src/Mod/Sketcher/Gui/CMakeLists.txt index 1e1011d8e2..2b8aa6e7f7 100644 --- a/src/Mod/Sketcher/Gui/CMakeLists.txt +++ b/src/Mod/Sketcher/Gui/CMakeLists.txt @@ -39,7 +39,7 @@ set(SketcherGui_UIC_SRCS SketchMirrorDialog.ui SketcherSettings.ui SketcherSettingsGrid.ui - SketcherSettingsColors.ui + SketcherSettingsAppearance.ui SketcherSettingsDisplay.ui SketchRectangularArrayDialog.ui SketcherRegularPolygonDialog.ui diff --git a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp index c08e135aa2..3905fdcaaa 100644 --- a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp +++ b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -33,6 +34,8 @@ #include #include +#include +#include #include #include #include @@ -42,6 +45,7 @@ #include #include #include +#include #include #include @@ -140,6 +144,185 @@ Sketcher::SketchObject* getSketchObject() // ================================================================================ +// Copy + +bool copySelectionToClipboard(Sketcher::SketchObject* obj) { + std::vector listOfGeoId = getListOfSelectedGeoIds(true); + if (listOfGeoId.empty()) { return false; } + sort(listOfGeoId.begin(), listOfGeoId.end()); + + //Export selected geometries as a formatted string. + std::vector shapeGeometry; + for (auto geoId : listOfGeoId) { + Part::Geometry* geoNew = obj->getGeometry(geoId)->copy(); + shapeGeometry.push_back(geoNew); + } + std::string geosAsStr = Sketcher::PythonConverter::convert( + "objectStr", + shapeGeometry, + Sketcher::PythonConverter::Mode::OmitInternalGeometry); + + // Export constraints of selected geos. + std::vector shapeConstraints; + for (auto constr : obj->Constraints.getValues()) { + + auto isSelectedGeoOrAxis = [](const std::vector& vec, int value) { + return (std::find(vec.begin(), vec.end(), value) != vec.end()) + || value == GeoEnum::GeoUndef || value == GeoEnum::RtPnt + || value == GeoEnum::VAxis || value == GeoEnum::HAxis; + }; + + if (!isSelectedGeoOrAxis(listOfGeoId, constr->First) + || !isSelectedGeoOrAxis(listOfGeoId, constr->Second) + || !isSelectedGeoOrAxis(listOfGeoId, constr->Third)) { + continue; + } + + Constraint* temp = constr->copy(); + for (size_t j = 0; j < listOfGeoId.size(); j++) { + if (temp->First == listOfGeoId[j]) { + temp->First = j; + } + if (temp->Second == listOfGeoId[j]) { + temp->Second = j; + } + if (temp->Third == listOfGeoId[j]) { + temp->Third = j; + } + } + shapeConstraints.push_back(temp); + } + std::string cstrAsStr = Sketcher::PythonConverter::convert("objectStr", shapeConstraints, Sketcher::PythonConverter::GeoIdMode::AddLastGeoIdToGeoIds); + + std::string exportedData = "# Copied from sketcher. From:\n#objectStr = " + Gui::Command::getObjectCmd(obj) + "\n" + + geosAsStr + "\n" + cstrAsStr; + + if (!exportedData.empty()) { + QClipboard* clipboard = QGuiApplication::clipboard(); + clipboard->setText(QString::fromStdString(exportedData)); + return true; + } + return false; +} + +DEF_STD_CMD_A(CmdSketcherCopyClipboard) + +CmdSketcherCopyClipboard::CmdSketcherCopyClipboard() + : Command("Sketcher_CopyClipboard") +{ + sAppModule = "Sketcher"; + sGroup = "Sketcher"; + sMenuText = QT_TR_NOOP("C&opy in sketcher"); + sToolTipText = QT_TR_NOOP("Copy selected geometries and constraints to the clipboard"); + sWhatsThis = "Sketcher_CopyClipboard"; + sStatusTip = sToolTipText; + sPixmap = "edit-copy"; + sAccel = keySequenceToAccel(QKeySequence::Copy); + eType = ForEdit; +} + +void CmdSketcherCopyClipboard::activated(int iMsg) +{ + Q_UNUSED(iMsg); + copySelectionToClipboard(getSketchObject()); +} + +bool CmdSketcherCopyClipboard::isActive() +{ + return isCommandActive(getActiveGuiDocument(), true); +} + +// ================================================================================ + +// Cut + +DEF_STD_CMD_A(CmdSketcherCut) + +CmdSketcherCut::CmdSketcherCut() + : Command("Sketcher_Cut") +{ + sAppModule = "Sketcher"; + sGroup = "Sketcher"; + sMenuText = QT_TR_NOOP("C&ut in sketcher"); + sToolTipText = QT_TR_NOOP("Cut selected geometries and constraints to the clipboard"); + sWhatsThis = "Sketcher_Cut"; + sStatusTip = sToolTipText; + sPixmap = "edit-cut"; + sAccel = keySequenceToAccel(QKeySequence::Cut); + eType = ForEdit; +} + +void CmdSketcherCut::activated(int iMsg) +{ + Q_UNUSED(iMsg); + if (copySelectionToClipboard(getSketchObject())) { + + Gui::Document* doc = getActiveGuiDocument(); + ReleaseHandler(doc); + auto* vp = static_cast(doc->getInEdit()); + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Cut in Sketcher")); + vp->deleteSelected(); + Gui::Command::commitCommand(); + } +} + +bool CmdSketcherCut::isActive() +{ + return isCommandActive(getActiveGuiDocument(), true); +} + +// ================================================================================ + +// Paste + +DEF_STD_CMD_A(CmdSketcherPaste) + +CmdSketcherPaste::CmdSketcherPaste() + : Command("Sketcher_Paste") +{ + sAppModule = "Sketcher"; + sGroup = "Sketcher"; + sMenuText = QT_TR_NOOP("P&aste in sketcher"); + sToolTipText = QT_TR_NOOP("Paste selected geometries and constraints from the clipboard"); + sWhatsThis = "Sketcher_Paste"; + sStatusTip = sToolTipText; + sPixmap = "edit-paste"; + sAccel = keySequenceToAccel(QKeySequence::Paste); + eType = ForEdit; +} + +void CmdSketcherPaste::activated(int iMsg) +{ + Q_UNUSED(iMsg); + Gui::Document* doc = getActiveGuiDocument(); + ReleaseHandler(doc); + auto* vp = static_cast(doc->getInEdit()); + Sketcher::SketchObject* obj = vp->getSketchObject(); + + std::string data = QGuiApplication::clipboard()->text().toStdString(); + if (data.find("# Copied from sketcher.", 0) == std::string::npos) { + return; + } + data = "objectStr = " + Gui::Command::getObjectCmd(obj) +"\n" + data; + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Paste in Sketcher")); + + Gui::Command::doCommand(Gui::Command::Doc, data.c_str()); + + obj->solve(true); + vp->draw(false, false); + + Gui::Command::commitCommand(); +} + +bool CmdSketcherPaste::isActive() +{ + return isCommandActive(getActiveGuiDocument(), false); +} + +// ================================================================================ + // Select Constraints of selected elements DEF_STD_CMD_A(CmdSketcherSelectConstraints) @@ -2432,5 +2615,8 @@ void CreateSketcherCommandsConstraintAccel() rcCmdMgr.addCommand(new CmdSketcherDeleteAllGeometry()); rcCmdMgr.addCommand(new CmdSketcherDeleteAllConstraints()); rcCmdMgr.addCommand(new CmdSketcherRemoveAxesAlignment()); + rcCmdMgr.addCommand(new CmdSketcherCopyClipboard()); + rcCmdMgr.addCommand(new CmdSketcherCut()); + rcCmdMgr.addCommand(new CmdSketcherPaste()); } // clang-format on diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerBSpline.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerBSpline.h index 65ab45d998..2cae185c6e 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerBSpline.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerBSpline.h @@ -90,7 +90,7 @@ public: } else if (Mode == STATUS_SEEK_ADDITIONAL_CONTROLPOINTS) { - drawControlPolygonToPosition(onSketchPos); + drawBSplineToPosition(onSketchPos); drawCursorToPosition(onSketchPos); @@ -313,7 +313,7 @@ public: // run this in the end to draw lines and position text - drawControlPolygonToPosition(prevCursorPosition); + drawBSplineToPosition(prevCursorPosition); drawCursorToPosition(prevCursorPosition); } catch (const Base::Exception&) { @@ -331,6 +331,9 @@ public: } // TODO: On pressing, say, W, modify last pole's weight // TODO: On pressing, say, M, modify next knot's multiplicity + else { + DrawSketchHandler::registerPressedKey(pressed, key); + } return; } @@ -411,13 +414,50 @@ private: void drawControlPolygonToPosition(Base::Vector2d position) { - std::vector editcurve(BSplinePoles); editcurve.push_back(position); drawEdit(editcurve); } + void drawBSplineToPosition(Base::Vector2d position) + { + std::vector editcurve; + for (auto& pole : BSplinePoles) { + editcurve.emplace_back(pole.x, pole.y, 0.0); + } + editcurve.emplace_back(position.x, position.y, 0.0); + size_t degree = std::min(editcurve.size() - 1, static_cast(SplineDegree)); + bool periodic = (ConstrMethod != 0); + + std::vector weights(editcurve.size(), 1.0); + std::vector knots; + std::vector mults; + if (!periodic) { + for (size_t i = 0; i < editcurve.size() - degree + 1; ++i) { + knots.push_back(i); + } + mults.resize(editcurve.size() - degree + 1, 1); + mults.front() = degree + 1; + mults.back() = degree + 1; + } + else { + for (size_t i = 0; i < editcurve.size() + 1; ++i) { + knots.push_back(i); + } + mults.resize(editcurve.size() + 1, 1); + } + + // TODO: This maybe optimized by storing the spline as an attribute. + Part::GeomBSplineCurve editBSpline(editcurve, weights, knots, mults, degree, periodic); + editBSpline.setPoles(editcurve); + + std::vector editBSplines; + editBSplines.push_back(&editBSpline); + + drawEdit(editBSplines); + } + void drawCursorToPosition(Base::Vector2d position) { if (!BSplinePoles.empty()) { diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerBSplineByInterpolation.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerBSplineByInterpolation.h index 593c5f9cf9..3880a0c741 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerBSplineByInterpolation.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerBSplineByInterpolation.h @@ -90,7 +90,7 @@ public: } else if (Mode == STATUS_SEEK_ADDITIONAL_POINTS) { - drawControlPolygonToPosition(onSketchPos); + drawBSplineToPosition(onSketchPos); drawCursorToPosition(onSketchPos); @@ -311,7 +311,7 @@ public: // run this in the end to draw lines and position text - drawControlPolygonToPosition(prevCursorPosition); + drawBSplineToPosition(prevCursorPosition); drawCursorToPosition(prevCursorPosition); } catch (const Base::Exception&) { @@ -409,13 +409,32 @@ private: // NOTE: In this context, it is not a control polygon, but a 1-degree interpolation void drawControlPolygonToPosition(Base::Vector2d position) { - std::vector editcurve(BSplineKnots); editcurve.push_back(position); drawEdit(editcurve); } + void drawBSplineToPosition(Base::Vector2d position) + { + std::vector editcurve(BSplineKnots); + editcurve.push_back(position); + + std::vector editCurveForOCCT; + for (auto& p : editcurve) { + editCurveForOCCT.emplace_back(p.x, p.y, 0.0); + } + + // TODO: This maybe optimized by storing the spline as an attribute. + Part::GeomBSplineCurve editBSpline; + editBSpline.interpolate(editCurveForOCCT, ConstrMethod != 0); + + std::vector editBSplines; + editBSplines.push_back(&editBSpline); + + drawEdit(editBSplines); + } + void drawCursorToPosition(Base::Vector2d position) { if (!BSplineKnots.empty()) { diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerLineSet.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerLineSet.h index 299f196d96..54a9fce65b 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerLineSet.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerLineSet.h @@ -98,11 +98,8 @@ public: void registerPressedKey(bool pressed, int key) override { - if (Mode != STATUS_SEEK_Second) { - return; // SegmentMode can be changed only in STATUS_SEEK_Second mode - } - - if (key == SoKeyboardEvent::M && pressed && previousCurve != -1) { + if (Mode == STATUS_SEEK_Second && key == SoKeyboardEvent::M && pressed + && previousCurve != -1) { // loop through the following modes: // SEGMENT_MODE_Line, TRANSITION_MODE_Free / TRANSITION_MODE_Tangent // SEGMENT_MODE_Line, TRANSITION_MODE_Perpendicular_L @@ -184,6 +181,9 @@ public: } mouseMove(onSketchPos); // trigger an update of EditCurve } + else { + DrawSketchHandler::registerPressedKey(pressed, key); + } } void mouseMove(Base::Vector2d onSketchPos) override diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp b/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp index 2dbdff3588..b88aaad185 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp +++ b/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp @@ -145,6 +145,38 @@ void EditModeCoinManager::ParameterObserver::initParameters() [this](const std::string& param) { updateElementSizeParameters(param); }}, + {"EdgeWidth", + [this, &drawingParameters = Client.drawingParameters](const std::string& param) { + updateWidth(drawingParameters.CurveWidth, param, 2); + }}, + {"ConstructionWidth", + [this, &drawingParameters = Client.drawingParameters](const std::string& param) { + updateWidth(drawingParameters.ConstructionWidth, param, 1); + }}, + {"InternalWidth", + [this, &drawingParameters = Client.drawingParameters](const std::string& param) { + updateWidth(drawingParameters.InternalWidth, param, 1); + }}, + {"ExternalWidth", + [this, &drawingParameters = Client.drawingParameters](const std::string& param) { + updateWidth(drawingParameters.ExternalWidth, param, 1); + }}, + {"EdgePattern", + [this, &drawingParameters = Client.drawingParameters](const std::string& param) { + updatePattern(drawingParameters.CurvePattern, param, 0b1111111111111111); + }}, + {"ConstructionPattern", + [this, &drawingParameters = Client.drawingParameters](const std::string& param) { + updatePattern(drawingParameters.ConstructionPattern, param, 0b1111110011111100); + }}, + {"InternalPattern", + [this, &drawingParameters = Client.drawingParameters](const std::string& param) { + updatePattern(drawingParameters.InternalPattern, param, 0b1111110011111100); + }}, + {"ExternalPattern", + [this, &drawingParameters = Client.drawingParameters](const std::string& param) { + updatePattern(drawingParameters.ExternalPattern, param, 0b1110010011100100); + }}, {"CreateLineColor", [this, drawingParameters = Client.drawingParameters](const std::string& param) { updateColor(drawingParameters.CreateCurveColor, param); @@ -384,6 +416,30 @@ void EditModeCoinManager::ParameterObserver::updateElementSizeParameters( Client.updateInventorNodeSizes(); } +void EditModeCoinManager::ParameterObserver::updateWidth(int& width, + const std::string& parametername, + int def) +{ + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Mod/Sketcher/View"); + + width = hGrp->GetInt(parametername.c_str(), def); + + Client.updateInventorWidths(); +} + +void EditModeCoinManager::ParameterObserver::updatePattern(unsigned int& pattern, + const std::string& parametername, + unsigned int def) +{ + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Mod/Sketcher/View"); + + pattern = hGrp->GetInt(parametername.c_str(), def); + + Client.updateInventorPatterns(); +} + void EditModeCoinManager::ParameterObserver::updateColor(SbColor& sbcolor, const std::string& parametername) { @@ -691,14 +747,17 @@ EditModeCoinManager::detectPreselection(SoPickedPoint* Point, const SbVec2s& cur } // checking for a hit in the curves - if (tail == editModeScenegraphNodes.CurveSet[l]) { - const SoDetail* curve_detail = Point->getDetail(editModeScenegraphNodes.CurveSet[l]); - if (curve_detail && curve_detail->getTypeId() == SoLineDetail::getClassTypeId()) { - // get the index - int curveIndex = static_cast(curve_detail)->getLineIndex(); - result.GeoIndex = coinMapping.getCurveGeoId(curveIndex, l); + for (int t = 0; t < geometryLayerParameters.getSubLayerCount(); t++) { + if (tail == editModeScenegraphNodes.CurveSet[l][t]) { + const SoDetail* curve_detail = + Point->getDetail(editModeScenegraphNodes.CurveSet[l][t]); + if (curve_detail && curve_detail->getTypeId() == SoLineDetail::getClassTypeId()) { + // get the index + int curveIndex = static_cast(curve_detail)->getLineIndex(); + result.GeoIndex = coinMapping.getCurveGeoId(curveIndex, l, t); - return result; + return result; + } } } } @@ -1036,14 +1095,14 @@ void EditModeCoinManager::updateInventorNodeSizes() { auto layersconfiguration = viewProvider.VisualLayerList.getValues(); + updateInventorWidths(); + for (int l = 0; l < geometryLayerParameters.getCoinLayerCount(); l++) { editModeScenegraphNodes.PointsDrawStyle[l]->pointSize = 8 * drawingParameters.pixelScalingFactor; editModeScenegraphNodes.PointSet[l]->markerIndex = Gui::Inventor::MarkerBitmaps::getMarkerIndex("CIRCLE_FILLED", drawingParameters.markerSize); - editModeScenegraphNodes.CurvesDrawStyle[l]->lineWidth = - layersconfiguration[l].getLineWidth() * drawingParameters.pixelScalingFactor; } editModeScenegraphNodes.RootCrossDrawStyle->lineWidth = @@ -1064,6 +1123,29 @@ void EditModeCoinManager::updateInventorNodeSizes() pEditModeConstraintCoinManager->rebuildConstraintNodes(); } +void EditModeCoinManager::updateInventorWidths() +{ + editModeScenegraphNodes.CurvesDrawStyle->lineWidth = + drawingParameters.CurveWidth * drawingParameters.pixelScalingFactor; + editModeScenegraphNodes.CurvesConstructionDrawStyle->lineWidth = + drawingParameters.ConstructionWidth * drawingParameters.pixelScalingFactor; + editModeScenegraphNodes.CurvesInternalDrawStyle->lineWidth = + drawingParameters.InternalWidth * drawingParameters.pixelScalingFactor; + editModeScenegraphNodes.CurvesExternalDrawStyle->lineWidth = + drawingParameters.ExternalWidth * drawingParameters.pixelScalingFactor; +} + +void EditModeCoinManager::updateInventorPatterns() +{ + editModeScenegraphNodes.CurvesDrawStyle->linePattern = drawingParameters.CurvePattern; + editModeScenegraphNodes.CurvesConstructionDrawStyle->linePattern = + drawingParameters.ConstructionPattern; + editModeScenegraphNodes.CurvesInternalDrawStyle->linePattern = + drawingParameters.InternalPattern; + editModeScenegraphNodes.CurvesExternalDrawStyle->linePattern = + drawingParameters.ExternalPattern; +} + void EditModeCoinManager::updateInventorColors() { editModeScenegraphNodes.RootCrossMaterials->diffuseColor.set1Value( diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManager.h b/src/Mod/Sketcher/Gui/EditModeCoinManager.h index aaf2a9b14e..338d983452 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManager.h +++ b/src/Mod/Sketcher/Gui/EditModeCoinManager.h @@ -141,6 +141,8 @@ class SketcherGuiExport EditModeCoinManager void updateLineRenderingOrderParameters(const std::string& parametername); void updateConstraintPresentationParameters(const std::string& parametername); void updateElementSizeParameters(const std::string& parametername); + void updateWidth(int& width, const std::string& parametername, int def); + void updatePattern(unsigned int& pattern, const std::string& pname, unsigned int def); void updateColor(SbColor& sbcolor, const std::string& parametername); void updateUnit(const std::string& parametername); @@ -282,6 +284,10 @@ private: void updateInventorColors(); + void updateInventorPatterns(); + + void updateInventorWidths(); + /** @name coin nodes creation*/ void createEditModeInventorNodes(); //@} diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h b/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h index a59f7766a6..19a77466e6 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h +++ b/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h @@ -40,7 +40,6 @@ #include #include - #include #include @@ -140,6 +139,16 @@ struct DrawingParameters 17; // Font size to be used by SoDatumLabel, which uses a QPainter and a QFont internally int constraintIconSize = 15; // Size of constraint icons int markerSize = 7; // Size used for markers + + int CurveWidth = 2; // width of normal edges + int ConstructionWidth = 1; // width of construction edges + int InternalWidth = 1; // width of internal edges + int ExternalWidth = 1; // width of external edges + + unsigned int CurvePattern = 0b1111111111111111; // pattern of normal edges + unsigned int ConstructionPattern = 0b1111110011111100; // pattern of construction edges + unsigned int InternalPattern = 0b1111110011111100; // pattern of internal edges + unsigned int ExternalPattern = 0b1110010011100100; // pattern of external edges //@} }; @@ -155,9 +164,9 @@ struct GeometryLayerNodes /** @name Curve nodes*/ //@{ - std::vector& CurvesMaterials; // The materials for the curves - std::vector& CurvesCoordinate; // The coordinates of the segments of the curves - std::vector& CurveSet; // The set of curves + std::vector>& CurvesMaterials; + std::vector>& CurvesCoordinate; + std::vector>& CurveSet; //@} }; @@ -176,9 +185,10 @@ struct GeometryLayerNodes class MultiFieldId { public: - explicit constexpr MultiFieldId(int fieldindex = -1, int layerid = 0) + explicit constexpr MultiFieldId(int fieldindex = -1, int layerid = 0, int geotypeid = 0) : fieldIndex(fieldindex) , layerId(layerid) + , geoTypeId(geotypeid) {} MultiFieldId(const MultiFieldId&) = default; @@ -189,16 +199,19 @@ public: inline bool operator==(const MultiFieldId& obj) const { - return this->fieldIndex == obj.fieldIndex && this->layerId == obj.layerId; + return this->fieldIndex == obj.fieldIndex && this->layerId == obj.layerId + && this->geoTypeId == obj.geoTypeId; } inline bool operator!=(const MultiFieldId& obj) const { - return this->fieldIndex != obj.fieldIndex || this->layerId != obj.layerId; + return this->fieldIndex != obj.fieldIndex || this->layerId != obj.layerId + || this->geoTypeId != obj.geoTypeId; } int fieldIndex = -1; int layerId = 0; + int geoTypeId = 0; static const MultiFieldId Invalid; }; @@ -255,7 +268,16 @@ private: Default = 0 }; + public: + enum class SubLayer + { + Normal = 0, + Construction = 1, + Internal = 2, + External = 3, + }; + void reset() { CoinLayers = 1; @@ -280,8 +302,46 @@ public: CoinLayers = layernumber; } + int inline getSubLayerCount() const + { + return SubLayers; + } + + int inline getSubLayerIndex(const int geoId, const Sketcher::GeometryFacade* geom) const + { + bool isConstruction = geom->getConstruction(); + bool isInternal = geom->isInternalAligned(); + bool isExternal = geoId <= Sketcher::GeoEnum::RefExt; + + return static_cast(isExternal ? SubLayer::External + : isInternal ? SubLayer::Internal + : isConstruction ? SubLayer::Construction + : SubLayer::Normal); + } + + bool isNormalSubLayer(int t) const + { + return t == static_cast(SubLayer::Normal); + } + + bool isConstructionSubLayer(int t) const + { + return t == static_cast(SubLayer::Construction); + } + + bool isInternalSubLayer(int t) const + { + return t == static_cast(SubLayer::Internal); + } + + bool isExternalSubLayer(int t) const + { + return t == static_cast(SubLayer::External); + } + private: int CoinLayers = 1; // defaults to a single Coin Geometry Layer. + int SubLayers = 4; // Normal, Construction, Internal, External. }; /** @brief Struct to hold the results of analysis performed on geometry @@ -340,10 +400,14 @@ struct EditModeScenegraphNodes /** @name Curve nodes*/ //@{ SmSwitchboard* CurvesGroup; - std::vector CurvesMaterials; - std::vector CurvesCoordinate; - std::vector CurvesDrawStyle; - std::vector CurveSet; + std::vector> CurvesMaterials; + std::vector> CurvesCoordinate; + std::vector> CurveSet; + SoDrawStyle* CurvesDrawStyle; + SoDrawStyle* CurvesConstructionDrawStyle; + SoDrawStyle* CurvesInternalDrawStyle; + SoDrawStyle* CurvesExternalDrawStyle; + SoDrawStyle* HiddenCurvesDrawStyle; //@} /** @name Axes nodes*/ @@ -395,7 +459,7 @@ struct EditModeScenegraphNodes }; /** @brief Helper struct containing index conversions (mappings) between - * {GeoId, PointPos} and MF indices per layer, and VertexId and MF indices per layer. + * {GeoId, PointPos} and MF indices per layer, sublayers, and VertexId and MF indices per layer. * * These are updated with every draw of the scenegraph. */ @@ -404,6 +468,9 @@ struct CoinMapping void clear() { + for (size_t l = 0; l < CurvIdToGeoId.size(); ++l) { + CurvIdToGeoId[l].clear(); + } CurvIdToGeoId.clear(); PointIdToGeoId.clear(); GeoElementId2SetId.clear(); @@ -412,9 +479,9 @@ struct CoinMapping /// given the MF index of a curve and the coin layer in which it is drawn returns the GeoId of /// the curve - int getCurveGeoId(int curveindex, int layerindex) + int getCurveGeoId(int curveindex, int layerindex, int sublayerindex = 0) { - return CurvIdToGeoId[layerindex][curveindex]; + return CurvIdToGeoId[layerindex][sublayerindex][curveindex]; } /// given the MF index of a point and the coin layer in which it is drawn returns the GeoId of /// the point @@ -461,7 +528,8 @@ struct CoinMapping //* These map a MF index (second index) within a coin layer (first index) for points or curves // to a GeoId */ - std::vector> CurvIdToGeoId; // conversion of SoLineSet index to GeoId + std::vector>> + CurvIdToGeoId; // conversion of SoLineSet index to GeoId std::vector> PointIdToGeoId; // conversion of SoCoordinate3 index to GeoId //* This maps an MF index (second index) of a point within a coin layer (first index) to a diff --git a/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.cpp b/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.cpp index 5dc22f984a..0af1df62e6 100644 --- a/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.cpp +++ b/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.cpp @@ -814,6 +814,17 @@ Restart: case DistanceY: { assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount); + double helperStartAngle1 = 0.; // for arc helpers + double helperStartAngle2 = 0.; + double helperRange1 = 0.; + double helperRange2 = 0.; + double radius1 = 0.; + double radius2 = 0.; + Base::Vector3d center1(0., 0., 0.); + Base::Vector3d center2(0., 0., 0.); + + int numPoints = 2; + // pnt1 will be initialized to (0,0,0) if only one point is given auto pnt1 = geolistfacade.getPoint(Constr->First, Constr->FirstPos); @@ -838,17 +849,15 @@ Restart: pnt2.ProjectToLine(pnt1 - l2p1, l2p2 - l2p1); pnt2 += pnt1; } - else { - if (isCircleOrArc(*geo1)) { - // circular to line distance - auto [radius, ct] = getRadiusCenterCircleArc(geo1); - // project the center on the line (translated to origin) - pnt1.ProjectToLine(ct - l2p1, l2p2 - l2p1); - Base::Vector3d dir = pnt1; - dir.Normalize(); - pnt1 += ct; - pnt2 = ct + dir * radius; - } + else if (isCircleOrArc(*geo1)) { + // circular to line distance + auto [radius, ct] = getRadiusCenterCircleArc(geo1); + // project the center on the line (translated to origin) + pnt1.ProjectToLine(ct - l2p1, l2p2 - l2p1); + Base::Vector3d dir = pnt1; + dir.Normalize(); + pnt1 += ct; + pnt2 = ct + dir * radius; } } else if (isCircleOrArc(*geo2)) { @@ -890,10 +899,8 @@ Restart: break; } - // NOLINTBEGIN - SoDatumLabel* asciiText = static_cast( - sep->getChild(static_cast(ConstraintNodePosition::DatumLabelIndex))); - // NOLINTEND + int index = static_cast(ConstraintNodePosition::DatumLabelIndex); + auto* asciiText = static_cast(sep->getChild(index)); // NOLINT // Get presentation string (w/o units if option is set) asciiText->string = @@ -909,13 +916,102 @@ Restart: asciiText->datumtype = SoDatumLabel::DISTANCEY; } + // Check if arc helpers are needed + if (Constr->Second != GeoEnum::GeoUndef + && Constr->SecondPos == Sketcher::PointPos::none) { + auto geo1 = geolistfacade.getGeometryFromGeoId(Constr->First); + auto geo2 = geolistfacade.getGeometryFromGeoId(Constr->Second); + + if (isArcOfCircle(*geo1)) { + auto arc = static_cast(geo1); // NOLINT + radius1 = arc->getRadius(); + center1 = arc->getCenter(); + + double angle = + toVector2d(isLineSegment(*geo2) ? pnt2 - center1 : pnt1 - center1) + .Angle(); + double startAngle, endAngle; + arc->getRange(startAngle, endAngle, /*emulateCCW=*/true); + + findHelperAngles(helperStartAngle1, + helperRange1, + angle, + startAngle, + endAngle); + + if (helperRange1 != 0.) { + // We override to draw the full helper as it does not look good + // otherwise We still use findHelperAngles before to find if helper + // is needed. + helperStartAngle1 = endAngle; + helperRange1 = 2 * M_PI - (endAngle - startAngle); + + numPoints++; + } + } + if (isArcOfCircle(*geo2)) { + auto arc = static_cast(geo2); // NOLINT + radius2 = arc->getRadius(); + center2 = arc->getCenter(); + + double angle = + toVector2d(pnt2 - center2).Angle(); // between -pi and pi + double startAngle, endAngle; // between 0 and 2*pi + arc->getRange(startAngle, endAngle, /*emulateCCW=*/true); + + findHelperAngles(helperStartAngle2, + helperRange2, + angle, + startAngle, + endAngle); + + if (helperRange2 != 0.) { + helperStartAngle2 = endAngle; + helperRange2 = 2 * M_PI - (endAngle - startAngle); + + numPoints++; + } + } + } + // Assign the Datum Points - asciiText->pnts.setNum(2); + asciiText->pnts.setNum(numPoints); SbVec3f* verts = asciiText->pnts.startEditing(); verts[0] = SbVec3f(pnt1.x, pnt1.y, zConstrH); verts[1] = SbVec3f(pnt2.x, pnt2.y, zConstrH); + if (numPoints > 2) { + if (helperRange1 != 0.) { + verts[2] = SbVec3f(center1.x, center1.y, zConstrH); + asciiText->param3 = helperStartAngle1; + asciiText->param4 = helperRange1; + asciiText->param5 = radius1; + } + else { + verts[2] = SbVec3f(center2.x, center2.y, zConstrH); + asciiText->param3 = helperStartAngle2; + asciiText->param4 = helperRange2; + asciiText->param5 = radius2; + } + if (numPoints > 3) { + verts[3] = SbVec3f(center2.x, center2.y, zConstrH); + asciiText->param6 = helperStartAngle2; + asciiText->param7 = helperRange2; + asciiText->param8 = radius2; + } + else { + asciiText->param6 = 0.; + asciiText->param7 = 0.; + asciiText->param8 = 0.; + } + } + else { + asciiText->param3 = 0.; + asciiText->param4 = 0.; + asciiText->param5 = 0.; + } + asciiText->pnts.finishEditing(); // Assign the Label Distance @@ -1281,20 +1377,25 @@ Restart: const Part::Geometry* geo = geolistfacade.getGeometryFromGeoId(Constr->First); if (geo->is()) { - const Part::GeomLineSegment* lineSeg = - static_cast(geo); + auto* lineSeg = static_cast(geo); p0 = Base::convertTo( (lineSeg->getEndPoint() + lineSeg->getStartPoint()) / 2); + double l1 = 2 * distance + - (lineSeg->getEndPoint() - lineSeg->getStartPoint()).Length() / 2; + endLineLength1 = 2 * distance; + endLineLength2 = l1 > 0. ? l1 : 0.; Base::Vector3d dir = lineSeg->getEndPoint() - lineSeg->getStartPoint(); startangle = 0.; range = atan2(dir.y, dir.x); } else if (geo->is()) { - const Part::GeomArcOfCircle* arc = - static_cast(geo); + auto* arc = static_cast(geo); p0 = Base::convertTo(arc->getCenter()); + endLineLength1 = 2 * distance - arc->getRadius(); + endLineLength2 = endLineLength1; + double endangle; arc->getRange(startangle, endangle, /*emulateCCWXY=*/true); range = endangle - startangle; @@ -1333,51 +1434,43 @@ Restart: double helperStartAngle = 0.; double helperRange = 0.; - if (Constr->First != GeoEnum::GeoUndef) { - const Part::Geometry* geo = - geolistfacade.getGeometryFromGeoId(Constr->First); + if (Constr->First == GeoEnum::GeoUndef) { + break; + } - if (geo->is()) { - auto* arc = static_cast(geo); - double radius = arc->getRadius(); - double angle = (double)Constr->LabelPosition; - double startAngle, endAngle; - arc->getRange(startAngle, endAngle, /*emulateCCW=*/true); - if (angle == 10) { - angle = (startAngle + endAngle) / 2; - } - if (!(angle > startAngle && angle < endAngle)) { - if (angle < startAngle - && startAngle - angle < angle + 2 * M_PI - endAngle) { - helperStartAngle = angle; - helperRange = startAngle - angle; - } - else { - if (angle < endAngle) { - angle += 2 * M_PI; - } - helperStartAngle = endAngle; - helperRange = angle - endAngle; - } - } - Base::Vector3d center = arc->getCenter(); - pnt1 = center - radius * Base::Vector3d(cos(angle), sin(angle), 0.); - pnt2 = center + radius * Base::Vector3d(cos(angle), sin(angle), 0.); + const Part::Geometry* geo = geolistfacade.getGeometryFromGeoId(Constr->First); + + if (geo->is()) { + auto* arc = static_cast(geo); + double radius = arc->getRadius(); + double angle = (double)Constr->LabelPosition; // between -pi and pi + double startAngle, endAngle; // between 0 and 2*pi + arc->getRange(startAngle, endAngle, /*emulateCCW=*/true); + + if (angle == 10) { + angle = (startAngle + endAngle) / 2; } - else if (geo->is()) { - auto* circle = static_cast(geo); - double radius = circle->getRadius(); - double angle = (double)Constr->LabelPosition; - if (angle == 10) { - angle = 0; - } - Base::Vector3d center = circle->getCenter(); - pnt1 = center - radius * Base::Vector3d(cos(angle), sin(angle), 0.); - pnt2 = center + radius * Base::Vector3d(cos(angle), sin(angle), 0.); - } - else { - break; + + findHelperAngles(helperStartAngle, + helperRange, + angle, + startAngle, + endAngle); + + Base::Vector3d center = arc->getCenter(); + pnt1 = center - radius * Base::Vector3d(cos(angle), sin(angle), 0.); + pnt2 = center + radius * Base::Vector3d(cos(angle), sin(angle), 0.); + } + else if (geo->is()) { + auto* circle = static_cast(geo); + double radius = circle->getRadius(); + double angle = (double)Constr->LabelPosition; + if (angle == 10) { + angle = 0; } + Base::Vector3d center = circle->getCenter(); + pnt1 = center - radius * Base::Vector3d(cos(angle), sin(angle), 0.); + pnt2 = center + radius * Base::Vector3d(cos(angle), sin(angle), 0.); } else { break; @@ -1415,54 +1508,45 @@ Restart: double helperStartAngle = 0.; double helperRange = 0.; - if (Constr->First != GeoEnum::GeoUndef) { - const Part::Geometry* geo = - geolistfacade.getGeometryFromGeoId(Constr->First); + if (Constr->First == GeoEnum::GeoUndef) { + break; + } + const Part::Geometry* geo = geolistfacade.getGeometryFromGeoId(Constr->First); - if (geo->is()) { - auto* arc = static_cast(geo); - double radius = arc->getRadius(); - double angle = (double)Constr->LabelPosition; - double startAngle, endAngle; - arc->getRange(startAngle, endAngle, /*emulateCCW=*/true); - if (angle == 10) { - angle = (startAngle + endAngle) / 2; - } - if (!(angle > startAngle && angle < endAngle)) { - if (angle < startAngle - && startAngle - angle < angle + 2 * M_PI - endAngle) { - helperStartAngle = angle; - helperRange = startAngle - angle; - } - else { - if (angle < endAngle) { - angle += 2 * M_PI; - } - helperStartAngle = endAngle; - helperRange = angle - endAngle; - } - } - pnt1 = arc->getCenter(); - pnt2 = pnt1 + radius * Base::Vector3d(cos(angle), sin(angle), 0.); + if (geo->is()) { + auto* arc = static_cast(geo); + double radius = arc->getRadius(); + double angle = (double)Constr->LabelPosition; // between -pi and pi + double startAngle, endAngle; // between 0 and 2*pi + arc->getRange(startAngle, endAngle, /*emulateCCW=*/true); + + if (angle == 10) { + angle = (startAngle + endAngle) / 2; } - else if (geo->is()) { - auto* circle = static_cast(geo); - auto gf = GeometryFacade::getFacade(geo); - double radius; + findHelperAngles(helperStartAngle, + helperRange, + angle, + startAngle, + endAngle); - radius = circle->getRadius(); + pnt1 = arc->getCenter(); + pnt2 = pnt1 + radius * Base::Vector3d(cos(angle), sin(angle), 0.); + } + else if (geo->is()) { + auto* circle = static_cast(geo); + auto gf = GeometryFacade::getFacade(geo); - double angle = (double)Constr->LabelPosition; - if (angle == 10) { - angle = 0; - } - pnt1 = circle->getCenter(); - pnt2 = pnt1 + radius * Base::Vector3d(cos(angle), sin(angle), 0.); - } - else { - break; + double radius; + + radius = circle->getRadius(); + + double angle = (double)Constr->LabelPosition; + if (angle == 10) { + angle = 0; } + pnt1 = circle->getCenter(); + pnt2 = pnt1 + radius * Base::Vector3d(cos(angle), sin(angle), 0.); } else { break; @@ -1518,6 +1602,39 @@ Restart: } } +void EditModeConstraintCoinManager::findHelperAngles(double& helperStartAngle, + double& helperRange, + double angle, + double startAngle, + double endAngle) +{ + double margin = 0.2; // about 10deg + if (angle < 0) { + angle = angle + 2 * M_PI; + } + // endAngle can be more than 2*pi as its startAngle + arcAngle + if (endAngle > 2 * M_PI && angle < endAngle - 2 * M_PI) { + angle = angle + 2 * M_PI; + } + if (!(angle > startAngle && angle < endAngle)) { + if ((angle < startAngle && startAngle - angle < angle + 2 * M_PI - endAngle) + || (angle > endAngle && startAngle + 2 * M_PI - angle < angle - endAngle)) { + if (angle > startAngle) { + angle -= 2 * M_PI; + } + helperStartAngle = angle - margin; + helperRange = startAngle - angle + margin; + } + else { + if (angle < endAngle) { + angle += 2 * M_PI; + } + helperStartAngle = endAngle; + helperRange = angle - endAngle + margin; + } + } +} + Base::Vector3d EditModeConstraintCoinManager::seekConstraintPosition(const Base::Vector3d& origPos, const Base::Vector3d& norm, const Base::Vector3d& dir, @@ -1587,14 +1704,20 @@ void EditModeConstraintCoinManager::updateConstraintColor( std::vector PtNum; std::vector pcolor; // point color - std::vector CurvNum; - std::vector color; // curve color + std::vector> CurvNum; + std::vector> color; // curve color for (int l = 0; l < geometryLayerParameters.getCoinLayerCount(); l++) { PtNum.push_back(editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.getNum()); pcolor.push_back(editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.startEditing()); - CurvNum.push_back(editModeScenegraphNodes.CurvesMaterials[l]->diffuseColor.getNum()); - color.push_back(editModeScenegraphNodes.CurvesMaterials[l]->diffuseColor.startEditing()); + CurvNum.emplace_back(); + color.emplace_back(); + for (int t = 0; t < geometryLayerParameters.getSubLayerCount(); t++) { + CurvNum[l].push_back( + editModeScenegraphNodes.CurvesMaterials[l][t]->diffuseColor.getNum()); + color[l].push_back( + editModeScenegraphNodes.CurvesMaterials[l][t]->diffuseColor.startEditing()); + } } int maxNumberOfConstraints = std::min(editModeScenegraphNodes.constrGroup->getNumChildren(), @@ -1653,9 +1776,11 @@ void EditModeConstraintCoinManager::updateConstraintColor( if (multifieldIndex != MultiFieldId::Invalid) { int index = multifieldIndex.fieldIndex; int layer = multifieldIndex.layerId; - if (layer < static_cast(CurvNum.size()) && index >= 0 - && index < CurvNum[layer]) { - color[layer][index] = drawingParameters.SelectColor; + int sublayer = multifieldIndex.geoTypeId; + if (layer < static_cast(CurvNum.size()) + && sublayer < static_cast(CurvNum[layer].size()) && index >= 0 + && index < CurvNum[layer][sublayer]) { + color[layer][sublayer][index] = drawingParameters.SelectColor; } } } @@ -1730,7 +1855,9 @@ void EditModeConstraintCoinManager::updateConstraintColor( for (int l = 0; l < geometryLayerParameters.getCoinLayerCount(); l++) { editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.finishEditing(); - editModeScenegraphNodes.CurvesMaterials[l]->diffuseColor.finishEditing(); + for (int t = 0; t < geometryLayerParameters.getSubLayerCount(); t++) { + editModeScenegraphNodes.CurvesMaterials[l][t]->diffuseColor.finishEditing(); + } } } diff --git a/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.h b/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.h index 657bd6adbb..8db7b6d798 100644 --- a/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.h +++ b/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.h @@ -250,6 +250,13 @@ private: /// Essentially a version of sendConstraintIconToCoin, with a blank icon void clearCoinImage(SoImage* soImagePtr); + + /// Find helper angle for radius/diameter constraint + void findHelperAngles(double& helperStartAngle, + double& helperAngle, + double angle, + double startAngle, + double endAngle); //@} private: diff --git a/src/Mod/Sketcher/Gui/EditModeGeometryCoinConverter.cpp b/src/Mod/Sketcher/Gui/EditModeGeometryCoinConverter.cpp index d1b0679788..5235a0224c 100644 --- a/src/Mod/Sketcher/Gui/EditModeGeometryCoinConverter.cpp +++ b/src/Mod/Sketcher/Gui/EditModeGeometryCoinConverter.cpp @@ -55,27 +55,30 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli arcGeoIds.clear(); // end information layer - Coords.clear(); Points.clear(); + Coords.clear(); Index.clear(); coinMapping.clear(); pointCounter.clear(); - curveCounter.clear(); for (auto l = 0; l < geometryLayerParameters.getCoinLayerCount(); l++) { - Coords.emplace_back(); Points.emplace_back(); + Coords.emplace_back(); Index.emplace_back(); coinMapping.CurvIdToGeoId.emplace_back(); + for (int t = 0; t < geometryLayerParameters.getSubLayerCount(); t++) { + Coords[l].emplace_back(); + Index[l].emplace_back(); + coinMapping.CurvIdToGeoId[l].emplace_back(); + } coinMapping.PointIdToGeoId.emplace_back(); coinMapping.PointIdToVertexId.emplace_back(); } pointCounter.resize(geometryLayerParameters.getCoinLayerCount(), 0); - curveCounter.resize(geometryLayerParameters.getCoinLayerCount(), 0); // RootPoint // TODO: RootPoint is here added in layer0. However, this layer may be hidden. The point should, @@ -83,8 +86,8 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli // empty layer. Points[0].emplace_back(0., 0., 0.); coinMapping.PointIdToGeoId[0].push_back(-1); // root point - coinMapping.PointIdToVertexId[0].push_back( - -1); // VertexId is the reference used for point selection/preselection + coinMapping.PointIdToVertexId[0].push_back(-1); + // VertexId is the reference used for point selection/preselection coinMapping.GeoElementId2SetId.emplace(std::piecewise_construct, std::forward_as_tuple(Sketcher::GeoElementId::RtPnt), @@ -93,7 +96,8 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli auto setTracking = [this](int geoId, int coinLayer, EditModeGeometryCoinConverter::PointsMode pointmode, - int numberCurves) { + int numberCurves, + int sublayer) { int numberPoints = 0; if (pointmode == PointsMode::InsertSingle) { @@ -153,12 +157,14 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli coinMapping.GeoElementId2SetId.emplace( std::piecewise_construct, std::forward_as_tuple(geoId, Sketcher::PointPos::none), - std::forward_as_tuple(static_cast(coinMapping.CurvIdToGeoId[coinLayer].size()), - coinLayer)); + std::forward_as_tuple( + static_cast(coinMapping.CurvIdToGeoId[coinLayer][sublayer].size()), + coinLayer, + sublayer)); } for (int i = 0; i < numberCurves; i++) { - coinMapping.CurvIdToGeoId[coinLayer].push_back(geoId); + coinMapping.CurvIdToGeoId[coinLayer][sublayer].push_back(geoId); } }; @@ -168,7 +174,8 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli const auto geom = geolistfacade.getGeometryFacadeFromGeoId(GeoId); const auto type = geom->getGeometry()->getTypeId(); - auto layerId = getSafeGeomLayerId(geom); + int layerId = getSafeGeomLayerId(geom); + int subLayerId = geometryLayerParameters.getSubLayerIndex(GeoId, geom); auto coinLayer = geometryLayerParameters.getSafeCoinLayer(layerId); @@ -176,43 +183,55 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli convert(geom, GeoId); + EditModeGeometryCoinConverter::AnalyseMode::BoundingBoxMagnitude>(geom, + GeoId, + subLayerId); setTracking(GeoId, coinLayer, EditModeGeometryCoinConverter::PointsMode::InsertSingle, - 0); + 0, + subLayerId); } else if (type == Part::GeomLineSegment::getClassTypeId()) { // add a line convert(geom, GeoId); + EditModeGeometryCoinConverter::AnalyseMode::BoundingBoxMagnitude>(geom, + GeoId, + subLayerId); setTracking(GeoId, coinLayer, EditModeGeometryCoinConverter::PointsMode::InsertStartEnd, - 1); + 1, + subLayerId); } else if (type.isDerivedFrom( Part::GeomConic::getClassTypeId())) { // add a closed curve conic convert(geom, GeoId); + EditModeGeometryCoinConverter::AnalyseMode::BoundingBoxMagnitude>(geom, + GeoId, + subLayerId); setTracking(GeoId, coinLayer, EditModeGeometryCoinConverter::PointsMode::InsertMidOnly, - 1); + 1, + subLayerId); } else if (type.isDerivedFrom( Part::GeomArcOfConic::getClassTypeId())) { // add an arc of conic convert(geom, GeoId); + EditModeGeometryCoinConverter::AnalyseMode::BoundingBoxMagnitude>(geom, + GeoId, + subLayerId); setTracking(GeoId, coinLayer, EditModeGeometryCoinConverter::PointsMode::InsertStartEndMid, - 1); + 1, + subLayerId); arcGeoIds.push_back(GeoId); } else if (type == Part::GeomBSplineCurve::getClassTypeId()) { // add a bspline (a bounded @@ -221,58 +240,53 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli EditModeGeometryCoinConverter::PointsMode::InsertStartEnd, EditModeGeometryCoinConverter::CurveMode::OpenCurve, EditModeGeometryCoinConverter::AnalyseMode:: - BoundingBoxMagnitudeAndBSplineCurvature>(geom, GeoId); + BoundingBoxMagnitudeAndBSplineCurvature>(geom, GeoId, subLayerId); setTracking(GeoId, coinLayer, EditModeGeometryCoinConverter::PointsMode::InsertStartEnd, - 1); + 1, + subLayerId); bsplineGeoIds.push_back(GeoId); } } - for (auto l = 0; l < geometryLayerParameters.getCoinLayerCount(); l++) { + // Coin Nodes Editing + int vOrFactor = ViewProviderSketchCoinAttorney::getViewOrientationFactor(viewProvider); + double linez = vOrFactor * drawingParameters.zLowLines; // NOLINT + double pointz = vOrFactor * drawingParameters.zLowPoints; - // Coin Nodes Editing - geometryLayerNodes.CurvesCoordinate[l]->point.setNum(Coords[l].size()); - geometryLayerNodes.CurveSet[l]->numVertices.setNum(Index[l].size()); - geometryLayerNodes.CurvesMaterials[l]->diffuseColor.setNum(Index[l].size()); + for (auto l = 0; l < geometryLayerParameters.getCoinLayerCount(); l++) { geometryLayerNodes.PointsCoordinate[l]->point.setNum(Points[l].size()); geometryLayerNodes.PointsMaterials[l]->diffuseColor.setNum(Points[l].size()); - - SbVec3f* verts = geometryLayerNodes.CurvesCoordinate[l]->point.startEditing(); - int32_t* index = geometryLayerNodes.CurveSet[l]->numVertices.startEditing(); SbVec3f* pverts = geometryLayerNodes.PointsCoordinate[l]->point.startEditing(); - int i = 0; // setting up the line set - for (std::vector::const_iterator it = Coords[l].begin(); - it != Coords[l].end(); - ++it, i++) { - verts[i].setValue(it->x, - it->y, - ViewProviderSketchCoinAttorney::getViewOrientationFactor(viewProvider) - * drawingParameters.zLowLines); + int i = 0; // setting up the point set + for (auto& point : Points[l]) { + pverts[i++].setValue(point.x, point.y, pointz); } - - i = 0; // setting up the indexes of the line set - for (std::vector::const_iterator it = Index[l].begin(); it != Index[l].end(); - ++it, i++) { - index[i] = *it; - } - - i = 0; // setting up the point set - for (std::vector::const_iterator it = Points[l].begin(); - it != Points[l].end(); - ++it, i++) { - pverts[i].setValue( - it->x, - it->y, - ViewProviderSketchCoinAttorney::getViewOrientationFactor(viewProvider) - * drawingParameters.zLowPoints); - } - - geometryLayerNodes.CurvesCoordinate[l]->point.finishEditing(); - geometryLayerNodes.CurveSet[l]->numVertices.finishEditing(); geometryLayerNodes.PointsCoordinate[l]->point.finishEditing(); + + for (auto t = 0; t < geometryLayerParameters.getSubLayerCount(); t++) { + geometryLayerNodes.CurvesCoordinate[l][t]->point.setNum(Coords[l][t].size()); + geometryLayerNodes.CurveSet[l][t]->numVertices.setNum(Index[l][t].size()); + geometryLayerNodes.CurvesMaterials[l][t]->diffuseColor.setNum(Index[l][t].size()); + + SbVec3f* verts = geometryLayerNodes.CurvesCoordinate[l][t]->point.startEditing(); + int32_t* index = geometryLayerNodes.CurveSet[l][t]->numVertices.startEditing(); + + i = 0; // setting up the line set + for (auto& coord : Coords[l][t]) { + verts[i++].setValue(coord.x, coord.y, linez); // NOLINT + } + + i = 0; // setting up the indexes of the line set + for (auto it : Index[l][t]) { + index[i++] = it; + } + + geometryLayerNodes.CurvesCoordinate[l][t]->point.finishEditing(); + geometryLayerNodes.CurveSet[l][t]->numVertices.finishEditing(); + } } } @@ -281,7 +295,8 @@ template void EditModeGeometryCoinConverter::convert(const Sketcher::GeometryFacade* geometryfacade, - [[maybe_unused]] int geoid) + [[maybe_unused]] int geoid, + int subLayer) { auto geo = static_cast(geometryfacade->getGeometry()); auto layerId = getSafeGeomLayerId(geometryfacade); @@ -317,9 +332,9 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeometryFacade* geom // Curves if constexpr (curvemode == CurveMode::StartEndPointsOnly) { - addPoint(Coords[coinLayer], geo->getStartPoint()); - addPoint(Coords[coinLayer], geo->getEndPoint()); - Index[coinLayer].push_back(2); + addPoint(Coords[coinLayer][subLayer], geo->getStartPoint()); + addPoint(Coords[coinLayer][subLayer], geo->getEndPoint()); + Index[coinLayer][subLayer].push_back(2); } else if constexpr (curvemode == CurveMode::ClosedCurve) { int numSegments = drawingParameters.curvedEdgeCountSegments; @@ -331,13 +346,13 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeometryFacade* geom for (int i = 0; i < numSegments; i++) { Base::Vector3d pnt = geo->value(i * segment); - addPoint(Coords[coinLayer], pnt); + addPoint(Coords[coinLayer][subLayer], pnt); } Base::Vector3d pnt = geo->value(0); - addPoint(Coords[coinLayer], pnt); + addPoint(Coords[coinLayer][subLayer], pnt); - Index[coinLayer].push_back(numSegments + 1); + Index[coinLayer][subLayer].push_back(numSegments + 1); } else if constexpr (curvemode == CurveMode::OpenCurve) { int numSegments = drawingParameters.curvedEdgeCountSegments; @@ -349,13 +364,13 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeometryFacade* geom for (int i = 0; i < numSegments; i++) { Base::Vector3d pnt = geo->value(geo->getFirstParameter() + i * segment); - addPoint(Coords[coinLayer], pnt); + addPoint(Coords[coinLayer][subLayer], pnt); } Base::Vector3d pnt = geo->value(geo->getLastParameter()); - addPoint(Coords[coinLayer], pnt); + addPoint(Coords[coinLayer][subLayer], pnt); - Index[coinLayer].push_back(numSegments + 1); + Index[coinLayer][subLayer].push_back(numSegments + 1); if constexpr (analysemode == AnalyseMode::BoundingBoxMagnitudeAndBSplineCurvature) { //*************************************************************************************************************** diff --git a/src/Mod/Sketcher/Gui/EditModeGeometryCoinConverter.h b/src/Mod/Sketcher/Gui/EditModeGeometryCoinConverter.h index 950635138a..889b5b0a2e 100644 --- a/src/Mod/Sketcher/Gui/EditModeGeometryCoinConverter.h +++ b/src/Mod/Sketcher/Gui/EditModeGeometryCoinConverter.h @@ -152,7 +152,9 @@ public: private: template - void convert(const Sketcher::GeometryFacade* geometryfacade, [[maybe_unused]] int geoId); + void convert(const Sketcher::GeometryFacade* geometryfacade, + [[maybe_unused]] int geoId, + int subLayerId = 0); private: /// Reference to ViewProviderSketch in order to access the public and the Attorney Interface @@ -160,13 +162,12 @@ private: GeometryLayerNodes& geometryLayerNodes; - std::vector> Coords; std::vector> Points; - std::vector> Index; + std::vector>> Coords; + std::vector>> Index; // temporal counters, one per layer std::vector pointCounter; - std::vector curveCounter; // temporal global vertex counter int vertexCounter = 0; diff --git a/src/Mod/Sketcher/Gui/EditModeGeometryCoinManager.cpp b/src/Mod/Sketcher/Gui/EditModeGeometryCoinManager.cpp index 120e8dd352..8c6b030a34 100644 --- a/src/Mod/Sketcher/Gui/EditModeGeometryCoinManager.cpp +++ b/src/Mod/Sketcher/Gui/EditModeGeometryCoinManager.cpp @@ -73,19 +73,21 @@ void EditModeGeometryCoinManager::processGeometry(const GeoListFacade& geolistfa { // enable all layers editModeScenegraphNodes.PointsGroup->enable.setNum(geometryLayerParameters.getCoinLayerCount()); - editModeScenegraphNodes.CurvesGroup->enable.setNum(geometryLayerParameters.getCoinLayerCount()); + editModeScenegraphNodes.CurvesGroup->enable.setNum( + geometryLayerParameters.getCoinLayerCount() * geometryLayerParameters.getSubLayerCount()); SbBool* swsp = editModeScenegraphNodes.PointsGroup->enable.startEditing(); SbBool* swsc = editModeScenegraphNodes.CurvesGroup->enable.startEditing(); - auto setEnableLayer = [swsp, swsc](int l, bool enabled) { - swsp[l] = enabled; // layer defaults to enabled - swsc[l] = enabled; // layer defaults to enabled - }; - auto layersconfigurations = viewProvider.VisualLayerList.getValues(); for (auto l = 0; l < geometryLayerParameters.getCoinLayerCount(); l++) { - setEnableLayer(l, layersconfigurations[l].isVisible()); + auto enabled = layersconfigurations[l].isVisible(); + + swsp[l] = enabled; + int slCount = geometryLayerParameters.getSubLayerCount(); + for (int t = 0; t < slCount; t++) { + swsc[l * slCount + t] = enabled; + } } editModeScenegraphNodes.PointsGroup->enable.finishEditing(); @@ -122,14 +124,6 @@ void EditModeGeometryCoinManager::updateGeometryColor(const GeoListFacade& geoli bool issketchinvalid) { // Lambdas for convenience retrieval of geometry information - auto isConstructionGeom = [&geolistfacade](int GeoId) { - auto geom = geolistfacade.getGeometryFacadeFromGeoId(GeoId); - if (geom) { - return geom->getConstruction(); - } - return false; - }; - auto isDefinedGeomPoint = [&geolistfacade](int GeoId) { auto geom = geolistfacade.getGeometryFacadeFromGeoId(GeoId); if (geom) { @@ -162,6 +156,9 @@ void EditModeGeometryCoinManager::updateGeometryColor(const GeoListFacade& geoli return false; }; + bool sketchFullyConstrained = + ViewProviderSketchCoinAttorney::isSketchFullyConstrained(viewProvider); + // Update Colors SbColor* crosscolor = editModeScenegraphNodes.RootCrossMaterials->diffuseColor.startEditing(); @@ -169,17 +166,11 @@ void EditModeGeometryCoinManager::updateGeometryColor(const GeoListFacade& geoli ViewProviderSketchCoinAttorney::getViewOrientationFactor(viewProvider); for (auto l = 0; l < geometryLayerParameters.getCoinLayerCount(); l++) { - + float x, y, z; int PtNum = editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.getNum(); SbColor* pcolor = editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.startEditing(); - int CurvNum = editModeScenegraphNodes.CurvesMaterials[l]->diffuseColor.getNum(); - SbColor* color = editModeScenegraphNodes.CurvesMaterials[l]->diffuseColor.startEditing(); - - SbVec3f* verts = editModeScenegraphNodes.CurvesCoordinate[l]->point.startEditing(); SbVec3f* pverts = editModeScenegraphNodes.PointsCoordinate[l]->point.startEditing(); - float x, y, z; - // colors of the point set if (issketchinvalid) { for (int i = 0; i < PtNum; i++) { @@ -189,9 +180,7 @@ void EditModeGeometryCoinManager::updateGeometryColor(const GeoListFacade& geoli else { for (int i = 0; i < PtNum; i++) { - if (!(i == 0 && l == 0) - && ViewProviderSketchCoinAttorney::isSketchFullyConstrained( - viewProvider)) { // root point is not coloured + if (!(i == 0 && l == 0) && sketchFullyConstrained) { // root point is not coloured pcolor[i] = drawingParameters.FullyConstrainedColor; } else { @@ -345,98 +334,99 @@ void EditModeGeometryCoinManager::updateGeometryColor(const GeoListFacade& geoli drawingParameters.zMidLines, drawingParameters.zLowLines); - int j = 0; // vertexindex + for (auto t = 0; t < geometryLayerParameters.getSubLayerCount(); t++) { + int CurvNum = editModeScenegraphNodes.CurvesMaterials[l][t]->diffuseColor.getNum(); + SbColor* color = + editModeScenegraphNodes.CurvesMaterials[l][t]->diffuseColor.startEditing(); + SbVec3f* verts = editModeScenegraphNodes.CurvesCoordinate[l][t]->point.startEditing(); - for (int i = 0; i < CurvNum; i++) { - int GeoId = coinMapping.getCurveGeoId(i, l); - // CurvId has several vertices associated to 1 material - // edit->CurveSet->numVertices => [i] indicates number of vertex for line i. - int indexes = (editModeScenegraphNodes.CurveSet[l]->numVertices[i]); + int j = 0; // vertexindex + for (int i = 0; i < CurvNum; i++) { + int GeoId = coinMapping.getCurveGeoId(i, l, t); + // CurvId has several vertices associated to 1 material + // edit->CurveSet->numVertices => [i] indicates number of vertex for line i. + int indexes = (editModeScenegraphNodes.CurveSet[l][t]->numVertices[i]); - bool selected = ViewProviderSketchCoinAttorney::isCurveSelected(viewProvider, GeoId); - bool preselected = (preselectcurve == GeoId); + bool selected = + ViewProviderSketchCoinAttorney::isCurveSelected(viewProvider, GeoId); + bool preselected = (preselectcurve == GeoId); + bool constrainedElement = isFullyConstraintElement(GeoId); - bool constrainedElement = isFullyConstraintElement(GeoId); + if (selected || preselected) { + color[i] = selected ? (preselected ? drawingParameters.PreselectSelectedColor + : drawingParameters.SelectColor) + : drawingParameters.PreselectColor; - if (selected && preselected) { - color[i] = drawingParameters.PreselectSelectedColor; - for (int k = j; j < k + indexes; j++) { - verts[j].getValue(x, y, z); - verts[j] = SbVec3f(x, y, viewOrientationFactor * drawingParameters.zHighLine); - } - } - else if (selected) { - color[i] = drawingParameters.SelectColor; - for (int k = j; j < k + indexes; j++) { - verts[j].getValue(x, y, z); - verts[j] = SbVec3f(x, y, viewOrientationFactor * drawingParameters.zHighLine); - } - } - else if (preselected) { - color[i] = drawingParameters.PreselectColor; - for (int k = j; j < k + indexes; j++) { - verts[j].getValue(x, y, z); - verts[j] = SbVec3f(x, y, viewOrientationFactor * drawingParameters.zHighLine); - } - } - else if (GeoId <= Sketcher::GeoEnum::RefExt) { // external Geometry - color[i] = drawingParameters.CurveExternalColor; - for (int k = j; j < k + indexes; j++) { - verts[j].getValue(x, y, z); - verts[j] = SbVec3f(x, y, viewOrientationFactor * zExtLine); - } - } - else if (issketchinvalid) { - color[i] = drawingParameters.InvalidSketchColor; - for (int k = j; j < k + indexes; j++) { - verts[j].getValue(x, y, z); - verts[j] = SbVec3f(x, y, viewOrientationFactor * zNormLine); - } - } - else if (isConstructionGeom(GeoId)) { - if (isInternalAlignedGeom(GeoId)) { - if (constrainedElement) { - color[i] = drawingParameters.FullyConstraintInternalAlignmentColor; + for (int k = j; j < k + indexes; j++) { + verts[j].getValue(x, y, z); + verts[j] = + SbVec3f(x, y, viewOrientationFactor * drawingParameters.zHighLine); } - else { - color[i] = drawingParameters.InternalAlignedGeoColor; + } + else if (geometryLayerParameters.isExternalSubLayer(t)) { + color[i] = drawingParameters.CurveExternalColor; + for (int k = j; j < k + indexes; j++) { + verts[j].getValue(x, y, z); + verts[j] = SbVec3f(x, y, viewOrientationFactor * zExtLine); } } else { - if (constrainedElement) { - color[i] = drawingParameters.FullyConstraintConstructionElementColor; + if (issketchinvalid) { + color[i] = drawingParameters.InvalidSketchColor; + + for (int k = j; j < k + indexes; j++) { + verts[j].getValue(x, y, z); + verts[j] = SbVec3f(x, y, viewOrientationFactor * zNormLine); + } + } + else if (geometryLayerParameters.isConstructionSubLayer(t)) { + if (constrainedElement) { + color[i] = drawingParameters.FullyConstraintConstructionElementColor; + } + else { + color[i] = drawingParameters.CurveDraftColor; + } + + for (int k = j; j < k + indexes; j++) { + verts[j].getValue(x, y, z); + verts[j] = SbVec3f(x, y, viewOrientationFactor * zConstrLine); + } + } + else if (geometryLayerParameters.isInternalSubLayer(t)) { + if (constrainedElement) { + color[i] = drawingParameters.FullyConstraintInternalAlignmentColor; + } + else { + color[i] = drawingParameters.InternalAlignedGeoColor; + } + + for (int k = j; j < k + indexes; j++) { + verts[j].getValue(x, y, z); + verts[j] = SbVec3f(x, y, viewOrientationFactor * zConstrLine); + } } else { - color[i] = drawingParameters.CurveDraftColor; + if (sketchFullyConstrained) { + color[i] = drawingParameters.FullyConstrainedColor; + } + else if (constrainedElement) { + color[i] = drawingParameters.FullyConstraintElementColor; + } + else { + color[i] = drawingParameters.CurveColor; + } + + for (int k = j; j < k + indexes; j++) { + verts[j].getValue(x, y, z); + verts[j] = SbVec3f(x, y, viewOrientationFactor * zNormLine); + } } } + } - for (int k = j; j < k + indexes; j++) { - verts[j].getValue(x, y, z); - verts[j] = SbVec3f(x, y, viewOrientationFactor * zConstrLine); - } - } - else if (ViewProviderSketchCoinAttorney::isSketchFullyConstrained(viewProvider)) { - color[i] = drawingParameters.FullyConstrainedColor; - for (int k = j; j < k + indexes; j++) { - verts[j].getValue(x, y, z); - verts[j] = SbVec3f(x, y, viewOrientationFactor * zNormLine); - } - } - else if (isFullyConstraintElement(GeoId)) { - color[i] = drawingParameters.FullyConstraintElementColor; - for (int k = j; j < k + indexes; j++) { - verts[j].getValue(x, y, z); - verts[j] = SbVec3f(x, y, viewOrientationFactor * zNormLine); - } - } - else { - color[i] = drawingParameters.CurveColor; - for (int k = j; j < k + indexes; j++) { - verts[j].getValue(x, y, z); - verts[j] = SbVec3f(x, y, viewOrientationFactor * zNormLine); - } - } + editModeScenegraphNodes.CurvesMaterials[l][t]->diffuseColor.finishEditing(); + editModeScenegraphNodes.CurvesCoordinate[l][t]->point.finishEditing(); + editModeScenegraphNodes.CurveSet[l][t]->numVertices.finishEditing(); } // colors of the cross @@ -464,11 +454,7 @@ void EditModeGeometryCoinManager::updateGeometryColor(const GeoListFacade& geoli } } - // end editing - editModeScenegraphNodes.CurvesMaterials[l]->diffuseColor.finishEditing(); editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.finishEditing(); - editModeScenegraphNodes.CurvesCoordinate[l]->point.finishEditing(); - editModeScenegraphNodes.CurveSet[l]->numVertices.finishEditing(); } editModeScenegraphNodes.RootCrossMaterials->diffuseColor.finishEditing(); @@ -565,45 +551,80 @@ void EditModeGeometryCoinManager::createEditModePointInventorNodes() void EditModeGeometryCoinManager::createEditModeCurveInventorNodes() { - auto layersconfigurations = viewProvider.VisualLayerList.getValue(); + editModeScenegraphNodes.CurvesDrawStyle = new SoDrawStyle; + editModeScenegraphNodes.CurvesDrawStyle->setName("CurvesDrawStyle"); + editModeScenegraphNodes.CurvesDrawStyle->lineWidth = + drawingParameters.CurveWidth * drawingParameters.pixelScalingFactor; + editModeScenegraphNodes.CurvesDrawStyle->linePattern = drawingParameters.CurvePattern; + editModeScenegraphNodes.CurvesDrawStyle->linePatternScaleFactor = 2; + + editModeScenegraphNodes.CurvesConstructionDrawStyle = new SoDrawStyle; + editModeScenegraphNodes.CurvesConstructionDrawStyle->setName("CurvesConstructionDrawStyle"); + editModeScenegraphNodes.CurvesConstructionDrawStyle->lineWidth = + drawingParameters.ConstructionWidth * drawingParameters.pixelScalingFactor; + editModeScenegraphNodes.CurvesConstructionDrawStyle->linePattern = + drawingParameters.ConstructionPattern; + editModeScenegraphNodes.CurvesConstructionDrawStyle->linePatternScaleFactor = 2; + + editModeScenegraphNodes.CurvesInternalDrawStyle = new SoDrawStyle; + editModeScenegraphNodes.CurvesInternalDrawStyle->setName("CurvesInternalDrawStyle"); + editModeScenegraphNodes.CurvesInternalDrawStyle->lineWidth = + drawingParameters.InternalWidth * drawingParameters.pixelScalingFactor; + editModeScenegraphNodes.CurvesInternalDrawStyle->linePattern = + drawingParameters.InternalPattern; + editModeScenegraphNodes.CurvesInternalDrawStyle->linePatternScaleFactor = 2; + + editModeScenegraphNodes.CurvesExternalDrawStyle = new SoDrawStyle; + editModeScenegraphNodes.CurvesExternalDrawStyle->setName("CurvesExternalDrawStyle"); + editModeScenegraphNodes.CurvesExternalDrawStyle->lineWidth = + drawingParameters.ExternalWidth * drawingParameters.pixelScalingFactor; + editModeScenegraphNodes.CurvesExternalDrawStyle->linePattern = + drawingParameters.ExternalPattern; + editModeScenegraphNodes.CurvesExternalDrawStyle->linePatternScaleFactor = 2; for (int i = 0; i < geometryLayerParameters.getCoinLayerCount(); i++) { - SoSeparator* sep = new SoSeparator; - sep->ref(); + editModeScenegraphNodes.CurvesMaterials.emplace_back(); + editModeScenegraphNodes.CurvesCoordinate.emplace_back(); + editModeScenegraphNodes.CurveSet.emplace_back(); + for (int t = 0; t < geometryLayerParameters.getSubLayerCount(); t++) { + SoSeparator* sep = new SoSeparator; + sep->ref(); - auto somaterial = new SoMaterial; - editModeScenegraphNodes.CurvesMaterials.push_back(somaterial); - editModeScenegraphNodes.CurvesMaterials[i]->setName(concat("CurvesMaterials", i).c_str()); - sep->addChild(editModeScenegraphNodes.CurvesMaterials[i]); + auto somaterial = new SoMaterial; + somaterial->setName(concat("CurvesMaterials", i * 10 + t).c_str()); + editModeScenegraphNodes.CurvesMaterials[i].push_back(somaterial); + sep->addChild(editModeScenegraphNodes.CurvesMaterials[i][t]); - auto MtlBind = new SoMaterialBinding; - MtlBind->setName(concat("CurvesMaterialsBinding", i).c_str()); - MtlBind->value = SoMaterialBinding::PER_FACE; - sep->addChild(MtlBind); + auto MtlBind = new SoMaterialBinding; + MtlBind->setName(concat("CurvesMaterialsBinding", i * 10 + t).c_str()); + MtlBind->value = SoMaterialBinding::PER_FACE; + sep->addChild(MtlBind); - auto coords = new SoCoordinate3; - editModeScenegraphNodes.CurvesCoordinate.push_back(coords); - editModeScenegraphNodes.CurvesCoordinate[i]->setName(concat("CurvesCoordinate", i).c_str()); - sep->addChild(editModeScenegraphNodes.CurvesCoordinate[i]); + auto coords = new SoCoordinate3; + coords->setName(concat("CurvesCoordinate", i * 10 + t).c_str()); + editModeScenegraphNodes.CurvesCoordinate[i].push_back(coords); + sep->addChild(editModeScenegraphNodes.CurvesCoordinate[i][t]); - auto drawstyle = new SoDrawStyle; - editModeScenegraphNodes.CurvesDrawStyle.push_back(drawstyle); - editModeScenegraphNodes.CurvesDrawStyle[i]->setName(concat("CurvesDrawStyle", i).c_str()); + if (geometryLayerParameters.isConstructionSubLayer(t)) { + sep->addChild(editModeScenegraphNodes.CurvesConstructionDrawStyle); + } + else if (geometryLayerParameters.isInternalSubLayer(t)) { + sep->addChild(editModeScenegraphNodes.CurvesInternalDrawStyle); + } + else if (geometryLayerParameters.isExternalSubLayer(t)) { + sep->addChild(editModeScenegraphNodes.CurvesExternalDrawStyle); + } + else { + sep->addChild(editModeScenegraphNodes.CurvesDrawStyle); + } - editModeScenegraphNodes.CurvesDrawStyle[i]->lineWidth = - layersconfigurations[i].getLineWidth() * drawingParameters.pixelScalingFactor; - editModeScenegraphNodes.CurvesDrawStyle[i]->linePattern = - layersconfigurations[i].getLinePattern(); - editModeScenegraphNodes.CurvesDrawStyle[i]->linePatternScaleFactor = 5; + auto solineset = new SoLineSet; + solineset->setName(concat("CurvesLineSet", i * 10 + t).c_str()); + editModeScenegraphNodes.CurveSet[i].push_back(solineset); + sep->addChild(editModeScenegraphNodes.CurveSet[i][t]); - sep->addChild(editModeScenegraphNodes.CurvesDrawStyle[i]); - - auto solineset = new SoLineSet; - editModeScenegraphNodes.CurveSet.push_back(solineset); - editModeScenegraphNodes.CurveSet[i]->setName(concat("CurvesLineSet", i).c_str()); - sep->addChild(editModeScenegraphNodes.CurveSet[i]); - - editModeScenegraphNodes.CurvesGroup->addChild(sep); - sep->unref(); + editModeScenegraphNodes.CurvesGroup->addChild(sep); + sep->unref(); + } } } diff --git a/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineApproximate.svg b/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineApproximate.svg index 66e92f507b..e0c8e5ac32 100644 --- a/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineApproximate.svg +++ b/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineApproximate.svg @@ -15,6 +15,17 @@ xmlns:dc="http://purl.org/dc/elements/1.1/"> + + + + + @@ -464,7 +484,8 @@ transform="matrix(0.1460346,0,0,0.1460346,-220.10298,-55.131225)"> + id="g4428-3-2" + style="display:inline"> diff --git a/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineInsertKnot.svg b/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineInsertKnot.svg index fa676a240d..569e11ffd3 100644 --- a/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineInsertKnot.svg +++ b/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineInsertKnot.svg @@ -380,7 +380,7 @@ id="path3826" /> + diff --git a/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_JoinCurves.svg b/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_JoinCurves.svg index 0ac32bc5e1..3cf03594f5 100644 --- a/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_JoinCurves.svg +++ b/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_JoinCurves.svg @@ -261,11 +261,11 @@ @@ -316,6 +316,15 @@ y1="35.978416" x2="25.988253" y2="29.916241" /> + @@ -368,7 +377,7 @@ + transform="matrix(-1,0,0,1,47.987644,0)"> + + + + diff --git a/src/Mod/Sketcher/Gui/ShortcutListener.cpp b/src/Mod/Sketcher/Gui/ShortcutListener.cpp index 805e54f1f4..b161cb4194 100644 --- a/src/Mod/Sketcher/Gui/ShortcutListener.cpp +++ b/src/Mod/Sketcher/Gui/ShortcutListener.cpp @@ -31,12 +31,6 @@ using namespace SketcherGui; -// ******************** ViewProvider attorney *********************************************// -inline void ViewProviderSketchShortcutListenerAttorney::deleteSelected(ViewProviderSketch& vp) -{ - vp.deleteSelected(); -}; - // ******************** ShortcutListener *********************************************// ShortcutListener::ShortcutListener(ViewProviderSketch* vp) { @@ -55,7 +49,7 @@ bool ShortcutListener::eventFilter(QObject* obj, QEvent* event) switch (kevent->key()) { case Qt::Key_Delete: kevent->accept(); - ViewProviderSketchShortcutListenerAttorney::deleteSelected(*pViewProvider); + pViewProvider->deleteSelected(); return true; default: break; diff --git a/src/Mod/Sketcher/Gui/ShortcutListener.h b/src/Mod/Sketcher/Gui/ShortcutListener.h index 414da71fa3..70366adc83 100644 --- a/src/Mod/Sketcher/Gui/ShortcutListener.h +++ b/src/Mod/Sketcher/Gui/ShortcutListener.h @@ -31,15 +31,6 @@ namespace SketcherGui class ViewProviderSketch; -class ViewProviderSketchShortcutListenerAttorney -{ -private: - static inline void deleteSelected(ViewProviderSketch& vp); - - - friend class ShortcutListener; -}; - class ShortcutListener: public QObject { // Q_OBJECT diff --git a/src/Mod/Sketcher/Gui/SketcherSettings.cpp b/src/Mod/Sketcher/Gui/SketcherSettings.cpp index a316235e49..5c30e5fced 100644 --- a/src/Mod/Sketcher/Gui/SketcherSettings.cpp +++ b/src/Mod/Sketcher/Gui/SketcherSettings.cpp @@ -34,7 +34,7 @@ #include "SketcherSettings.h" #include "ui_SketcherSettings.h" -#include "ui_SketcherSettingsColors.h" +#include "ui_SketcherSettingsAppearance.h" #include "ui_SketcherSettingsDisplay.h" #include "ui_SketcherSettingsGrid.h" @@ -43,6 +43,49 @@ using namespace SketcherGui; /* TRANSLATOR SketcherGui::SketcherSettings */ +QList getPenStyles() +{ + QList styles; + styles << 0b1111111111111111 // solid + << 0b1110111011101110 // dashed 3:1 + << 0b1111110011111100 // dashed 6:2 + << 0b0000111100001111 // dashed 4:4 + << 0b1010101010101010 // point 1:1 + << 0b1110010011100100 // dash point + << 0b1111111100111100; // dash long-dash + return styles; +} + +const QVector binaryPatternToDashPattern(int binaryPattern) +{ + QVector dashPattern; + int count = 0; + bool isDash = (binaryPattern & 0x8000) != 0; // Check the highest bit + + for (int i = 0; i < 16; ++i) { + bool currentBit = (binaryPattern & (0x8000 >> i)) != 0; + if (currentBit == isDash) { + ++count; // Counting dashes or spaces + } + else { + // Adjust count to be odd for dashes and even for spaces (see qt doc) + count = (count % 2 == (isDash ? 0 : 1)) ? count + 1 : count; + dashPattern << count; + count = 1; // Reset count for next dash/space + isDash = !isDash; + } + } + count = (count % 2 == (isDash ? 0 : 1)) ? count + 1 : count; + dashPattern << count; // Add the last count + + if ((dashPattern.size() % 2) == 1) { + // prevent this error : qWarning("QPen::setDashPattern: Pattern not of even length"); + dashPattern << 1; + } + + return dashPattern; +} + SketcherSettings::SketcherSettings(QWidget* parent) : PreferencePage(parent) , ui(new Ui_SketcherSettings) @@ -206,17 +249,17 @@ SketcherSettingsGrid::SketcherSettingsGrid(QWidget* parent) { ui->setupUi(this); - QList> styles; - styles << qMakePair(Qt::SolidLine, 0xffff) << qMakePair(Qt::DashLine, 0x0f0f) - << qMakePair(Qt::DotLine, 0xaaaa); + QList styles = getPenStyles(); ui->gridLinePattern->setIconSize(QSize(80, 12)); ui->gridDivLinePattern->setIconSize(QSize(80, 12)); - for (QList>::iterator it = styles.begin(); it != styles.end(); ++it) { + for (auto& style : styles) { QPixmap px(ui->gridLinePattern->iconSize()); px.fill(Qt::transparent); QBrush brush(Qt::black); - QPen pen(it->first); + + QPen pen; + pen.setDashPattern(binaryPatternToDashPattern(style)); pen.setBrush(brush); pen.setWidth(2); @@ -226,8 +269,8 @@ SketcherSettingsGrid::SketcherSettingsGrid(QWidget* parent) painter.drawLine(0, mid, ui->gridLinePattern->iconSize().width(), mid); painter.end(); - ui->gridLinePattern->addItem(QIcon(px), QString(), QVariant(it->second)); - ui->gridDivLinePattern->addItem(QIcon(px), QString(), QVariant(it->second)); + ui->gridLinePattern->addItem(QIcon(px), QString(), QVariant(style)); + ui->gridDivLinePattern->addItem(QIcon(px), QString(), QVariant(style)); } } @@ -273,13 +316,13 @@ void SketcherSettingsGrid::loadSettings() ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Sketcher/General"); - int pattern = hGrp->GetInt("GridLinePattern", 0x0f0f); + int pattern = hGrp->GetInt("GridLinePattern", 0b0000111100001111); int index = ui->gridLinePattern->findData(QVariant(pattern)); if (index < 0) { index = 1; } ui->gridLinePattern->setCurrentIndex(index); - pattern = hGrp->GetInt("GridDivLinePattern", 0xffff); + pattern = hGrp->GetInt("GridDivLinePattern", 0b1111111111111111); index = ui->gridDivLinePattern->findData(QVariant(pattern)); if (index < 0) { index = 0; @@ -412,24 +455,51 @@ void SketcherSettingsDisplay::onBtnTVApplyClicked(bool) } -/* TRANSLATOR SketcherGui::SketcherSettingsColors */ +/* TRANSLATOR SketcherGui::SketcherSettingsAppearance */ -SketcherSettingsColors::SketcherSettingsColors(QWidget* parent) +SketcherSettingsAppearance::SketcherSettingsAppearance(QWidget* parent) : PreferencePage(parent) - , ui(new Ui_SketcherSettingsColors) + , ui(new Ui_SketcherSettingsAppearance) { ui->setupUi(this); + + QList styles = getPenStyles(); + + ui->EdgePattern->setIconSize(QSize(70, 12)); + ui->ConstructionPattern->setIconSize(QSize(70, 12)); + ui->InternalPattern->setIconSize(QSize(70, 12)); + ui->ExternalPattern->setIconSize(QSize(70, 12)); + for (auto& style : styles) { + QPixmap px(ui->EdgePattern->iconSize()); + px.fill(Qt::transparent); + QBrush brush(Qt::black); + QPen pen; + pen.setDashPattern(binaryPatternToDashPattern(style)); + pen.setBrush(brush); + pen.setWidth(2); + + QPainter painter(&px); + painter.setPen(pen); + double mid = ui->EdgePattern->iconSize().height() / 2.0; + painter.drawLine(0, mid, ui->EdgePattern->iconSize().width(), mid); + painter.end(); + + ui->EdgePattern->addItem(QIcon(px), QString(), QVariant(style)); + ui->ConstructionPattern->addItem(QIcon(px), QString(), QVariant(style)); + ui->InternalPattern->addItem(QIcon(px), QString(), QVariant(style)); + ui->ExternalPattern->addItem(QIcon(px), QString(), QVariant(style)); + } } /** * Destroys the object and frees any allocated resources */ -SketcherSettingsColors::~SketcherSettingsColors() +SketcherSettingsAppearance::~SketcherSettingsAppearance() { // no need to delete child widgets, Qt does it all for us } -void SketcherSettingsColors::saveSettings() +void SketcherSettingsAppearance::saveSettings() { // Sketcher ui->SketchEdgeColor->onSave(); @@ -455,9 +525,32 @@ void SketcherSettingsColors::saveSettings() ui->CursorTextColor->onSave(); ui->CursorCrosshairColor->onSave(); ui->CreateLineColor->onSave(); + + ui->EdgeWidth->onSave(); + ui->ConstructionWidth->onSave(); + ui->InternalWidth->onSave(); + ui->ExternalWidth->onSave(); + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Mod/Sketcher/View"); + QVariant data = ui->EdgePattern->itemData(ui->EdgePattern->currentIndex()); + int pattern = data.toInt(); + hGrp->SetInt("EdgePattern", pattern); + + data = ui->ConstructionPattern->itemData(ui->ConstructionPattern->currentIndex()); + pattern = data.toInt(); + hGrp->SetInt("ConstructionPattern", pattern); + + data = ui->InternalPattern->itemData(ui->InternalPattern->currentIndex()); + pattern = data.toInt(); + hGrp->SetInt("InternalPattern", pattern); + + data = ui->ExternalPattern->itemData(ui->ExternalPattern->currentIndex()); + pattern = data.toInt(); + hGrp->SetInt("ExternalPattern", pattern); } -void SketcherSettingsColors::loadSettings() +void SketcherSettingsAppearance::loadSettings() { // Sketcher ui->SketchEdgeColor->onRestore(); @@ -483,12 +576,47 @@ void SketcherSettingsColors::loadSettings() ui->CursorTextColor->onRestore(); ui->CursorCrosshairColor->onRestore(); ui->CreateLineColor->onRestore(); + + ui->EdgeWidth->onRestore(); + ui->ConstructionWidth->onRestore(); + ui->InternalWidth->onRestore(); + ui->ExternalWidth->onRestore(); + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Mod/Sketcher/View"); + int pattern = hGrp->GetInt("EdgePattern", 0b1111111111111111); + int index = ui->EdgePattern->findData(QVariant(pattern)); + if (index < 0) { + index = 0; + } + ui->EdgePattern->setCurrentIndex(index); + + pattern = hGrp->GetInt("ConstructionPattern", 0b1111110011111100); + index = ui->ConstructionPattern->findData(QVariant(pattern)); + if (index < 0) { + index = 0; + } + ui->ConstructionPattern->setCurrentIndex(index); + + pattern = hGrp->GetInt("InternalPattern", 0b1111110011111100); + index = ui->InternalPattern->findData(QVariant(pattern)); + if (index < 0) { + index = 0; + } + ui->InternalPattern->setCurrentIndex(index); + + pattern = hGrp->GetInt("ExternalPattern", 0b1110010011100100); + index = ui->ExternalPattern->findData(QVariant(pattern)); + if (index < 0) { + index = 0; + } + ui->ExternalPattern->setCurrentIndex(index); } /** * Sets the strings of the subwidgets using the current language. */ -void SketcherSettingsColors::changeEvent(QEvent* e) +void SketcherSettingsAppearance::changeEvent(QEvent* e) { if (e->type() == QEvent::LanguageChange) { ui->retranslateUi(this); diff --git a/src/Mod/Sketcher/Gui/SketcherSettings.h b/src/Mod/Sketcher/Gui/SketcherSettings.h index 1d3f52effc..ddc8261635 100644 --- a/src/Mod/Sketcher/Gui/SketcherSettings.h +++ b/src/Mod/Sketcher/Gui/SketcherSettings.h @@ -32,7 +32,7 @@ namespace SketcherGui class Ui_SketcherSettings; class Ui_SketcherSettingsGrid; class Ui_SketcherSettingsDisplay; -class Ui_SketcherSettingsColors; +class Ui_SketcherSettingsAppearance; class SketcherGeneralWidget; /** * The SketcherSettings class implements a preference page to change sketcher settings. @@ -107,13 +107,13 @@ private: * The SketcherSettings class implements a preference page to change sketcher settings. * @author Werner Mayer */ -class SketcherSettingsColors: public Gui::Dialog::PreferencePage +class SketcherSettingsAppearance: public Gui::Dialog::PreferencePage { Q_OBJECT public: - explicit SketcherSettingsColors(QWidget* parent = nullptr); - ~SketcherSettingsColors() override; + explicit SketcherSettingsAppearance(QWidget* parent = nullptr); + ~SketcherSettingsAppearance() override; void saveSettings() override; void loadSettings() override; @@ -122,7 +122,7 @@ protected: void changeEvent(QEvent* e) override; private: - std::unique_ptr ui; + std::unique_ptr ui; }; } // namespace SketcherGui diff --git a/src/Mod/Sketcher/Gui/SketcherSettingsAppearance.ui b/src/Mod/Sketcher/Gui/SketcherSettingsAppearance.ui new file mode 100644 index 0000000000..fa9cf59e7b --- /dev/null +++ b/src/Mod/Sketcher/Gui/SketcherSettingsAppearance.ui @@ -0,0 +1,967 @@ + + + SketcherGui::SketcherSettingsAppearance + + + + 0 + 0 + 689 + 863 + + + + Appearance + + + + + + Working colors + + + + + + + + + 200 + 0 + + + + Creating line + + + + + + + Color used while new sketch elements are created + + + + 204 + 204 + 204 + + + + CreateLineColor + + + View + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 200 + 0 + + + + Coordinate text + + + + + + + Text color of the coordinates + + + + 0 + 0 + 255 + + + + CursorTextColor + + + View + + + + + + + Cursor crosshair + + + + + + + Color of crosshair cursor. +(The one you get when creating a new sketch element.) + + + + 255 + 255 + 255 + + + + CursorCrosshairColor + + + View + + + + + + + + + + + + Geometric element colors + + + + + + + 95 + 0 + + + + Constrained + + + + + + + + 95 + 0 + + + + Unconstrained + + + + + + + Pattern + + + + + + + Width + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Vertex + + + + + + + + 0 + 0 + + + + Color of fully constrained vertex color in edit mode + + + + 255 + 149 + 128 + + + + FullyConstraintConstructionPointColor + + + View + + + + + + + + 0 + 0 + + + + Color of vertices being edited + + + + 255 + 38 + 0 + + + + EditedVertexColor + + + View + + + + + + + Edge + + + + + + + + 0 + 0 + + + + Color of fully constrained edge color in edit mode + + + + 128 + 208 + 160 + + + + FullyConstraintElementColor + + + View + + + + + + + + 0 + 0 + + + + Color of edges being edited + + + + 255 + 255 + 255 + + + + EditedEdgeColor + + + View + + + + + + + Line pattern of normal edges. + + + -1 + + + + + + + Width of normal edges. + + + mm + + + 1 + + + 99 + + + 2 + + + EdgeWidth + + + Mod/Sketcher/View + + + + + + + Construction geometry + + + + + + + + 0 + 0 + + + + Color of fully constrained construction edge color in edit mode + + + + 143 + 169 + 253 + + + + FullyConstraintConstructionElementColor + + + View + + + + + + + + 0 + 0 + + + + Color of construction geometry in edit mode + + + + 0 + 0 + 220 + + + + ConstructionColor + + + View + + + + + + + Line pattern of construction edges. + + + -1 + + + + + + + Width of construction edges. + + + mm + + + 1 + + + 99 + + + 1 + + + ConstructionWidth + + + Mod/Sketcher/View + + + + + + + Internal alignment edge + + + + + + + + 0 + 0 + + + + Color of fully constrained internal alignment edge color in edit mode + + + + 222 + 222 + 200 + + + + FullyConstraintInternalAlignmentColor + + + View + + + + + + + + 0 + 0 + + + + Color of edges of internal alignment geometry + + + + 178 + 178 + 127 + + + + InternalAlignedGeoColor + + + View + + + + + + + Line pattern of internal aligned edges. + + + -1 + + + + + + + Width of internal aligned edges. + + + mm + + + 1 + + + 99 + + + 1 + + + InternalWidth + + + Mod/Sketcher/View + + + + + + + External geometry + + + + + + + + 0 + 0 + + + + Color of external geometry in edit mode + + + + 204 + 51 + 115 + + + + ExternalColor + + + View + + + + + + + Line pattern of external edges. + + + -1 + + + + + + + Width of external edges. + + + mm + + + 1 + + + 99 + + + 1 + + + ExternalWidth + + + Mod/Sketcher/View + + + + + + + + 200 + 0 + + + + Fully constrained Sketch + + + + + + + + 0 + 0 + + + + Color of fully constrained geometry in edit mode + + + + 0 + 255 + 0 + + + + FullyConstrainedColor + + + View + + + + + + + Invalid Sketch + + + + + + + + 0 + 0 + + + + Color of geometry indicating an invalid sketch + + + + 255 + 109 + 0 + + + + InvalidSketchColor + + + View + + + + + + + + + + Constraint colors + + + + + + + + + 200 + 0 + + + + Constraint symbols + + + + + + + Color of driving constraints in edit mode + + + + 255 + 38 + 0 + + + + ConstrainedIcoColor + + + View + + + + + + + Dimensional constraint + + + + + + + Color of dimensional driving constraints + + + + 255 + 38 + 0 + + + + ConstrainedDimColor + + + View + + + + + + + Reference constraint + + + + + + + Color of reference constraints in edit mode + + + + 0 + 38 + 255 + + + + NonDrivingConstrDimColor + + + View + + + + + + + Expression dependent constraint + + + + + + + Color of expression dependent constraints in edit mode + + + + 255 + 127 + 38 + + + + ExprBasedConstrDimColor + + + View + + + + + + + Deactivated constraint + + + + + + + Color of deactivated constraints in edit mode + + + + 127 + 127 + 127 + + + + DeactivatedConstrDimColor + + + View + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Colors outside Sketcher + + + + + + + + + 200 + 0 + + + + Edge + + + + + + + Color of edges + + + + 255 + 255 + 255 + + + + SketchEdgeColor + + + View + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Vertex + + + + + + + Color of vertices + + + + 255 + 255 + 255 + + + + SketchVertexColor + + + View + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Gui::PrefSpinBox + QSpinBox +
Gui/PrefWidgets.h
+
+ + Gui::ColorButton + QPushButton +
Gui/Widgets.h
+
+ + Gui::PrefColorButton + Gui::ColorButton +
Gui/PrefWidgets.h
+
+
+ + +
diff --git a/src/Mod/Sketcher/Gui/SketcherSettingsColors.ui b/src/Mod/Sketcher/Gui/SketcherSettingsColors.ui deleted file mode 100644 index ddf7005ffc..0000000000 --- a/src/Mod/Sketcher/Gui/SketcherSettingsColors.ui +++ /dev/null @@ -1,835 +0,0 @@ - - - SketcherGui::SketcherSettingsColors - - - - 0 - 0 - 689 - 863 - - - - Colors - - - - - - Working colors - - - - - - - - - 240 - 0 - - - - Creating line - - - - - - - Color used while new sketch elements are created - - - - 204 - 204 - 204 - - - - CreateLineColor - - - View - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 240 - 0 - - - - Coordinate text - - - - - - - Text color of the coordinates - - - - 0 - 0 - 255 - - - - CursorTextColor - - - View - - - - - - - Cursor crosshair - - - - - - - Color of crosshair cursor. -(The one you get when creating a new sketch element.) - - - - 255 - 255 - 255 - - - - CursorCrosshairColor - - - View - - - - - - - - - - - - Geometric element colors - - - - - - - - - 120 - 0 - - - - Constrained - - - - - - - - 120 - 0 - - - - Unconstrained - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Edge - - - - - - - - 0 - 0 - - - - Color of fully constrained edge color in edit mode - - - - 128 - 208 - 160 - - - - FullyConstraintElementColor - - - View - - - - - - - - 0 - 0 - - - - Color of edges being edited - - - - 255 - 255 - 255 - - - - EditedEdgeColor - - - View - - - - - - - Vertex - - - - - - - - 0 - 0 - - - - Color of fully constrained vertex color in edit mode - - - - 255 - 149 - 128 - - - - FullyConstraintConstructionPointColor - - - View - - - - - - - - 0 - 0 - - - - Color of vertices being edited - - - - 255 - 38 - 0 - - - - EditedVertexColor - - - View - - - - - - - Construction geometry - - - - - - - - 0 - 0 - - - - Color of fully constrained construction edge color in edit mode - - - - 143 - 169 - 253 - - - - FullyConstraintConstructionElementColor - - - View - - - - - - - - 0 - 0 - - - - Color of construction geometry in edit mode - - - - 0 - 0 - 220 - - - - ConstructionColor - - - View - - - - - - - - 240 - 0 - - - - Internal alignment edge - - - - - - - - 0 - 0 - - - - Color of fully constrained internal alignment edge color in edit mode - - - - 222 - 222 - 200 - - - - FullyConstraintInternalAlignmentColor - - - View - - - - - - - - 0 - 0 - - - - Color of edges of internal alignment geometry - - - - 178 - 178 - 127 - - - - InternalAlignedGeoColor - - - View - - - - - - - External geometry - - - - - - - - 0 - 0 - - - - Color of external geometry in edit mode - - - - 204 - 51 - 115 - - - - ExternalColor - - - View - - - - - - - - - - - - 240 - 0 - - - - Fully constrained Sketch - - - - - - - - 0 - 0 - - - - Color of fully constrained geometry in edit mode - - - - 0 - 255 - 0 - - - - FullyConstrainedColor - - - View - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Invalid Sketch - - - - - - - - 0 - 0 - - - - Color of geometry indicating an invalid sketch - - - - 255 - 109 - 0 - - - - InvalidSketchColor - - - View - - - - - - - - - - - - Constraint colors - - - - - - - - - 240 - 0 - - - - Constraint symbols - - - - - - - Color of driving constraints in edit mode - - - - 255 - 38 - 0 - - - - ConstrainedIcoColor - - - View - - - - - - - Dimensional constraint - - - - - - - Color of dimensional driving constraints - - - - 255 - 38 - 0 - - - - ConstrainedDimColor - - - View - - - - - - - Reference constraint - - - - - - - Color of reference constraints in edit mode - - - - 0 - 38 - 255 - - - - NonDrivingConstrDimColor - - - View - - - - - - - Expression dependent constraint - - - - - - - Color of expression dependent constraints in edit mode - - - - 255 - 127 - 38 - - - - ExprBasedConstrDimColor - - - View - - - - - - - Deactivated constraint - - - - - - - Color of deactivated constraints in edit mode - - - - 127 - 127 - 127 - - - - DeactivatedConstrDimColor - - - View - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - Colors outside Sketcher - - - - - - - - - 240 - 0 - - - - Edge - - - - - - - Color of edges - - - - 255 - 255 - 255 - - - - SketchEdgeColor - - - View - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Vertex - - - - - - - Color of vertices - - - - 255 - 255 - 255 - - - - SketchVertexColor - - - View - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Gui::ColorButton - QPushButton -
Gui/Widgets.h
-
- - Gui::PrefColorButton - Gui::ColorButton -
Gui/PrefWidgets.h
-
-
- - -
diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstraints.cpp b/src/Mod/Sketcher/Gui/TaskSketcherConstraints.cpp index b927ec8ac3..363aea9859 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstraints.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstraints.cpp @@ -262,12 +262,12 @@ public: static QIcon snell_driven( Gui::BitmapFactory().iconFromTheme("Constraint_SnellsLaw_Driven")); - auto selicon = [](const Sketcher::Constraint* constr, + auto selicon = [this](const Sketcher::Constraint* constr, const QIcon& normal, const QIcon& driven) -> QIcon { if (!constr->isActive) { QIcon darkIcon; - int w = QApplication::style()->pixelMetric(QStyle::PM_ListViewIconSize); + int w = listWidget()->style()->pixelMetric(QStyle::PM_ListViewIconSize); darkIcon.addPixmap(normal.pixmap(w, w, QIcon::Disabled, QIcon::Off), QIcon::Normal, QIcon::Off); @@ -466,7 +466,7 @@ protected: QStyleOptionViewItem options = option; initStyleOption(&options, index); - options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter); + options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, option.widget); ConstraintItem* item = dynamic_cast(view->item(index.row())); if (!item || item->sketch->Constraints.getSize() <= item->ConstraintNbr) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp index 249d05dd88..41835ede72 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp @@ -115,26 +115,37 @@ class ElementItemDelegate: public QStyledItemDelegate { Q_OBJECT public: + /// Enum containing all controls rendered in this item. Controls in that enum MUST be in order. + enum SubControl : int { + CheckBox, + LineSelect, + StartSelect, + EndSelect, + MidSelect, + Label + }; + explicit ElementItemDelegate(ElementView* parent); - ~ElementItemDelegate() override; + ~ElementItemDelegate() override = default; 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; + ElementItem* getElementItem(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. + QRect subControlRect(SubControl element, const QStyleOptionViewItem& option, const QModelIndex& index) const; + void drawSubControl(SubControl element, QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + + const int gap = 4; // 4px of spacing between consecutive elements Q_SIGNALS: void itemHovered(QModelIndex); void itemChecked(QModelIndex, Qt::CheckState state); }; +// clang-format on // helper class to store additional information about the listWidget entry. class ElementItem: public QListWidgetItem { @@ -154,8 +165,13 @@ public: Hidden = 2, }; - ElementItem(int elementnr, int startingVertex, int midVertex, int endVertex, - Base::Type geometryType, GeometryState state, const QString& lab, + ElementItem(int elementnr, + int startingVertex, + int midVertex, + int endVertex, + Base::Type geometryType, + GeometryState state, + const QString& lab, ViewProviderSketch* sketchView) : ElementNbr(elementnr) , StartingVertex(startingVertex) @@ -177,9 +193,13 @@ public: ~ElementItem() override {} - bool isVisible() + bool canBeHidden() const { + return State != GeometryState::External; + } + bool isVisible() const + { if (State != GeometryState::External) { const auto geo = sketchView->getSketchObject()->getGeometry(ElementNbr); if (geo) { @@ -195,6 +215,40 @@ public: return true; } + QVariant data(int role) const override + { + // In order for content-box to include size of the 4 geometry icons we need to provide + // Qt with information about decoration (icon) size. This is hack to work around Qt + // limitation of not knowing about padding, border and margin boxes of stylesheets + // thus being unable to provide proper sizeHint for stylesheets to render correctly + if (role == Qt::DecorationRole) { + int size = listWidget()->style()->pixelMetric(QStyle::PM_ListViewIconSize); + + return QIcon(QPixmap(QSize(size, size))); + } + + return QListWidgetItem::data(role); + } + + bool isGeometrySelected(Sketcher::PointPos pos) const + { + switch (pos) { + case Sketcher::PointPos::none: + return isLineSelected; + case Sketcher::PointPos::start: + return isStartingPointSelected; + case Sketcher::PointPos::end: + return isEndPointSelected; + case Sketcher::PointPos::mid: + return isMidPointSelected; + } + } + + Sketcher::SketchObject* getSketchObject() const + { + return sketchView->getSketchObject(); + } + int ElementNbr; int StartingVertex; int MidVertex; @@ -218,6 +272,7 @@ public: private: ViewProviderSketch* sketchView; }; +// clang-format off class ElementFilterList: public QListWidget { @@ -498,6 +553,7 @@ ElementView::~ElementView() void ElementView::changeLayer(int layer) { App::Document* doc = App::GetApplication().getActiveDocument(); + if (!doc) return; @@ -508,7 +564,6 @@ void ElementView::changeLayer(int layer) auto geoids = getGeoIdsOfEdgesFromNames(sketchobject, ft->getSubNames()); - auto geometry = sketchobject->Geometry.getValues(); auto newgeometry(geometry); @@ -541,6 +596,47 @@ void ElementView::changeLayer(int layer) doc->commitTransaction(); } +void ElementView::changeLayer(ElementItem* item, int layer) +{ + App::Document* doc = App::GetApplication().getActiveDocument(); + + if (!doc) { + return; + } + + doc->openTransaction("Geometry Layer Change"); + + auto sketchObject = item->getSketchObject(); + + auto geometry = sketchObject->Geometry.getValues(); + auto newGeometry(geometry); + + auto geoid = item->ElementNbr; + + // currently only internal geometry can be changed from one layer to another + if (geoid >= 0) { + auto currentLayer = getSafeGeomLayerId(geometry[geoid]); + + if (currentLayer != layer) { + auto geo = geometry[geoid]->clone(); + setSafeGeomLayerId(geo, layer); + newGeometry[geoid] = geo; + + sketchObject->Geometry.setValues(std::move(newGeometry)); + sketchObject->solve(); + } + } + else { + Gui::TranslatedUserWarning( + 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")); + } + + doc->commitTransaction(); +} + void ElementView::contextMenuEvent(QContextMenuEvent* event) { QMenu menu; @@ -738,17 +834,16 @@ void ElementView::deleteSelectedItems() void ElementView::onIndexHovered(QModelIndex index) { + update(index); + Q_EMIT onItemHovered(itemFromIndex(index)); } -void ElementView::onIndexChecked(QModelIndex, Qt::CheckState state) +void ElementView::onIndexChecked(QModelIndex index, Qt::CheckState state) { - if (state == Qt::Checked) { - changeLayer(static_cast(ElementItem::Layer::Default)); - } - else { - changeLayer(static_cast(ElementItem::Layer::Hidden)); - } + auto item = itemFromIndex(index); + + changeLayer(item, static_cast(state == Qt::Checked ? ElementItem::Layer::Default : ElementItem::Layer::Hidden)); } ElementItem* ElementView::itemFromIndex(const QModelIndex& index) @@ -756,154 +851,224 @@ ElementItem* ElementView::itemFromIndex(const QModelIndex& index) return static_cast(QListWidget::itemFromIndex(index)); } +// clang-format on /* ElementItem delegate ---------------------------------------------------- */ - ElementItemDelegate::ElementItemDelegate(ElementView* parent) : QStyledItemDelegate(parent) -{// This class relies on the parent being an ElementView, see getElementtItem +{ // This class relies on the parent being an ElementView, see getElementtItem } -ElementItemDelegate::~ElementItemDelegate() -{} - - -void ElementItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, +void ElementItemDelegate::paint(QPainter* painter, + const QStyleOptionViewItem& option, const QModelIndex& index) const { - ElementItem* item = getElementtItem(index); + ElementItem* item = getElementItem(index); - if (item) { + if (!item) { + return; + } - QStyleOptionButton checkboxstyle; - checkboxstyle.rect = option.rect; + auto style = option.widget ? option.widget->style() : QApplication::style(); - checkboxstyle.state |= QStyle::State_Enabled; + QStyleOptionViewItem itemOption = option; - if (item->isVisible()) - checkboxstyle.state |= QStyle::State_On; - else - checkboxstyle.state |= QStyle::State_Off; + initStyleOption(&itemOption, index); - QRect checkboxrect = - QApplication::style()->subElementRect(QStyle::SE_CheckBoxIndicator, &checkboxstyle); + if (item->isLineSelected || item->isStartingPointSelected || item->isEndPointSelected + || item->isMidPointSelected) { + itemOption.state |= QStyle::State_Active; + } - customIconsMargin = leftMargin + checkboxrect.width(); + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &itemOption, painter, option.widget); - QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkboxstyle, painter); + drawSubControl(SubControl::CheckBox, painter, option, index); + drawSubControl(SubControl::LineSelect, painter, option, index); + drawSubControl(SubControl::StartSelect, painter, option, index); + drawSubControl(SubControl::EndSelect, painter, option, index); + drawSubControl(SubControl::MidSelect, painter, option, index); + drawSubControl(SubControl::Label, painter, option, index); +} - int height = option.rect.height(); - int width = height;// icons are square. - int x0 = option.rect.x() + customIconsMargin; - int iconsize = height - 2 * border; - int btny = option.rect.y() + border; +QRect ElementItemDelegate::subControlRect(SubControl element, + const QStyleOptionViewItem& option, + const QModelIndex& index) const +{ + auto itemOption = option; - if (item->isLineSelected || item->isStartingPointSelected || item->isEndPointSelected - || item->isMidPointSelected) {// option.state & QStyle::State_Selected + auto style = option.widget ? option.widget->style() : QApplication::style(); - auto unselecticon = [&](int iconnumber) { - QRect rect {x0 + border + width * iconnumber, btny, iconsize, iconsize}; - painter->fillRect(rect, option.palette.base()); - }; + initStyleOption(&itemOption, index); - QRect selection = QRect(customIconsMargin, - option.rect.y(), - option.rect.width() - customIconsMargin, - option.rect.height()); + QRect checkBoxRect = + style->subElementRect(QStyle::SE_CheckBoxIndicator, &itemOption, option.widget); - painter->fillRect(selection, option.palette.highlight());// paint the item as selected + checkBoxRect.moveTo(gap, + option.rect.top() + (option.rect.height() - checkBoxRect.height()) / 2); - // Repaint individual icons - if (!item->isLineSelected) - unselecticon(0); + if (element == SubControl::CheckBox) { + return checkBoxRect; + } - if (!item->isStartingPointSelected) - unselecticon(1); + QRect selectRect = + style->subElementRect(QStyle::SE_ItemViewItemDecoration, &itemOption, option.widget) + .translated(checkBoxRect.right() + gap, 0); - if (!item->isEndPointSelected) - unselecticon(2); + unsigned pos = element - SubControl::LineSelect; - if (!item->isMidPointSelected) - unselecticon(3); + auto rect = selectRect.translated((selectRect.width() + gap) * pos, 0); + + if (element != SubControl::Label) { + return rect; + } + + rect.setRight(itemOption.rect.right()); + + return rect; +} + +void ElementItemDelegate::drawSubControl(SubControl element, + QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index) const +{ + auto item = getElementItem(index); + auto style = option.widget ? option.widget->style() : QApplication::style(); + + auto rect = subControlRect(element, option, index); + + auto mousePos = option.widget->mapFromGlobal(QCursor::pos()); + auto isHovered = rect.contains(mousePos); + + auto drawSelectIcon = [&](Sketcher::PointPos pos) { + auto icon = ElementWidgetIcons::getIcon(item->GeometryType, pos, item->State); + auto opacity = 0.4f; + + if (isHovered) { + opacity = 0.8f; } - auto& iconEdge = - ElementWidgetIcons::getIcon(item->GeometryType, Sketcher::PointPos::none, item->State); - auto& iconStart = - ElementWidgetIcons::getIcon(item->GeometryType, Sketcher::PointPos::start, item->State); - auto& iconEnd = - ElementWidgetIcons::getIcon(item->GeometryType, Sketcher::PointPos::end, item->State); - auto& iconMid = - ElementWidgetIcons::getIcon(item->GeometryType, Sketcher::PointPos::mid, item->State); - // getIcon(item->GeometryType); + if (item->isGeometrySelected(pos)) { + opacity = 1.0f; + } - painter->drawPixmap(x0 + border, btny, iconEdge.pixmap(iconsize, iconsize)); - painter->drawPixmap(x0 + border + width, btny, iconStart.pixmap(iconsize, iconsize)); - painter->drawPixmap(x0 + border + width * 2, btny, iconEnd.pixmap(iconsize, iconsize)); - painter->drawPixmap(x0 + border + width * 3, btny, iconMid.pixmap(iconsize, iconsize)); + painter->setOpacity(opacity); + painter->drawPixmap(rect, icon.pixmap(rect.size())); + }; - // Label : - painter->drawText( - x0 + width * 4 + 3 * border, option.rect.y() + height - textBottomMargin, item->label); + painter->save(); + + switch (element) { + case SubControl::CheckBox: { + QStyleOptionButton checkboxOption; + + checkboxOption.initFrom(option.widget); + checkboxOption.rect = rect; + + checkboxOption.state.setFlag(QStyle::State_Enabled, item->canBeHidden()); + + if (isHovered) { + checkboxOption.state |= QStyle::State_MouseOver; + } + + if (item->isVisible()) { + checkboxOption.state |= QStyle::State_On; + } + else { + checkboxOption.state |= QStyle::State_Off; + } + + style->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, + &checkboxOption, + painter, + option.widget); + + break; + } + + case LineSelect: { + drawSelectIcon(Sketcher::PointPos::none); + break; + } + + case StartSelect: { + drawSelectIcon(Sketcher::PointPos::start); + break; + } + + case EndSelect: { + drawSelectIcon(Sketcher::PointPos::end); + break; + } + + case MidSelect: { + drawSelectIcon(Sketcher::PointPos::mid); + break; + } + + case Label: { + QRect rect = subControlRect(SubControl::Label, option, index); + + auto labelBoundingBox = painter->fontMetrics().tightBoundingRect(item->label); + + painter->drawText(rect.x(), + option.rect.bottom() + - (option.rect.height() - labelBoundingBox.height()) / 2, + item->label); + + break; + } } + + painter->restore(); } +// clang-format off bool ElementItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) { - auto getSubElementType = [&](ElementItem* item, int xPos, int width) { - bool label = (xPos > option.rect.x() + customIconsMargin + width * 4 + border); + auto item = getElementItem(index); - if ((xPos < option.rect.x() + customIconsMargin + width + border) - || (item->GeometryType != Part::GeomPoint::getClassTypeId() && label)) + auto getSubElementType = [&](QPoint pos) { + if (subControlRect(SubControl::LineSelect, option, index).contains(pos)) { return SubElementType::edge; - if (xPos < option.rect.x() + customIconsMargin + width * 2 + border - || (item->GeometryType == Part::GeomPoint::getClassTypeId() && label)) + } else if (subControlRect(SubControl::StartSelect, option, index).contains(pos)) { return SubElementType::start; - if (xPos < option.rect.x() + customIconsMargin + width * 3 + border) + } else if (subControlRect(SubControl::EndSelect, option, index).contains(pos)) { return SubElementType::end; - else if (xPos < option.rect.x() + customIconsMargin + width * 4 + border) + } else if (subControlRect(SubControl::MidSelect, option, index).contains(pos)) { return SubElementType::mid; - else - return SubElementType::none; + } else { + // depending on geometry type by default we select either point or edge + return item->GeometryType == Part::GeomPoint::getClassTypeId() ? SubElementType::start : SubElementType::edge; + } }; if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) { - QMouseEvent* mEvent = static_cast(event); - ElementItem* item = getElementtItem(index); + auto mouseEvent = static_cast(event); - int xPos = mEvent->pos().x(); - int width = option.rect.height();// icons are square + item->clickedOn = getSubElementType(mouseEvent->pos()); + item->rightClicked = mouseEvent->button() == Qt::RightButton; - item->clickedOn = getSubElementType(item, xPos, width); + if (item->canBeHidden()) { + QRect checkboxRect = subControlRect(SubControl::CheckBox, option, index); - if (mEvent->button() == Qt::RightButton) - item->rightClicked = true; - - QRect checkboxrect = QRect( - leftMargin, option.rect.y(), customIconsMargin - leftMargin, option.rect.height()); - - if (mEvent->button() == Qt::LeftButton && checkboxrect.contains(mEvent->pos())) { - Q_EMIT itemChecked(index, item->isVisible() ? Qt::Unchecked : Qt::Checked); + if (mouseEvent->button() == Qt::LeftButton && checkboxRect.contains(mouseEvent->pos())) { + Q_EMIT itemChecked(index, item->isVisible() ? Qt::Unchecked : Qt::Checked); + } } } else if (event->type() == QEvent::MouseMove) { - SubElementType typeUnderMouse; - QMouseEvent* mEvent = static_cast(event); - int xPos = mEvent->pos().x(); - int width = option.rect.height();// icons are square + auto mouseEvent = static_cast(event); - ElementItem* item = getElementtItem(index); + item->hovered = getSubElementType(mouseEvent->pos()); - typeUnderMouse = getSubElementType(item, xPos, width); - - item->hovered = typeUnderMouse; Q_EMIT itemHovered(index); } return QStyledItemDelegate::editorEvent(event, model, option, index); } -ElementItem* ElementItemDelegate::getElementtItem(const QModelIndex& index) const +ElementItem* ElementItemDelegate::getElementItem(const QModelIndex& index) const { ElementView* elementView = static_cast(parent()); return elementView->itemFromIndex(index); diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.h b/src/Mod/Sketcher/Gui/TaskSketcherElements.h index 1284a804d2..6965b44ea1 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.h @@ -109,6 +109,7 @@ Q_SIGNALS: private: void changeLayer(int layer); + void changeLayer(ElementItem* item, int layer); }; class ElementFilterList; diff --git a/src/Mod/Sketcher/Gui/Utils.cpp b/src/Mod/Sketcher/Gui/Utils.cpp index dc76c2ae1c..2c8af981d8 100644 --- a/src/Mod/Sketcher/Gui/Utils.cpp +++ b/src/Mod/Sketcher/Gui/Utils.cpp @@ -88,6 +88,15 @@ bool Sketcher::isBSplineCurve(const Part::Geometry& geom) return geom.is(); } +bool Sketcher::isPeriodicBSplineCurve(const Part::Geometry& geom) +{ + if (geom.is()) { + auto* spline = static_cast(&geom); + return spline->isPeriodic(); + } + return false; +} + bool Sketcher::isPoint(const Part::Geometry& geom) { return geom.is(); diff --git a/src/Mod/Sketcher/Gui/Utils.h b/src/Mod/Sketcher/Gui/Utils.h index 09f984110a..07d6a6acff 100644 --- a/src/Mod/Sketcher/Gui/Utils.h +++ b/src/Mod/Sketcher/Gui/Utils.h @@ -61,6 +61,7 @@ bool isLineSegment(const Part::Geometry&); bool isArcOfHyperbola(const Part::Geometry&); bool isArcOfParabola(const Part::Geometry&); bool isBSplineCurve(const Part::Geometry&); +bool isPeriodicBSplineCurve(const Part::Geometry&); bool isPoint(const Part::Geometry&); bool isCircleOrArc(const Part::Geometry& geo); diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index ca815cdee5..05f7db40c7 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -937,8 +937,9 @@ bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVe -16000, -16000);// certainly far away from any clickable place, to avoid // re-trigger of double-click if next click happens fast. - - Mode = STATUS_NONE; + if (Mode != STATUS_SELECT_Wire) { + Mode = STATUS_NONE; + } } else { DoubleClick::prvClickTime = SbTime::getTimeOfDay(); @@ -1031,6 +1032,11 @@ bool ViewProviderSketch::mouseButtonPressed(int Button, bool pressed, const SbVe } Mode = STATUS_NONE; return true; + case STATUS_SELECT_Wire: { + toggleWireSelelection(preselection.PreselectCurve); + Mode = STATUS_NONE; + return true; + } case STATUS_SELECT_Constraint: if (pp) { auto sels = preselection.PreselectConstraintSet; @@ -1251,7 +1257,9 @@ void ViewProviderSketch::editDoubleClicked() Base::Console().Log("double click point:%d\n", preselection.PreselectPoint); } else if (preselection.isPreselectCurveValid()) { - Base::Console().Log("double click edge:%d\n", preselection.PreselectCurve); + // We cannot do toggleWireSelelection directly here because the released event with + //STATUS_NONE return false which clears the selection. + Mode = STATUS_SELECT_Wire; } else if (preselection.isCrossPreselected()) { Base::Console().Log("double click cross:%d\n", @@ -1278,6 +1286,70 @@ void ViewProviderSketch::editDoubleClicked() } } +void ViewProviderSketch::toggleWireSelelection(int clickedGeoId) +{ + Sketcher::SketchObject* obj = getSketchObject(); + + const Part::Geometry* geo1 = obj->getGeometry(clickedGeoId); + if (isPoint(*geo1) || isCircle(*geo1) || isEllipse(*geo1) || isPeriodicBSplineCurve(*geo1)) { + return; + } + + const char* type1 = (clickedGeoId >= 0) ? "Edge" : "ExternalEdge"; + std::stringstream ss1; + ss1 << type1 << clickedGeoId + 1; + bool selecting = isSelected(ss1.str()); + + std::vector connectedEdges = { clickedGeoId }; + bool partHasBeenAdded = true; + while (partHasBeenAdded) { + partHasBeenAdded = false; + for (int geoId = 0; geoId <= obj->getHighestCurveIndex(); geoId++) { + if (geoId == clickedGeoId || std::find(connectedEdges.begin(), connectedEdges.end(), geoId) != connectedEdges.end()) { + continue; + } + + const Part::Geometry* geo = obj->getGeometry(geoId); + if (isPoint(*geo) || isCircle(*geo) || isEllipse(*geo) || isPeriodicBSplineCurve(*geo1)) { + continue; + } + + Base::Vector3d p11 = obj->getPoint(geoId, PointPos::start); + Base::Vector3d p12 = obj->getPoint(geoId, PointPos::end); + bool connected = false; + for (auto conGeoId : connectedEdges) { + Base::Vector3d p21 = obj->getPoint(conGeoId, PointPos::start); + Base::Vector3d p22 = obj->getPoint(conGeoId, PointPos::end); + if ((p11 - p21).Length() < Precision::Confusion() + || (p11 - p22).Length() < Precision::Confusion() + || (p12 - p21).Length() < Precision::Confusion() + || (p12 - p22).Length() < Precision::Confusion()) { + connected = true; + } + } + + if (connected) { + connectedEdges.push_back(geoId); + partHasBeenAdded = true; + break; + } + } + } + + for (auto geoId : connectedEdges) { + std::stringstream ss; + const char* type = (geoId >= 0) ? "Edge" : "ExternalEdge"; + ss << type << geoId + 1; + if (!selecting && isSelected(ss.str())) { + rmvSelection(ss.str()); + } + else if (selecting && !isSelected(ss.str())) { + addSelection2(ss.str()); + } + } + +} + bool ViewProviderSketch::mouseMove(const SbVec2s& cursorPos, Gui::View3DInventorViewer* viewer) { // maximum radius for mouse moves when selecting a geometry before switching to drag mode @@ -3814,6 +3886,11 @@ void ViewProviderSketch::generateContextMenu() int selectedConics = 0; int selectedPoints = 0; int selectedConstraints = 0; + int selectedBsplines = 0; + int selectedBsplineKnots = 0; + int selectedOrigin = 0; + int selectedEndPoints = 0; + bool onlyOrigin = false; Gui::MenuItem menu; menu.setCommand("Sketcher context"); @@ -3824,31 +3901,70 @@ void ViewProviderSketch::generateContextMenu() // if something is selected, count different elements in the current selection if (selection.size() > 0) { const std::vector SubNames = selection[0].getSubNames(); - - for (auto& name : SubNames) { - if (name.substr(0, 4) == "Edge") { - ++selectedEdges; - + const Sketcher::SketchObject* obj; + if (selection[0].getObject()->isDerivedFrom()) { + obj = static_cast(selection[0].getObject()); + for (auto& name : SubNames) { int geoId = std::atoi(name.substr(4, 4000).c_str()) - 1; - if (geoId >= 0) { - const Part::Geometry* geo = getSketchObject()->getGeometry(geoId); - if (isLineSegment(*geo)) { - ++selectedLines; - } - else { - ++selectedConics; + const Part::Geometry* geo = getSketchObject()->getGeometry(geoId); + if (name.substr(0, 4) == "Edge") { + ++selectedEdges; + + if (geoId >= 0) { + if (isLineSegment(*geo)) { + ++selectedLines; + } + else if (geo->is()) { + ++selectedBsplines; + } + else { + ++selectedConics; + } } } - } - else if (name.substr(0, 4) == "Vert") { - ++selectedPoints; - } - else if (name.substr(0, 4) == "Cons") { - ++selectedConstraints; + else if (name.substr(0, 4) == "Vert") { + ++selectedPoints; + Sketcher::PointPos posId; + getIdsFromName(name, obj, geoId, posId); + if (isBsplineKnotOrEndPoint(obj, geoId, posId)) { + ++selectedBsplineKnots; + } + if (Sketcher::PointPos::start != posId || Sketcher::PointPos::end != posId) { + ++selectedEndPoints; + } + } + else if (name.substr(0, 4) == "Cons") { + ++selectedConstraints; + } + else if (name.substr(2, 5) == "Axis") { + ++selectedEdges; + ++selectedLines; + ++selectedOrigin; + } + else if (name.substr(0, 4) == "Root") { + ++selectedPoints; + ++selectedOrigin; + } } } + if (selectedPoints + selectedEdges == selectedOrigin) { + onlyOrigin = true; + } // build context menu items depending on the selection - if (selectedEdges >= 1 && selectedPoints == 0) { + if (selectedBsplines > 0 && selectedBsplines == selectedEdges && selectedPoints == 0 + && !onlyOrigin) { + menu << "Sketcher_BSplineInsertKnot" + << "Sketcher_BSplineIncreaseDegree" + << "Sketcher_BSplineDecreaseDegree"; + } + else if (selectedBsplineKnots > 0 && selectedBsplineKnots == selectedPoints + && selectedEdges == 0 && !onlyOrigin) { + if (selectedBsplineKnots == 1) { + menu << "Sketcher_BSplineIncreaseKnotMultiplicity" + << "Sketcher_BSplineDecreaseKnotMultiplicity"; + } + } + if (selectedEdges >= 1 && selectedPoints == 0 && selectedBsplines == 0 && !onlyOrigin) { menu << "Sketcher_Dimension"; if (selectedConics == 0) { menu << "Sketcher_ConstrainHorVer" @@ -3876,9 +3992,9 @@ void ViewProviderSketch::generateContextMenu() menu << "Sketcher_ConstrainTangent"; } } - else if (selectedEdges == 1 && selectedPoints >= 1) { + else if (selectedEdges == 1 && selectedPoints >= 1 && !onlyOrigin) { menu << "Sketcher_Dimension"; - if (selectedConics == 0) { + if (selectedConics == 0 && selectedBsplines == 0) { menu << "Sketcher_ConstrainCoincidentUnified" << "Sketcher_ConstrainHorVer" << "Sketcher_ConstrainVertical" @@ -3886,6 +4002,10 @@ void ViewProviderSketch::generateContextMenu() if (selectedPoints == 2) { menu << "Sketcher_ConstrainSymmetric"; } + if (selectedPoints == 1) { + menu << "Sketcher_ConstrainPerpendicular" + << "Sketcher_ConstrainTangent"; + } } else { menu << "Sketcher_ConstrainCoincidentUnified" @@ -3893,7 +4013,7 @@ void ViewProviderSketch::generateContextMenu() << "Sketcher_ConstrainTangent"; } } - else if (selectedEdges == 0 && selectedPoints >= 1) { + else if (selectedEdges == 0 && selectedPoints >= 1 && !onlyOrigin) { menu << "Sketcher_Dimension"; if (selectedPoints > 1) { @@ -3902,8 +4022,15 @@ void ViewProviderSketch::generateContextMenu() << "Sketcher_ConstrainVertical" << "Sketcher_ConstrainHorizontal"; } + if (selectedPoints == 2) { + menu << "Sketcher_ConstrainPerpendicular" + << "Sketcher_ConstrainTangent"; + if (selectedEndPoints == 2) { + menu << "Sketcher_JoinCurves"; + } + } } - else if (selectedLines >= 1 && selectedPoints >= 1) { + else if (selectedLines >= 1 && selectedPoints >= 1 && !onlyOrigin) { menu << "Sketcher_Dimension" << "Sketcher_ConstrainHorVer" << "Sketcher_ConstrainVertical" diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.h b/src/Mod/Sketcher/Gui/ViewProviderSketch.h index 71f770aec7..bcadc5bed6 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.h +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.h @@ -549,6 +549,7 @@ public: STATUS_SELECT_Edge, /**< enum value an edge was selected. */ STATUS_SELECT_Constraint, /**< enum value a constraint was selected. */ STATUS_SELECT_Cross, /**< enum value the base coordinate system was selected. */ + STATUS_SELECT_Wire, /**< enum value and edge was double clicked. */ STATUS_SKETCH_DragPoint, /**< enum value while dragging a point. */ STATUS_SKETCH_DragCurve, /**< enum value while dragging a curve. */ STATUS_SKETCH_DragConstraint, /**< enum value while dragging a compatible constraint. */ @@ -671,6 +672,7 @@ public: const Gui::View3DInventorViewer* viewer) override; //@} + void deleteSelected(); /// Control the overlays appearing on the Tree and reflecting different sketcher states QIcon mergeColorfulOverlayIcons(const QIcon& orig) const override; @@ -701,7 +703,6 @@ public: friend class ViewProviderSketchDrawSketchHandlerAttorney; friend class ViewProviderSketchCoinAttorney; friend class ViewProviderSketchSnapAttorney; - friend class ViewProviderSketchShortcutListenerAttorney; //@} protected: /** @name enter/exit edit mode */ @@ -720,6 +721,8 @@ protected: void deactivateHandler(); /// get called if a subelement is double clicked while editing void editDoubleClicked(); + /// get called when an edge is double clicked to select/unselect the whole wire + void toggleWireSelelection(int geoId); //@} /** @name Solver Information */ @@ -864,9 +867,6 @@ private: bool isConstraintSelected(int constraintId) const; - //********* ViewProviderSketchShortcutListenerAttorney ***********// - void deleteSelected(); - //********* ViewProviderSketchDrawSketchHandlerAttorney **********// void setConstraintSelectability(bool enabled = true); void setPositionText(const Base::Vector2d& Pos, const SbString& txt); diff --git a/src/Mod/Sketcher/Gui/Workbench.cpp b/src/Mod/Sketcher/Gui/Workbench.cpp index 5c2d74ed2e..c1be2c1ac5 100644 --- a/src/Mod/Sketcher/Gui/Workbench.cpp +++ b/src/Mod/Sketcher/Gui/Workbench.cpp @@ -543,7 +543,11 @@ inline void SketcherAddWorkbenchTools(Gui::MenuItem& consaccel) << "Sketcher_RemoveAxesAlignment" << "Separator" << "Sketcher_DeleteAllGeometry" - << "Sketcher_DeleteAllConstraints"; + << "Sketcher_DeleteAllConstraints" + << "Separator" + << "Sketcher_CopyClipboard" + << "Sketcher_Cut" + << "Sketcher_Paste"; } template<> diff --git a/src/Mod/TechDraw/App/Cosmetic.cpp b/src/Mod/TechDraw/App/Cosmetic.cpp index aa47615f21..5b65996ff1 100644 --- a/src/Mod/TechDraw/App/Cosmetic.cpp +++ b/src/Mod/TechDraw/App/Cosmetic.cpp @@ -257,6 +257,8 @@ unsigned int CosmeticEdge::getMemSize () const void CosmeticEdge::Save(Base::Writer &writer) const { + // TODO: this should be using m_format->Save(writer) instead of saving the individual + // fields. writer.Stream() << writer.ind() << "