diff --git a/conda/build.sh b/conda/build.sh index bb409f7292..f8e271f76d 100755 --- a/conda/build.sh +++ b/conda/build.sh @@ -31,7 +31,7 @@ if [[ ${HOST} =~ "Darwin" ]]; then # install space-mouse if [ ! -d "/Library/Frameworks/3DconnexionClient.framework" ]; then - echo "Installing 3D connexion space mouse drivers." + echo "Installing 3D connexion spacemouse drivers." curl -o /tmp/3dFW.dmg -L 'https://download.3dconnexion.com/drivers/mac/10-6-6_360DF97D-ED08-4ccf-A55E-0BF905E58476/3DxWareMac_v10-6-6_r3234.dmg' hdiutil attach -readonly /tmp/3dFW.dmg sudo installer -package /Volumes/3Dconnexion\ Software/Install\ 3Dconnexion\ software.pkg -target / diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 281b56946e..67aa53a8d2 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -138,6 +138,7 @@ #include #include #include +#include using namespace App; using namespace std; @@ -2525,6 +2526,19 @@ void Application::initConfig(int argc, char ** argv) // capture path SaveEnv("PATH"); + + // Save version numbers of the libraries +#ifdef OCC_VERSION_STRING_EXT + mConfig["OCC_VERSION"] = OCC_VERSION_STRING_EXT; +#endif + mConfig["BOOST_VERSION"] = BOOST_LIB_VERSION; + mConfig["PYTHON_VERSION"] = PY_VERSION; + mConfig["QT_VERSION"] = QT_VERSION_STR; + mConfig["EIGEN_VERSION"] = FC_EIGEN3_VERSION; + mConfig["PYSIDE_VERSION"] = FC_PYSIDE_VERSION; + mConfig["XERCESC_VERSION"] = FC_XERCESC_VERSION; + + logStatus(); } @@ -2617,11 +2631,12 @@ std::list Application::processFiles(const std::list& f processed.push_back(*it); } else if (file.hasExtension("py")) { - try{ + try { + Base::Interpreter().addPythonPath(file.dirPath().c_str()); Base::Interpreter().loadModule(file.fileNamePure().c_str()); processed.push_back(*it); } - catch(const PyException&) { + catch (const PyException&) { // if loading the module does not work, try just running the script (run in __main__) Base::Interpreter().runFile(file.filePath().c_str(),true); processed.push_back(*it); @@ -2879,6 +2894,7 @@ QString getUserHome() * Returns a directory location where persistent data shared across applications can be stored. * This method returns the old non-XDG-compliant root path where to store config files and application data. */ +#if defined(FC_OS_WIN32) QString getOldGenericDataLocation(QString home) { #if defined(FC_OS_WIN32) @@ -2894,6 +2910,7 @@ QString getOldGenericDataLocation(QString home) return home; } +#endif /*! * \brief getSubDirectories @@ -3105,17 +3122,6 @@ void Application::ExtractUserPath() boost::filesystem::path config = findPath(configHome, customHome, subdirs, true); mConfig["UserConfigPath"] = pathToString(config) + PATHSEP; - std::vector oldsubdirs; - getOldDataLocation(mConfig, oldsubdirs); - boost::filesystem::path appData = findPath(getOldGenericDataLocation(homePath), customData, oldsubdirs, false); - - // If in new location user.cfg doesn't exist but in the old location then copy it - boost::filesystem::path oldUsercfg = appData / "user.cfg"; - boost::filesystem::path newUsercfg = config / "user.cfg"; - if (boost::filesystem::exists(oldUsercfg) && !boost::filesystem::exists(newUsercfg)) { - boost::filesystem::copy(oldUsercfg, newUsercfg); - } - // User cache path // diff --git a/src/App/Application.h b/src/App/Application.h index e11b3b1d6f..84ddbb64ec 100644 --- a/src/App/Application.h +++ b/src/App/Application.h @@ -330,13 +330,13 @@ public: //@} /** @name methods for the open handler - * With this facility a Application module can register - * a ending (filetype) which he can handle to open. + * With this facility an Application module can register + * an ending (filetype) which it can handle to open. * The ending and the module name are stored and if the file - * type is opened the module get loaded and need to register a + * type is opened the module gets loaded and needs to register an * OpenHandler class in the OpenHandlerFactorySingleton. - * After the module is loaded a OpenHandler of this type is created - * and the file get loaded. + * After the module is loaded, an OpenHandler of this type is created + * and the file gets loaded. * @see OpenHandler * @see OpenHandlerFactorySingleton */ @@ -446,7 +446,7 @@ protected: void renameDocument(const char *OldName, const char *NewName); /** @name I/O of the document - * This slot get connected to all App::Documents created + * This slot gets connected to all App::Documents created */ //@{ void slotBeforeChangeDocument(const App::Document&, const App::Property&); diff --git a/src/App/CMakeLists.txt b/src/App/CMakeLists.txt index be1699a541..cf28254422 100644 --- a/src/App/CMakeLists.txt +++ b/src/App/CMakeLists.txt @@ -48,6 +48,7 @@ fc_copy_file_if_different( # ----------------------------------------------------------------------------- include_directories( + ${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}/src ${CMAKE_SOURCE_DIR}/src ${CMAKE_CURRENT_BINARY_DIR} diff --git a/src/App/Document.h b/src/App/Document.h index 6d0e7c5ec2..9ca8bc97e8 100644 --- a/src/App/Document.h +++ b/src/App/Document.h @@ -367,7 +367,7 @@ public: * When undo, Gui component can query getAvailableUndo(id) to see if it is * possible to undo with a given ID. If there more than one undo * transactions, meaning that there are other transactions before the given - * ID. The Gui component shall ask user if he wants to undo multiple steps. + * ID. The Gui component shall ask user if they want to undo multiple steps. * And if the user agrees, call undo(id) to unroll all transaction before * and including the the one with the give ID. Same applies for redo. * diff --git a/src/App/DocumentPy.xml b/src/App/DocumentPy.xml index c3dbdf7157..97a419a4ba 100644 --- a/src/App/DocumentPy.xml +++ b/src/App/DocumentPy.xml @@ -38,6 +38,24 @@ Restore the document from disk + + + Checks if the document is saved + + + + + Get the program version that a project file was created with + + + + + +For a regular document it returns its file name property. +For a temporary document it returns its transient directory. + + + Merges this document with another project file @@ -144,12 +162,42 @@ object of this document. Clear the undo stack of the document + + + Clear the whole document + + + + + Set a flag that allows or forbids to close a document + + + + + Check if the document can be closed. The default value is True + + recompute(objs=None): Recompute the document and returns the amount of recomputed features - + + + Check if any object must be recomputed + + + + + Purge the touched state of all objects + + + + + Check if any object is in touched state + + + Return the object with the given name diff --git a/src/App/DocumentPyImp.cpp b/src/App/DocumentPyImp.cpp index 18d1e1830a..0896d182b1 100644 --- a/src/App/DocumentPyImp.cpp +++ b/src/App/DocumentPyImp.cpp @@ -148,6 +148,30 @@ PyObject* DocumentPy::restore(PyObject * args) Py_Return; } +PyObject* DocumentPy::isSaved(PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + bool ok = getDocumentPtr()->isSaved(); + return Py::new_reference_to(Py::Boolean(ok)); +} + +PyObject* DocumentPy::getProgramVersion(PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + const char* version = getDocumentPtr()->getProgramVersion(); + return Py::new_reference_to(Py::String(version)); +} + +PyObject* DocumentPy::getFileName(PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + const char* fn = getDocumentPtr()->getFileName(); + return Py::new_reference_to(Py::String(fn)); +} + PyObject* DocumentPy::mergeProject(PyObject * args) { char* filename; @@ -446,14 +470,39 @@ PyObject* DocumentPy::redo(PyObject * args) Py_Return; } -PyObject* DocumentPy::clearUndos(PyObject * args) +PyObject* DocumentPy::clearUndos(PyObject * args) { - if (!PyArg_ParseTuple(args, "")) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "")) + return nullptr; getDocumentPtr()->clearUndos(); Py_Return; } +PyObject* DocumentPy::clearDocument(PyObject * args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + getDocumentPtr()->clearDocument(); + Py_Return; +} + +PyObject* DocumentPy::setClosable(PyObject* args) +{ + PyObject* close; + if (!PyArg_ParseTuple(args, "O!", &PyBool_Type, &close)) + return nullptr; + getDocumentPtr()->setClosable(PyObject_IsTrue(close) ? true : false); + Py_Return; +} + +PyObject* DocumentPy::isClosable(PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + bool ok = getDocumentPtr()->isClosable(); + return Py::new_reference_to(Py::Boolean(ok)); +} + PyObject* DocumentPy::recompute(PyObject * args) { PyObject *pyobjs = Py_None; @@ -497,6 +546,30 @@ PyObject* DocumentPy::recompute(PyObject * args) } PY_CATCH; } +PyObject* DocumentPy::mustExecute(PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + bool ok = getDocumentPtr()->mustExecute(); + return Py::new_reference_to(Py::Boolean(ok)); +} + +PyObject* DocumentPy::isTouched(PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + bool ok = getDocumentPtr()->isTouched(); + return Py::new_reference_to(Py::Boolean(ok)); +} + +PyObject* DocumentPy::purgeTouched(PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + getDocumentPtr()->purgeTouched(); + Py_Return; +} + PyObject* DocumentPy::getObject(PyObject *args) { long id = -1; diff --git a/src/App/DynamicProperty.cpp b/src/App/DynamicProperty.cpp index 5851814b34..16b88739f9 100644 --- a/src/App/DynamicProperty.cpp +++ b/src/App/DynamicProperty.cpp @@ -37,7 +37,7 @@ #include #include -FC_LOG_LEVEL_INIT("DynamicProperty",true,true) +FC_LOG_LEVEL_INIT("Property",true,true) using namespace App; @@ -149,23 +149,45 @@ const char* DynamicProperty::getPropertyDocumentation(const char *name) const Property* DynamicProperty::addDynamicProperty(PropertyContainer &pc, const char* type, const char* name, const char* group, const char* doc, short attr, bool ro, bool hidden) { + if(!type) + type = ""; + + std::string _name; + + static ParameterGrp::handle hGrp = GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Document"); + if(hGrp->GetBool("AutoNameDynamicProperty",false)) { + if(!name || !name[0]) + name = type; + _name = getUniquePropertyName(pc,name); + if(_name != name) { + FC_WARN(pc.getFullName() << " rename dynamic property from '" + << name << "' to '" << _name << "'"); + } + name = _name.c_str(); + } else if(!name) + name = ""; // setting a bad name to trigger exception + + auto prop = pc.getPropertyByName(name); + if(prop && prop->getContainer()==&pc) + FC_THROWM(Base::NameError, "Property " << pc.getFullName() << '.' << name << " already exists"); + + if(Base::Tools::getIdentifier(name) != name) + FC_THROWM(Base::NameError, "Invalid property name '" << name << "'"); + Base::BaseClass* base = static_cast(Base::Type::createInstanceByName(type,true)); - if (!base) - return 0; + if (!base) + FC_THROWM(Base::RuntimeError, "Failed to create property " + << pc.getFullName() << '.' << name << " of type " << type); if (!base->getTypeId().isDerivedFrom(Property::getClassTypeId())) { delete base; - std::stringstream str; - str << "'" << type << "' is not a property type"; - throw Base::ValueError(str.str()); + FC_THROWM(Base::ValueError, "Invalid type " << type << " for property " << pc.getFullName() << '.' << name); } // get unique name Property* pcProperty = static_cast(base); - if (!name || !name[0]) - name = type; - auto res = props.get<0>().emplace(pcProperty, - getUniquePropertyName(pc,name), nullptr, group, doc, attr, ro, hidden); + auto res = props.get<0>().emplace(pcProperty,name, nullptr, group, doc, attr, ro, hidden); pcProperty->setContainer(&pc); pcProperty->myName = res.first->name.c_str(); diff --git a/src/App/FreeCADInit.py b/src/App/FreeCADInit.py index 2a7b1d91a7..cafbffcd8a 100644 --- a/src/App/FreeCADInit.py +++ b/src/App/FreeCADInit.py @@ -133,6 +133,9 @@ def InitApplications(): if os.path.isdir(HomeMod): HomeMods = os.listdir(HomeMod) for i in HomeMods: ModDict[i.lower()] = os.path.join(HomeMod,i) + elif os.path.isdir(os.path.join(os.path.expanduser("~"),".FreeCAD","Mod")): + # Check if old location exists + Wrn ("User path has changed to " + FreeCAD.getUserAppDataDir() + ". Please move user modules and macros\n") # Search for additional modules in the macro directory if os.path.isdir(MacroMod): MacroMods = os.listdir(MacroMod) diff --git a/src/App/ObjectIdentifier.h b/src/App/ObjectIdentifier.h index 5c09f1035b..16a16400bc 100644 --- a/src/App/ObjectIdentifier.h +++ b/src/App/ObjectIdentifier.h @@ -346,7 +346,7 @@ public: * * The dependency is a map from document object to a set of property names. * An object identifier may references multiple objects using syntax like - * 'Part.Group[0].Width'. + * 'Part.Group[0].Width'. * * Also, we use set of string instead of set of Property pointer, because * the property may not exist at the time this ObjectIdentifier is @@ -368,7 +368,7 @@ public: /** Get dependencies of this object identifier * - * @param deps: returns the depdenencies. + * @param deps: returns the dependencies. * @param needProps: whether need property dependencies. * @param labels: optional return of any label references. * @@ -378,7 +378,7 @@ public: * dependencies will be returned. */ void getDep(Dependencies &deps, bool needProps, std::vector *labels=0) const; - + /// Returns all label references void getDepLabels(std::vector &labels) const; diff --git a/src/App/PropertyContainer.cpp b/src/App/PropertyContainer.cpp index 5fda5ce279..a7438cea2f 100644 --- a/src/App/PropertyContainer.cpp +++ b/src/App/PropertyContainer.cpp @@ -332,21 +332,21 @@ void PropertyContainer::Restore(Base::XMLReader &reader) reader.readElement("Property"); std::string PropName = reader.getAttribute("name"); std::string TypeName = reader.getAttribute("type"); - auto prop = dynamicProps.restore(*this,PropName.c_str(),TypeName.c_str(),reader); - if(!prop) - prop = getPropertyByName(PropName.c_str()); - - decltype(Property::StatusBits) status; - if(reader.hasAttribute("status")) { - status = decltype(status)(reader.getAttributeAsUnsigned("status")); - if(prop) - prop->setStatusValue(status.to_ulong()); - } // NOTE: We must also check the type of the current property because a // subclass of PropertyContainer might change the type of a property but // not its name. In this case we would force to read-in a wrong property // type and the behaviour would be undefined. try { + auto prop = getPropertyByName(PropName.c_str()); + if(!prop) + prop = dynamicProps.restore(*this,PropName.c_str(),TypeName.c_str(),reader); + + decltype(Property::StatusBits) status; + if(reader.hasAttribute("status")) { + status = decltype(status)(reader.getAttributeAsUnsigned("status")); + if(prop) + prop->setStatusValue(status.to_ulong()); + } // name and type match if (prop && strcmp(prop->getTypeId().getName(), TypeName.c_str()) == 0) { if (!prop->testStatus(Property::Transient) diff --git a/src/App/PropertyLinks.cpp b/src/App/PropertyLinks.cpp index 4d4e696d74..5bdbb5286f 100644 --- a/src/App/PropertyLinks.cpp +++ b/src/App/PropertyLinks.cpp @@ -1321,20 +1321,20 @@ void PropertyLinkSub::Save (Base::Writer &writer) const writer.Stream() << writer.ind() << "getExportName() << "\" sub=\""; if(exporting) { std::string exportName; - writer.Stream() << exportSubName(exportName,obj,sub.c_str()); + writer.Stream() << encodeAttribute(exportSubName(exportName,obj,sub.c_str())); if(shadow.second.size() && _lSubList[i]==shadow.first) writer.Stream() << "\" " ATTR_MAPPED "=\"1"; } else { - writer.Stream() << sub; + writer.Stream() << encodeAttribute(sub); if(_lSubList[i].size()) { if(sub!=_lSubList[i]) { // Stores the actual value that is shadowed. For new version FC, // we will restore this shadowed value instead. - writer.Stream() << "\" " ATTR_SHADOWED "=\"" << _lSubList[i]; + writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(_lSubList[i]); }else if(shadow.first.size()) { // Here means the user set value is old style element name. // We shall then store the shadow somewhere else. - writer.Stream() << "\" " ATTR_SHADOW "=\"" << shadow.first; + writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.first); } } } @@ -3316,16 +3316,17 @@ void PropertyXLink::Save (Base::Writer &writer) const { const auto &sub = shadowSub.second.empty()?subName:shadowSub.second; if(exporting) { std::string exportName; - writer.Stream() << "\" sub=\"" << exportSubName(exportName,_pcLink,sub.c_str()); + writer.Stream() << "\" sub=\"" << + encodeAttribute(exportSubName(exportName,_pcLink,sub.c_str())); if(shadowSub.second.size() && shadowSub.first==subName) writer.Stream() << "\" " ATTR_MAPPED "=\"1"; }else{ - writer.Stream() << "\" sub=\"" << sub; + writer.Stream() << "\" sub=\"" << encodeAttribute(sub); if(sub.size()) { if(sub!=subName) - writer.Stream() << "\" " ATTR_SHADOWED "=\"" << subName; + writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(subName); else if(shadowSub.first.size()) - writer.Stream() << "\" " ATTR_SHADOW "=\"" << shadowSub.first; + writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadowSub.first); } } writer.Stream() << "\"/>" << std::endl; @@ -3341,16 +3342,16 @@ void PropertyXLink::Save (Base::Writer &writer) const { writer.Stream() << writer.ind() << "" << endl; diff --git a/src/Base/PyExport.h b/src/Base/PyExport.h index 530250c0f9..e6bd78e800 100644 --- a/src/Base/PyExport.h +++ b/src/Base/PyExport.h @@ -79,7 +79,7 @@ class PyObjectBase; * @remark One big consequence of this specification is that the programmer must know whether the Python interpreter * gets the Python object or not. If the interpreter gets the object then it decrements the counter later on when * the internal variable is freed. In case the interpreter doesn't get this object then the programmer must do the - * decrement on his own. + * decrement on their own. * * @note To not to undermine this specification the programmer must make sure to get the Python object always via * GetPyObject(). diff --git a/src/Gui/3Dconnexion/3DConnexion.xml b/src/Gui/3Dconnexion/3DConnexion.xml index b35fa02e77..a3a635a3c2 100644 --- a/src/Gui/3Dconnexion/3DConnexion.xml +++ b/src/Gui/3Dconnexion/3DConnexion.xml @@ -35,6 +35,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 7e197b646c..3697a2eb5a 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -87,6 +87,7 @@ #include "SoFCDB.h" #include "PythonConsolePy.h" #include "PythonDebugger.h" +#include "MainWindowPy.h" #include "MDIViewPy.h" #include "View3DPy.h" #include "DlgOnlineHelpImp.h" @@ -99,6 +100,7 @@ #include "ExpressionBindingPy.h" #include "ViewProviderLinkPy.h" +#include "EditorView.h" #include "TextDocumentEditorView.h" #include "SplitView3DInventor.h" #include "View3DInventor.h" @@ -137,6 +139,7 @@ #include #include "View3DViewerPy.h" #include +#include using namespace Gui; @@ -450,6 +453,8 @@ Application::Application(bool GUIenabled) Py_DECREF(descr); } + App::Application::Config()["COIN_VERSION"] = COIN_VERSION; + // Python console binding PythonDebugModule ::init_module(); PythonStdout ::init_type(); @@ -457,6 +462,7 @@ Application::Application(bool GUIenabled) OutputStdout ::init_type(); OutputStderr ::init_type(); PythonStdin ::init_type(); + MainWindowPy ::init_type(); MDIViewPy ::init_type(); View3DInventorPy ::init_type(); View3DInventorViewerPy ::init_type(); @@ -1782,6 +1788,8 @@ void Application::initTypes(void) Gui::AbstractSplitView ::init(); Gui::SplitView3DInventor ::init(); Gui::TextDocumentEditorView ::init(); + Gui::EditorView ::init(); + Gui::PythonEditorView ::init(); // View Provider Gui::ViewProvider ::init(); Gui::ViewProviderExtension ::init(); diff --git a/src/Gui/ApplicationPy.cpp b/src/Gui/ApplicationPy.cpp index 82a883cb1f..7f149f6960 100644 --- a/src/Gui/ApplicationPy.cpp +++ b/src/Gui/ApplicationPy.cpp @@ -44,6 +44,7 @@ #include "Command.h" #include "Document.h" #include "MainWindow.h" +#include "MainWindowPy.h" #include "Macro.h" #include "EditorView.h" #include "PythonEditor.h" @@ -705,20 +706,13 @@ PyObject* Application::sSendFocusView(PyObject * /*self*/, PyObject *args) PyObject* Application::sGetMainWindow(PyObject * /*self*/, PyObject *args) { if (!PyArg_ParseTuple(args, "")) - return NULL; + return nullptr; - PythonWrapper wrap; - if (!wrap.loadCoreModule() || - !wrap.loadGuiModule() || - !wrap.loadWidgetsModule()) { - PyErr_SetString(PyExc_RuntimeError, "Failed to load Python wrapper for Qt"); - return 0; - } try { - return Py::new_reference_to(wrap.fromQWidget(Gui::getMainWindow(), "QMainWindow")); + return Py::new_reference_to(MainWindowPy::createWrapper(Gui::getMainWindow())); } catch (const Py::Exception&) { - return 0; + return nullptr; } } diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 50a596949b..c492e461ec 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -1037,6 +1037,7 @@ SOURCE_GROUP("View3D\\Inventor" FILES ${Inventor_SRCS}) SET(Widget_CPP_SRCS FileDialog.cpp MainWindow.cpp + MainWindowPy.cpp PrefWidgets.cpp InputField.cpp ProgressBar.cpp @@ -1054,6 +1055,7 @@ SET(Widget_CPP_SRCS SET(Widget_HPP_SRCS FileDialog.h MainWindow.h + MainWindowPy.h PrefWidgets.h InputField.h ProgressBar.h diff --git a/src/Gui/CallTips.cpp b/src/Gui/CallTips.cpp index 2a1a5e63b9..9927de2f10 100644 --- a/src/Gui/CallTips.cpp +++ b/src/Gui/CallTips.cpp @@ -277,9 +277,13 @@ QMap CallTipsList::extractTips(const QString& context) const // Make sure it's not a type object union PyType_Object typetype = {&PyType_Type}; if (PyObject_IsInstance(obj.ptr(), typetype.o) != 1) { + // For wrapped objects with PySide2 use the object, not its type + // as otherwise attributes added at runtime won't be listed (e.g. MainWindowPy) + QString typestr(QLatin1String(Py_TYPE(obj.ptr())->tp_name)); + // this should be now a user-defined Python class // http://stackoverflow.com/questions/12233103/in-python-at-runtime-determine-if-an-object-is-a-class-old-and-new-type-instan - if (Py_TYPE(obj.ptr())->tp_flags & Py_TPFLAGS_HEAPTYPE) { + if (!typestr.startsWith(QLatin1String("PySide")) && Py_TYPE(obj.ptr())->tp_flags & Py_TPFLAGS_HEAPTYPE) { obj = type; } } diff --git a/src/Gui/CommandPyImp.cpp b/src/Gui/CommandPyImp.cpp index 332434ce65..853c19bf5d 100644 --- a/src/Gui/CommandPyImp.cpp +++ b/src/Gui/CommandPyImp.cpp @@ -101,7 +101,7 @@ PyObject* CommandPy::listByShortcut(PyObject *args) re.setCaseSensitivity(Qt::CaseInsensitive); if (!re.isValid()){ std::stringstream str; - str << "Invalid regular expression: " << shortcut_to_find; + str << "Invalid regular expression:" << ' ' << shortcut_to_find; throw Py::RuntimeError(str.str()); } diff --git a/src/Gui/CommandView.cpp b/src/Gui/CommandView.cpp index b6b858bc82..f9bfed6a9d 100644 --- a/src/Gui/CommandView.cpp +++ b/src/Gui/CommandView.cpp @@ -203,7 +203,7 @@ Action * StdPerspectiveCamera::createAction(void) //=========================================================================== // The two commands below are provided for convenience so that they can be bound -// to a button of a space mouse +// to a button of a spacemouse //=========================================================================== // Std_ViewSaveCamera @@ -352,17 +352,16 @@ void StdCmdFreezeViews::activated(int iMsg) getGuiApplication()->sendMsgToActiveView("GetCamera",&ppReturn); QList acts = pcAction->actions(); - int index = 0; + int index = 1; for (QList::ConstIterator it = acts.begin()+offset; it != acts.end(); ++it, index++) { if (!(*it)->isVisible()) { savedViews++; - QString viewnr = QString(QObject::tr("Restore view &%1")).arg(index+1); + QString viewnr = QString(QObject::tr("Restore view &%1")).arg(index); (*it)->setText(viewnr); (*it)->setToolTip(QString::fromLatin1(ppReturn)); (*it)->setVisible(true); - if (index < 9) { - int accel = Qt::CTRL+Qt::Key_1; - (*it)->setShortcut(accel+index); + if (index < 10) { + (*it)->setShortcut(QKeySequence(QString::fromLatin1("CTRL+%1").arg(index))); } break; } @@ -499,9 +498,8 @@ void StdCmdFreezeViews::onRestoreViews() acts[i+offset]->setText(viewnr); acts[i+offset]->setToolTip(setting); acts[i+offset]->setVisible(true); - if ( i < 9 ) { - int accel = Qt::CTRL+Qt::Key_1; - acts[i+offset]->setShortcut(accel+i); + if (i < 9) { + acts[i+offset]->setShortcut(QKeySequence(QString::fromLatin1("CTRL+%1").arg(i+1))); } } @@ -3684,116 +3682,6 @@ void StdTreeDrag::activated(int) } } -//=========================================================================== -// Std_GroupMoveUp -//=========================================================================== -DEF_STD_CMD_A(StdGroupMoveUp) - -StdGroupMoveUp::StdGroupMoveUp() - : Command("Std_GroupMoveUp") -{ - sGroup = QT_TR_NOOP("TreeView"); - sMenuText = QT_TR_NOOP("Move up in group"); - sToolTipText = QT_TR_NOOP("Move object one place higher in its group"); - sStatusTip = sToolTipText; - sWhatsThis = "Std_GroupMoveUp"; - sPixmap = "button_up"; - sAccel = "Alt+Up"; - eType = 0; -} - -void StdGroupMoveUp::activated(int iMsg) -{ - Q_UNUSED(iMsg); - - TreeWidget *tree = TreeWidget::instance(); - if (!tree) { - return; - } - - std::vector selected; - if (!tree->getSelectedSiblingObjectItems(selected)) { - return; - } - - DocumentObjectItem *previous; - if (!tree->allowMoveUpInGroup(selected, &previous)) { - return; - } - - TreeWidget::moveSiblings(selected, previous, -1); -} - -bool StdGroupMoveUp::isActive(void) -{ - TreeWidget *tree = TreeWidget::instance(); - if (!tree) { - return false; - } - - std::vector selected; - if (!tree->getSelectedSiblingObjectItems(selected)) { - return false; - } - - return tree->allowMoveUpInGroup(selected); -} - -//=========================================================================== -// Std_GroupMoveDown -//=========================================================================== -DEF_STD_CMD_A(StdGroupMoveDown) - -StdGroupMoveDown::StdGroupMoveDown() - : Command("Std_GroupMoveDown") -{ - sGroup = QT_TR_NOOP("TreeView"); - sMenuText = QT_TR_NOOP("Move down in group"); - sToolTipText = QT_TR_NOOP("Move object one place lower in its group"); - sStatusTip = sToolTipText; - sWhatsThis = "Std_GroupMoveDown"; - sPixmap = "button_down"; - sAccel = "Alt+Down"; - eType = 0; -} - -void StdGroupMoveDown::activated(int iMsg) -{ - Q_UNUSED(iMsg); - - TreeWidget *tree = TreeWidget::instance(); - if (!tree) { - return; - } - - std::vector selected; - if (!tree->getSelectedSiblingObjectItems(selected)) { - return; - } - - DocumentObjectItem *next ; - if (!tree->allowMoveDownInGroup(selected, &next)) { - return; - } - - TreeWidget::moveSiblings(selected, next, +1); -} - -bool StdGroupMoveDown::isActive(void) -{ - TreeWidget *tree = TreeWidget::instance(); - if (!tree) { - return false; - } - - std::vector selected; - if (!tree->getSelectedSiblingObjectItems(selected)) { - return false; - } - - return tree->allowMoveDownInGroup(selected); -} - //====================================================================== // Std_TreeViewActions //=========================================================================== @@ -3828,11 +3716,6 @@ public: addCommand(new StdTreeDrag(),cmds.size()); addCommand(new StdTreeSelection(),cmds.size()); - - addCommand(); - - addCommand(new StdGroupMoveUp()); - addCommand(new StdGroupMoveDown()); }; virtual const char* className() const {return "StdCmdTreeViewActions";} }; diff --git a/src/Gui/CornerCrossLetters.h b/src/Gui/CornerCrossLetters.h new file mode 100644 index 0000000000..16b251e3d7 --- /dev/null +++ b/src/Gui/CornerCrossLetters.h @@ -0,0 +1,235 @@ +#ifndef GUI_CORNERCROSSLETTERS_H +#define GUI_CORNERCROSSLETTERS_H + +namespace Gui { + +//**************************************************************************** + +// Pixel map representations of an "X", a "Y" and a "Z" for the axis cross. +// Each character is defined as a 21x27 grayscale RGBA pixel map +// 21x27 is 3x a standard 5x7 representation with 1px margin +// This allows good anti-aliasing aspect at any usable size +// It is generated with Gimp using Bitstream Charter Bold font, 28 px, on transparent background +// It is then directly exported as C source type after image is vertically flipped for direct GL usage +// With enabled options "Use macros instead of struct" and "Save alpha channel" + +#define XPM_WIDTH (21) +#define XPM_HEIGHT (27) +#define XPM_BYTES_PER_PIXEL (4) /* 2:RGB16, 3:RGB, 4:RGBA */ +#define XPM_PIXEL_DATA ((unsigned char*) XPM_pixel_data) +static const unsigned char XPM_pixel_data[21 * 27 * 4 + 1] = +("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\261\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\320\000\000\000\003\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000w\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000Y\000\000\000\000\000\000\000\000\000\000\000\036\000\000\000\364\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000j\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\032\000\000" + "\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\264\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000u\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\355\000\000\000\023\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\245\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\363" + "\000\000\000\035\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\004\000\000\000\322\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\230\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000<\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000l\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000:\000\000\000\376\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000\062\000\000\000" + "\000\000\000\000\000\000\000\000\002\000\000\000\317\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\306\000\000\000" + "\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\233\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\305\000\000\000\001\000\000\000\000\000\000\000h\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\371\000\000\000*\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\021\000\000\000\352\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000]\000\000\000\022\000\000\000\354\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\177\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000^\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\347\000\000\000\241\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\325\000\000\000\006\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\277\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000\071\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000(\000\000\000\371\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\222\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\215\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000\026\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\003\000\000\000\315\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\202\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000l\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\370\000\000\000$\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\027\000\000" + "\000\360\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\305\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\265\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\243\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\302" + "\000\000\000\015\000\000\000\351\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000N\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000>\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\376\000\000\000\063\000\000\000\000\000\000\000i\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\337\000\000\000\011\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\004\000\000\000\324\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\241\000\000\000" + "\000\000\000\000\000\000\000\000\003\000\000\000\326\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\201\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000u\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\365\000\000\000\033\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000M\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\367\000\000\000#\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\034\000\000\000\363\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\200\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\276\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\264\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\253\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\345\000\000\000\012\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\062\000\000\000\376\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000M\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000G\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000_\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\243\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\336\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000"); + +#define YPM_WIDTH (21) +#define YPM_HEIGHT (27) +#define YPM_BYTES_PER_PIXEL (4) /* 2:RGB16, 3:RGB, 4:RGBA */ +#define YPM_PIXEL_DATA ((unsigned char*) YPM_pixel_data) +static const unsigned char YPM_pixel_data[21 * 27 * 4 + 1] = +("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\\\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\330\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\\\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\330\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\\\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\330\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\\\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\330\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\\\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\330\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\\\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\330\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\\\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\330\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\\\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\330\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\242\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\365\000\000\000\026\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000'\000\000\000\374\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\216\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\246\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\367\000\000\000\033\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000+\000\000" + "\000\375\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\225" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\253\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\371\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\371\000\000\000\037\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000/\000\000\000\376\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000d\000\000\000\340\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\235\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\260\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\332\000\000\000\003" + "\000\000\000e\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\373\000\000\000$\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\063\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\\\000\000\000\000\000\000\000\004\000\000\000\335\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\244\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\265\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\330\000\000\000\003\000\000\000" + "\000\000\000\000\000\000\000\000a\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\375\000\000\000*\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\070\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000Z\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\003\000\000\000\332\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\253\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\272\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\327\000\000\000\002\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000]\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\376\000\000\000" + "\060\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000<\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000X\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\000\000\000\327" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\262\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\277\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\325\000\000\000\002\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000Y\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\066\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000"); + +#define ZPM_WIDTH (21) +#define ZPM_HEIGHT (27) +#define ZPM_BYTES_PER_PIXEL (4) /* 2:RGB16, 3:RGB, 4:RGBA */ +#define ZPM_PIXEL_DATA ((unsigned char*) ZPM_pixel_data) +static const unsigned char ZPM_pixel_data[21 * 27 * 4 + 1] = +("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000T\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\214\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000T\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\214\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000L\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\214\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\003\000\000\000\312\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\360\000\000" + "\000u\000\000\000t\000\000\000t\000\000\000t\000\000\000t\000\000\000t\000\000\000t\000\000\000t\000\000\000t\000\000\000?\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000.\000\000\000\372\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000d\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\204\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\357\000\000\000\031\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\007\000\000\000\327\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\255\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000;\000\000\000\376\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000R\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\225\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\346\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\015" + "\000\000\000\342\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\233\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000K\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000A\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\246" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\334\000\000\000\011\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\025\000\000\000\353\000\000\000\377\000\000\000\377\000\000\000\377\000" + "\000\000\212\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\\\000\000" + "\000\377\000\000\000\377\000\000\000\377\000\000\000\374\000\000\000\062\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\266\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\317" + "\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\036\000\000\000\363" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000x\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000m\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\367\000\000\000%\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\011\000\000\000t\000\000\000t\000\000" + "\000t\000\000\000t\000\000\000t\000\000\000t\000\000\000t\000\000\000t\000\000\000v\000\000\000\363\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\300\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\024\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000" + "\000?\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\024\000\000\000\377\000\000\000\377\000" + "\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000H\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\024\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000" + "\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377\000\000\000\377" + "\000\000\000\377\000\000\000\377\000\000\000H\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"); + +} + +#endif //GUI_CORNERCROSSLETTERS_H diff --git a/src/Gui/DAGView/DAGView.cpp b/src/Gui/DAGView/DAGView.cpp index d026e245f5..a69eec18a7 100644 --- a/src/Gui/DAGView/DAGView.cpp +++ b/src/Gui/DAGView/DAGView.cpp @@ -56,8 +56,8 @@ View::View(QWidget* parentIn): QGraphicsView(parentIn) { this->setRenderHint(QPainter::Antialiasing, true); this->setRenderHint(QPainter::TextAntialiasing, true); - Application::Instance->signalActiveDocument.connect(boost::bind(&View::slotActiveDocument, this, bp::_1)); - Application::Instance->signalDeleteDocument.connect(boost::bind(&View::slotDeleteDocument, this, bp::_1)); + conActive = Application::Instance->signalActiveDocument.connect(boost::bind(&View::slotActiveDocument, this, bp::_1)); + conDelete = Application::Instance->signalDeleteDocument.connect(boost::bind(&View::slotDeleteDocument, this, bp::_1)); //just update the dagview when the gui process is idle. connect(QAbstractEventDispatcher::instance(), SIGNAL(awake()), this, SLOT(awakeSlot())); @@ -65,8 +65,6 @@ View::View(QWidget* parentIn): QGraphicsView(parentIn) View::~View() { - Application::Instance->signalActiveDocument.disconnect(boost::bind(&View::slotActiveDocument, this, bp::_1)); - Application::Instance->signalDeleteDocument.disconnect(boost::bind(&View::slotDeleteDocument, this, bp::_1)); } void View::slotActiveDocument(const Document &documentIn) diff --git a/src/Gui/DAGView/DAGView.h b/src/Gui/DAGView/DAGView.h index 610bedf4c7..7a82dbe415 100644 --- a/src/Gui/DAGView/DAGView.h +++ b/src/Gui/DAGView/DAGView.h @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -56,6 +57,8 @@ namespace Gui typedef std::map > ModelMap; ModelMap modelMap; + boost::signals2::scoped_connection conActive; + boost::signals2::scoped_connection conDelete; }; //! @brief dock window for DAG viewer @@ -64,7 +67,7 @@ namespace Gui Q_OBJECT public: DockWindow(Gui::Document* gDocumentIn = 0, QWidget *parent = 0); - ~DockWindow(){}; + ~DockWindow(){} private: View *dagView; diff --git a/src/Gui/DlgGeneral.ui b/src/Gui/DlgGeneral.ui index 6e0f6c1fda..58315e57a2 100644 --- a/src/Gui/DlgGeneral.ui +++ b/src/Gui/DlgGeneral.ui @@ -7,7 +7,7 @@ 0 0 425 - 578 + 598 @@ -89,13 +89,29 @@ + + + + If enabled, numerical keypad decimal separator will be substituted with locale separator + + + Substitute decimal separator (needs restart) + + + SubstituteDecimalSeparator + + + General + + + - Preference Packs + Preference packs @@ -577,13 +593,13 @@ horizontal space in Python console - Gui::PrefCheckBox - QCheckBox + Gui::PrefSpinBox + QSpinBox
Gui/PrefWidgets.h
- Gui::PrefSpinBox - QSpinBox + Gui::PrefCheckBox + QCheckBox
Gui/PrefWidgets.h
diff --git a/src/Gui/DlgGeneralImp.cpp b/src/Gui/DlgGeneralImp.cpp index a1053da4dd..5052ea1a4e 100644 --- a/src/Gui/DlgGeneralImp.cpp +++ b/src/Gui/DlgGeneralImp.cpp @@ -140,6 +140,7 @@ void DlgGeneralImp::saveSettings() App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")-> SetASCII("AutoloadModule", startWbName.toLatin1()); + ui->SubstituteDecimal->onSave(); ui->RecentFiles->onSave(); ui->SplashScreen->onSave(); ui->PythonWordWrap->onSave(); @@ -204,6 +205,7 @@ void DlgGeneralImp::loadSettings() QString startWbName = QLatin1String(start.c_str()); ui->AutoloadModuleCombo->setCurrentIndex(ui->AutoloadModuleCombo->findData(startWbName)); + ui->SubstituteDecimal->onRestore(); ui->RecentFiles->onRestore(); ui->SplashScreen->onRestore(); ui->PythonWordWrap->onRestore(); diff --git a/src/Gui/DlgMacroExecuteImp.cpp b/src/Gui/DlgMacroExecuteImp.cpp index c40f3d9a72..9233326010 100644 --- a/src/Gui/DlgMacroExecuteImp.cpp +++ b/src/Gui/DlgMacroExecuteImp.cpp @@ -28,6 +28,7 @@ # include # include # include +# include #endif #include "DlgMacroExecuteImp.h" @@ -84,10 +85,13 @@ DlgMacroExecuteImp::DlgMacroExecuteImp( QWidget* parent, Qt::WindowFlags fl ) { ui->setupUi(this); // retrieve the macro path from parameter or use the user data as default - std::string path = getWindowParameter()->GetASCII("MacroPath", - App::Application::getUserMacroDir().c_str()); - this->macroPath = QString::fromUtf8(path.c_str()); - ui->fileChooser->setFileName(this->macroPath); + { + QSignalBlocker blocker(ui->fileChooser); + std::string path = getWindowParameter()->GetASCII("MacroPath", + App::Application::getUserMacroDir().c_str()); + this->macroPath = QString::fromUtf8(path.c_str()); + ui->fileChooser->setFileName(this->macroPath); + } // Fill the List box QStringList labels; labels << tr("Macros"); @@ -532,7 +536,7 @@ Note: your changes will be applied when you next switch workbenches\n")); Base::Console().Warning("Toolbar walkthrough: Unable to find actionMacros combo box\n"); } else { int macroIndex = macroListBox->findText(fn); //fn is the macro filename - macroListBox->setCurrentIndex(macroIndex); //select it for the user so he doesn't have to + macroListBox->setCurrentIndex(macroIndex); //select it for the user so they don't have to } QLineEdit* menuText = setupCustomMacrosPage->findChild(QString::fromLatin1("actionMenu")); @@ -576,7 +580,7 @@ Note: your changes will be applied when you next switch workbenches\n")); moveActionRightButton->setStyleSheet(QString::fromLatin1("background-color: red")); } /** tailor instructions depending on whether user already has custom toolbar created - * if not he needs to click New button to create one first + * if not, they need to click New button to create one first **/ QString instructions2 = tr("Walkthrough instructions: Click right arrow button (->), then Close."); diff --git a/src/Gui/DlgSettings3DView.ui b/src/Gui/DlgSettings3DView.ui index c4191841d9..6dadb4bf13 100644 --- a/src/Gui/DlgSettings3DView.ui +++ b/src/Gui/DlgSettings3DView.ui @@ -24,24 +24,76 @@
- - - Main coordinate system will always be shown in + + + + + Main coordinate system will always be shown in lower right corner within opened files - - - Show coordinate system in the corner - - - true - - - CornerCoordSystem - - - View - - + + + Show coordinate system in the corner + + + true + + + CornerCoordSystem + + + View + + + + + + + Qt::Horizontal + + + + 40 + + + + + + + + Relative size : + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Size of main coordinate system representation +in the corner -- in % of height/width of viewport + + + 2 + + + 100 + + + 10 + + + % + + + CornerCoordSystemSize + + + View + + + + @@ -626,6 +678,11 @@ bounding box size of the 3D object that is currently displayed. QDoubleSpinBox
Gui/PrefWidgets.h
+ + Gui::PrefSpinBox + QSpinBox +
Gui/PrefWidgets.h
+
@@ -677,5 +734,11 @@ bounding box size of the 3D object that is currently displayed. + + CheckBox_CornerCoordSystem + toggled(bool) + SpinBox_CornerCoordSystemSize + setEnabled(bool) + diff --git a/src/Gui/DlgSettings3DViewImp.cpp b/src/Gui/DlgSettings3DViewImp.cpp index b1fe1d71aa..5459ff4c91 100644 --- a/src/Gui/DlgSettings3DViewImp.cpp +++ b/src/Gui/DlgSettings3DViewImp.cpp @@ -89,6 +89,7 @@ void DlgSettings3DViewImp::saveSettings() hGrp->SetInt("MarkerSize", vBoxMarkerSize.toInt()); ui->CheckBox_CornerCoordSystem->onSave(); + ui->SpinBox_CornerCoordSystemSize->onSave(); ui->CheckBox_ShowAxisCross->onSave(); ui->CheckBox_WbByTab->onSave(); ui->CheckBox_ShowFPS->onSave(); @@ -106,6 +107,7 @@ void DlgSettings3DViewImp::saveSettings() void DlgSettings3DViewImp::loadSettings() { ui->CheckBox_CornerCoordSystem->onRestore(); + ui->SpinBox_CornerCoordSystemSize->onRestore(); ui->CheckBox_ShowAxisCross->onRestore(); ui->CheckBox_WbByTab->onRestore(); ui->CheckBox_ShowFPS->onRestore(); diff --git a/src/Gui/DlgSettingsCacheDirectory.ui b/src/Gui/DlgSettingsCacheDirectory.ui index 8cd6560015..34052b94a3 100644 --- a/src/Gui/DlgSettingsCacheDirectory.ui +++ b/src/Gui/DlgSettingsCacheDirectory.ui @@ -6,7 +6,7 @@ 0 0 - 488 + 425 360 diff --git a/src/Gui/DlgSettingsMacro.ui b/src/Gui/DlgSettingsMacro.ui index 7840a14aad..7ec3cebad1 100644 --- a/src/Gui/DlgSettingsMacro.ui +++ b/src/Gui/DlgSettingsMacro.ui @@ -220,6 +220,9 @@ The directory in which the application will search for macros + + Gui::FileChooser::Directory + MacroPath diff --git a/src/Gui/DlgUnitsCalculator.ui b/src/Gui/DlgUnitsCalculator.ui index c0e39feadc..b49c557b6a 100644 --- a/src/Gui/DlgUnitsCalculator.ui +++ b/src/Gui/DlgUnitsCalculator.ui @@ -80,7 +80,7 @@ - List of last used calculations + List of last used calculations. To add a calculation press Return in the value input field diff --git a/src/Gui/Document.cpp b/src/Gui/Document.cpp index d5110459e2..6fa31a532e 100644 --- a/src/Gui/Document.cpp +++ b/src/Gui/Document.cpp @@ -727,9 +727,6 @@ void Document::slotNewObject(const App::DocumentObject& Obj) activeView->getViewer()->addViewProvider(pcProvider); } - // If no tree rank was assigned, do it now, otherwise keep the current one - pcProvider->TreeRank.setValue(pcProvider->TreeRank.getValue()); - // adding to the tree signalNewObject(*pcProvider); pcProvider->pcDocument = this; @@ -1081,15 +1078,21 @@ static bool checkCanonicalPath(const std::map &docs) FC_WARN(" Document: " << docName(d).toUtf8().constData() << ": " << d->FileName.getValue()); if (count == 3) { - ts << QObject::tr("\n\nPlease check report view for more..."); + ts << "\n\n" + << QObject::tr("Please check report view for more..."); } else if (count < 3) { - ts << QObject::tr("\n\nPhysical path: ") << v.first - << QObject::tr("\nDocument: ") << docName(doc) - << QObject::tr("\n Path: ") << QString::fromUtf8(doc->FileName.getValue()); + ts << "\n\n" + << QObject::tr("Physical path:") << ' ' << v.first + << "\n" + << QObject::tr("Document:") << ' ' << docName(doc) + << "\n " + << QObject::tr("Path:") << ' ' << QString::fromUtf8(doc->FileName.getValue()); for (auto d : v.second) { if (d == doc) continue; - ts << QObject::tr("\nDocument: ") << docName(d) - << QObject::tr("\n Path: ") << QString::fromUtf8(d->FileName.getValue()); + ts << "\n" + << QObject::tr("Document:") << ' ' << docName(d) + << "\n " + << QObject::tr("Path:") << ' ' << QString::fromUtf8(d->FileName.getValue()); } } ++count; @@ -1432,29 +1435,13 @@ void Document::RestoreDocFile(Base::Reader &reader) expanded = true; } } - - int rank = 0; - if (localreader->hasAttribute("rank")) { - rank = localreader->getAttributeAsInteger("rank"); - } - ViewProvider* pObj = getViewProviderByName(name.c_str()); if (pObj) // check if this feature has been registered pObj->Restore(*localreader); - - ViewProviderDocumentObject *vpdo = dynamic_cast(pObj); - if (vpdo) { - if (rank <= 0 ) { - // For backward compatibility, use object ID as tree rank - rank = vpdo->getObject()->getID(); - } - vpdo->TreeRank.setValue(rank); - - if (expanded) { - this->signalExpandObject(*vpdo, TreeItemMode::ExpandItem, 0, 0); - } + if (pObj && expanded) { + Gui::ViewProviderDocumentObject* vp = static_cast(pObj); + this->signalExpandObject(*vp, TreeItemMode::ExpandItem,0,0); } - localreader->readEndElement("ViewProvider"); } localreader->readEndElement("ViewProviderData"); @@ -1574,11 +1561,6 @@ void Document::SaveDocFile (Base::Writer &writer) const if (obj->hasExtensions()) writer.Stream() << " Extensions=\"True\""; - ViewProviderDocumentObject *vpdo = dynamic_cast(obj); - if (vpdo && vpdo->TreeRank.getValue()) { - writer.Stream() << " rank=\"" << vpdo->TreeRank.getValue() << "\""; - } - writer.Stream() << ">" << std::endl; obj->Save(writer); writer.Stream() << writer.ind() << "" << std::endl; diff --git a/src/Gui/DocumentRecovery.cpp b/src/Gui/DocumentRecovery.cpp index aea8bee3c4..9b3186733a 100644 --- a/src/Gui/DocumentRecovery.cpp +++ b/src/Gui/DocumentRecovery.cpp @@ -706,10 +706,17 @@ void DocumentRecoveryCleaner::clearDirectory(const QFileInfo& dir) void DocumentRecoveryCleaner::subtractFiles(QStringList& files) { if (!ignoreFiles.isEmpty() && !files.isEmpty()) { +#if QT_VERSION >= QT_VERSION_CHECK(5,14,0) + auto set1 = QSet(files.begin(), files.end()); + auto set2 = QSet(ignoreFiles.begin(), ignoreFiles.end()); + set1.subtract(set2); + files = QList(set1.begin(), set1.end()); +#else QSet set1 = files.toSet(); QSet set2 = ignoreFiles.toSet(); set1.subtract(set2); files = set1.toList(); +#endif } } diff --git a/src/Gui/EditorView.cpp b/src/Gui/EditorView.cpp index 196a077085..8180a66253 100644 --- a/src/Gui/EditorView.cpp +++ b/src/Gui/EditorView.cpp @@ -83,6 +83,8 @@ public: /* TRANSLATOR Gui::EditorView */ +TYPESYSTEM_SOURCE_ABSTRACT(Gui::EditorView, Gui::MDIView) + /** * Constructs a EditorView which is a child of 'parent', with the * name 'name'. @@ -114,7 +116,8 @@ EditorView::EditorView(QPlainTextEdit* editor, QWidget* parent) // Create the layout containing the workspace and a tab bar QFrame* hbox = new QFrame(this); - hbox->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); + hbox->setFrameShape(QFrame::StyledPanel); + hbox->setFrameShadow(QFrame::Sunken); QVBoxLayout* layout = new QVBoxLayout(); layout->setMargin(1); layout->addWidget(d->textEdit); @@ -586,6 +589,8 @@ void EditorView::focusInEvent (QFocusEvent *) // --------------------------------------------------------- +TYPESYSTEM_SOURCE_ABSTRACT(Gui::PythonEditorView, Gui::EditorView) + PythonEditorView::PythonEditorView(PythonEditor* editor, QWidget* parent) : EditorView(editor, parent), _pye(editor) { @@ -816,9 +821,16 @@ void SearchBar::findText(bool skip, bool next, const QString& str) textEditor->setTextCursor(newCursor); - QPalette palette; - palette.setColor(QPalette::Active, QPalette::Base, found ? Qt::white : QColor(255, 80, 80)); - searchText->setPalette(palette); + QString styleSheet; + if (!found) { + styleSheet = QString::fromLatin1( + " QLineEdit {\n" + " background-color: rgb(221,144,161);\n" + " }\n" + ); + } + + searchText->setStyleSheet(styleSheet); } void SearchBar::updateButtons() diff --git a/src/Gui/EditorView.h b/src/Gui/EditorView.h index 80882adb41..cc249fe2aa 100644 --- a/src/Gui/EditorView.h +++ b/src/Gui/EditorView.h @@ -49,6 +49,8 @@ class GuiExport EditorView : public MDIView, public WindowParameter { Q_OBJECT + TYPESYSTEM_HEADER(); + public: enum DisplayName { FullName, @@ -118,6 +120,8 @@ class GuiExport PythonEditorView : public EditorView { Q_OBJECT + TYPESYSTEM_HEADER(); + public: PythonEditorView(PythonEditor* editor, QWidget* parent); ~PythonEditorView(); diff --git a/src/Gui/GestureNavigationStyle.cpp b/src/Gui/GestureNavigationStyle.cpp index 946efb1dcf..77676c00a8 100644 --- a/src/Gui/GestureNavigationStyle.cpp +++ b/src/Gui/GestureNavigationStyle.cpp @@ -53,7 +53,7 @@ * So, to avoid entering Tilt mode, the style implements its own tap-and-hold * detection, and a special Pan state for the state machine - StickyPanState. * - * This style wasn't tested with space mouse during development (I don't have one). + * This style wasn't tested with spacemouse during development (I don't have one). * * See also GestureNavigationStyle-state-machine-diagram.docx for a crude * diagram of the state machine. diff --git a/src/Gui/MDIViewPy.cpp b/src/Gui/MDIViewPy.cpp index 33ec535da1..a196101b45 100644 --- a/src/Gui/MDIViewPy.cpp +++ b/src/Gui/MDIViewPy.cpp @@ -53,6 +53,14 @@ void MDIViewPy::init_type() behaviors().supportRepr(); behaviors().supportGetattr(); behaviors().supportSetattr(); + behaviors().set_tp_new(extension_object_new); + + add_varargs_method("printView",&MDIViewPy::printView,"printView()"); + add_varargs_method("printPdf",&MDIViewPy::printPdf,"printPdf()"); + add_varargs_method("printPreview",&MDIViewPy::printPreview,"printPreview()"); + + add_varargs_method("undoActions",&MDIViewPy::undoActions,"undoActions()"); + add_varargs_method("redoActions",&MDIViewPy::redoActions,"redoActions()"); add_varargs_method("message",&MDIViewPy::sendMessage,"deprecated: use sendMessage"); add_varargs_method("sendMessage",&MDIViewPy::sendMessage,"sendMessage(str)"); @@ -60,6 +68,26 @@ void MDIViewPy::init_type() add_varargs_method("fitAll",&MDIViewPy::fitAll,"fitAll()"); add_varargs_method("setActiveObject", &MDIViewPy::setActiveObject, "setActiveObject(name,object,subname=None)\nadd or set a new active object"); add_varargs_method("getActiveObject", &MDIViewPy::getActiveObject, "getActiveObject(name,resolve=True)\nreturns the active object for the given type"); + add_varargs_method("cast_to_base", &MDIViewPy::cast_to_base, "cast_to_base() cast to MDIView class"); +} + +PyObject *MDIViewPy::extension_object_new(struct _typeobject * /*type*/, PyObject * /*args*/, PyObject * /*kwds*/) +{ + return new MDIViewPy(nullptr); +} + +Py::Object MDIViewPy::type() +{ + return Py::Object( reinterpret_cast( behaviors().type_object() ) ); +} + +Py::ExtensionObject MDIViewPy::create(MDIView *mdi) +{ + Py::Callable class_type(type()); + Py::Tuple arg; + auto inst = Py::ExtensionObject(class_type.apply(arg, Py::Dict())); + inst.extensionObject()->_view = mdi; + return inst; } MDIViewPy::MDIViewPy(MDIView *mdi) @@ -79,10 +107,73 @@ Py::Object MDIViewPy::repr() std::ostringstream s_out; if (!_view) throw Py::RuntimeError("Cannot print representation of deleted object"); - s_out << "MDIView"; + s_out << _view->getTypeId().getName(); return Py::String(s_out.str()); } +Py::Object MDIViewPy::printView(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + + if (_view) + _view->print(); + + return Py::None(); +} + +Py::Object MDIViewPy::printPdf(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + + if (_view) + _view->printPdf(); + + return Py::None(); +} + +Py::Object MDIViewPy::printPreview(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + + if (_view) + _view->printPreview(); + + return Py::None(); +} + +Py::Object MDIViewPy::undoActions(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + + Py::List list; + if (_view) { + QStringList undo = _view->undoActions(); + for (const auto& it : undo) + list.append(Py::String(it.toStdString())); + } + + return list; +} + +Py::Object MDIViewPy::redoActions(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + + Py::List list; + if (_view) { + QStringList redo = _view->redoActions(); + for (const auto& it : redo) + list.append(Py::String(it.toStdString())); + } + + return list; +} + Py::Object MDIViewPy::sendMessage(const Py::Tuple& args) { const char **ppReturn = 0; @@ -198,3 +289,8 @@ Py::Object MDIViewPy::getActiveObject(const Py::Tuple& args) Py::asObject(parent->getPyObject()), Py::String(subname.c_str())); } + +Py::Object MDIViewPy::cast_to_base(const Py::Tuple&) +{ + return Py::Object(this); +} diff --git a/src/Gui/MDIViewPy.h b/src/Gui/MDIViewPy.h index 469bdca6f3..86ced43a8b 100644 --- a/src/Gui/MDIViewPy.h +++ b/src/Gui/MDIViewPy.h @@ -35,18 +35,36 @@ class MDIView; class GuiExport MDIViewPy : public Py::PythonExtension { public: - static void init_type(void); // announce properties and methods + static void init_type(); // announce properties and methods + static PyObject *extension_object_new( PyTypeObject *subtype, PyObject * /*args*/, PyObject * /*kwds*/ ); + + static Py::Object type(); + static Py::ExtensionObject create(MDIView *mdi); MDIViewPy(MDIView *mdi); ~MDIViewPy(); Py::Object repr(); + /** @name Printing */ + //@{ + Py::Object printView(const Py::Tuple&); + Py::Object printPdf(const Py::Tuple&); + Py::Object printPreview(const Py::Tuple&); + //@} + + /** @name Undo/Redo actions */ + //@{ + Py::Object undoActions(const Py::Tuple&); + Py::Object redoActions(const Py::Tuple&); + //@} + Py::Object sendMessage(const Py::Tuple&); Py::Object supportMessage(const Py::Tuple&); Py::Object fitAll(const Py::Tuple&); Py::Object setActiveObject(const Py::Tuple&); Py::Object getActiveObject(const Py::Tuple&); + Py::Object cast_to_base(const Py::Tuple&); MDIView* getMDIViewPtr() {return _view.data();} diff --git a/src/Gui/MainWindowPy.cpp b/src/Gui/MainWindowPy.cpp new file mode 100644 index 0000000000..f5b1400a56 --- /dev/null +++ b/src/Gui/MainWindowPy.cpp @@ -0,0 +1,180 @@ +/*************************************************************************** + * Copyright (c) 2021 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +#endif + +#include "MainWindowPy.h" +#include "MainWindow.h" +#include "MDIView.h" +#include "MDIViewPy.h" +#include "PythonWrapper.h" +#include + + +using namespace Gui; + + +void MainWindowPy::init_type() +{ + behaviors().name("MainWindowPy"); + behaviors().doc("Python binding class for the MainWindow class"); + // you must have overwritten the virtual functions + behaviors().supportRepr(); + behaviors().supportGetattr(); + behaviors().supportSetattr(); + behaviors().set_tp_new(extension_object_new); + + add_varargs_method("getWindows",&MainWindowPy::getWindows,"getWindows()"); + add_varargs_method("getWindowsOfType",&MainWindowPy::getWindowsOfType,"getWindowsOfType(typeid)"); + add_varargs_method("setActiveWindow", &MainWindowPy::setActiveWindow, "setActiveWindow(MDIView)"); + add_varargs_method("getActiveWindow", &MainWindowPy::getActiveWindow, "getActiveWindow()"); +} + +PyObject *MainWindowPy::extension_object_new(struct _typeobject * /*type*/, PyObject * /*args*/, PyObject * /*kwds*/) +{ + return new MainWindowPy(nullptr); +} + +Py::Object MainWindowPy::type() +{ + return Py::Object( reinterpret_cast( behaviors().type_object() ) ); +} + +Py::ExtensionObject MainWindowPy::create(MainWindow *mw) +{ + Py::Callable class_type(type()); + Py::Tuple arg; + auto inst = Py::ExtensionObject(class_type.apply(arg, Py::Dict())); + inst.extensionObject()->_mw = mw; + return inst; +} + +Py::Object MainWindowPy::createWrapper(MainWindow *mw) +{ + PythonWrapper wrap; + if (!wrap.loadCoreModule() || + !wrap.loadGuiModule() || + !wrap.loadWidgetsModule()) { + throw Py::RuntimeError("Failed to load Python wrapper for Qt"); + } + + // copy attributes + std::list attr = {"getWindows", "getWindowsOfType", "setActiveWindow", "getActiveWindow"}; + + Py::Object py = wrap.fromQWidget(mw, "QMainWindow"); + Py::ExtensionObject inst(create(mw)); + for (const auto& it : attr) { + py.setAttr(it, inst.getAttr(it)); + } + return py; +} + +MainWindowPy::MainWindowPy(MainWindow *mw) + : _mw(mw) +{ +} + +MainWindowPy::~MainWindowPy() +{ + // in case the class is instantiated on the stack + ob_refcnt = 0; +} + +Py::Object MainWindowPy::repr() +{ + std::string s; + std::ostringstream s_out; + if (!_mw) + throw Py::RuntimeError("Cannot print representation of deleted object"); + s_out << "MainWindow"; + return Py::String(s_out.str()); +} + +Py::Object MainWindowPy::getWindows(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + + Py::List mdis; + if (_mw) { + QList windows = _mw->windows(); + for (auto it : windows) { + MDIView* view = qobject_cast(it); + if (view) { + mdis.append(Py::asObject(view->getPyObject())); + } + } + } + + return mdis; +} + +Py::Object MainWindowPy::getWindowsOfType(const Py::Tuple& args) +{ + PyObject* t; + if (!PyArg_ParseTuple(args.ptr(), "O!", &Base::TypePy::Type, &t)) + throw Py::Exception(); + + Base::Type typeId = *static_cast(t)->getBaseTypePtr(); + + Py::List mdis; + if (_mw) { + QList windows = _mw->windows(); + for (auto it : windows) { + MDIView* view = qobject_cast(it); + if (view && view->isDerivedFrom(typeId)) { + mdis.append(Py::asObject(view->getPyObject())); + } + } + } + + return mdis; +} + +Py::Object MainWindowPy::setActiveWindow(const Py::Tuple& args) +{ + Py::ExtensionObject mdi(args[0].callMemberFunction("cast_to_base")); + if (_mw) { + _mw->setActiveWindow(mdi.extensionObject()->getMDIViewPtr()); + } + + return Py::None(); +} + +Py::Object MainWindowPy::getActiveWindow(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + + if (_mw) { + MDIView* mdi = _mw->activeWindow(); + if (mdi) { + return Py::asObject(mdi->getPyObject()); + } + } + return Py::None(); +} diff --git a/src/Gui/MainWindowPy.h b/src/Gui/MainWindowPy.h new file mode 100644 index 0000000000..abc03bb013 --- /dev/null +++ b/src/Gui/MainWindowPy.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * Copyright (c) 2021 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 GUI_MAINWINDOWPY_H +#define GUI_MAINWINDOWPY_H + +#include +#include +#include +#include + +namespace Gui { +class MainWindow; + +class GuiExport MainWindowPy : public Py::PythonExtension +{ +public: + static void init_type(); + static PyObject *extension_object_new( PyTypeObject *subtype, PyObject * /*args*/, PyObject * /*kwds*/ ); + + static Py::Object createWrapper(MainWindow *mw); + static Py::Object type(); + static Py::ExtensionObject create(MainWindow *mw); + + MainWindowPy(MainWindow *mw); + ~MainWindowPy(); + + Py::Object repr(); + + Py::Object getWindows(const Py::Tuple&); + Py::Object getWindowsOfType(const Py::Tuple&); + Py::Object setActiveWindow(const Py::Tuple&); + Py::Object getActiveWindow(const Py::Tuple&); + +private: + QPointer _mw; +}; + +} // namespace Gui + +#endif //GUI_MAINWINDOWPY_H diff --git a/src/Gui/PropertyPage.cpp b/src/Gui/PropertyPage.cpp index 303b730516..abb87001a0 100644 --- a/src/Gui/PropertyPage.cpp +++ b/src/Gui/PropertyPage.cpp @@ -22,6 +22,9 @@ #include "PreCompiled.h" +#ifndef _PreComp_ +# include +#endif #include "PropertyPage.h" #include "PrefWidgets.h" diff --git a/src/Gui/PythonConsole.cpp b/src/Gui/PythonConsole.cpp index 3eacfdacfa..7fd9cbf6cf 100644 --- a/src/Gui/PythonConsole.cpp +++ b/src/Gui/PythonConsole.cpp @@ -431,7 +431,8 @@ PythonConsole::PythonConsole(QWidget *parent) // create the window for call tips d->callTipsList = new CallTipsList(this); - d->callTipsList->setFrameStyle(QFrame::Box|QFrame::Raised); + d->callTipsList->setFrameStyle(QFrame::Box); + d->callTipsList->setFrameShadow(QFrame::Raised); d->callTipsList->setLineWidth(2); installEventFilter(d->callTipsList); viewport()->installEventFilter(d->callTipsList); @@ -1254,7 +1255,7 @@ void PythonConsole::contextMenuEvent ( QContextMenuEvent * e ) ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter(). GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("General"); - a = menu.addAction(tr("&Copy"), this, SLOT(copy()), Qt::CTRL+Qt::Key_C); + a = menu.addAction(tr("&Copy"), this, SLOT(copy()), QKeySequence(QString::fromLatin1("CTRL+C"))); a->setEnabled(textCursor().hasSelection()); a = menu.addAction(tr("&Copy command"), this, SLOT(onCopyCommand())); @@ -1273,11 +1274,11 @@ void PythonConsole::contextMenuEvent ( QContextMenuEvent * e ) menu.addSeparator(); - a = menu.addAction(tr("&Paste"), this, SLOT(paste()), Qt::CTRL+Qt::Key_V); + a = menu.addAction(tr("&Paste"), this, SLOT(paste()), QKeySequence(QString::fromLatin1("CTRL+V"))); const QMimeData *md = QApplication::clipboard()->mimeData(); a->setEnabled( mayPasteHere && md && canInsertFromMimeData(md)); - a = menu.addAction(tr("Select All"), this, SLOT(selectAll()), Qt::CTRL+Qt::Key_A); + a = menu.addAction(tr("Select All"), this, SLOT(selectAll()), QKeySequence(QString::fromLatin1("CTRL+A"))); a->setEnabled(!document()->isEmpty()); a = menu.addAction(tr("Clear console"), this, SLOT(onClearConsole())); diff --git a/src/Gui/PythonEditor.cpp b/src/Gui/PythonEditor.cpp index c4c5c0ac14..808ec5c6d0 100644 --- a/src/Gui/PythonEditor.cpp +++ b/src/Gui/PythonEditor.cpp @@ -77,10 +77,10 @@ PythonEditor::PythonEditor(QWidget* parent) // set acelerators QShortcut* comment = new QShortcut(this); - comment->setKey(Qt::ALT + Qt::Key_C); + comment->setKey(QKeySequence(QString::fromLatin1("ALT+C"))); QShortcut* uncomment = new QShortcut(this); - uncomment->setKey(Qt::ALT + Qt::Key_U); + uncomment->setKey(QKeySequence(QString::fromLatin1("ALT+U"))); connect(comment, SIGNAL(activated()), this, SLOT(onComment())); @@ -157,8 +157,8 @@ void PythonEditor::contextMenuEvent ( QContextMenuEvent * e ) QMenu* menu = createStandardContextMenu(); if (!isReadOnly()) { menu->addSeparator(); - menu->addAction( tr("Comment"), this, SLOT( onComment() ), Qt::ALT + Qt::Key_C ); - menu->addAction( tr("Uncomment"), this, SLOT( onUncomment() ), Qt::ALT + Qt::Key_U ); + menu->addAction( tr("Comment"), this, SLOT( onComment() ), QKeySequence(QString::fromLatin1("ALT+C"))); + menu->addAction( tr("Uncomment"), this, SLOT( onUncomment() ), QKeySequence(QString::fromLatin1("ALT+U"))); } menu->exec(e->globalPos()); diff --git a/src/Gui/SplitView3DInventor.cpp b/src/Gui/SplitView3DInventor.cpp index 7db754c7dd..654542dc85 100644 --- a/src/Gui/SplitView3DInventor.cpp +++ b/src/Gui/SplitView3DInventor.cpp @@ -38,6 +38,7 @@ #include "Application.h" #include "NavigationStyle.h" #include "View3DPy.h" +#include using namespace Gui; @@ -47,7 +48,7 @@ TYPESYSTEM_SOURCE_ABSTRACT(Gui::AbstractSplitView,Gui::MDIView) AbstractSplitView::AbstractSplitView(Gui::Document* pcDocument, QWidget* parent, Qt::WindowFlags wflags) : MDIView(pcDocument,parent, wflags) { - _viewerPy = 0; + _viewerPy = nullptr; // important for highlighting setMouseTracking(true); } @@ -59,7 +60,7 @@ AbstractSplitView::~AbstractSplitView() delete *it; } if (_viewerPy) { - static_cast(_viewerPy)->_view = 0; + Base::PyGILStateLocker lock; Py_DECREF(_viewerPy); } } @@ -72,6 +73,12 @@ void AbstractSplitView::deleteSelf() MDIView::deleteSelf(); } +void AbstractSplitView::viewAll() +{ + for (std::vector::iterator it = _viewer.begin(); it != _viewer.end(); ++it) + (*it)->viewAll(); +} + bool AbstractSplitView::containsViewProvider(const ViewProvider* vp) const { for (auto it = _viewer.begin(); it != _viewer.end(); ++it) { @@ -91,6 +98,7 @@ void AbstractSplitView::setupSettings() // apply the user settings OnChange(*hGrp,"EyeDistance"); OnChange(*hGrp,"CornerCoordSystem"); + OnChange(*hGrp,"CornerCoordSystemSize"); OnChange(*hGrp,"UseAutoRotation"); OnChange(*hGrp,"Gradient"); OnChange(*hGrp,"BackgroundColor"); @@ -241,6 +249,10 @@ void AbstractSplitView::OnChange(ParameterGrp::SubjectType &rCaller,ParameterGrp for (std::vector::iterator it = _viewer.begin(); it != _viewer.end(); ++it) (*it)->setFeedbackVisibility(rGrp.GetBool("CornerCoordSystem",true)); } + else if (strcmp(Reason,"CornerCoordSystemSize") == 0) { + for (std::vector::iterator it = _viewer.begin(); it != _viewer.end(); ++it) + (*it)->setFeedbackSize(rGrp.GetInt("CornerCoordSystemSize",10)); + } else if (strcmp(Reason,"UseAutoRotation") == 0) { for (std::vector::iterator it = _viewer.begin(); it != _viewer.end(); ++it) (*it)->setAnimationEnabled(rGrp.GetBool("UseAutoRotation",false)); @@ -307,8 +319,7 @@ const char *AbstractSplitView::getName(void) const bool AbstractSplitView::onMsg(const char* pMsg, const char**) { if (strcmp("ViewFit",pMsg) == 0 ) { - for (std::vector::iterator it = _viewer.begin(); it != _viewer.end(); ++it) - (*it)->viewAll(); + viewAll(); return true; } else if (strcmp("ViewBottom",pMsg) == 0) { @@ -439,6 +450,8 @@ void AbstractSplitViewPy::init_type() behaviors().doc("Python binding class for the Inventor viewer class"); // you must have overwritten the virtual functions behaviors().supportRepr(); + behaviors().supportGetattr(); + behaviors().supportSetattr(); behaviors().supportSequenceType(); add_varargs_method("fitAll",&AbstractSplitViewPy::fitAll,"fitAll()"); @@ -452,10 +465,12 @@ void AbstractSplitViewPy::init_type() add_varargs_method("viewIsometric",&AbstractSplitViewPy::viewIsometric,"viewIsometric()"); add_varargs_method("getViewer",&AbstractSplitViewPy::getViewer,"getViewer(index)"); add_varargs_method("close",&AbstractSplitViewPy::close,"close()"); + add_varargs_method("cast_to_base", &AbstractSplitViewPy::cast_to_base, "cast_to_base() cast to MDIView class"); + behaviors().readyType(); } AbstractSplitViewPy::AbstractSplitViewPy(AbstractSplitView *vi) - : _view(vi) + : base(vi) { } @@ -463,30 +478,61 @@ AbstractSplitViewPy::~AbstractSplitViewPy() { } -void AbstractSplitViewPy::testExistence() +Py::Object AbstractSplitViewPy::cast_to_base(const Py::Tuple&) { - if (!(_view && _view->getViewer(0))) - throw Py::RuntimeError("Object already deleted"); + return Gui::MDIViewPy::create(base.getMDIViewPtr()); } Py::Object AbstractSplitViewPy::repr() { - std::string s; std::ostringstream s_out; - if (!_view) + if (!getSplitViewPtr()) throw Py::RuntimeError("Cannot print representation of deleted object"); s_out << "AbstractSplitView"; return Py::String(s_out.str()); } +// Since with PyCXX it's not possible to make a sub-class of MDIViewPy +// a trick is to use MDIViewPy as class member and override getattr() to +// join the attributes of both classes. This way all methods of MDIViewPy +// appear for SheetViewPy, too. +Py::Object AbstractSplitViewPy::getattr(const char * attr) +{ + getSplitViewPtr(); + std::string name( attr ); + if (name == "__dict__" || name == "__class__") { + Py::Dict dict_self(BaseType::getattr("__dict__")); + Py::Dict dict_base(base.getattr("__dict__")); + for (auto it : dict_base) { + dict_self.setItem(it.first, it.second); + } + return dict_self; + } + + try { + return BaseType::getattr(attr); + } + catch (Py::AttributeError& e) { + e.clear(); + return base.getattr(attr); + } +} + +AbstractSplitView* AbstractSplitViewPy::getSplitViewPtr() +{ + AbstractSplitView* view = qobject_cast(base.getMDIViewPtr()); + if (!(view && view->getViewer(0))) + throw Py::RuntimeError("Object already deleted"); + return view; +} + Py::Object AbstractSplitViewPy::fitAll(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - testExistence(); try { - _view->onMsg("ViewFit", 0); + getSplitViewPtr()->onMsg("ViewFit", 0); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -504,10 +550,9 @@ Py::Object AbstractSplitViewPy::viewBottom(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - testExistence(); try { - _view->onMsg("ViewBottom", 0); + getSplitViewPtr()->onMsg("ViewBottom", 0); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -526,10 +571,9 @@ Py::Object AbstractSplitViewPy::viewFront(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - testExistence(); try { - _view->onMsg("ViewFront", 0); + getSplitViewPtr()->onMsg("ViewFront", 0); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -548,10 +592,9 @@ Py::Object AbstractSplitViewPy::viewLeft(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - testExistence(); try { - _view->onMsg("ViewLeft", 0); + getSplitViewPtr()->onMsg("ViewLeft", 0); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -570,10 +613,9 @@ Py::Object AbstractSplitViewPy::viewRear(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - testExistence(); try { - _view->onMsg("ViewRear", 0); + getSplitViewPtr()->onMsg("ViewRear", 0); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -592,10 +634,9 @@ Py::Object AbstractSplitViewPy::viewRight(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - testExistence(); try { - _view->onMsg("ViewRight", 0); + getSplitViewPtr()->onMsg("ViewRight", 0); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -614,10 +655,9 @@ Py::Object AbstractSplitViewPy::viewTop(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - testExistence(); try { - _view->onMsg("ViewTop", 0); + getSplitViewPtr()->onMsg("ViewTop", 0); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -636,10 +676,9 @@ Py::Object AbstractSplitViewPy::viewIsometric(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - testExistence(); try { - _view->onMsg("ViewAxo", 0); + getSplitViewPtr()->onMsg("ViewAxo", 0); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -659,10 +698,9 @@ Py::Object AbstractSplitViewPy::getViewer(const Py::Tuple& args) int viewIndex; if (!PyArg_ParseTuple(args.ptr(), "i", &viewIndex)) throw Py::Exception(); - testExistence(); try { - Gui::View3DInventorViewer* view = _view->getViewer(viewIndex); + Gui::View3DInventorViewer* view = getSplitViewPtr()->getViewer(viewIndex); if (!view) throw Py::IndexError("Index out of range"); return Py::asObject(view->getPyObject()); @@ -673,6 +711,10 @@ Py::Object AbstractSplitViewPy::getViewer(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } + catch (const Py::Exception&) { + // re-throw + throw; + } catch(...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -680,29 +722,28 @@ Py::Object AbstractSplitViewPy::getViewer(const Py::Tuple& args) Py::Object AbstractSplitViewPy::sequence_item(ssize_t viewIndex) { - testExistence(); - if (viewIndex >= _view->getSize() || viewIndex < 0) + AbstractSplitView* view = getSplitViewPtr(); + if (viewIndex >= view->getSize() || viewIndex < 0) throw Py::IndexError("Index out of range"); - PyObject* viewer = _view->getViewer(viewIndex)->getPyObject(); + PyObject* viewer = view->getViewer(viewIndex)->getPyObject(); return Py::asObject(viewer); } int AbstractSplitViewPy::sequence_length() { - testExistence(); - return _view->getSize(); + AbstractSplitView* view = getSplitViewPtr(); + return view->getSize(); } Py::Object AbstractSplitViewPy::close(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - testExistence(); - _view->close(); - if (_view->parentWidget()) - _view->parentWidget()->deleteLater(); - _view = 0; + AbstractSplitView* view = getSplitViewPtr(); + view->close(); + if (view->parentWidget()) + view->parentWidget()->deleteLater(); return Py::None(); } @@ -791,3 +832,5 @@ SplitView3DInventor::SplitView3DInventor(int views, Gui::Document* pcDocument, Q SplitView3DInventor::~SplitView3DInventor() { } + +#include "moc_SplitView3DInventor.cpp" diff --git a/src/Gui/SplitView3DInventor.h b/src/Gui/SplitView3DInventor.h index bbbdd6d3fd..da0bfb56a6 100644 --- a/src/Gui/SplitView3DInventor.h +++ b/src/Gui/SplitView3DInventor.h @@ -25,6 +25,7 @@ #define GUI_SPLITVIEW3DINVENTOR_H #include "MDIView.h" +#include "MDIViewPy.h" #include #include @@ -39,6 +40,8 @@ class AbstractSplitViewPy; */ class GuiExport AbstractSplitView : public MDIView, public ParameterGrp::ObserverType { + Q_OBJECT + TYPESYSTEM_HEADER(); public: @@ -53,6 +56,7 @@ public: virtual void OnChange(ParameterGrp::SubjectType &rCaller,ParameterGrp::MessageType Reason); virtual void onUpdate(void); virtual void deleteSelf(); + virtual void viewAll(); View3DInventorViewer *getViewer(unsigned int) const; void setOverrideCursor(const QCursor&); @@ -75,12 +79,16 @@ protected: class AbstractSplitViewPy : public Py::PythonExtension { public: - static void init_type(void); // announce properties and methods + using BaseType = Py::PythonExtension; + static void init_type(); // announce properties and methods AbstractSplitViewPy(AbstractSplitView *vi); ~AbstractSplitViewPy(); + AbstractSplitView* getSplitViewPtr(); Py::Object repr(); + Py::Object getattr(const char *); + Py::Object cast_to_base(const Py::Tuple&); Py::Object fitAll(const Py::Tuple&); Py::Object viewBottom(const Py::Tuple&); @@ -96,9 +104,7 @@ public: int sequence_length(); private: - AbstractSplitView* _view; - friend class AbstractSplitView; - void testExistence(); + Gui::MDIViewPy base; }; /** The SplitView3DInventor class allows to create a window with two or more Inventor views. diff --git a/src/Gui/TextEdit.cpp b/src/Gui/TextEdit.cpp index 219a3d72fc..a694c57c66 100644 --- a/src/Gui/TextEdit.cpp +++ b/src/Gui/TextEdit.cpp @@ -45,7 +45,7 @@ TextEdit::TextEdit(QWidget* parent) //Note: Set the correct context to this shortcut as we may use several instances of this //class at a time QShortcut* shortcut = new QShortcut(this); - shortcut->setKey(Qt::CTRL+Qt::Key_Space); + shortcut->setKey(QKeySequence(QString::fromLatin1("CTRL+Space"))); shortcut->setContext(Qt::WidgetShortcut); connect(shortcut, SIGNAL(activated()), this, SLOT(complete())); @@ -178,7 +178,8 @@ void TextEdit::complete() void TextEdit::createListBox() { listBox = new CompletionList(this); - listBox->setFrameStyle(QFrame::Box|QFrame::Raised); + listBox->setFrameStyle(QFrame::Box); + listBox->setFrameShadow(QFrame::Raised); listBox->setLineWidth(2); installEventFilter(listBox); viewport()->installEventFilter(listBox); diff --git a/src/Gui/TextEdit.h b/src/Gui/TextEdit.h index ad7bded512..4a20c450c0 100644 --- a/src/Gui/TextEdit.h +++ b/src/Gui/TextEdit.h @@ -137,7 +137,7 @@ private: /** * The CompletionList class provides a list box that pops up in a text edit if the user has pressed - * an accelerator to complete the current word he is typing in. + * an accelerator to complete the current word they are typing in. * @author Werner Mayer */ class CompletionList : public QListWidget diff --git a/src/Gui/Tree.cpp b/src/Gui/Tree.cpp index 53545268cb..2e2c0f41a5 100644 --- a/src/Gui/Tree.cpp +++ b/src/Gui/Tree.cpp @@ -67,7 +67,7 @@ #include "Widgets.h" #include "ExpressionCompleter.h" -FC_LOG_LEVEL_INIT("Tree",false,true,true) +FC_LOG_LEVEL_INIT("Tree", false, true, true) #define _TREE_PRINT(_level,_func,_msg) \ _FC_PRINT(FC_LOG_INSTANCE,_level,_func, '['< TreeWidget::documentPixmap; std::unique_ptr TreeWidget::documentPartialPixmap; -std::set TreeWidget::Instances; -static TreeWidget *_LastSelectedTreeWidget; +std::set TreeWidget::Instances; +static TreeWidget* _LastSelectedTreeWidget; const int TreeWidget::DocumentType = 1000; const int TreeWidget::ObjectType = 1001; bool _DragEventFilter; @@ -113,7 +113,7 @@ void TreeParams::set##_name(_type value) {\ FC_TREEPARAM_DEFS -void TreeParams::OnChange(Base::Subject &, const char* sReason) { +void TreeParams::OnChange(Base::Subject&, const char* sReason) { #undef FC_TREEPARAM_DEF #define FC_TREEPARAM_DEF(_name,_type,_Type,_default) \ @@ -134,7 +134,7 @@ void TreeParams::OnChange(Base::Subject &, const char* sReason) { } void TreeParams::onSyncSelectionChanged() { - if(!TreeParams::Instance()->SyncSelection() || !Gui::Selection().hasSelection()) + if (!TreeParams::Instance()->SyncSelection() || !Gui::Selection().hasSelection()) return; TreeWidget::scrollItemToTop(); } @@ -148,9 +148,9 @@ void TreeParams::onDocumentModeChanged() { App::GetApplication().setActiveDocument(App::GetApplication().getActiveDocument()); } -TreeParams *TreeParams::Instance() { - static TreeParams *instance; - if(!instance) +TreeParams* TreeParams::Instance() { + static TreeParams* instance; + if (!instance) instance = new TreeParams; return instance; } @@ -170,7 +170,7 @@ struct Stats { DEFINE_STATS - void init() { + void init() { #undef DEFINE_STAT #define DEFINE_STAT(_name) \ FC_DURATION_INIT(_name);\ @@ -200,8 +200,8 @@ struct Stats { struct TimingInfo { bool timed = false; FC_TIME_POINT t; - FC_DURATION &d; - TimingInfo(FC_DURATION &d) + FC_DURATION& d; + TimingInfo(FC_DURATION& d) :d(d) { _FC_TIME_INIT(t); @@ -210,9 +210,9 @@ struct TimingInfo { stop(); } void stop() { - if(!timed) { + if (!timed) { timed = true; - FC_DURATION_PLUS(d,t); + FC_DURATION_PLUS(d, t); } } void reset() { @@ -244,10 +244,10 @@ typedef std::set DocumentObjectItems; class Gui::DocumentObjectData { public: - DocumentItem *docItem; + DocumentItem* docItem; DocumentObjectItems items; - ViewProviderDocumentObject *viewObject; - DocumentObjectItem *rootItem; + ViewProviderDocumentObject* viewObject; + DocumentObjectItem* rootItem; std::vector children; std::set childSet; bool removeChildrenFromRoot; @@ -261,16 +261,16 @@ public: Connection connectTool; Connection connectStat; - DocumentObjectData(DocumentItem *docItem, ViewProviderDocumentObject* vpd) - : docItem(docItem), viewObject(vpd),rootItem(0) + DocumentObjectData(DocumentItem* docItem, ViewProviderDocumentObject* vpd) + : docItem(docItem), viewObject(vpd), rootItem(0) { // Setup connections connectIcon = viewObject->signalChangeIcon.connect( - boost::bind(&DocumentObjectData::slotChangeIcon, this)); + boost::bind(&DocumentObjectData::slotChangeIcon, this)); connectTool = viewObject->signalChangeToolTip.connect( - boost::bind(&DocumentObjectData::slotChangeToolTip, this, bp::_1)); + boost::bind(&DocumentObjectData::slotChangeToolTip, this, bp::_1)); connectStat = viewObject->signalChangeStatusTip.connect( - boost::bind(&DocumentObjectData::slotChangeStatusTip, this, bp::_1)); + boost::bind(&DocumentObjectData::slotChangeStatusTip, this, bp::_1)); removeChildrenFromRoot = viewObject->canRemoveChildrenFromRoot(); itemHidden = !viewObject->showInTree(); @@ -278,7 +278,7 @@ public: label2 = viewObject->getObject()->Label2.getValue(); } - const char *getTreeName() const { + const char* getTreeName() const { return docItem->getTreeName(); } @@ -290,28 +290,29 @@ public: bool updateChildren(bool checkVisibility) { auto newChildren = viewObject->claimChildren(); auto obj = viewObject->getObject(); - std::set newSet; + std::set newSet; bool updated = false; for (auto child : newChildren) { auto childVp = docItem->getViewProvider(child); if (!childVp) continue; - if(child && child->getNameInDocument()) { - if(!newSet.insert(child).second) { + if (child && child->getNameInDocument()) { + if (!newSet.insert(child).second) { TREE_WARN("duplicate child item " << obj->getFullName() << '.' << child->getNameInDocument()); - }else if(!childSet.erase(child)) { + } + else if (!childSet.erase(child)) { // this means new child detected updated = true; - if(child->getDocument()==obj->getDocument() && - child->getDocument()==docItem->document()->getDocument()) + if (child->getDocument() == obj->getDocument() && + child->getDocument() == docItem->document()->getDocument()) { - auto &parents = docItem->_ParentMap[child]; - if(parents.insert(obj).second && child->Visibility.getValue()) { + auto& parents = docItem->_ParentMap[child]; + if (parents.insert(obj).second && child->Visibility.getValue()) { bool showable = false; - for(auto parent : parents) { - if(!parent->hasChildElement() - && parent->getLinkedObject(false)==parent) + for (auto parent : parents) { + if (!parent->hasChildElement() + && parent->getLinkedObject(false) == parent) { showable = true; break; @@ -340,23 +341,13 @@ public: } } } - - // Sort the child items by their tree rank - std::stable_sort(newChildren.begin(), newChildren.end(), - [this](App::DocumentObject *a, App::DocumentObject *b) { - ViewProviderDocumentObject *vpa = this->docItem->getViewProvider(a); - ViewProviderDocumentObject *vpb = this->docItem->getViewProvider(b); - return vpa->TreeRank.getValue() < vpb->TreeRank.getValue(); - }); - - // Mark updated in case the order of the children did change - updated = updated || children!=newChildren; - + // We still need to check the order of the children + updated = updated || children != newChildren; children.swap(newChildren); childSet.swap(newSet); - if(updated && checkVisibility) { - for(auto child : children) { + if (updated && checkVisibility) { + for (auto child : children) { auto childVp = docItem->getViewProvider(child); if (childVp && child->getDocument() == obj->getDocument()) childVp->setShowable(docItem->isObjectShowable(child)); @@ -366,9 +357,9 @@ public: } void testStatus(bool resetStatus = false) { - QIcon icon,icon2; - for(auto item : items) - item->testStatus(resetStatus,icon,icon2); + QIcon icon, icon2; + for (auto item : items) + item->testStatus(resetStatus, icon, icon2); } void slotChangeIcon() { @@ -376,34 +367,34 @@ public: } void slotChangeToolTip(const QString& tip) { - for(auto item : items) + for (auto item : items) item->setToolTip(0, tip); } void slotChangeStatusTip(const QString& tip) { - for(auto item : items) + for (auto item : items) item->setStatusTip(0, tip); } }; // --------------------------------------------------------------------------- -class DocumentItem::ExpandInfo: +class DocumentItem::ExpandInfo : public std::unordered_map { public: - void restore(Base::XMLReader &reader) { + void restore(Base::XMLReader& reader) { int level = reader.level(); int count = reader.getAttributeAsInteger("count"); - for(int i=0;irestore(reader); } - reader.readEndElement("Expand",level-1); + reader.readEndElement("Expand", level - 1); } }; @@ -415,21 +406,21 @@ TreeWidgetEditDelegate::TreeWidgetEditDelegate(QObject* parent) } QWidget* TreeWidgetEditDelegate::createEditor( - QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const + QWidget* parent, const QStyleOptionViewItem&, const QModelIndex& index) const { auto ti = static_cast(index.internalPointer()); - if(ti->type()!=TreeWidget::ObjectType || index.column()>1) + if (ti->type() != TreeWidget::ObjectType || index.column() > 1) return 0; - DocumentObjectItem *item = static_cast(ti); - App::DocumentObject *obj = item->object()->getObject(); - auto &prop = index.column()?obj->Label2:obj->Label; + DocumentObjectItem* item = static_cast(ti); + App::DocumentObject* obj = item->object()->getObject(); + auto& prop = index.column() ? obj->Label2 : obj->Label; std::ostringstream str; str << "Change " << obj->getNameInDocument() << '.' << prop.getName(); App::GetApplication().setActiveTransaction(str.str().c_str()); FC_LOG("create editor transaction " << App::GetApplication().getActiveTransaction()); - ExpLineEdit *le = new ExpLineEdit(parent); + ExpLineEdit* le = new ExpLineEdit(parent); le->setFrame(false); le->setReadOnly(prop.isReadOnly()); le->bind(App::ObjectIdentifier(prop)); @@ -439,14 +430,14 @@ QWidget* TreeWidgetEditDelegate::createEditor( // --------------------------------------------------------------------------- -TreeWidget::TreeWidget(const char *name, QWidget* parent) - : QTreeWidget(parent), SelectionObserver(true,0), contextItem(0) +TreeWidget::TreeWidget(const char* name, QWidget* parent) + : QTreeWidget(parent), SelectionObserver(true, 0), contextItem(0) , searchObject(0), searchDoc(0), searchContextDoc(0) , editingItem(0), currentDocItem(0) , myName(name) { Instances.insert(this); - if(!_LastSelectedTreeWidget) + if (!_LastSelectedTreeWidget) _LastSelectedTreeWidget = this; this->setDragEnabled(true); @@ -460,27 +451,27 @@ TreeWidget::TreeWidget(const char *name, QWidget* parent) this->showHiddenAction = new QAction(this); this->showHiddenAction->setCheckable(true); connect(this->showHiddenAction, SIGNAL(triggered()), - this, SLOT(onShowHidden())); + this, SLOT(onShowHidden())); this->hideInTreeAction = new QAction(this); this->hideInTreeAction->setCheckable(true); connect(this->hideInTreeAction, SIGNAL(triggered()), - this, SLOT(onHideInTree())); + this, SLOT(onHideInTree())); this->createGroupAction = new QAction(this); connect(this->createGroupAction, SIGNAL(triggered()), - this, SLOT(onCreateGroup())); + this, SLOT(onCreateGroup())); this->relabelObjectAction = new QAction(this); #ifndef Q_OS_MAC this->relabelObjectAction->setShortcut(Qt::Key_F2); #endif connect(this->relabelObjectAction, SIGNAL(triggered()), - this, SLOT(onRelabelObject())); + this, SLOT(onRelabelObject())); this->finishEditingAction = new QAction(this); connect(this->finishEditingAction, SIGNAL(triggered()), - this, SLOT(onFinishEditing())); + this, SLOT(onFinishEditing())); this->selectDependentsAction = new QAction(this); connect(this->selectDependentsAction, SIGNAL(triggered()), @@ -488,34 +479,34 @@ TreeWidget::TreeWidget(const char *name, QWidget* parent) this->closeDocAction = new QAction(this); connect(this->closeDocAction, SIGNAL(triggered()), - this, SLOT(onCloseDoc())); + this, SLOT(onCloseDoc())); this->reloadDocAction = new QAction(this); connect(this->reloadDocAction, SIGNAL(triggered()), - this, SLOT(onReloadDoc())); + this, SLOT(onReloadDoc())); this->skipRecomputeAction = new QAction(this); this->skipRecomputeAction->setCheckable(true); connect(this->skipRecomputeAction, SIGNAL(toggled(bool)), - this, SLOT(onSkipRecompute(bool))); + this, SLOT(onSkipRecompute(bool))); this->allowPartialRecomputeAction = new QAction(this); this->allowPartialRecomputeAction->setCheckable(true); connect(this->allowPartialRecomputeAction, SIGNAL(toggled(bool)), - this, SLOT(onAllowPartialRecompute(bool))); + this, SLOT(onAllowPartialRecompute(bool))); this->markRecomputeAction = new QAction(this); connect(this->markRecomputeAction, SIGNAL(triggered()), - this, SLOT(onMarkRecompute())); + this, SLOT(onMarkRecompute())); this->recomputeObjectAction = new QAction(this); connect(this->recomputeObjectAction, SIGNAL(triggered()), - this, SLOT(onRecomputeObject())); + this, SLOT(onRecomputeObject())); this->searchObjectsAction = new QAction(this); this->searchObjectsAction->setText(tr("Search...")); this->searchObjectsAction->setStatusTip(tr("Search for objects")); connect(this->searchObjectsAction, SIGNAL(triggered()), - this, SLOT(onSearchObjects())); + this, SLOT(onSearchObjects())); // Setup connections connectNewDocument = Application::Instance->signalNewDocument.connect(boost::bind(&TreeWidget::slotNewDocument, this, bp::_1, bp::_2)); @@ -529,7 +520,7 @@ TreeWidget::TreeWidget(const char *name, QWidget* parent) // change, not view provider's own property, which is what the signal below // for connectChangedViewObj = Application::Instance->signalChangedObject.connect( - boost::bind(&TreeWidget::slotChangedViewObject, this, bp::_1, bp::_2)); + boost::bind(&TreeWidget::slotChangedViewObject, this, bp::_1, bp::_2)); // make sure to show a horizontal scrollbar if needed this->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); @@ -554,31 +545,28 @@ TreeWidget::TreeWidget(const char *name, QWidget* parent) this->selectTimer->setSingleShot(true); connect(this->statusTimer, SIGNAL(timeout()), - this, SLOT(onUpdateStatus())); + this, SLOT(onUpdateStatus())); connect(this, SIGNAL(itemEntered(QTreeWidgetItem*, int)), - this, SLOT(onItemEntered(QTreeWidgetItem*))); + this, SLOT(onItemEntered(QTreeWidgetItem*))); connect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), - this, SLOT(onItemCollapsed(QTreeWidgetItem*))); + this, SLOT(onItemCollapsed(QTreeWidgetItem*))); connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)), - this, SLOT(onItemExpanded(QTreeWidgetItem*))); + this, SLOT(onItemExpanded(QTreeWidgetItem*))); connect(this, SIGNAL(itemSelectionChanged()), - this, SLOT(onItemSelectionChanged())); + this, SLOT(onItemSelectionChanged())); connect(this, SIGNAL(itemChanged(QTreeWidgetItem*, int)), - this, SLOT(onItemChanged(QTreeWidgetItem*, int))); - connect(MainWindow::getInstance(), SIGNAL(tabifiedDockWidgetActivated(QDockWidget *)), - this, SLOT(onTabifiedDockWidgetActivated(QDockWidget *))); - + this, SLOT(onItemChanged(QTreeWidgetItem*, int))); connect(this->preselectTimer, SIGNAL(timeout()), - this, SLOT(onPreSelectTimer())); + this, SLOT(onPreSelectTimer())); connect(this->selectTimer, SIGNAL(timeout()), - this, SLOT(onSelectTimer())); + this, SLOT(onSelectTimer())); preselectTime.start(); setupText(); - if(!documentPixmap) { + if (!documentPixmap) { documentPixmap.reset(new QPixmap(Gui::BitmapFactory().pixmap("Document"))); QIcon icon(*documentPixmap); - documentPartialPixmap.reset(new QPixmap(icon.pixmap(documentPixmap->size(),QIcon::Disabled))); + documentPartialPixmap.reset(new QPixmap(icon.pixmap(documentPixmap->size(), QIcon::Disabled))); } } @@ -592,113 +580,115 @@ TreeWidget::~TreeWidget() connectShowHidden.disconnect(); connectChangedViewObj.disconnect(); Instances.erase(this); - if(_LastSelectedTreeWidget == this) + if (_LastSelectedTreeWidget == this) _LastSelectedTreeWidget = 0; } -const char *TreeWidget::getTreeName() const { +const char* TreeWidget::getTreeName() const { return myName.c_str(); } // reimpelement to select only objects in the active document void TreeWidget::selectAll() { auto gdoc = Application::Instance->getDocument( - App::GetApplication().getActiveDocument()); - if(!gdoc) + App::GetApplication().getActiveDocument()); + if (!gdoc) return; auto itDoc = DocumentMap.find(gdoc); - if(itDoc == DocumentMap.end()) + if (itDoc == DocumentMap.end()) return; - if(TreeParams::Instance()->RecordSelection()) + if (TreeParams::Instance()->RecordSelection()) Gui::Selection().selStackPush(); Gui::Selection().clearSelection(); - Gui::Selection().setSelection(gdoc->getDocument()->getName(),gdoc->getDocument()->getObjects()); + Gui::Selection().setSelection(gdoc->getDocument()->getName(), gdoc->getDocument()->getObjects()); } -bool TreeWidget::isObjectShowable(App::DocumentObject *obj) { - if(!obj || !obj->getNameInDocument()) +bool TreeWidget::isObjectShowable(App::DocumentObject* obj) { + if (!obj || !obj->getNameInDocument()) return true; - Gui::Document *doc = Application::Instance->getDocument(obj->getDocument()); - if(!doc) + Gui::Document* doc = Application::Instance->getDocument(obj->getDocument()); + if (!doc) return true; - if(Instances.empty()) + if (Instances.empty()) return true; auto tree = *Instances.begin(); auto it = tree->DocumentMap.find(doc); - if(it != tree->DocumentMap.end()) + if (it != tree->DocumentMap.end()) return it->second->isObjectShowable(obj); return true; } static bool _DisableCheckTopParent; -void TreeWidget::checkTopParent(App::DocumentObject *&obj, std::string &subname) { - if(_DisableCheckTopParent) +void TreeWidget::checkTopParent(App::DocumentObject*& obj, std::string& subname) { + if (_DisableCheckTopParent) return; - if(Instances.size() && obj && obj->getNameInDocument()) { + if (Instances.size() && obj && obj->getNameInDocument()) { auto tree = *Instances.begin(); auto it = tree->DocumentMap.find(Application::Instance->getDocument(obj->getDocument())); - if(it != tree->DocumentMap.end()) { - if(tree->statusTimer->isActive()) { + if (it != tree->DocumentMap.end()) { + if (tree->statusTimer->isActive()) { bool locked = tree->blockConnection(true); tree->_updateStatus(false); tree->blockConnection(locked); } - auto parent = it->second->getTopParent(obj,subname); - if(parent) + auto parent = it->second->getTopParent(obj, subname); + if (parent) obj = parent; } } } void TreeWidget::resetItemSearch() { - if(!searchObject) + if (!searchObject) return; auto it = ObjectTable.find(searchObject); - if(it != ObjectTable.end()) { - for(auto &data : it->second) { - if(!data) + if (it != ObjectTable.end()) { + for (auto& data : it->second) { + if (!data) continue; - for(auto item : data->items) + for (auto item : data->items) static_cast(item)->restoreBackground(); } } searchObject = 0; } -void TreeWidget::startItemSearch(QLineEdit *edit) { +void TreeWidget::startItemSearch(QLineEdit* edit) { resetItemSearch(); searchDoc = 0; searchContextDoc = 0; auto sels = selectedItems(); - if(sels.size() == 1) { - if(sels.front()->type() == DocumentType) { + if (sels.size() == 1) { + if (sels.front()->type() == DocumentType) { searchDoc = static_cast(sels.front())->document(); - } else if(sels.front()->type() == ObjectType) { + } + else if (sels.front()->type() == ObjectType) { auto item = static_cast(sels.front()); searchDoc = item->object()->getDocument(); searchContextDoc = item->getOwnerDocument()->document(); } - }else + } + else searchDoc = Application::Instance->activeDocument(); - App::DocumentObject *obj = 0; - if(searchContextDoc && searchContextDoc->getDocument()->getObjects().size()) + App::DocumentObject* obj = 0; + if (searchContextDoc && searchContextDoc->getDocument()->getObjects().size()) obj = searchContextDoc->getDocument()->getObjects().front(); - else if(searchDoc && searchDoc->getDocument()->getObjects().size()) + else if (searchDoc && searchDoc->getDocument()->getObjects().size()) obj = searchDoc->getDocument()->getObjects().front(); - if(obj) + if (obj) static_cast(edit)->setDocumentObject(obj); } -void TreeWidget::itemSearch(const QString &text, bool select) { +void TreeWidget::itemSearch(const QString& text, bool select) { resetItemSearch(); auto docItem = getDocumentItem(searchDoc); - if(!docItem) { + if (!docItem) { docItem = getDocumentItem(Application::Instance->activeDocument()); - if(!docItem) { + if (!docItem) { FC_TRACE("item search no document"); resetItemSearch(); return; @@ -706,122 +696,125 @@ void TreeWidget::itemSearch(const QString &text, bool select) { } auto doc = docItem->document()->getDocument(); - const auto &objs = doc->getObjects(); - if(objs.empty()) { + const auto& objs = doc->getObjects(); + if (objs.empty()) { FC_TRACE("item search no objects"); return; } std::string txt(text.toUtf8().constData()); try { - if(txt.empty()) + if (txt.empty()) return; - if(txt.find("<<") == std::string::npos) { + if (txt.find("<<") == std::string::npos) { auto pos = txt.find('.'); - if(pos==std::string::npos) + if (pos == std::string::npos) txt += '.'; - else if(pos!=txt.size()-1) { - txt.insert(pos+1,"<<"); - if(txt.back()!='.') + else if (pos != txt.size() - 1) { + txt.insert(pos + 1, "<<"); + if (txt.back() != '.') txt += '.'; txt += ">>."; } - }else if(txt.back() != '.') + } + else if (txt.back() != '.') txt += '.'; txt += "_self"; - auto path = App::ObjectIdentifier::parse(objs.front(),txt); - if(path.getPropertyName() != "_self") { + auto path = App::ObjectIdentifier::parse(objs.front(), txt); + if (path.getPropertyName() != "_self") { FC_TRACE("Object " << txt << " not found in " << doc->getName()); return; } auto obj = path.getDocumentObject(); - if(!obj) { + if (!obj) { FC_TRACE("Object " << txt << " not found in " << doc->getName()); return; } std::string subname = path.getSubObjectName(); - App::DocumentObject *parent = 0; - if(searchContextDoc) { + App::DocumentObject* parent = 0; + if (searchContextDoc) { auto it = DocumentMap.find(searchContextDoc); - if(it!=DocumentMap.end()) { - parent = it->second->getTopParent(obj,subname); - if(parent) { + if (it != DocumentMap.end()) { + parent = it->second->getTopParent(obj, subname); + if (parent) { obj = parent; docItem = it->second; doc = docItem->document()->getDocument(); } } } - if(!parent) { - parent = docItem->getTopParent(obj,subname); - while(!parent) { - if(docItem->document()->getDocument() == obj->getDocument()) { + if (!parent) { + parent = docItem->getTopParent(obj, subname); + while (!parent) { + if (docItem->document()->getDocument() == obj->getDocument()) { // this shouldn't happen FC_LOG("Object " << txt << " not found in " << doc->getName()); return; } auto it = DocumentMap.find(Application::Instance->getDocument(obj->getDocument())); - if(it==DocumentMap.end()) + if (it == DocumentMap.end()) return; docItem = it->second; - parent = docItem->getTopParent(obj,subname); + parent = docItem->getTopParent(obj, subname); } obj = parent; } - auto item = docItem->findItemByObject(true,obj,subname.c_str()); - if(!item) { + auto item = docItem->findItemByObject(true, obj, subname.c_str()); + if (!item) { FC_TRACE("item " << txt << " not found in " << doc->getName()); return; } scrollToItem(item); Selection().setPreselect(obj->getDocument()->getName(), - obj->getNameInDocument(), subname.c_str(),0,0,0,2); - if(select) { + obj->getNameInDocument(), subname.c_str(), 0, 0, 0, 2); + if (select) { Gui::Selection().selStackPush(); Gui::Selection().clearSelection(); Gui::Selection().addSelection(obj->getDocument()->getName(), - obj->getNameInDocument(),subname.c_str()); + obj->getNameInDocument(), subname.c_str()); Gui::Selection().selStackPush(); - }else{ + } + else { searchObject = item->object()->getObject(); item->setBackground(0, QColor(255, 255, 0, 100)); } FC_TRACE("found item " << txt); - } catch(...) + } + catch (...) { FC_TRACE("item " << txt << " search exception in " << doc->getName()); } } -Gui::Document *TreeWidget::selectedDocument() { - for(auto tree : Instances) { - if(!tree->isVisible()) +Gui::Document* TreeWidget::selectedDocument() { + for (auto tree : Instances) { + if (!tree->isVisible()) continue; auto sels = tree->selectedItems(); - if(sels.size()==1 && sels[0]->type()==DocumentType) + if (sels.size() == 1 && sels[0]->type() == DocumentType) return static_cast(sels[0])->document(); } return 0; } void TreeWidget::updateStatus(bool delay) { - for(auto tree : Instances) + for (auto tree : Instances) tree->_updateStatus(delay); } void TreeWidget::_updateStatus(bool delay) { - if(!delay) { - if(ChangedObjects.size() || NewObjects.size()) + if (!delay) { + if (ChangedObjects.size() || NewObjects.size()) onUpdateStatus(); return; } int timeout = TreeParams::Instance()->StatusTimeout(); - if (timeout<0) + if (timeout < 0) timeout = 1; FC_LOG("delay update status"); statusTimer->start(timeout); } -void TreeWidget::contextMenuEvent (QContextMenuEvent * e) +void TreeWidget::contextMenuEvent(QContextMenuEvent* e) { // ask workbenches and view provider, ... MenuItem view; @@ -837,7 +830,7 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e) QActionGroup subMenuGroup(&subMenu); subMenuGroup.setExclusive(true); connect(&subMenuGroup, SIGNAL(triggered(QAction*)), - this, SLOT(onActivateDocument(QAction*))); + this, SLOT(onActivateDocument(QAction*))); MenuManager::getInstance()->setupContextMenu(&view, contextMenu); // get the current item @@ -851,11 +844,11 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e) contextMenu.addAction(this->showHiddenAction); contextMenu.addAction(this->searchObjectsAction); contextMenu.addAction(this->closeDocAction); - if(doc->testStatus(App::Document::PartialDoc)) + if (doc->testStatus(App::Document::PartialDoc)) contextMenu.addAction(this->reloadDocAction); else { - for(auto d : doc->getDependentDocuments()) { - if(d->testStatus(App::Document::PartialDoc)) { + for (auto d : doc->getDependentDocuments()) { + if (d->testStatus(App::Document::PartialDoc)) { contextMenu.addAction(this->reloadDocAction); break; } @@ -864,7 +857,7 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e) this->skipRecomputeAction->setChecked(doc->testStatus(App::Document::SkipRecompute)); contextMenu.addAction(this->skipRecomputeAction); this->allowPartialRecomputeAction->setChecked(doc->testStatus(App::Document::AllowPartialRecompute)); - if(doc->testStatus(App::Document::SkipRecompute)) + if (doc->testStatus(App::Document::SkipRecompute)) contextMenu.addAction(this->allowPartialRecomputeAction); contextMenu.addAction(this->markRecomputeAction); contextMenu.addAction(this->createGroupAction); @@ -886,7 +879,7 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e) break; } } - + showHiddenAction->setChecked(doc->ShowHidden.getValue()); contextMenu.addAction(this->showHiddenAction); @@ -918,7 +911,7 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e) if (!editAct.isEmpty()) { QAction* topact = contextMenu.actions().front(); for (QList::iterator it = editAct.begin(); it != editAct.end(); ++it) - contextMenu.insertAction(topact,*it); + contextMenu.insertAction(topact, *it); QAction* first = editAct.front(); contextMenu.setDefaultAction(first); if (objitem->object()->isEditing()) @@ -954,18 +947,21 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e) if (contextMenu.actions().count() > 0) { try { contextMenu.exec(QCursor::pos()); - } catch (Base::Exception &e) { + } + catch (Base::Exception& e) { e.ReportException(); - } catch (std::exception &e) { + } + catch (std::exception& e) { FC_ERR("C++ exception: " << e.what()); - } catch (...) { + } + catch (...) { FC_ERR("Unknown exception"); } contextItem = 0; } } -void TreeWidget::hideEvent(QHideEvent *ev) { +void TreeWidget::hideEvent(QHideEvent* ev) { // No longer required. Visibility is now handled inside onUpdateStatus() by // UpdateDisabler. #if 0 @@ -976,14 +972,14 @@ void TreeWidget::hideEvent(QHideEvent *ev) { QTreeWidget::hideEvent(ev); } -void TreeWidget::showEvent(QShowEvent *ev) { +void TreeWidget::showEvent(QShowEvent* ev) { // No longer required. Visibility is now handled inside onUpdateStatus() by // UpdateDisabler. #if 0 TREE_TRACE("attaching selection observer"); this->attachSelection(); int timeout = TreeParams::Instance()->SelectionTimeout(); - if(timeout<=0) + if (timeout <= 0) timeout = 1; selectTimer->start(timeout); _updateStatus(); @@ -999,8 +995,8 @@ void TreeWidget::onCreateGroup() DocumentItem* docitem = static_cast(this->contextItem); App::Document* doc = docitem->document()->getDocument(); QString cmd = QString::fromLatin1("App.getDocument(\"%1\").addObject" - "(\"App::DocumentObjectGroup\",\"%2\")") - .arg(QString::fromLatin1(doc->getName()), name); + "(\"App::DocumentObjectGroup\",\"%2\")") + .arg(QString::fromLatin1(doc->getName()), name); Gui::Command::runCommand(Gui::Command::App, cmd.toUtf8()); } else if (this->contextItem->type() == ObjectType) { @@ -1009,10 +1005,10 @@ void TreeWidget::onCreateGroup() App::DocumentObject* obj = objitem->object()->getObject(); App::Document* doc = obj->getDocument(); QString cmd = QString::fromLatin1("App.getDocument(\"%1\").getObject(\"%2\")" - ".newObject(\"App::DocumentObjectGroup\",\"%3\")") - .arg(QString::fromLatin1(doc->getName()), - QString::fromLatin1(obj->getNameInDocument()), - name); + ".newObject(\"App::DocumentObjectGroup\",\"%3\")") + .arg(QString::fromLatin1(doc->getName()), + QString::fromLatin1(obj->getNameInDocument()), + name); Gui::Command::runCommand(Gui::Command::App, cmd.toUtf8()); } } @@ -1037,7 +1033,7 @@ void TreeWidget::onStartEditing() if (!obj || !obj->getNameInDocument()) return; auto doc = const_cast(objitem->getOwnerDocument()->document()); - MDIView *view = doc->getActiveView(); + MDIView* view = doc->getActiveView(); if (view) getMainWindow()->setActiveWindow(view); // Always open a transaction here doesn't make much sense because: @@ -1053,7 +1049,7 @@ void TreeWidget::onStartEditing() if (!ok) doc->abortCommand(); #else editingItem = objitem; - if(!doc->setEdit(objitem->object(), edit)) + if (!doc->setEdit(objitem->object(), edit)) editingItem = 0; #endif } @@ -1186,99 +1182,99 @@ void TreeWidget::onMarkRecompute() void TreeWidget::onRecomputeObject() { std::vector objs; - for(auto ti : selectedItems()) { + for (auto ti : selectedItems()) { if (ti->type() == ObjectType) { DocumentObjectItem* objitem = static_cast(ti); objs.push_back(objitem->object()->getObject()); objs.back()->enforceRecompute(); } } - if(objs.empty()) + if (objs.empty()) return; App::AutoTransaction committer("Recompute object"); - objs.front()->getDocument()->recompute(objs,true); + objs.front()->getDocument()->recompute(objs, true); } -DocumentItem *TreeWidget::getDocumentItem(const Gui::Document *doc) const { +DocumentItem* TreeWidget::getDocumentItem(const Gui::Document* doc) const { auto it = DocumentMap.find(doc); - if(it != DocumentMap.end()) + if (it != DocumentMap.end()) return it->second; return 0; } -void TreeWidget::selectAllInstances(const ViewProviderDocumentObject &vpd) { - if(!isConnectionAttached()) +void TreeWidget::selectAllInstances(const ViewProviderDocumentObject& vpd) { + if (!isConnectionAttached()) return; - if(selectTimer->isActive()) + if (selectTimer->isActive()) onSelectTimer(); else _updateStatus(false); - for(const auto &v : DocumentMap) + for (const auto& v : DocumentMap) v.second->selectAllInstances(vpd); } -TreeWidget *TreeWidget::instance() { +TreeWidget* TreeWidget::instance() { auto res = _LastSelectedTreeWidget; - if(res && res->isVisible()) + if (res && res->isVisible()) return res; - for(auto inst : Instances) { - if(!res) res = inst; - if(inst->isVisible()) + for (auto inst : Instances) { + if (!res) res = inst; + if (inst->isVisible()) return inst; } return res; } -std::vector TreeWidget::getSelection(App::Document *doc) +std::vector TreeWidget::getSelection(App::Document* doc) { std::vector ret; - TreeWidget *tree = instance(); - if(!tree || !tree->isConnectionAttached()) { - for(auto pTree : Instances) - if(pTree->isConnectionAttached()) { + TreeWidget* tree = instance(); + if (!tree || !tree->isConnectionAttached()) { + for (auto pTree : Instances) + if (pTree->isConnectionAttached()) { tree = pTree; break; } } - if(!tree) return ret; + if (!tree) return ret; - if(tree->selectTimer->isActive()) + if (tree->selectTimer->isActive()) tree->onSelectTimer(); else tree->_updateStatus(false); - for(auto ti : tree->selectedItems()) { - if(ti->type() != ObjectType) continue; + for (auto ti : tree->selectedItems()) { + if (ti->type() != ObjectType) continue; auto item = static_cast(ti); auto vp = item->object(); auto obj = vp->getObject(); - if(!obj || !obj->getNameInDocument()) { + if (!obj || !obj->getNameInDocument()) { FC_WARN("skip invalid object"); continue; } - if(doc && obj->getDocument()!=doc) { + if (doc && obj->getDocument() != doc) { FC_LOG("skip objects not from current document"); continue; } - ViewProviderDocumentObject *parentVp = 0; + ViewProviderDocumentObject* parentVp = 0; auto parent = item->getParentItem(); - if(parent) { + if (parent) { parentVp = parent->object(); - if(!parentVp->getObject()->getNameInDocument()) { + if (!parentVp->getObject()->getNameInDocument()) { FC_WARN("skip '" << obj->getFullName() << "' with invalid parent"); continue; } } ret.emplace_back(); - auto &sel = ret.back(); + auto& sel = ret.back(); sel.topParent = 0; std::ostringstream ss; - item->getSubName(ss,sel.topParent); - if(!sel.topParent) + item->getSubName(ss, sel.topParent); + if (!sel.topParent) sel.topParent = obj; else ss << obj->getNameInDocument() << '.'; @@ -1289,33 +1285,33 @@ std::vector TreeWidget::getSelection(App::Document *doc) return ret; } -void TreeWidget::selectAllLinks(App::DocumentObject *obj) { - if(!isConnectionAttached()) +void TreeWidget::selectAllLinks(App::DocumentObject* obj) { + if (!isConnectionAttached()) return; - if(!obj || !obj->getNameInDocument()) { + if (!obj || !obj->getNameInDocument()) { TREE_ERR("invalid object"); return; } - if(selectTimer->isActive()) + if (selectTimer->isActive()) onSelectTimer(); else _updateStatus(false); - for(auto link: App::GetApplication().getLinksTo(obj,App::GetLinkRecursive)) + for (auto link : App::GetApplication().getLinksTo(obj, App::GetLinkRecursive)) { - if(!link || !link->getNameInDocument()) { + if (!link || !link->getNameInDocument()) { TREE_ERR("invalid linked object"); continue; } auto vp = dynamic_cast( - Application::Instance->getViewProvider(link)); - if(!vp) { + Application::Instance->getViewProvider(link)); + if (!vp) { TREE_ERR("invalid view provider of the linked object"); continue; } - for(auto &v : DocumentMap) + for (auto& v : DocumentMap) v.second->selectAllInstances(*vp); } } @@ -1331,40 +1327,40 @@ void TreeWidget::onActivateDocument(QAction* active) QByteArray docname = active->data().toByteArray(); Gui::Document* doc = Application::Instance->getDocument((const char*)docname); if (doc && !doc->setActiveView()) - doc->setActiveView(0,View3DInventor::getClassTypeId()); + doc->setActiveView(0, View3DInventor::getClassTypeId()); } -Qt::DropActions TreeWidget::supportedDropActions () const +Qt::DropActions TreeWidget::supportedDropActions() const { return Qt::LinkAction | Qt::CopyAction | Qt::MoveAction; } -bool TreeWidget::event(QEvent *e) +bool TreeWidget::event(QEvent* e) { #if 0 if (e->type() == QEvent::ShortcutOverride) { - QKeyEvent* ke = static_cast(e); + QKeyEvent* ke = static_cast(e); switch (ke->key()) { - case Qt::Key_Delete: - ke->accept(); + case Qt::Key_Delete: + ke->accept(); } } #endif return QTreeWidget::event(e); } -bool TreeWidget::eventFilter(QObject *, QEvent *ev) { +bool TreeWidget::eventFilter(QObject*, QEvent* ev) { switch (ev->type()) { case QEvent::KeyPress: case QEvent::KeyRelease: { - QKeyEvent *ke = static_cast(ev); + QKeyEvent* ke = static_cast(ev); if (ke->key() != Qt::Key_Escape) { // Qt 5 only recheck key modifier on mouse move, so generate a fake // event to trigger drag cursor change - QMouseEvent *mouseEvent = new QMouseEvent(QEvent::MouseMove, - mapFromGlobal(QCursor::pos()), QCursor::pos(), Qt::NoButton, - QApplication::mouseButtons(), QApplication::queryKeyboardModifiers()); - QApplication::postEvent(this,mouseEvent); + QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseMove, + mapFromGlobal(QCursor::pos()), QCursor::pos(), Qt::NoButton, + QApplication::mouseButtons(), QApplication::queryKeyboardModifiers()); + QApplication::postEvent(this, mouseEvent); } break; } @@ -1374,47 +1370,74 @@ bool TreeWidget::eventFilter(QObject *, QEvent *ev) { return false; } -void TreeWidget::keyPressEvent(QKeyEvent *event) +void TreeWidget::keyPressEvent(QKeyEvent* event) { #if 0 if (event && event->matches(QKeySequence::Delete)) { event->ignore(); } #endif - if(event->matches(QKeySequence::Find)) { + if (event->matches(QKeySequence::Find)) { event->accept(); onSearchObjects(); return; - }else if(event->key() == Qt::Key_Left) { + } + else if (event->modifiers() == Qt::AltModifier) { + if (event->key() == Qt::Key_Left) { + for (auto& item : selectedItems()) { + item->setExpanded(false); + } + event->accept(); + return; + } + else if (event->key() == Qt::Key_Right) { + for (auto& item : selectedItems()) { + item->setExpanded(true); + } + event->accept(); + return; + } + else if (event->key() == Qt::Key_Up) { + for (auto& item : selectedItems()) { + item->setExpanded(true); + for (auto& child : childrenOfItem(*item)) { + child->setExpanded(false); + } + } + event->accept(); + return; + } + else if (event->key() == Qt::Key_Down) { + for (auto& item : selectedItems()) { + item->setExpanded(true); + for (auto& child : childrenOfItem(*item)) { + child->setExpanded(true); + } + } + event->accept(); + return; + } + } + else if (event->key() == Qt::Key_Left) { auto index = currentIndex(); - if(index.column()==1) { + if (index.column() == 1) { setCurrentIndex(model()->index(index.row(), 0, index.parent())); event->accept(); return; } - }else if(event->key() == Qt::Key_Right) { + } + else if (event->key() == Qt::Key_Right) { auto index = currentIndex(); - if(index.column()==0) { + if (index.column() == 0) { setCurrentIndex(model()->index(index.row(), 1, index.parent())); event->accept(); return; } } - - if (event->modifiers() == Qt::AltModifier - && (event->key() == Qt::Key_Up || event->key() == Qt::Key_Down)) { - // Consume the Alt+Up/Down keypresses. This is because if the "Move Up/Down in Group" - // key shortcut is inactive, they are interpreted as ordinary up/down single selection - // move, which results in confusing behavior on item group borders crossing - event->accept(); - return; - } - - QTreeWidget::keyPressEvent(event); } -void TreeWidget::mouseDoubleClickEvent (QMouseEvent * event) +void TreeWidget::mouseDoubleClickEvent(QMouseEvent* event) { QTreeWidgetItem* item = itemAt(event->pos()); if (!item) return; @@ -1424,13 +1447,13 @@ void TreeWidget::mouseDoubleClickEvent (QMouseEvent * event) //QTreeWidget::mouseDoubleClickEvent(event); Gui::Document* doc = static_cast(item)->document(); if (!doc) return; - if(doc->getDocument()->testStatus(App::Document::PartialDoc)) { + if (doc->getDocument()->testStatus(App::Document::PartialDoc)) { contextItem = item; onReloadDoc(); return; } - if(!doc->setActiveView()) - doc->setActiveView(0,View3DInventor::getClassTypeId()); + if (!doc->setActiveView()) + doc->setActiveView(0, View3DInventor::getClassTypeId()); } else if (item->type() == TreeWidget::ObjectType) { DocumentObjectItem* objitem = static_cast(item); @@ -1465,19 +1488,22 @@ void TreeWidget::mouseDoubleClickEvent (QMouseEvent * event) manager->addLine(MacroManager::Gui, ss.str().c_str()); } } - } catch (Base::Exception &e) { + } + catch (Base::Exception& e) { e.ReportException(); - } catch (std::exception &e) { + } + catch (std::exception& e) { FC_ERR("C++ exception: " << e.what()); - } catch (...) { + } + catch (...) { FC_ERR("Unknown exception"); } } void TreeWidget::startDragging() { - if(state() != NoState) + if (state() != NoState) return; - if(selectedItems().empty()) + if (selectedItems().empty()) return; setState(DraggingState); @@ -1489,21 +1515,21 @@ void TreeWidget::startDragging() { void TreeWidget::startDrag(Qt::DropActions supportedActions) { QTreeWidget::startDrag(supportedActions); - if(_DragEventFilter) { + if (_DragEventFilter) { _DragEventFilter = false; qApp->removeEventFilter(this); } } -QMimeData * TreeWidget::mimeData (const QList items) const +QMimeData* TreeWidget::mimeData(const QList items) const { #if 0 // all selected items must reference an object from the same document - App::Document* doc=0; - for (QList::ConstIterator it = items.begin(); it != items.end(); ++it) { + App::Document* doc = 0; + for (QList::ConstIterator it = items.begin(); it != items.end(); ++it) { if ((*it)->type() != TreeWidget::ObjectType) return 0; - App::DocumentObject* obj = static_cast(*it)->object()->getObject(); + App::DocumentObject* obj = static_cast(*it)->object()->getObject(); if (!doc) doc = obj->getDocument(); else if (doc != obj->getDocument()) @@ -1513,29 +1539,29 @@ QMimeData * TreeWidget::mimeData (const QList items) const return QTreeWidget::mimeData(items); } -bool TreeWidget::dropMimeData(QTreeWidgetItem *parent, int index, - const QMimeData *data, Qt::DropAction action) +bool TreeWidget::dropMimeData(QTreeWidgetItem* parent, int index, + const QMimeData* data, Qt::DropAction action) { return QTreeWidget::dropMimeData(parent, index, data, action); } -void TreeWidget::dragEnterEvent(QDragEnterEvent * event) +void TreeWidget::dragEnterEvent(QDragEnterEvent* event) { QTreeWidget::dragEnterEvent(event); } -void TreeWidget::dragLeaveEvent(QDragLeaveEvent * event) +void TreeWidget::dragLeaveEvent(QDragLeaveEvent* event) { QTreeWidget::dragLeaveEvent(event); } -void TreeWidget::dragMoveEvent(QDragMoveEvent *event) +void TreeWidget::dragMoveEvent(QDragMoveEvent* event) { // Qt5 does not change drag cursor in response to modifier key press, // because QDrag installs a event filter that eats up key event. We install // a filter after Qt and generate fake mouse move event in response to key // press event, which triggers QDrag to update its cursor - if(!_DragEventFilter) { + if (!_DragEventFilter) { _DragEventFilter = true; qApp->installEventFilter(this); } @@ -1552,9 +1578,9 @@ void TreeWidget::dragMoveEvent(QDragMoveEvent *event) } else if (targetItem->type() == TreeWidget::DocumentType) { leaveEvent(0); - if(modifier== Qt::ControlModifier) + if (modifier == Qt::ControlModifier) event->setDropAction(Qt::CopyAction); - else if(modifier== Qt::AltModifier) + else if (modifier == Qt::AltModifier) event->setDropAction(Qt::LinkAction); else event->setDropAction(Qt::MoveAction); @@ -1568,23 +1594,23 @@ void TreeWidget::dragMoveEvent(QDragMoveEvent *event) try { auto items = selectedItems(); - if(modifier == Qt::ControlModifier) + if (modifier == Qt::ControlModifier) event->setDropAction(Qt::CopyAction); - else if(modifier== Qt::AltModifier && items.size()==1) + else if (modifier == Qt::AltModifier && items.size() == 1) event->setDropAction(Qt::LinkAction); else event->setDropAction(Qt::MoveAction); auto da = event->dropAction(); - bool dropOnly = da==Qt::CopyAction || da==Qt::MoveAction; + bool dropOnly = da == Qt::CopyAction || da == Qt::MoveAction; - if (da!=Qt::LinkAction && !vp->canDropObjects()) { - if(!(event->possibleActions() & Qt::LinkAction) || items.size()!=1) { + if (da != Qt::LinkAction && !vp->canDropObjects()) { + if (!(event->possibleActions() & Qt::LinkAction) || items.size() != 1) { TREE_TRACE("cannot drop"); event->ignore(); return; } } - for(auto ti : items) { + for (auto ti : items) { if (ti->type() != TreeWidget::ObjectType) { TREE_TRACE("cannot drop"); event->ignore(); @@ -1594,14 +1620,14 @@ void TreeWidget::dragMoveEvent(QDragMoveEvent *event) auto obj = item->object()->getObject(); - if(!dropOnly && !vp->canDragAndDropObject(obj)) { + if (!dropOnly && !vp->canDragAndDropObject(obj)) { // check if items can be dragged auto parentItem = item->getParentItem(); - if(parentItem - && (!parentItem->object()->canDragObjects() - || !parentItem->object()->canDragObject(item->object()->getObject()))) + if (parentItem + && (!parentItem->object()->canDragObjects() + || !parentItem->object()->canDragObject(item->object()->getObject()))) { - if(!(event->possibleActions() & Qt::CopyAction)) { + if (!(event->possibleActions() & Qt::CopyAction)) { TREE_TRACE("Cannot drag object"); event->ignore(); return; @@ -1611,18 +1637,18 @@ void TreeWidget::dragMoveEvent(QDragMoveEvent *event) } std::ostringstream str; - auto owner = item->getRelativeParent(str,targetItemObj); + auto owner = item->getRelativeParent(str, targetItemObj); auto subname = str.str(); // 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) { - if(items.size()>1) { + if (da != Qt::LinkAction && !vp->canDropObjectEx(obj, owner, subname.c_str(), item->mySubs)) { + if (event->possibleActions() & Qt::LinkAction) { + if (items.size() > 1) { TREE_TRACE("Cannot replace with more than one object"); event->ignore(); return; } - if(!targetItemObj->getParentItem()) { + if (!targetItemObj->getParentItem()) { TREE_TRACE("Cannot replace without parent"); event->ignore(); return; @@ -1632,18 +1658,21 @@ void TreeWidget::dragMoveEvent(QDragMoveEvent *event) } TREE_TRACE("cannot drop " << obj->getFullName() << ' ' - << (owner?owner->getFullName():"") << '.' << subname); + << (owner ? owner->getFullName() : "") << '.' << subname); event->ignore(); return; } } - } catch (Base::Exception &e){ + } + catch (Base::Exception& e) { e.ReportException(); event->ignore(); - } catch (std::exception &e) { + } + catch (std::exception& e) { FC_ERR("C++ exception: " << e.what()); event->ignore(); - } catch (...) { + } + catch (...) { FC_ERR("Unknown exception"); event->ignore(); } @@ -1678,7 +1707,7 @@ struct ItemInfo2 { std::string topSubname; }; -void TreeWidget::dropEvent(QDropEvent *event) +void TreeWidget::dropEvent(QDropEvent* event) { //FIXME: This should actually be done inside dropMimeData @@ -1691,27 +1720,27 @@ void TreeWidget::dropEvent(QDropEvent *event) if (targetItem->isSelected()) return; - App::Document *thisDoc; + App::Document* thisDoc; Base::EmptySequencer seq; // filter out the selected items we cannot handle - std::vector > > items; + std::vector > > items; auto sels = selectedItems(); items.reserve(sels.size()); - for(auto ti : sels) { + for (auto ti : sels) { if (ti->type() != TreeWidget::ObjectType) continue; // ignore child elements if the parent is selected - if(sels.contains(ti->parent())) + if (sels.contains(ti->parent())) continue; if (ti == targetItem) continue; auto item = static_cast(ti); items.emplace_back(); - auto &info = items.back(); + auto& info = items.back(); info.first = item; - info.second.insert(info.second.end(),item->mySubs.begin(),item->mySubs.end()); + info.second.insert(info.second.end(), item->mySubs.begin(), item->mySubs.end()); } if (items.empty()) @@ -1719,15 +1748,15 @@ void TreeWidget::dropEvent(QDropEvent *event) std::string errMsg; - if(QApplication::keyboardModifiers()== Qt::ControlModifier) + if (QApplication::keyboardModifiers() == Qt::ControlModifier) event->setDropAction(Qt::CopyAction); - else if(QApplication::keyboardModifiers()== Qt::AltModifier - && (items.size()==1||targetItem->type()==TreeWidget::DocumentType)) + else if (QApplication::keyboardModifiers() == Qt::AltModifier + && (items.size() == 1 || targetItem->type() == TreeWidget::DocumentType)) event->setDropAction(Qt::LinkAction); else event->setDropAction(Qt::MoveAction); auto da = event->dropAction(); - bool dropOnly = da==Qt::CopyAction || da==Qt::LinkAction; + bool dropOnly = da == Qt::CopyAction || da == Qt::LinkAction; if (targetItem->type() == TreeWidget::ObjectType) { // add object to group @@ -1735,60 +1764,61 @@ void TreeWidget::dropEvent(QDropEvent *event) thisDoc = targetItemObj->getOwnerDocument()->document()->getDocument(); Gui::ViewProviderDocumentObject* vp = targetItemObj->object(); - if(!vp || !vp->getObject() || !vp->getObject()->getNameInDocument()) { + if (!vp || !vp->getObject() || !vp->getObject()->getNameInDocument()) { TREE_TRACE("invalid object"); return; } - if (da!=Qt::LinkAction && !vp->canDropObjects()) { - if(!(event->possibleActions() & Qt::LinkAction) || items.size()!=1) { + if (da != Qt::LinkAction && !vp->canDropObjects()) { + if (!(event->possibleActions() & Qt::LinkAction) || items.size() != 1) { TREE_TRACE("Cannot drop objects"); return; // no group like object } } std::ostringstream targetSubname; - App::DocumentObject *targetParent = 0; - targetItemObj->getSubName(targetSubname,targetParent); + App::DocumentObject* targetParent = 0; + targetItemObj->getSubName(targetSubname, targetParent); Selection().selStackPush(); Selection().clearCompleteSelection(); - if(targetParent) { + if (targetParent) { targetSubname << vp->getObject()->getNameInDocument() << '.'; Selection().addSelection(targetParent->getDocument()->getName(), - targetParent->getNameInDocument(), targetSubname.str().c_str()); - } else { + targetParent->getNameInDocument(), targetSubname.str().c_str()); + } + else { targetParent = targetItemObj->object()->getObject(); Selection().addSelection(targetParent->getDocument()->getName(), - targetParent->getNameInDocument()); + targetParent->getNameInDocument()); } bool syncPlacement = TreeParams::Instance()->SyncPlacement() && targetItemObj->isGroup(); bool setSelection = true; - std::vector > droppedObjects; + std::vector > droppedObjects; std::vector infos; // Only keep text names here, because you never know when doing drag // and drop some object may delete other objects. infos.reserve(items.size()); - for(auto &v : items) { + for (auto& v : items) { infos.emplace_back(); - auto &info = infos.back(); + auto& info = infos.back(); auto item = v.first; Gui::ViewProviderDocumentObject* vpc = item->object(); App::DocumentObject* obj = vpc->getObject(); std::ostringstream str; - App::DocumentObject *topParent=0; - auto owner = item->getRelativeParent(str,targetItemObj,&topParent,&info.topSubname); - if(syncPlacement && topParent) { + App::DocumentObject* topParent = 0; + auto owner = item->getRelativeParent(str, targetItemObj, &topParent, &info.topSubname); + if (syncPlacement && topParent) { info.topDoc = topParent->getDocument()->getName(); info.topObj = topParent->getNameInDocument(); } info.subname = str.str(); info.doc = obj->getDocument()->getName(); info.obj = obj->getNameInDocument(); - if(owner) { + if (owner) { info.ownerDoc = owner->getDocument()->getName(); info.owner = owner->getNameInDocument(); } @@ -1796,16 +1826,16 @@ void TreeWidget::dropEvent(QDropEvent *event) info.subs.swap(v.second); // check if items can be dragged - if(!dropOnly && - item->myOwner == targetItemObj->myOwner && - vp->canDragAndDropObject(item->object()->getObject())) + if (!dropOnly && + item->myOwner == targetItemObj->myOwner && + vp->canDragAndDropObject(item->object()->getObject())) { // check if items can be dragged auto parentItem = item->getParentItem(); - if(!parentItem) + if (!parentItem) info.dragging = true; - else if(parentItem->object()->canDragObjects() - && parentItem->object()->canDragObject(item->object()->getObject())) + else if (parentItem->object()->canDragObjects() + && parentItem->object()->canDragObject(item->object()->getObject())) { info.dragging = true; auto vpp = parentItem->object(); @@ -1814,16 +1844,16 @@ void TreeWidget::dropEvent(QDropEvent *event) } } - if (da!=Qt::LinkAction - && !vp->canDropObjectEx(obj,owner,info.subname.c_str(),item->mySubs)) + if (da != Qt::LinkAction + && !vp->canDropObjectEx(obj, owner, info.subname.c_str(), item->mySubs)) { - if(event->possibleActions() & Qt::LinkAction) { - if(items.size()>1) { + if (event->possibleActions() & Qt::LinkAction) { + if (items.size() > 1) { TREE_TRACE("Cannot replace with more than one object"); return; } auto ext = vp->getObject()->getExtensionByType(true); - if((!ext || !ext->getLinkedObjectProperty()) && !targetItemObj->getParentItem()) { + if ((!ext || !ext->getLinkedObjectProperty()) && !targetItemObj->getParentItem()) { TREE_TRACE("Cannot replace without parent"); return; } @@ -1839,80 +1869,81 @@ void TreeWidget::dropEvent(QDropEvent *event) std::set inList; auto parentObj = targetObj; - if(da == Qt::LinkAction && targetItemObj->getParentItem()) + if (da == Qt::LinkAction && targetItemObj->getParentItem()) parentObj = targetItemObj->getParentItem()->object()->getObject(); inList = parentObj->getInListEx(true); inList.insert(parentObj); std::string target = targetObj->getNameInDocument(); auto targetDoc = targetObj->getDocument(); - for (auto &info : infos) { - auto &subname = info.subname; + for (auto& info : infos) { + auto& subname = info.subname; targetObj = targetDoc->getObject(target.c_str()); vp = Base::freecad_dynamic_cast( - Application::Instance->getViewProvider(targetObj)); - if(!vp) { + Application::Instance->getViewProvider(targetObj)); + if (!vp) { FC_ERR("Cannot find drop target object " << target); break; } auto doc = App::GetApplication().getDocument(info.doc.c_str()); - if(!doc) { + if (!doc) { FC_WARN("Cannot find document " << info.doc); continue; } auto obj = doc->getObject(info.obj.c_str()); auto vpc = dynamic_cast( - Application::Instance->getViewProvider(obj)); - if(!vpc) { + Application::Instance->getViewProvider(obj)); + if (!vpc) { FC_WARN("Cannot find dragging object " << info.obj); continue; } - ViewProviderDocumentObject *vpp = 0; - if(da!=Qt::LinkAction && info.parentDoc.size()) { + ViewProviderDocumentObject* vpp = 0; + if (da != Qt::LinkAction && info.parentDoc.size()) { auto parentDoc = App::GetApplication().getDocument(info.parentDoc.c_str()); - if(parentDoc) { + if (parentDoc) { auto parent = parentDoc->getObject(info.parent.c_str()); vpp = dynamic_cast( - Application::Instance->getViewProvider(parent)); + Application::Instance->getViewProvider(parent)); } - if(!vpp) { + if (!vpp) { FC_WARN("Cannot find dragging object's parent " << info.parent); continue; } } - App::DocumentObject *owner = 0; - if(info.ownerDoc.size()) { + App::DocumentObject* owner = 0; + if (info.ownerDoc.size()) { auto ownerDoc = App::GetApplication().getDocument(info.ownerDoc.c_str()); - if(ownerDoc) + if (ownerDoc) owner = ownerDoc->getObject(info.owner.c_str()); - if(!owner) { + if (!owner) { FC_WARN("Cannot find dragging object's top parent " << info.owner); continue; } } Base::Matrix4D mat; - App::PropertyPlacement *propPlacement = 0; - if(syncPlacement) { - if(info.topObj.size()) { + App::PropertyPlacement* propPlacement = 0; + if (syncPlacement) { + if (info.topObj.size()) { auto doc = App::GetApplication().getDocument(info.topDoc.c_str()); - if(doc) { + if (doc) { auto topObj = doc->getObject(info.topObj.c_str()); - if(topObj) { - auto sobj = topObj->getSubObject(info.topSubname.c_str(),0,&mat); - if(sobj == obj) { + if (topObj) { + auto sobj = topObj->getSubObject(info.topSubname.c_str(), 0, &mat); + if (sobj == obj) { propPlacement = Base::freecad_dynamic_cast( - obj->getPropertyByName("Placement")); + obj->getPropertyByName("Placement")); } } } - }else{ + } + else { propPlacement = Base::freecad_dynamic_cast( - obj->getPropertyByName("Placement")); - if(propPlacement) + obj->getPropertyByName("Placement")); + if (propPlacement) mat = propPlacement->getValue().toMatrix(); } } @@ -1921,34 +1952,34 @@ void TreeWidget::dropEvent(QDropEvent *event) auto manager = Application::Instance->macroManager(); std::ostringstream ss; - if(vpp) { + if (vpp) { auto lines = manager->getLines(); ss << Command::getObjectCmd(vpp->getObject()) << ".ViewObject.dragObject(" << Command::getObjectCmd(obj) << ')'; vpp->dragObject(obj); - if(manager->getLines() == lines) - manager->addLine(MacroManager::Gui,ss.str().c_str()); + if (manager->getLines() == lines) + manager->addLine(MacroManager::Gui, ss.str().c_str()); owner = 0; subname.clear(); ss.str(""); obj = doc->getObject(info.obj.c_str()); - if(!obj || !obj->getNameInDocument()) { + if (!obj || !obj->getNameInDocument()) { FC_WARN("Dropping object deleted: " << info.doc << '#' << info.obj); continue; } } - if(da == Qt::MoveAction) { + if (da == Qt::MoveAction) { // Try to adjust relative links to avoid cyclic dependency, may // throw exception if failed ss.str(""); ss << Command::getObjectCmd(obj) << ".adjustRelativeLinks(" << Command::getObjectCmd(targetObj) << ")"; - manager->addLine(MacroManager::Gui,ss.str().c_str()); + manager->addLine(MacroManager::Gui, ss.str().c_str()); std::set visited; - if(obj->adjustRelativeLinks(inList,&visited)) { + if (obj->adjustRelativeLinks(inList, &visited)) { inList = parentObj->getInListEx(true); inList.insert(parentObj); @@ -1958,60 +1989,63 @@ void TreeWidget::dropEvent(QDropEvent *event) } } - if(inList.count(obj)) + if (inList.count(obj)) FC_THROWM(Base::RuntimeError, - "Dependency loop detected for " << obj->getFullName()); + "Dependency loop detected for " << obj->getFullName()); std::string dropName; ss.str(""); - if(da == Qt::LinkAction) { + if (da == Qt::LinkAction) { auto parentItem = targetItemObj->getParentItem(); if (parentItem) { ss << Command::getObjectCmd( - parentItem->object()->getObject(),0,".replaceObject(",true) + parentItem->object()->getObject(), 0, ".replaceObject(", true) << Command::getObjectCmd(targetObj) << "," << Command::getObjectCmd(obj) << ")"; std::ostringstream ss; dropParent = 0; - parentItem->getSubName(ss,dropParent); - if(dropParent) + parentItem->getSubName(ss, dropParent); + if (dropParent) ss << parentItem->object()->getObject()->getNameInDocument() << '.'; else dropParent = parentItem->object()->getObject(); ss << obj->getNameInDocument() << '.'; dropName = ss.str(); - } else { + } + else { TREE_WARN("ignore replace operation without parent"); continue; } Gui::Command::runCommand(Gui::Command::App, ss.str().c_str()); - }else{ + } + else { ss << Command::getObjectCmd(vp->getObject()) << ".ViewObject.dropObject(" << Command::getObjectCmd(obj); - if(owner) { + if (owner) { ss << "," << Command::getObjectCmd(owner) << ",'" << subname << "',["; - }else + } + else ss << ",None,'',["; - for(auto &sub : info.subs) + for (auto& sub : info.subs) ss << "'" << sub << "',"; ss << "])"; auto lines = manager->getLines(); - dropName = vp->dropObjectEx(obj,owner,subname.c_str(),info.subs); - if(manager->getLines() == lines) - manager->addLine(MacroManager::Gui,ss.str().c_str()); - if(dropName.size()) + dropName = vp->dropObjectEx(obj, owner, subname.c_str(), info.subs); + if (manager->getLines() == lines) + manager->addLine(MacroManager::Gui, ss.str().c_str()); + if (dropName.size()) dropName = targetSubname.str() + dropName; } touched = true; // Construct the subname pointing to the dropped object - if(dropName.empty()) { + if (dropName.empty()) { auto pos = targetSubname.tellp(); targetSubname << obj->getNameInDocument() << '.' << std::ends; dropName = targetSubname.str(); @@ -2019,55 +2053,58 @@ void TreeWidget::dropEvent(QDropEvent *event) } Base::Matrix4D newMat; - auto sobj = dropParent->getSubObject(dropName.c_str(),0,&newMat); - if(!sobj) { + auto sobj = dropParent->getSubObject(dropName.c_str(), 0, &newMat); + if (!sobj) { FC_LOG("failed to find dropped object " - << dropParent->getFullName() << '.' << dropName); + << dropParent->getFullName() << '.' << dropName); setSelection = false; continue; } - if(da!=Qt::CopyAction && propPlacement) { + if (da != Qt::CopyAction && propPlacement) { // try to adjust placement - if((info.dragging && sobj==obj) || - (!info.dragging && sobj->getLinkedObject(false)==obj)) + if ((info.dragging && sobj == obj) || + (!info.dragging && sobj->getLinkedObject(false) == obj)) { - if(!info.dragging) + if (!info.dragging) propPlacement = Base::freecad_dynamic_cast( - sobj->getPropertyByName("Placement")); - if(propPlacement) { + sobj->getPropertyByName("Placement")); + if (propPlacement) { newMat *= propPlacement->getValue().inverse().toMatrix(); newMat.inverseGauss(); - Base::Placement pla(newMat*mat); + Base::Placement pla(newMat * mat); propPlacement->setValueIfChanged(pla); } } } - droppedObjects.emplace_back(dropParent,dropName); + droppedObjects.emplace_back(dropParent, dropName); } Base::FlagToggler<> guard(_DisableCheckTopParent); - if(setSelection && droppedObjects.size()) { + if (setSelection && droppedObjects.size()) { Selection().selStackPush(); Selection().clearCompleteSelection(); - for(auto &v : droppedObjects) + for (auto& v : droppedObjects) Selection().addSelection(v.first->getDocument()->getName(), v.first->getNameInDocument(), v.second.c_str()); Selection().selStackPush(); } - } catch (const Base::Exception& e) { + } + catch (const Base::Exception& e) { e.ReportException(); errMsg = e.what(); - } catch (std::exception &e) { + } + catch (std::exception& e) { FC_ERR("C++ exception: " << e.what()); errMsg = e.what(); - } catch (...) { + } + catch (...) { FC_ERR("Unknown exception"); errMsg = "Unknown exception"; } - if(errMsg.size()) { + if (errMsg.size()) { committer.close(true); QMessageBox::critical(getMainWindow(), QObject::tr("Drag & drop failed"), - QString::fromUtf8(errMsg.c_str())); + QString::fromUtf8(errMsg.c_str())); return; } } @@ -2080,39 +2117,41 @@ void TreeWidget::dropEvent(QDropEvent *event) bool syncPlacement = TreeParams::Instance()->SyncPlacement(); // check if items can be dragged - for(auto &v : items) { + for (auto& v : items) { auto item = v.first; auto obj = item->object()->getObject(); auto parentItem = item->getParentItem(); - if(!parentItem) { - if(da==Qt::MoveAction && obj->getDocument()==thisDoc) + if (!parentItem) { + if (da == Qt::MoveAction && obj->getDocument() == thisDoc) continue; - }else if(dropOnly || item->myOwner!=targetItem) { + } + else if (dropOnly || item->myOwner != targetItem) { // We will not drag item out of parent if either, 1) the CTRL // key is held, or 2) the dragging item is not inside the // dropping document tree. parentItem = 0; - }else if(!parentItem->object()->canDragObjects() - || !parentItem->object()->canDragObject(obj)) + } + else if (!parentItem->object()->canDragObjects() + || !parentItem->object()->canDragObject(obj)) { TREE_ERR("'" << obj->getFullName() << "' cannot be dragged out of '" << parentItem->object()->getObject()->getFullName() << "'"); return; } infos.emplace_back(); - auto &info = infos.back(); + auto& info = infos.back(); info.doc = obj->getDocument()->getName(); info.obj = obj->getNameInDocument(); - if(parentItem) { + if (parentItem) { auto parent = parentItem->object()->getObject(); info.parentDoc = parent->getDocument()->getName(); info.parent = parent->getNameInDocument(); } - if(syncPlacement) { + if (syncPlacement) { std::ostringstream ss; - App::DocumentObject *topParent=0; - item->getSubName(ss,topParent); - if(topParent) { + App::DocumentObject* topParent = 0; + item->getSubName(ss, topParent); + if (topParent) { info.topDoc = topParent->getDocument()->getName(); info.topObj = topParent->getNameInDocument(); ss << obj->getNameInDocument() << '.'; @@ -2128,66 +2167,68 @@ void TreeWidget::dropEvent(QDropEvent *event) // Open command auto manager = Application::Instance->macroManager(); App::AutoTransaction committer( - da==Qt::LinkAction?"Link object": - da==Qt::CopyAction?"Copy object":"Move object"); + da == Qt::LinkAction ? "Link object" : + da == Qt::CopyAction ? "Copy object" : "Move object"); try { std::vector droppedObjs; - for (auto &info : infos) { + for (auto& info : infos) { auto doc = App::GetApplication().getDocument(info.doc.c_str()); - if(!doc) continue; + if (!doc) continue; auto obj = doc->getObject(info.obj.c_str()); auto vpc = dynamic_cast( - Application::Instance->getViewProvider(obj)); - if(!vpc) { + Application::Instance->getViewProvider(obj)); + if (!vpc) { FC_WARN("Cannot find dragging object " << info.obj); continue; } Base::Matrix4D mat; - App::PropertyPlacement *propPlacement = 0; - if(syncPlacement) { - if(info.topObj.size()) { + App::PropertyPlacement* propPlacement = 0; + if (syncPlacement) { + if (info.topObj.size()) { auto doc = App::GetApplication().getDocument(info.topDoc.c_str()); - if(doc) { + if (doc) { auto topObj = doc->getObject(info.topObj.c_str()); - if(topObj) { - auto sobj = topObj->getSubObject(info.topSubname.c_str(),0,&mat); - if(sobj == obj) { + if (topObj) { + auto sobj = topObj->getSubObject(info.topSubname.c_str(), 0, &mat); + if (sobj == obj) { propPlacement = dynamic_cast( - obj->getPropertyByName("Placement")); + obj->getPropertyByName("Placement")); } } } - }else{ + } + else { propPlacement = dynamic_cast( - obj->getPropertyByName("Placement")); - if(propPlacement) + obj->getPropertyByName("Placement")); + if (propPlacement) mat = propPlacement->getValue().toMatrix(); } } - if(da == Qt::LinkAction) { + if (da == Qt::LinkAction) { std::string name = thisDoc->getUniqueObjectName("Link"); - FCMD_DOC_CMD(thisDoc,"addObject('App::Link','" << name << "').setLink(" - << Command::getObjectCmd(obj) << ")"); + FCMD_DOC_CMD(thisDoc, "addObject('App::Link','" << name << "').setLink(" + << Command::getObjectCmd(obj) << ")"); auto link = thisDoc->getObject(name.c_str()); - if(!link) + if (!link) continue; - FCMD_OBJ_CMD(link,"Label='" << obj->getLinkedObject(true)->Label.getValue() << "'"); + FCMD_OBJ_CMD(link, "Label='" << obj->getLinkedObject(true)->Label.getValue() << "'"); propPlacement = dynamic_cast(link->getPropertyByName("Placement")); - if(propPlacement) + if (propPlacement) propPlacement->setValueIfChanged(Base::Placement(mat)); droppedObjs.push_back(link); - }else if(info.parent.size()) { + } + else if (info.parent.size()) { auto parentDoc = App::GetApplication().getDocument(info.parentDoc.c_str()); - if(!parentDoc) { + if (!parentDoc) { FC_WARN("Canont find document " << info.parentDoc); continue; } auto parent = parentDoc->getObject(info.parent.c_str()); auto vpp = dynamic_cast( - Application::Instance->getViewProvider(parent)); - if(!vpp) { + Application::Instance->getViewProvider(parent)); + if (!vpp) { FC_WARN("Cannot find dragging object's parent " << info.parent); continue; } @@ -2197,79 +2238,84 @@ void TreeWidget::dropEvent(QDropEvent *event) << ".ViewObject.dragObject(" << Command::getObjectCmd(obj) << ')'; auto lines = manager->getLines(); vpp->dragObject(obj); - if(manager->getLines() == lines) - manager->addLine(MacroManager::Gui,ss.str().c_str()); + if (manager->getLines() == lines) + manager->addLine(MacroManager::Gui, ss.str().c_str()); //make sure it is not part of a geofeaturegroup anymore. //When this has happen we need to handle all removed //objects auto grp = App::GeoFeatureGroupExtension::getGroupOfObject(obj); - if(grp) { - FCMD_OBJ_CMD(grp,"removeObject(" << Command::getObjectCmd(obj) << ")"); + if (grp) { + FCMD_OBJ_CMD(grp, "removeObject(" << Command::getObjectCmd(obj) << ")"); } // check if the object has been deleted obj = doc->getObject(info.obj.c_str()); - if(!obj || !obj->getNameInDocument()) + if (!obj || !obj->getNameInDocument()) continue; droppedObjs.push_back(obj); - if(propPlacement) + if (propPlacement) propPlacement->setValueIfChanged(Base::Placement(mat)); - } else { + } + else { std::ostringstream ss; ss << "App.getDocument('" << thisDoc->getName() << "')." - << (da==Qt::CopyAction?"copyObject(":"moveObject(") + << (da == Qt::CopyAction ? "copyObject(" : "moveObject(") << Command::getObjectCmd(obj) << ", True)"; - App::DocumentObject *res = 0; - if(da == Qt::CopyAction) { - auto copied = thisDoc->copyObject({obj},true); - if(copied.size()) + App::DocumentObject* res = 0; + if (da == Qt::CopyAction) { + auto copied = thisDoc->copyObject({ obj }, true); + if (copied.size()) res = copied.back(); - }else - res = thisDoc->moveObject(obj,true); - if(res) { + } + else + res = thisDoc->moveObject(obj, true); + if (res) { propPlacement = dynamic_cast( - res->getPropertyByName("Placement")); - if(propPlacement) + res->getPropertyByName("Placement")); + if (propPlacement) propPlacement->setValueIfChanged(Base::Placement(mat)); droppedObjs.push_back(res); } - manager->addLine(MacroManager::App,ss.str().c_str()); + manager->addLine(MacroManager::App, ss.str().c_str()); } } touched = true; Base::FlagToggler<> guard(_DisableCheckTopParent); - Selection().setSelection(thisDoc->getName(),droppedObjs); + Selection().setSelection(thisDoc->getName(), droppedObjs); - } catch (const Base::Exception& e) { + } + catch (const Base::Exception& e) { e.ReportException(); errMsg = e.what(); - } catch (std::exception &e) { + } + catch (std::exception& e) { FC_ERR("C++ exception: " << e.what()); errMsg = e.what(); - } catch (...) { + } + catch (...) { FC_ERR("Unknown exception"); errMsg = "Unknown exception"; } - if(errMsg.size()) { + if (errMsg.size()) { committer.close(true); QMessageBox::critical(getMainWindow(), QObject::tr("Drag & drop failed"), - QString::fromUtf8(errMsg.c_str())); + QString::fromUtf8(errMsg.c_str())); return; } } - if(touched && TreeParams::Instance()->RecomputeOnDrop()) + if (touched && TreeParams::Instance()->RecomputeOnDrop()) thisDoc->recompute(); - if(touched && TreeParams::Instance()->SyncView()) { + if (touched && TreeParams::Instance()->SyncView()) { auto gdoc = Application::Instance->getDocument(thisDoc); - if(gdoc) + if (gdoc) gdoc->setActiveView(); } } -void TreeWidget::drawRow(QPainter *painter, const QStyleOptionViewItem &options, const QModelIndex &index) const +void TreeWidget::drawRow(QPainter* painter, const QStyleOptionViewItem& options, const QModelIndex& index) const { QTreeWidget::drawRow(painter, options, index); // Set the text and highlighted text color of a hidden object to a dark @@ -2289,14 +2335,14 @@ void TreeWidget::drawRow(QPainter *painter, const QStyleOptionViewItem &options, void TreeWidget::slotNewDocument(const Gui::Document& Doc, bool isMainDoc) { - if(Doc.getDocument()->testStatus(App::Document::TempDoc)) + if (Doc.getDocument()->testStatus(App::Document::TempDoc)) return; DocumentItem* item = new DocumentItem(&Doc, this->rootItem); - if(isMainDoc) + if (isMainDoc) this->expandItem(item); item->setIcon(0, *documentPixmap); item->setText(0, QString::fromUtf8(Doc.getDocument()->Label.getValue())); - DocumentMap[ &Doc ] = item; + DocumentMap[&Doc] = item; } void TreeWidget::slotStartOpenDocument() { @@ -2317,8 +2363,8 @@ void TreeWidget::onReloadDoc() { App::Document* doc = docitem->document()->getDocument(); std::string name = doc->FileName.getValue(); Application::Instance->reopen(doc); - for(auto &v : DocumentMap) { - if(name == v.first->getDocument()->FileName.getValue()) { + for (auto& v : DocumentMap) { + if (name == v.first->getDocument()->FileName.getValue()) { scrollToItem(v.second); App::GetApplication().setActiveDocument(v.first->getDocument()); break; @@ -2336,11 +2382,14 @@ void TreeWidget::onCloseDoc() App::Document* doc = gui->getDocument(); if (gui->canClose(true, true)) Command::doCommand(Command::Doc, "App.closeDocument(\"%s\")", doc->getName()); - } catch (const Base::Exception& e) { + } + catch (const Base::Exception& e) { e.ReportException(); - } catch (std::exception &e) { + } + catch (std::exception& e) { FC_ERR("C++ exception: " << e.what()); - } catch (...) { + } + catch (...) { FC_ERR("Unknown exception"); } } @@ -2351,25 +2400,21 @@ void TreeWidget::slotRenameDocument(const Gui::Document& Doc) Q_UNUSED(Doc); } -void TreeWidget::slotChangedViewObject(const Gui::ViewProvider& vp, const App::Property &prop) +void TreeWidget::slotChangedViewObject(const Gui::ViewProvider& vp, const App::Property& prop) { - if(!App::GetApplication().isRestoring() - && vp.isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) + if (!App::GetApplication().isRestoring() + && vp.isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { - const auto &vpd = static_cast(vp); - if(&prop == &vpd.ShowInTree) { - ChangedObjects.emplace(vpd.getObject(),0); - _updateStatus(); - } - else if (&prop == &vpd.TreeRank) { - ReorderedObjects.insert(vpd.getObject()); + const auto& vpd = static_cast(vp); + if (&prop == &vpd.ShowInTree) { + ChangedObjects.emplace(vpd.getObject(), 0); _updateStatus(); } } } -void TreeWidget::slotTouchedObject(const App::DocumentObject &obj) { - ChangedObjects.emplace(const_cast(&obj),0); +void TreeWidget::slotTouchedObject(const App::DocumentObject& obj) { + ChangedObjects.emplace(const_cast(&obj), 0); _updateStatus(); } @@ -2377,7 +2422,7 @@ void TreeWidget::slotShowHidden(const Gui::Document& Doc) { auto it = DocumentMap.find(&Doc); if (it != DocumentMap.end()) - it->second->updateItemsVisibility(it->second,it->second->showHidden()); + it->second->updateItemsVisibility(it->second, it->second->showHidden()); } void TreeWidget::slotRelabelDocument(const Gui::Document& Doc) @@ -2395,7 +2440,7 @@ void TreeWidget::slotActiveDocument(const Gui::Document& Doc) return; // signal is emitted before the item gets created int displayMode = TreeParams::Instance()->DocumentMode(); for (auto it = DocumentMap.begin(); - it != DocumentMap.end(); ++it) + it != DocumentMap.end(); ++it) { QFont f = it->second->font(0); f.setBold(it == jt); @@ -2409,21 +2454,21 @@ void TreeWidget::slotActiveDocument(const Gui::Document& Doc) } struct UpdateDisabler { - QWidget &widget; - int &blocked; + QWidget& widget; + int& blocked; bool visible; bool focus; // Note! DO NOT block signal here, or else // QTreeWidgetItem::setChildIndicatorPolicy() does not work - UpdateDisabler(QWidget &w, int &blocked) + UpdateDisabler(QWidget& w, int& blocked) : widget(w), blocked(blocked), visible(false), focus(false) { - if(++blocked > 1) + if (++blocked > 1) return; focus = widget.hasFocus(); visible = widget.isVisible(); - if(visible) { + if (visible) { // setUpdatesEnabled(false) does not seem to speed up anything. // setVisible(false) on the other hand makes QTreeWidget::setData // (i.e. any change to QTreeWidgetItem) faster by 10+ times. @@ -2434,13 +2479,13 @@ struct UpdateDisabler { } } ~UpdateDisabler() { - if(blocked<=0 || --blocked!=0) + if (blocked <= 0 || --blocked != 0) return; - if(visible) { + if (visible) { widget.setVisible(true); // widget.setUpdatesEnabled(true); - if(focus) + if (focus) widget.setFocus(); } } @@ -2448,13 +2493,13 @@ struct UpdateDisabler { void TreeWidget::onUpdateStatus(void) { - if(this->state()==DraggingState || App::GetApplication().isRestoring()) { + if (this->state() == DraggingState || App::GetApplication().isRestoring()) { _updateStatus(); return; } - for(auto &v : DocumentMap) { - if(v.first->isPerformingTransaction()) { + for (auto& v : DocumentMap) { + if (v.first->isPerformingTransaction()) { // We have to delay item creation until undo/redo is done, because the // object re-creation while in transaction may break tree view item // update logic. For example, a parent object re-created before its @@ -2467,56 +2512,56 @@ void TreeWidget::onUpdateStatus(void) FC_LOG("begin update status"); - UpdateDisabler disabler(*this,updateBlocked); + UpdateDisabler disabler(*this, updateBlocked); std::vector errors; // Checking for new objects - for(auto &v : NewObjects) { + for (auto& v : NewObjects) { auto doc = App::GetApplication().getDocument(v.first.c_str()); - if(!doc) + if (!doc) continue; auto gdoc = Application::Instance->getDocument(doc); - if(!gdoc) + if (!gdoc) continue; auto docItem = getDocumentItem(gdoc); - if(!docItem) + if (!docItem) continue; - for(auto id : v.second) { + for (auto id : v.second) { auto obj = doc->getObjectByID(id); - if(!obj) + if (!obj) continue; - if(obj->isError()) + if (obj->isError()) errors.push_back(obj); - if(docItem->ObjectMap.count(obj)) + if (docItem->ObjectMap.count(obj)) continue; auto vpd = Base::freecad_dynamic_cast(gdoc->getViewProvider(obj)); - if(vpd) + if (vpd) docItem->createNewItem(*vpd); } } NewObjects.clear(); // Update children of changed objects - for(auto &v : ChangedObjects) { + for (auto& v : ChangedObjects) { auto obj = v.first; auto iter = ObjectTable.find(obj); - if(iter == ObjectTable.end()) + if (iter == ObjectTable.end()) continue; - if(v.second.test(CS_Error) && obj->isError()) + if (v.second.test(CS_Error) && obj->isError()) errors.push_back(obj); - if(iter->second.size()) { + if (iter->second.size()) { auto data = *iter->second.begin(); bool itemHidden = !data->viewObject->showInTree(); - if(data->itemHidden != itemHidden) { - for(auto &data : iter->second) { + if (data->itemHidden != itemHidden) { + for (auto& data : iter->second) { data->itemHidden = itemHidden; - if(data->docItem->showHidden()) + if (data->docItem->showHidden()) continue; - for(auto item : data->items) + for (auto item : data->items) item->setHidden(itemHidden); } } @@ -2526,88 +2571,47 @@ void TreeWidget::onUpdateStatus(void) } ChangedObjects.clear(); - // Sort parents of object items with adjusted order - std::set reorderParents; - for (auto &obj : ReorderedObjects) { - ViewProvider *vp = Application::Instance->getViewProvider(obj); - if (!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { - continue; - } - - auto docIt = DocumentMap.find(static_cast(vp)->getDocument()); - if (docIt == DocumentMap.end() || !docIt->second) { - continue; - } - - DocumentItem *docItem = docIt->second; - auto parentIt = docItem->_ParentMap.find(obj); - if (parentIt != docItem->_ParentMap.end() && parentIt->second.size() > 0) { - for (App::DocumentObject *parent : parentIt->second) { - auto dataIt = docItem->ObjectMap.find(parent); - if (dataIt == docItem->ObjectMap.end()) { - continue; - } - - for (DocumentObjectItem *parentItem : dataIt->second->items) { - reorderParents.insert(parentItem); - } - } - } - else { - reorderParents.insert(docItem); - } - } - ReorderedObjects.clear(); - - if (!reorderParents.empty()) { - for (QTreeWidgetItem *parentItem : reorderParents) { - sortObjectItems(parentItem, [](const DocumentObjectItem *a, const DocumentObjectItem *b) - { return a->object()->TreeRank.getValue() < b->object()->TreeRank.getValue(); }); - } - reorderParents.clear(); - } - FC_LOG("update item status"); TimingInit(); - for (auto pos = DocumentMap.begin();pos!=DocumentMap.end();++pos) { + for (auto pos = DocumentMap.begin(); pos != DocumentMap.end(); ++pos) { pos->second->testStatus(); } TimingPrint(); // Checking for just restored documents - for(auto &v : DocumentMap) { + for (auto& v : DocumentMap) { auto docItem = v.second; - for(auto obj : docItem->PopulateObjects) + for (auto obj : docItem->PopulateObjects) docItem->populateObject(obj); docItem->PopulateObjects.clear(); auto doc = v.first->getDocument(); - if(!docItem->connectChgObject.connected()) { + if (!docItem->connectChgObject.connected()) { docItem->connectChgObject = docItem->document()->signalChangedObject.connect( - boost::bind(&TreeWidget::slotChangeObject, this, bp::_1, bp::_2)); + boost::bind(&TreeWidget::slotChangeObject, this, bp::_1, bp::_2)); docItem->connectTouchedObject = doc->signalTouchedObject.connect( - boost::bind(&TreeWidget::slotTouchedObject, this, bp::_1)); + boost::bind(&TreeWidget::slotTouchedObject, this, bp::_1)); } - if(doc->testStatus(App::Document::PartialDoc)) + if (doc->testStatus(App::Document::PartialDoc)) docItem->setIcon(0, *documentPartialPixmap); - else if(docItem->_ExpandInfo) { - for(auto &entry : *docItem->_ExpandInfo) { - const char *name = entry.first.c_str(); + else if (docItem->_ExpandInfo) { + for (auto& entry : *docItem->_ExpandInfo) { + const char* name = entry.first.c_str(); bool legacy = name[0] == '*'; - if(legacy) + if (legacy) ++name; auto obj = doc->getObject(name); - if(!obj) + if (!obj) continue; auto iter = docItem->ObjectMap.find(obj); - if(iter==docItem->ObjectMap.end()) + if (iter == docItem->ObjectMap.end()) continue; - if(iter->second->rootItem) - docItem->restoreItemExpansion(entry.second,iter->second->rootItem); - else if(legacy && iter->second->items.size()) { + if (iter->second->rootItem) + docItem->restoreItemExpansion(entry.second, iter->second->rootItem); + else if (legacy && iter->second->items.size()) { auto item = *iter->second->items.begin(); item->setExpanded(true); } @@ -2616,10 +2620,10 @@ void TreeWidget::onUpdateStatus(void) docItem->_ExpandInfo.reset(); } - if(Selection().hasSelection() && !selectTimer->isActive() && !this->isConnectionBlocked()) { + if (Selection().hasSelection() && !selectTimer->isActive() && !this->isConnectionBlocked()) { this->blockConnection(true); currentDocItem = 0; - for(auto &v : DocumentMap) { + for (auto& v : DocumentMap) { v.second->setSelected(false); v.second->selectItems(); } @@ -2628,35 +2632,34 @@ void TreeWidget::onUpdateStatus(void) auto activeDocItem = getDocumentItem(Application::Instance->activeDocument()); - QTreeWidgetItem *errItem = 0; - for(auto obj : errors) { + QTreeWidgetItem* errItem = 0; + for (auto obj : errors) { DocumentObjectDataPtr data; - if(activeDocItem) { + if (activeDocItem) { auto it = activeDocItem->ObjectMap.find(obj); - if(it!=activeDocItem->ObjectMap.end()) + if (it != activeDocItem->ObjectMap.end()) data = it->second; } - if(!data) { + if (!data) { auto docItem = getDocumentItem( - Application::Instance->getDocument(obj->getDocument())); - if(docItem) { + Application::Instance->getDocument(obj->getDocument())); + if (docItem) { auto it = docItem->ObjectMap.find(obj); - if(it!=docItem->ObjectMap.end()) + if (it != docItem->ObjectMap.end()) data = it->second; } } - if(data) { + if (data) { auto item = data->rootItem; - if(!item && data->items.size()) { + if (!item && data->items.size()) { item = *data->items.begin(); - data->docItem->showItem(item,false,true); + data->docItem->showItem(item, false, true); } - if(!errItem) + if (!errItem) errItem = item; } } - - if(errItem) + if (errItem) scrollToItem(errItem); updateGeometries(); @@ -2665,64 +2668,44 @@ void TreeWidget::onUpdateStatus(void) FC_LOG("done update status"); } -void TreeWidget::onItemEntered(QTreeWidgetItem * item) +void TreeWidget::onItemEntered(QTreeWidgetItem* item) { // object item selected if (item && item->type() == TreeWidget::ObjectType) { DocumentObjectItem* objItem = static_cast(item); objItem->displayStatusInfo(); - if(TreeParams::Instance()->PreSelection()) { + if (TreeParams::Instance()->PreSelection()) { int timeout = TreeParams::Instance()->PreSelectionDelay(); - if(timeout < 0) + if (timeout < 0) timeout = 1; - if(preselectTime.elapsed() < timeout) + if (preselectTime.elapsed() < timeout) onPreSelectTimer(); - else{ + else { timeout = TreeParams::Instance()->PreSelectionTimeout(); - if(timeout < 0) + if (timeout < 0) timeout = 1; preselectTimer->start(timeout); Selection().rmvPreselect(); } } - } else if(TreeParams::Instance()->PreSelection()) + } + else if (TreeParams::Instance()->PreSelection()) Selection().rmvPreselect(); } -void TreeWidget::onTabifiedDockWidgetActivated(QDockWidget *dockWidget) { - - QWidget *parent = this->parentWidget(); - while (parent) { - if (parent == dockWidget) { - this->setFocus(); - return; - } - - parent = parent->parentWidget(); - } -} - -void TreeWidget::focusInEvent(QFocusEvent *event) { - - _LastSelectedTreeWidget = this; - Application::Instance->updateActions(true); - - QTreeWidget::focusInEvent(event); -} - -void TreeWidget::leaveEvent(QEvent *) { - if(!updateBlocked && TreeParams::Instance()->PreSelection()) { +void TreeWidget::leaveEvent(QEvent*) { + if (!updateBlocked && TreeParams::Instance()->PreSelection()) { preselectTimer->stop(); Selection().rmvPreselect(); } } void TreeWidget::onPreSelectTimer() { - if(!TreeParams::Instance()->PreSelection()) + if (!TreeParams::Instance()->PreSelection()) return; auto item = itemAt(viewport()->mapFromGlobal(QCursor::pos())); - if(!item || item->type()!=TreeWidget::ObjectType) + if (!item || item->type() != TreeWidget::ObjectType) return; preselectTime.restart(); @@ -2730,17 +2713,17 @@ void TreeWidget::onPreSelectTimer() { auto vp = objItem->object(); auto obj = vp->getObject(); std::ostringstream ss; - App::DocumentObject *parent = 0; - objItem->getSubName(ss,parent); - if(!parent) + App::DocumentObject* parent = 0; + objItem->getSubName(ss, parent); + if (!parent) parent = obj; - else if(!obj->redirectSubName(ss,parent,0)) + else if (!obj->redirectSubName(ss, parent, 0)) ss << obj->getNameInDocument() << '.'; - Selection().setPreselect(parent->getDocument()->getName(),parent->getNameInDocument(), - ss.str().c_str(),0,0,0,2); + Selection().setPreselect(parent->getDocument()->getName(), parent->getNameInDocument(), + ss.str().c_str(), 0, 0, 0, 2); } -void TreeWidget::onItemCollapsed(QTreeWidgetItem * item) +void TreeWidget::onItemCollapsed(QTreeWidgetItem* item) { // object item collapsed if (item && item->type() == TreeWidget::ObjectType) { @@ -2748,40 +2731,41 @@ void TreeWidget::onItemCollapsed(QTreeWidgetItem * item) } } -void TreeWidget::onItemExpanded(QTreeWidgetItem * item) +void TreeWidget::onItemExpanded(QTreeWidgetItem* item) { // object item expanded if (item && item->type() == TreeWidget::ObjectType) { DocumentObjectItem* objItem = static_cast(item); objItem->setExpandedStatus(true); - objItem->getOwnerDocument()->populateItem(objItem,false,false); + objItem->getOwnerDocument()->populateItem(objItem, false, false); } } void TreeWidget::scrollItemToTop() { auto doc = Application::Instance->activeDocument(); - for(auto tree : Instances) { - if(!tree->isConnectionAttached() || tree->isConnectionBlocked()) + for (auto tree : Instances) { + if (!tree->isConnectionAttached() || tree->isConnectionBlocked()) continue; tree->_updateStatus(false); - if(doc && Gui::Selection().hasSelection(doc->getDocument()->getName(),false)) { + if (doc && Gui::Selection().hasSelection(doc->getDocument()->getName(), false)) { auto it = tree->DocumentMap.find(doc); if (it != tree->DocumentMap.end()) { bool lock = tree->blockConnection(true); it->second->selectItems(DocumentItem::SR_FORCE_EXPAND); tree->blockConnection(lock); } - } else { + } + else { tree->blockConnection(true); - for (int i=0; irootItem->childCount(); i++) { + for (int i = 0; i < tree->rootItem->childCount(); i++) { auto docItem = dynamic_cast(tree->rootItem->child(i)); - if(!docItem) + if (!docItem) continue; auto doc = docItem->document()->getDocument(); - if(Gui::Selection().hasSelection(doc->getName())) { + if (Gui::Selection().hasSelection(doc->getName())) { tree->currentDocItem = docItem; docItem->selectItems(DocumentItem::SR_FORCE_EXPAND); tree->currentDocItem = 0; @@ -2797,10 +2781,10 @@ void TreeWidget::scrollItemToTop() void TreeWidget::expandSelectedItems(TreeItemMode mode) { - if(!isConnectionAttached()) + if (!isConnectionAttached()) return; - for(auto item : selectedItems()) { + for (auto item : selectedItems()) { switch (mode) { case TreeItemMode::ExpandPath: { QTreeWidgetItem* parentItem = item->parent(); @@ -2850,7 +2834,7 @@ void TreeWidget::setupText() this->selectDependentsAction->setText(tr("Add dependent objects to selection")); this->selectDependentsAction->setStatusTip(tr("Adds all dependent objects to the selection")); - + this->closeDocAction->setText(tr("Close document")); this->closeDocAction->setStatusTip(tr("Close the document")); @@ -2862,7 +2846,7 @@ void TreeWidget::setupText() this->allowPartialRecomputeAction->setText(tr("Allow partial recomputes")); this->allowPartialRecomputeAction->setStatusTip( - tr("Enable or disable recomputating editing object when 'skip recomputation' is enabled")); + tr("Enable or disable recomputating editing object when 'skip recomputation' is enabled")); this->markRecomputeAction->setText(tr("Mark to recompute")); this->markRecomputeAction->setStatusTip(tr("Mark this object to be recomputed")); @@ -2873,12 +2857,12 @@ void TreeWidget::setupText() this->recomputeObjectAction->setIcon(BitmapFactory().iconFromTheme("view-refresh")); } -void TreeWidget::syncView(ViewProviderDocumentObject *vp) +void TreeWidget::syncView(ViewProviderDocumentObject* vp) { - if(currentDocItem && TreeParams::Instance()->SyncView()) { + if (currentDocItem && TreeParams::Instance()->SyncView()) { bool focus = hasFocus(); currentDocItem->document()->setActiveView(vp); - if(focus) + if (focus) setFocus(); } } @@ -2886,12 +2870,12 @@ void TreeWidget::syncView(ViewProviderDocumentObject *vp) void TreeWidget::onShowHidden() { if (!this->contextItem) return; - DocumentItem *docItem = nullptr; - if(this->contextItem->type() == DocumentType) + DocumentItem* docItem = nullptr; + if (this->contextItem->type() == DocumentType) docItem = static_cast(contextItem); - else if(this->contextItem->type() == ObjectType) + else if (this->contextItem->type() == ObjectType) docItem = static_cast(contextItem)->getOwnerDocument(); - if(docItem) + if (docItem) docItem->setShowHidden(showHiddenAction->isChecked()); } @@ -2903,7 +2887,7 @@ void TreeWidget::onHideInTree() } } -void TreeWidget::changeEvent(QEvent *e) +void TreeWidget::changeEvent(QEvent* e) { if (e->type() == QEvent::LanguageChange) setupText(); @@ -2911,11 +2895,11 @@ void TreeWidget::changeEvent(QEvent *e) QTreeWidget::changeEvent(e); } -void TreeWidget::onItemSelectionChanged () +void TreeWidget::onItemSelectionChanged() { if (!this->isConnectionAttached() - || this->isConnectionBlocked() - || updateBlocked) + || this->isConnectionBlocked() + || updateBlocked) return; _LastSelectedTreeWidget = this; @@ -2923,7 +2907,7 @@ void TreeWidget::onItemSelectionChanged () // block tmp. the connection to avoid to notify us ourself bool lock = this->blockConnection(true); - if(selectTimer->isActive()) + if (selectTimer->isActive()) onSelectTimer(); else _updateStatus(false); @@ -2931,59 +2915,61 @@ void TreeWidget::onItemSelectionChanged () auto selItems = selectedItems(); // do not allow document item multi-selection - if(selItems.size()) { + if (selItems.size()) { auto firstType = selItems.back()->type(); - for(auto it=selItems.begin();it!=selItems.end();) { + for (auto it = selItems.begin(); it != selItems.end();) { auto item = *it; - if((firstType==ObjectType && item->type()!=ObjectType) - || (firstType==DocumentType && item!=selItems.back())) + if ((firstType == ObjectType && item->type() != ObjectType) + || (firstType == DocumentType && item != selItems.back())) { item->setSelected(false); it = selItems.erase(it); - } else + } + else ++it; } } - if(selItems.size()<=1) { - if(TreeParams::Instance()->RecordSelection()) + if (selItems.size() <= 1) { + if (TreeParams::Instance()->RecordSelection()) Gui::Selection().selStackPush(); // This special handling to deal with possible discrepancy of // Gui.Selection and Tree view selection because of newly added // DocumentObject::redirectSubName() Selection().clearCompleteSelection(); - DocumentObjectItem *item=0; - if(selItems.size()) { - if(selItems.front()->type() == ObjectType) + DocumentObjectItem* item = 0; + if (selItems.size()) { + if (selItems.front()->type() == ObjectType) item = static_cast(selItems.front()); - else if(selItems.front()->type() == DocumentType) { + else if (selItems.front()->type() == DocumentType) { auto ditem = static_cast(selItems.front()); - if(TreeParams::Instance()->SyncView()) { + if (TreeParams::Instance()->SyncView()) { bool focus = hasFocus(); ditem->document()->setActiveView(); - if(focus) + if (focus) setFocus(); } // For triggering property editor refresh Gui::Selection().signalSelectionChanged(SelectionChanges()); } } - for(auto &v : DocumentMap) { + for (auto& v : DocumentMap) { currentDocItem = v.second; v.second->clearSelection(item); currentDocItem = 0; } - if(TreeParams::Instance()->RecordSelection()) + if (TreeParams::Instance()->RecordSelection()) Gui::Selection().selStackPush(); - }else{ - for (auto pos = DocumentMap.begin();pos!=DocumentMap.end();++pos) { + } + else { + for (auto pos = DocumentMap.begin(); pos != DocumentMap.end(); ++pos) { currentDocItem = pos->second; pos->second->updateSelection(pos->second); currentDocItem = 0; } - if(TreeParams::Instance()->RecordSelection()) - Gui::Selection().selStackPush(true,true); + if (TreeParams::Instance()->RecordSelection()) + Gui::Selection().selStackPush(true, true); } this->blockConnection(lock); @@ -3006,7 +2992,20 @@ void TreeWidget::synchronizeSelectionCheckBoxes() { resizeColumnToContents(0); } -void TreeWidget::onItemChanged(QTreeWidgetItem *item, int column) { +QList TreeWidget::childrenOfItem(const QTreeWidgetItem& item) const { + QList children = QList(); + + // check item is in this tree + if (!this->indexFromItem(&item).isValid()) + return children; + + for (int i = 0; i < item.childCount(); i++) { + children.append(item.child(i)); + } + return children; +} + +void TreeWidget::onItemChanged(QTreeWidgetItem* item, int column) { if (column == 0 && isSelectionCheckBoxesEnabled()) { bool selected = item->isSelected(); bool checked = item->checkState(0) == Qt::Checked; @@ -3022,15 +3021,16 @@ void TreeWidget::onSelectTimer() { bool syncSelect = TreeParams::Instance()->SyncSelection(); bool locked = this->blockConnection(true); - if(Selection().hasSelection()) { - for(auto &v : DocumentMap) { + if (Selection().hasSelection()) { + for (auto& v : DocumentMap) { v.second->setSelected(false); currentDocItem = v.second; - v.second->selectItems(syncSelect?DocumentItem::SR_EXPAND:DocumentItem::SR_SELECT); + v.second->selectItems(syncSelect ? DocumentItem::SR_EXPAND : DocumentItem::SR_SELECT); currentDocItem = 0; } - }else{ - for(auto &v : DocumentMap) + } + else { + for (auto& v : DocumentMap) v.second->clearSelection(); } this->blockConnection(locked); @@ -3047,7 +3047,7 @@ void TreeWidget::onSelectionChanged(const SelectionChanges& msg) case SelectionChanges::SetSelection: case SelectionChanges::ClrSelection: { int timeout = TreeParams::Instance()->SelectionTimeout(); - if(timeout<=0) + if (timeout <= 0) timeout = 1; selectTimer->start(timeout); break; @@ -3057,238 +3057,35 @@ void TreeWidget::onSelectionChanged(const SelectionChanges& msg) } } -bool TreeWidget::getSelectedSiblingObjectItems(std::vector &items) const -{ - QList selected = this->selectedItems(); - if (selected.isEmpty()) { - return false; - } - - if (selected.first()->type() != TreeWidget::ObjectType) { - return false; - } - - QTreeWidgetItem *parentItem = selected.first()->parent(); - for (int i = 1; i < selected.size(); ++i) { - if (selected[i]->type() != TreeWidget::ObjectType || selected[i]->parent() != parentItem) { - return false; - } - } - - items.resize(items.size() + selected.size(), 0); - std::transform(selected.begin(), selected.end(), items.end() - selected.size(), - [](QTreeWidgetItem *i) { return static_cast(i); }); - std::sort(items.begin(), items.end(), - [parentItem](DocumentObjectItem *a, DocumentObjectItem *b) - { return parentItem->indexOfChild(a) < parentItem->indexOfChild(b); }); - - return true; -} - -bool TreeWidget::allowMoveUpInGroup(const std::vector &items, DocumentObjectItem **preceding) const -{ - if (!items.size()) { - return false; - } - - DocumentObjectItem *previous = items.front()->getPreviousSibling(); - if (!previous) { - return false; - } - - if (preceding) { - *preceding = previous; - } - - QTreeWidgetItem *parent = items.front()->parent(); - if (parent->type() == ObjectType) { - ViewProviderDocumentObject *docObj = static_cast(parent)->object(); - if (!docObj) { - return false; - } - - for (DocumentObjectItem *item : items) { - if (!docObj->allowTreeOrderSwap(item->object()->getObject(), previous->object()->getObject())) { - return false; - } - } - } - - return true; -} - -bool TreeWidget::allowMoveDownInGroup(const std::vector &items, DocumentObjectItem **succeeding) const -{ - if (!items.size()) { - return false; - } - - DocumentObjectItem *next = items.back()->getNextSibling(); - if (!next) { - return false; - } - - if (succeeding) { - *succeeding = next; - } - - QTreeWidgetItem *parent = items.back()->parent(); - if (parent->type() == ObjectType) { - ViewProviderDocumentObject *docObj = static_cast(parent)->object(); - if (!docObj) { - return false; - } - - for (DocumentObjectItem *item : items) { - if (!docObj->allowTreeOrderSwap(item->object()->getObject(), next->object()->getObject())) { - return false; - } - } - } - - return true; -} - -bool TreeWidget::moveSiblings(const std::vector &items, DocumentObjectItem *pivot, int direction) -{ - // Some sanity checks + make sure pivot is not within the items to be moved - if (!items.size() || !pivot || !direction || std::find(items.begin(), items.end(), pivot) != items.end()) { - return false; - } - - QTreeWidgetItem *parent = pivot->parent(); - if (!parent) { - return false; - } - - Gui::Document *doc = pivot->object()->getDocument(); - if (!doc) { - return false; - } - - int childCount = parent->childCount(); - std::vector groupItems(childCount, 0); - std::vector ranks(childCount); - - for (int i = 0; i < childCount; ++i) { - QTreeWidgetItem *treeItem = parent->child(i); - if (treeItem->type() != ObjectType) { - return false; - } - - groupItems[i] = static_cast(treeItem); - ranks[i] = static_cast(treeItem)->object()->TreeRank.getValue(); - } - - int insertIndex = parent->indexOfChild(pivot) + (direction > 0); - for (DocumentObjectItem *item : items) { - std::vector::iterator it = std::find(groupItems.begin(), groupItems.end(), item); - if (it == groupItems.end()) { - continue; - } - - int index = it - groupItems.begin(); - groupItems.erase(it); - - if (index < insertIndex) { - --insertIndex; - } - groupItems.insert(groupItems.begin() + insertIndex, item); - ++insertIndex; - } - - doc->openCommand(QT_TRANSLATE_NOOP("Command", direction > 0 ? "Move down in group" : "Move down in group")); - - int changes = 0; - for (int i = 0; i < childCount; ++i) { - App::PropertyInteger &rank = groupItems[i]->object()->TreeRank; - if (rank.getValue() != ranks[i]) { - rank.setValue(ranks[i]); - ++changes; - } - } - - return changes > 0; -} - -bool TreeWidget::sortObjectItems(QTreeWidgetItem *node, DocumentObjectItemComparator comparator) -{ - if (!node || !comparator || node->childCount() <= 0) { - return false; - } - - bool lock = blockConnection(true); - - std::vector sortedItems; - sortedItems.reserve(node->childCount()); - - for (int i = 0; i < node->childCount(); ++i) { - QTreeWidgetItem *treeItem = node->child(i); - if (treeItem->type() == TreeWidget::ObjectType) { - sortedItems.push_back(static_cast(treeItem)); - } - } - - std::stable_sort(sortedItems.begin(), sortedItems.end(), comparator); - - int sortedIndex = 0; - int swaps = 0; - for (int i = 0; i < node->childCount(); ++i) { - QTreeWidgetItem *treeItem = node->child(i); - if (treeItem->type() != TreeWidget::ObjectType) { - continue; - } - - DocumentObjectItem *sortedItem = sortedItems[sortedIndex++]; - if (sortedItem == treeItem) { - continue; - } - - std::vector expansion; - sortedItem->getExpandedSnapshot(expansion); - - node->removeChild(sortedItem); - node->insertChild(i, sortedItem); - ++swaps; - - std::vector::const_iterator expFrom = expansion.cbegin(); - sortedItem->applyExpandedSnapshot(expansion, expFrom); - } - - blockConnection(lock); - - return swaps > 0; -} - // ---------------------------------------------------------------------------- /* TRANSLATOR Gui::TreePanel */ -TreePanel::TreePanel(const char *name, QWidget* parent) - : QWidget(parent) +TreePanel::TreePanel(const char* name, QWidget* parent) + : QWidget(parent) { this->treeWidget = new TreeWidget(name, this); int indent = TreeParams::Instance()->Indentation(); - if(indent) + if (indent) this->treeWidget->setIndentation(indent); QVBoxLayout* pLayout = new QVBoxLayout(this); pLayout->setSpacing(0); - pLayout->setMargin (0); + pLayout->setMargin(0); pLayout->addWidget(this->treeWidget); connect(this->treeWidget, SIGNAL(emitSearchObjects()), - this, SLOT(showEditor())); + this, SLOT(showEditor())); - this->searchBox = new Gui::ExpressionLineEdit(this,true); + this->searchBox = new Gui::ExpressionLineEdit(this, true); static_cast(this->searchBox)->setExactMatch(Gui::ExpressionParameter::instance()->isExactMatch()); pLayout->addWidget(this->searchBox); this->searchBox->hide(); this->searchBox->installEventFilter(this); this->searchBox->setPlaceholderText(tr("Search")); connect(this->searchBox, SIGNAL(returnPressed()), - this, SLOT(accept())); + this, SLOT(accept())); connect(this->searchBox, SIGNAL(textChanged(QString)), - this, SLOT(itemSearch(QString))); + this, SLOT(itemSearch(QString))); } TreePanel::~TreePanel() @@ -3300,10 +3097,10 @@ void TreePanel::accept() QString text = this->searchBox->text(); hideEditor(); this->treeWidget->setFocus(); - this->treeWidget->itemSearch(text,true); + this->treeWidget->itemSearch(text, true); } -bool TreePanel::eventFilter(QObject *obj, QEvent *ev) +bool TreePanel::eventFilter(QObject* obj, QEvent* ev) { if (obj != this->searchBox) return false; @@ -3342,103 +3139,103 @@ void TreePanel::hideEditor() this->searchBox->hide(); this->treeWidget->resetItemSearch(); auto sels = this->treeWidget->selectedItems(); - if(sels.size()) + if (sels.size()) this->treeWidget->scrollToItem(sels.front()); } -void TreePanel::itemSearch(const QString &text) +void TreePanel::itemSearch(const QString& text) { - this->treeWidget->itemSearch(text,false); + this->treeWidget->itemSearch(text, false); } // ---------------------------------------------------------------------------- /* TRANSLATOR Gui::TreeDockWidget */ -TreeDockWidget::TreeDockWidget(Gui::Document* pcDocument,QWidget *parent) - : DockWindow(pcDocument,parent) +TreeDockWidget::TreeDockWidget(Gui::Document* pcDocument, QWidget* parent) + : DockWindow(pcDocument, parent) { setWindowTitle(tr("Tree view")); auto panel = new TreePanel("TreeView", this); QGridLayout* pLayout = new QGridLayout(this); pLayout->setSpacing(0); - pLayout->setMargin (0); - pLayout->addWidget(panel, 0, 0 ); + pLayout->setMargin(0); + pLayout->addWidget(panel, 0, 0); } TreeDockWidget::~TreeDockWidget() { } -void TreeWidget::selectLinkedObject(App::DocumentObject *linked) { - if(!isConnectionAttached() || isConnectionBlocked()) +void TreeWidget::selectLinkedObject(App::DocumentObject* linked) { + if (!isConnectionAttached() || isConnectionBlocked()) return; auto linkedVp = Base::freecad_dynamic_cast( - Application::Instance->getViewProvider(linked)); - if(!linkedVp) { + Application::Instance->getViewProvider(linked)); + if (!linkedVp) { TREE_ERR("invalid linked view provider"); return; } auto linkedDoc = getDocumentItem(linkedVp->getDocument()); - if(!linkedDoc) { + if (!linkedDoc) { TREE_ERR("cannot find document of linked object"); return; } - if(selectTimer->isActive()) + if (selectTimer->isActive()) onSelectTimer(); else _updateStatus(false); auto it = linkedDoc->ObjectMap.find(linked); - if(it == linkedDoc->ObjectMap.end()) { + if (it == linkedDoc->ObjectMap.end()) { TREE_ERR("cannot find tree item of linked object"); return; } auto linkedItem = it->second->rootItem; - if(!linkedItem) + if (!linkedItem) linkedItem = *it->second->items.begin(); - if(linkedDoc->showItem(linkedItem,true)) + if (linkedDoc->showItem(linkedItem, true)) scrollToItem(linkedItem); - if(linkedDoc->document()->getDocument() != App::GetApplication().getActiveDocument()) { + if (linkedDoc->document()->getDocument() != App::GetApplication().getActiveDocument()) { bool focus = hasFocus(); linkedDoc->document()->setActiveView(linkedItem->object()); - if(focus) + if (focus) setFocus(); } } // ---------------------------------------------------------------------------- -DocumentItem::DocumentItem(const Gui::Document* doc, QTreeWidgetItem * parent) +DocumentItem::DocumentItem(const Gui::Document* doc, QTreeWidgetItem* parent) : QTreeWidgetItem(parent, TreeWidget::DocumentType), pDocument(const_cast(doc)) { // Setup connections connectNewObject = doc->signalNewObject.connect(boost::bind(&DocumentItem::slotNewObject, this, bp::_1)); connectDelObject = doc->signalDeletedObject.connect( - boost::bind(&TreeWidget::slotDeleteObject, getTree(), bp::_1)); - if(!App::GetApplication().isRestoring()) { + boost::bind(&TreeWidget::slotDeleteObject, getTree(), bp::_1)); + if (!App::GetApplication().isRestoring()) { connectChgObject = doc->signalChangedObject.connect( - boost::bind(&TreeWidget::slotChangeObject, getTree(), bp::_1, bp::_2)); + boost::bind(&TreeWidget::slotChangeObject, getTree(), bp::_1, bp::_2)); connectTouchedObject = doc->getDocument()->signalTouchedObject.connect( - boost::bind(&TreeWidget::slotTouchedObject, getTree(), bp::_1)); + boost::bind(&TreeWidget::slotTouchedObject, getTree(), bp::_1)); } connectEdtObject = doc->signalInEdit.connect(boost::bind(&DocumentItem::slotInEdit, this, bp::_1)); connectResObject = doc->signalResetEdit.connect(boost::bind(&DocumentItem::slotResetEdit, this, bp::_1)); connectHltObject = doc->signalHighlightObject.connect( - boost::bind(&DocumentItem::slotHighlightObject, this, bp::_1, bp::_2, bp::_3, bp::_4, bp::_5)); + boost::bind(&DocumentItem::slotHighlightObject, this, bp::_1, bp::_2, bp::_3, bp::_4, bp::_5)); connectExpObject = doc->signalExpandObject.connect( - boost::bind(&DocumentItem::slotExpandObject, this, bp::_1, bp::_2, bp::_3, bp::_4)); + boost::bind(&DocumentItem::slotExpandObject, this, bp::_1, bp::_2, bp::_3, bp::_4)); connectScrObject = doc->signalScrollToObject.connect(boost::bind(&DocumentItem::slotScrollToObject, this, bp::_1)); auto adoc = doc->getDocument(); connectRecomputed = adoc->signalRecomputed.connect(boost::bind(&DocumentItem::slotRecomputed, this, bp::_1, bp::_2)); connectRecomputedObj = adoc->signalRecomputedObject.connect( - boost::bind(&DocumentItem::slotRecomputedObject, this, bp::_1)); + boost::bind(&DocumentItem::slotRecomputedObject, this, bp::_1)); - setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable/*|Qt::ItemIsEditable*/); + setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable/*|Qt::ItemIsEditable*/); treeName = getTree()->getTreeName(); } @@ -3458,11 +3255,11 @@ DocumentItem::~DocumentItem() connectRecomputedObj.disconnect(); } -TreeWidget *DocumentItem::getTree() const{ +TreeWidget* DocumentItem::getTree() const { return static_cast(treeWidget()); } -const char *DocumentItem::getTreeName() const { +const char* DocumentItem::getTreeName() const { return treeName; } @@ -3485,27 +3282,27 @@ void DocumentItem::slotInEdit(const Gui::ViewProviderDocumentObject& v) (void)v; ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView"); - unsigned long col = hGrp->GetUnsigned("TreeEditColor",4294902015); - QColor color((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff); + unsigned long col = hGrp->GetUnsigned("TreeEditColor", 4294902015); + QColor color((col >> 24) & 0xff, (col >> 16) & 0xff, (col >> 8) & 0xff); - if(!getTree()->editingItem) { + if (!getTree()->editingItem) { auto doc = Application::Instance->editDocument(); - if(!doc) + if (!doc) return; - ViewProviderDocumentObject *parentVp=0; + ViewProviderDocumentObject* parentVp = 0; std::string subname; - auto vp = doc->getInEdit(&parentVp,&subname); - if(!parentVp) + auto vp = doc->getInEdit(&parentVp, &subname); + if (!parentVp) parentVp = dynamic_cast(vp); - if(parentVp) - getTree()->editingItem = findItemByObject(true,parentVp->getObject(),subname.c_str()); + if (parentVp) + getTree()->editingItem = findItemByObject(true, parentVp->getObject(), subname.c_str()); } - if(getTree()->editingItem) - getTree()->editingItem->setBackground(0,color); - else{ - FOREACH_ITEM(item,v) - item->setBackground(0,color); + if (getTree()->editingItem) + getTree()->editingItem->setBackground(0, color); + else { + FOREACH_ITEM(item, v) + item->setBackground(0, color); END_FOREACH_ITEM } } @@ -3514,19 +3311,20 @@ void DocumentItem::slotResetEdit(const Gui::ViewProviderDocumentObject& v) { auto tree = getTree(); FOREACH_ITEM_ALL(item) - if(tree->editingItem) { - if(item == tree->editingItem) { - item->setData(0, Qt::BackgroundRole,QVariant()); + if (tree->editingItem) { + if (item == tree->editingItem) { + item->setData(0, Qt::BackgroundRole, QVariant()); break; } - }else if(item->object() == &v) - item->setData(0, Qt::BackgroundRole,QVariant()); + } + else if (item->object() == &v) + item->setData(0, Qt::BackgroundRole, QVariant()); END_FOREACH_ITEM - tree->editingItem = 0; + tree->editingItem = 0; } void DocumentItem::slotNewObject(const Gui::ViewProviderDocumentObject& obj) { - if(!obj.getObject() || !obj.getObject()->getNameInDocument()) { + if (!obj.getObject() || !obj.getObject()->getNameInDocument()) { FC_ERR("view provider not attached"); return; } @@ -3535,48 +3333,49 @@ void DocumentItem::slotNewObject(const Gui::ViewProviderDocumentObject& obj) { } bool DocumentItem::createNewItem(const Gui::ViewProviderDocumentObject& obj, - QTreeWidgetItem *parent, int index, DocumentObjectDataPtr data) + QTreeWidgetItem* parent, int index, DocumentObjectDataPtr data) { - const char *name; + const char* name; if (!obj.getObject() || - !(name=obj.getObject()->getNameInDocument()) || + !(name = obj.getObject()->getNameInDocument()) || obj.getObject()->testStatus(App::PartialObject)) return false; - if(!data) { - auto &pdata = ObjectMap[obj.getObject()]; - if(!pdata) { + if (!data) { + auto& pdata = ObjectMap[obj.getObject()]; + if (!pdata) { pdata = std::make_shared( - this, const_cast(&obj)); - auto &entry = getTree()->ObjectTable[obj.getObject()]; - if(entry.size()) + this, const_cast(&obj)); + auto& entry = getTree()->ObjectTable[obj.getObject()]; + if (entry.size()) pdata->updateChildren(*entry.begin()); else pdata->updateChildren(true); entry.insert(pdata); - }else if(pdata->rootItem && parent==NULL) { + } + else if (pdata->rootItem && parent == NULL) { Base::Console().Warning("DocumentItem::slotNewObject: Cannot add view provider twice.\n"); return false; } data = pdata; } - DocumentObjectItem* item = new DocumentObjectItem(this,data); - if(!parent || parent==this) { + DocumentObjectItem* item = new DocumentObjectItem(this, data); + if (!parent || parent == this) { parent = this; data->rootItem = item; - if(index<0) - index = findRootIndex(&obj); + if (index < 0) + index = findRootIndex(obj.getObject()); } - if(index<0) + if (index < 0) parent->addChild(item); else - parent->insertChild(index,item); + parent->insertChild(index, item); assert(item->parent() == parent); item->setText(0, QString::fromUtf8(data->label.c_str())); - if(data->label2.size()) + if (data->label2.size()) item->setText(1, QString::fromUtf8(data->label2.c_str())); - if(!obj.showInTree() && !showHidden()) + if (!obj.showInTree() && !showHidden()) item->setHidden(true); item->testStatus(true); @@ -3584,7 +3383,7 @@ bool DocumentItem::createNewItem(const Gui::ViewProviderDocumentObject& obj, return true; } -ViewProviderDocumentObject *DocumentItem::getViewProvider(App::DocumentObject *obj) { +ViewProviderDocumentObject* DocumentItem::getViewProvider(App::DocumentObject* obj) { // Note: It is possible that we receive an invalid pointer from // claimChildren(), e.g. if multiple properties were changed in // a transaction and slotChangedObject() is triggered by one @@ -3601,14 +3400,14 @@ ViewProviderDocumentObject *DocumentItem::getViewProvider(App::DocumentObject *o // getNameInDocument() check be sufficient? - if(!obj || !obj->getNameInDocument()) return 0; + if (!obj || !obj->getNameInDocument()) return 0; - ViewProvider *vp; - if(obj->getDocument() == pDocument->getDocument()) + ViewProvider* vp; + if (obj->getDocument() == pDocument->getDocument()) vp = pDocument->getViewProvider(obj); else vp = Application::Instance->getViewProvider(obj); - if(!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) + if (!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) return 0; return static_cast(vp); } @@ -3618,19 +3417,19 @@ void TreeWidget::slotDeleteDocument(const Gui::Document& Doc) NewObjects.erase(Doc.getDocument()->getName()); auto it = DocumentMap.find(&Doc); if (it != DocumentMap.end()) { - UpdateDisabler disabler(*this,updateBlocked); + UpdateDisabler disabler(*this, updateBlocked); auto docItem = it->second; - for(auto &v : docItem->ObjectMap) { - for(auto item : v.second->items) + for (auto& v : docItem->ObjectMap) { + for (auto item : v.second->items) item->myOwner = 0; auto obj = v.second->viewObject->getObject(); - if(obj->getDocument() == Doc.getDocument()) { + if (obj->getDocument() == Doc.getDocument()) { _slotDeleteObject(*v.second->viewObject, docItem); continue; } auto it = ObjectTable.find(obj); - assert(it!=ObjectTable.end()); - assert(it->second.size()>1); + assert(it != ObjectTable.end()); + assert(it->second.size() > 1); it->second.erase(v.second); } this->rootItem->takeChild(this->rootItem->indexOfChild(docItem)); @@ -3643,14 +3442,14 @@ void TreeWidget::slotDeleteObject(const Gui::ViewProviderDocumentObject& view) { _slotDeleteObject(view, 0); } -void TreeWidget::_slotDeleteObject(const Gui::ViewProviderDocumentObject& view, DocumentItem *deletingDoc) +void TreeWidget::_slotDeleteObject(const Gui::ViewProviderDocumentObject& view, DocumentItem* deletingDoc) { auto obj = view.getObject(); auto itEntry = ObjectTable.find(obj); - if(itEntry == ObjectTable.end()) + if (itEntry == ObjectTable.end()) return; - if(itEntry->second.empty()) { + if (itEntry->second.empty()) { ObjectTable.erase(itEntry); return; } @@ -3659,41 +3458,41 @@ void TreeWidget::_slotDeleteObject(const Gui::ViewProviderDocumentObject& view, bool needUpdate = false; - for(auto data : itEntry->second) { - DocumentItem *docItem = data->docItem; - if(docItem == deletingDoc) + for (auto data : itEntry->second) { + DocumentItem* docItem = data->docItem; + if (docItem == deletingDoc) continue; auto doc = docItem->document()->getDocument(); - auto &items = data->items; + auto& items = data->items; - if(obj->getDocument() == doc) + if (obj->getDocument() == doc) docItem->_ParentMap.erase(obj); bool lock = blockConnection(true); - for(auto cit=items.begin(),citNext=cit;cit!=items.end();cit=citNext) { + for (auto cit = items.begin(), citNext = cit; cit != items.end(); cit = citNext) { ++citNext; (*cit)->myOwner = 0; - delete *cit; + delete* cit; } blockConnection(lock); // Check for any child of the deleted object that is not in the tree, and put it // under document item. - for(auto child : data->children) { + for (auto child : data->children) { auto childVp = docItem->getViewProvider(child); if (!childVp || child->getDocument() != doc) continue; docItem->_ParentMap[child].erase(obj); auto cit = docItem->ObjectMap.find(child); - if (cit==docItem->ObjectMap.end() || cit->second->items.empty()) { + if (cit == docItem->ObjectMap.end() || cit->second->items.empty()) { if (docItem->createNewItem(*childVp)) needUpdate = true; } else { auto childItem = *cit->second->items.begin(); if (childItem->requiredAtRoot(false)) { - if (docItem->createNewItem(*childItem->object(),docItem,-1,childItem->myData)) + if (docItem->createNewItem(*childItem->object(), docItem, -1, childItem->myData)) needUpdate = true; } } @@ -3703,30 +3502,30 @@ void TreeWidget::_slotDeleteObject(const Gui::ViewProviderDocumentObject& view, } ObjectTable.erase(itEntry); - if(needUpdate) + if (needUpdate) _updateStatus(); } -bool DocumentItem::populateObject(App::DocumentObject *obj) { +bool DocumentItem::populateObject(App::DocumentObject* obj) { // make sure at least one of the item corresponding to obj is populated auto it = ObjectMap.find(obj); - if(it == ObjectMap.end()) + if (it == ObjectMap.end()) return false; - auto &items = it->second->items; - if(items.empty()) + auto& items = it->second->items; + if (items.empty()) return false; - for(auto item : items) { - if(item->populated) + for (auto item : items) { + if (item->populated) return true; } TREE_LOG("force populate object " << obj->getFullName()); auto item = *items.begin(); item->populated = true; - populateItem(item,true); + populateItem(item, true); return true; } -void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh, bool delay) +void DocumentItem::populateItem(DocumentObjectItem* item, bool refresh, bool delay) { (void)delay; @@ -3737,29 +3536,29 @@ void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh, bool del // a) the item is expanded, or b) there is at least one free child, i.e. // child originally located at root. - item->setChildIndicatorPolicy(item->myData->children.empty()? - QTreeWidgetItem::DontShowIndicator:QTreeWidgetItem::ShowIndicator); + item->setChildIndicatorPolicy(item->myData->children.empty() ? + QTreeWidgetItem::DontShowIndicator : QTreeWidgetItem::ShowIndicator); if (!item->populated && !item->isExpanded()) { bool doPopulate = false; - bool external = item->object()->getDocument()!=item->getOwnerDocument()->document(); - if(external) + bool external = item->object()->getDocument() != item->getOwnerDocument()->document(); + if (external) return; auto obj = item->object()->getObject(); auto linked = obj->getLinkedObject(true); - if (linked && linked->getDocument()!=obj->getDocument()) + if (linked && linked->getDocument() != obj->getDocument()) return; - for(auto child : item->myData->children) { + for (auto child : item->myData->children) { auto it = ObjectMap.find(child); - if(it == ObjectMap.end() || it->second->items.empty()) { + if (it == ObjectMap.end() || it->second->items.empty()) { auto vp = getViewProvider(child); - if(!vp) continue; + if (!vp) continue; doPopulate = true; break; } - if(item->myData->removeChildrenFromRoot) { - if(it->second->rootItem) { + if (item->myData->removeChildrenFromRoot) { + if (it->second->rootItem) { doPopulate = true; break; } @@ -3774,45 +3573,46 @@ void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh, bool del bool checkHidden = !showHidden(); bool updated = false; - int i=-1; + int i = -1; // iterate through the claimed children, and try to synchronize them with the // children tree item with the same order of appearance. int childCount = item->childCount(); - for(auto child : item->myData->children) { + for (auto child : item->myData->children) { ++i; // the current index of the claimed child bool found = false; - for (int j=i;jchild(j); + for (int j = i; j < childCount; ++j) { + QTreeWidgetItem* ci = item->child(j); if (ci->type() != TreeWidget::ObjectType) continue; - DocumentObjectItem *childItem = static_cast(ci); + DocumentObjectItem* childItem = static_cast(ci); if (childItem->object()->getObject() != child) continue; found = true; - if (j!=i) { // fix index if it is changed + if (j != i) { // fix index if it is changed childItem->setHighlight(false); item->removeChild(ci); - item->insertChild(i,ci); - assert(ci->parent()==item); - if(checkHidden) - updateItemsVisibility(ci,false); + item->insertChild(i, ci); + assert(ci->parent() == item); + if (checkHidden) + updateItemsVisibility(ci, false); } // Check if the item just changed its policy of whether to remove // children item from the root. - if(item->myData->removeChildrenFromRoot) { - if(childItem->myData->rootItem) { + if (item->myData->removeChildrenFromRoot) { + if (childItem->myData->rootItem) { assert(childItem != childItem->myData->rootItem); bool lock = getTree()->blockConnection(true); delete childItem->myData->rootItem; getTree()->blockConnection(lock); } - }else if(childItem->requiredAtRoot()) { - createNewItem(*childItem->object(),this,-1,childItem->myData); + } + else if (childItem->requiredAtRoot()) { + createNewItem(*childItem->object(), this, -1, childItem->myData); updated = true; } break; @@ -3825,24 +3625,25 @@ void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh, bool del // through slotNewObject -> populateItem auto it = ObjectMap.find(child); - if(it==ObjectMap.end() || it->second->items.empty()) { + if (it == ObjectMap.end() || it->second->items.empty()) { auto vp = getViewProvider(child); - if(!vp || !createNewItem(*vp,item,i,it==ObjectMap.end()?DocumentObjectDataPtr():it->second)) + if (!vp || !createNewItem(*vp, item, i, it == ObjectMap.end() ? DocumentObjectDataPtr() : it->second)) --i; else updated = true; continue; } - if(!item->myData->removeChildrenFromRoot || !it->second->rootItem) { - DocumentObjectItem *childItem = *it->second->items.begin(); - if(!createNewItem(*childItem->object(),item,i,it->second)) + if (!item->myData->removeChildrenFromRoot || !it->second->rootItem) { + DocumentObjectItem* childItem = *it->second->items.begin(); + if (!createNewItem(*childItem->object(), item, i, it->second)) --i; else updated = true; - }else { - DocumentObjectItem *childItem = it->second->rootItem; - if(item==childItem || item->isChildOfItem(childItem)) { + } + else { + DocumentObjectItem* childItem = it->second->rootItem; + if (item == childItem || item->isChildOfItem(childItem)) { TREE_ERR("Cyclic dependency in " << item->object()->getObject()->getFullName() << '.' << childItem->object()->getObject()->getFullName()); @@ -3852,27 +3653,27 @@ void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh, bool del it->second->rootItem = 0; childItem->setHighlight(false); this->removeChild(childItem); - item->insertChild(i,childItem); - assert(childItem->parent()==item); - if(checkHidden) - updateItemsVisibility(childItem,false); + item->insertChild(i, childItem); + assert(childItem->parent() == item); + if (checkHidden) + updateItemsVisibility(childItem, false); } } - for (++i;item->childCount()>i;) { - QTreeWidgetItem *ci = item->child(i); + for (++i; item->childCount() > i;) { + QTreeWidgetItem* ci = item->child(i); if (ci->type() == TreeWidget::ObjectType) { DocumentObjectItem* childItem = static_cast(ci); - if(childItem->requiredAtRoot()) { + if (childItem->requiredAtRoot()) { item->removeChild(childItem); - auto index = findRootIndex(childItem->object()); - if(index>=0) - this->insertChild(index,childItem); + auto index = findRootIndex(childItem->object()->getObject()); + if (index >= 0) + this->insertChild(index, childItem); else this->addChild(childItem); - assert(childItem->parent()==this); - if(checkHidden) - updateItemsVisibility(childItem,false); + assert(childItem->parent() == this); + if (checkHidden) + updateItemsVisibility(childItem, false); childItem->myData->rootItem = childItem; continue; } @@ -3882,229 +3683,229 @@ void DocumentItem::populateItem(DocumentObjectItem *item, bool refresh, bool del delete ci; getTree()->blockConnection(lock); } - if(updated) + if (updated) getTree()->_updateStatus(); } -int DocumentItem::findRootIndex(const ViewProviderDocumentObject *childObj) const { - if (!TreeParams::Instance()->KeepRootOrder() || !childObj || !childObj->getObject() - || !childObj->getObject()->getNameInDocument()) { +int DocumentItem::findRootIndex(App::DocumentObject* childObj) { + if (!TreeParams::Instance()->KeepRootOrder() || !childObj || !childObj->getNameInDocument()) return -1; - } - // Use view provider's tree rank to find correct place at the root level. + // object id is monotonically increasing, so use this as a hint to insert + // object back so that we can have a stable order in root level. + int count = this->childCount(); - if(!count) + if (!count) return -1; - int first,last; + int first, last; // find the last item - for(last=count-1;last>=0;--last) { + for (last = count - 1; last >= 0; --last) { auto citem = this->child(last); - if(citem->type() == TreeWidget::ObjectType) { - auto obj = static_cast(citem)->object(); - if (obj->TreeRank.getValue() <= childObj->TreeRank.getValue()) { + if (citem->type() == TreeWidget::ObjectType) { + auto obj = static_cast(citem)->object()->getObject(); + if (obj->getID() <= childObj->getID()) return last + 1; - } break; } } // find the first item - for(first=0;firstchild(first); - if(citem->type() == TreeWidget::ObjectType) { - auto obj = static_cast(citem)->object(); - if (obj->TreeRank.getValue() > childObj->TreeRank.getValue()) { + if (citem->type() == TreeWidget::ObjectType) { + auto obj = static_cast(citem)->object()->getObject(); + if (obj->getID() >= childObj->getID()) return first; - } break; } } // now do a binary search to find the lower bound, assuming the root level // object is already in order - count = last-first; + count = last - first; int pos; while (count > 0) { int step = count / 2; pos = first + step; - for(;pos<=last;++pos) { + for (; pos <= last; ++pos) { auto citem = this->child(pos); - if(citem->type() != TreeWidget::ObjectType) + if (citem->type() != TreeWidget::ObjectType) continue; - auto obj = static_cast(citem)->object(); - if (obj->TreeRank.getValue() < childObj->TreeRank.getValue()) { + auto obj = static_cast(citem)->object()->getObject(); + if (obj->getID() < childObj->getID()) { first = ++pos; - count -= step+1; - } else + count -= step + 1; + } + else count = step; break; } - if(pos>last) + if (pos > last) return -1; } - if(first>last) + if (first > last) return -1; return first; } void TreeWidget::slotChangeObject( - const Gui::ViewProviderDocumentObject& view, const App::Property &prop) { + const Gui::ViewProviderDocumentObject& view, const App::Property& prop) { auto obj = view.getObject(); - if(!obj || !obj->getNameInDocument()) + if (!obj || !obj->getNameInDocument()) return; auto itEntry = ObjectTable.find(obj); - if(itEntry == ObjectTable.end() || itEntry->second.empty()) + if (itEntry == ObjectTable.end() || itEntry->second.empty()) return; _updateStatus(); // Let's not waste time on the newly added Visibility property in // DocumentObject. - if(&prop == &obj->Visibility) + if (&prop == &obj->Visibility) return; - if(&prop == &obj->Label) { - const char *label = obj->Label.getValue(); + if (&prop == &obj->Label) { + const char* label = obj->Label.getValue(); auto firstData = *itEntry->second.begin(); - if(firstData->label != label) { - for(auto data : itEntry->second) { + if (firstData->label != label) { + for (auto data : itEntry->second) { data->label = label; auto displayName = QString::fromUtf8(label); - for(auto item : data->items) + for (auto item : data->items) item->setText(0, displayName); } } return; } - if(&prop == &obj->Label2) { - const char *label = obj->Label2.getValue(); + if (&prop == &obj->Label2) { + const char* label = obj->Label2.getValue(); auto firstData = *itEntry->second.begin(); - if(firstData->label2 != label) { - for(auto data : itEntry->second) { + if (firstData->label2 != label) { + for (auto data : itEntry->second) { data->label2 = label; auto displayName = QString::fromUtf8(label); - for(auto item : data->items) + for (auto item : data->items) item->setText(1, displayName); } } return; } - auto &s = ChangedObjects[obj]; - if(prop.testStatus(App::Property::Output) - || prop.testStatus(App::Property::NoRecompute)) + auto& s = ChangedObjects[obj]; + if (prop.testStatus(App::Property::Output) + || prop.testStatus(App::Property::NoRecompute)) { s.set(CS_Output); } } -void TreeWidget::updateChildren(App::DocumentObject *obj, - const std::set &dataSet, bool propOutput, bool force) +void TreeWidget::updateChildren(App::DocumentObject* obj, + const std::set& dataSet, bool propOutput, bool force) { bool childrenChanged = false; std::vector children; bool removeChildrenFromRoot = true; DocumentObjectDataPtr found; - for(auto data : dataSet) { - if(!found) { + for (auto data : dataSet) { + if (!found) { found = data; childrenChanged = found->updateChildren(force); removeChildrenFromRoot = found->viewObject->canRemoveChildrenFromRoot(); - if(!childrenChanged && found->removeChildrenFromRoot==removeChildrenFromRoot) + if (!childrenChanged && found->removeChildrenFromRoot == removeChildrenFromRoot) return; - }else if(childrenChanged) + } + else if (childrenChanged) data->updateChildren(found); data->removeChildrenFromRoot = removeChildrenFromRoot; DocumentItem* docItem = data->docItem; - for(auto item : data->items) - docItem->populateItem(item,true); + for (auto item : data->items) + docItem->populateItem(item, true); } - if(force) + if (force) return; - if(childrenChanged && propOutput) { + if (childrenChanged && propOutput) { // When a property is marked as output, it will not touch its object, // and thus, its property change will not be propagated through // recomputation. So we have to manually check for each links here. - for(auto link : App::GetApplication().getLinksTo(obj,App::GetLinkRecursive)) { - if(ChangedObjects.count(link)) + for (auto link : App::GetApplication().getLinksTo(obj, App::GetLinkRecursive)) { + if (ChangedObjects.count(link)) continue; std::vector linkedChildren; DocumentObjectDataPtr found; auto it = ObjectTable.find(link); - if(it == ObjectTable.end()) + if (it == ObjectTable.end()) continue; - for(auto data : it->second) { - if(!found) { + for (auto data : it->second) { + if (!found) { found = data; - if(!found->updateChildren(false)) + if (!found->updateChildren(false)) break; } data->updateChildren(found); DocumentItem* docItem = data->docItem; - for(auto item : data->items) - docItem->populateItem(item,true); + for (auto item : data->items) + docItem->populateItem(item, true); } } } - if(childrenChanged) { - if(!selectTimer->isActive()) + if (childrenChanged) { + if (!selectTimer->isActive()) onSelectionChanged(SelectionChanges()); //if the item is in a GeoFeatureGroup we may need to update that too, as the claim children //of the geofeaturegroup depends on what the childs claim auto grp = App::GeoFeatureGroupExtension::getGroupOfObject(obj); - if(grp && !ChangedObjects.count(grp)) { + if (grp && !ChangedObjects.count(grp)) { auto iter = ObjectTable.find(grp); - if(iter!=ObjectTable.end()) - updateChildren(grp,iter->second,true,false); + if (iter != ObjectTable.end()) + updateChildren(grp, iter->second, true, false); } } } -void DocumentItem::slotHighlightObject (const Gui::ViewProviderDocumentObject& obj, - const Gui::HighlightMode& high, bool set, const App::DocumentObject *parent, const char *subname) +void DocumentItem::slotHighlightObject(const Gui::ViewProviderDocumentObject& obj, + const Gui::HighlightMode& high, bool set, const App::DocumentObject* parent, const char* subname) { getTree()->_updateStatus(false); - if(parent && parent->getDocument()!=document()->getDocument()) { + if (parent && parent->getDocument() != document()->getDocument()) { auto it = getTree()->DocumentMap.find(Application::Instance->getDocument(parent->getDocument())); - if(it!=getTree()->DocumentMap.end()) - it->second->slotHighlightObject(obj,high,set,parent,subname); + if (it != getTree()->DocumentMap.end()) + it->second->slotHighlightObject(obj, high, set, parent, subname); return; } - FOREACH_ITEM(item,obj) - if(parent) { - App::DocumentObject *topParent = 0; + FOREACH_ITEM(item, obj) + if (parent) { + App::DocumentObject* topParent = 0; std::ostringstream ss; - item->getSubName(ss,topParent); - if(!topParent) { - if(parent!=obj.getObject()) + item->getSubName(ss, topParent); + if (!topParent) { + if (parent != obj.getObject()) continue; } } - item->setHighlight(set,high); - if(parent) - return; + item->setHighlight(set, high); + if (parent) + return; END_FOREACH_ITEM } -static unsigned int countExpandedItem(const QTreeWidgetItem *item) { +static unsigned int countExpandedItem(const QTreeWidgetItem* item) { unsigned int size = 0; - for(int i=0,count=item->childCount();ichildCount(); i < count; ++i) { auto citem = item->child(i); - if(citem->type()!=TreeWidget::ObjectType || !citem->isExpanded()) + if (citem->type() != TreeWidget::ObjectType || !citem->isExpanded()) continue; auto obj = static_cast(citem)->object()->getObject(); - if(obj->getNameInDocument()) + if (obj->getNameInDocument()) size += strlen(obj->getNameInDocument()) + countExpandedItem(citem); } return size; @@ -4114,179 +3915,181 @@ unsigned int DocumentItem::getMemSize(void) const { return countExpandedItem(this); } -static void saveExpandedItem(Base::Writer &writer, const QTreeWidgetItem *item) { +static void saveExpandedItem(Base::Writer& writer, const QTreeWidgetItem* item) { int itemCount = 0; - for(int i=0,count=item->childCount();ichildCount(); i < count; ++i) { auto citem = item->child(i); - if(citem->type()!=TreeWidget::ObjectType || !citem->isExpanded()) + if (citem->type() != TreeWidget::ObjectType || !citem->isExpanded()) continue; auto obj = static_cast(citem)->object()->getObject(); - if(obj->getNameInDocument()) + if (obj->getNameInDocument()) ++itemCount; } - if(!itemCount) { + if (!itemCount) { writer.Stream() << "/>" << std::endl; return; } - writer.Stream() << " count=\"" << itemCount << "\">" <" << std::endl; writer.incInd(); - for(int i=0,count=item->childCount();ichildCount(); i < count; ++i) { auto citem = item->child(i); - if(citem->type()!=TreeWidget::ObjectType || !citem->isExpanded()) + if (citem->type() != TreeWidget::ObjectType || !citem->isExpanded()) continue; auto obj = static_cast(citem)->object()->getObject(); - if(obj->getNameInDocument()) { + if (obj->getNameInDocument()) { writer.Stream() << writer.ind() << "getNameInDocument() << "\""; - saveExpandedItem(writer,static_cast(citem)); + saveExpandedItem(writer, static_cast(citem)); } } writer.decInd(); writer.Stream() << writer.ind() << "" << std::endl; } -void DocumentItem::Save (Base::Writer &writer) const { +void DocumentItem::Save(Base::Writer& writer) const { writer.Stream() << writer.ind() << "restore(reader); - for(auto inst : TreeWidget::Instances) { - if(inst!=getTree()) { + for (auto inst : TreeWidget::Instances) { + if (inst != getTree()) { auto docItem = inst->getDocumentItem(document()); - if(docItem) + if (docItem) docItem->_ExpandInfo = _ExpandInfo; } } } -void DocumentItem::restoreItemExpansion(const ExpandInfoPtr &info, DocumentObjectItem *item) { +void DocumentItem::restoreItemExpansion(const ExpandInfoPtr& info, DocumentObjectItem* item) { item->setExpanded(true); - if(!info) + if (!info) return; - for(int i=0,count=item->childCount();ichildCount(); i < count; ++i) { auto citem = item->child(i); - if(citem->type() != TreeWidget::ObjectType) + if (citem->type() != TreeWidget::ObjectType) continue; auto obj = static_cast(citem)->object()->getObject(); - if(!obj->getNameInDocument()) + if (!obj->getNameInDocument()) continue; auto it = info->find(obj->getNameInDocument()); - if(it != info->end()) - restoreItemExpansion(it->second,static_cast(citem)); + if (it != info->end()) + restoreItemExpansion(it->second, static_cast(citem)); } } -void DocumentItem::slotExpandObject (const Gui::ViewProviderDocumentObject& obj, - const Gui::TreeItemMode& mode, const App::DocumentObject *parent, const char *subname) +void DocumentItem::slotExpandObject(const Gui::ViewProviderDocumentObject& obj, + const Gui::TreeItemMode& mode, const App::DocumentObject* parent, const char* subname) { getTree()->_updateStatus(false); if ((mode == TreeItemMode::ExpandItem || - mode == TreeItemMode::ExpandPath) && + mode == TreeItemMode::ExpandPath) && obj.getDocument()->getDocument()->testStatus(App::Document::Restoring)) { if (!_ExpandInfo) _ExpandInfo.reset(new ExpandInfo); - _ExpandInfo->emplace(std::string("*") + obj.getObject()->getNameInDocument(),ExpandInfoPtr()); + _ExpandInfo->emplace(std::string("*") + obj.getObject()->getNameInDocument(), ExpandInfoPtr()); return; } - if (parent && parent->getDocument()!=document()->getDocument()) { + if (parent && parent->getDocument() != document()->getDocument()) { auto it = getTree()->DocumentMap.find(Application::Instance->getDocument(parent->getDocument())); - if (it!=getTree()->DocumentMap.end()) - it->second->slotExpandObject(obj,mode,parent,subname); + if (it != getTree()->DocumentMap.end()) + it->second->slotExpandObject(obj, mode, parent, subname); return; } - FOREACH_ITEM(item,obj) + FOREACH_ITEM(item, obj) // All document object items must always have a parent, either another // object item or document item. If not, then there is a bug somewhere // else. assert(item->parent()); - switch (mode) { - case TreeItemMode::ExpandPath: - if(!parent) { - QTreeWidgetItem* parentItem = item->parent(); - while (parentItem) { - parentItem->setExpanded(true); - parentItem = parentItem->parent(); - } - item->setExpanded(true); - break; + switch (mode) { + case TreeItemMode::ExpandPath: + if (!parent) { + QTreeWidgetItem* parentItem = item->parent(); + while (parentItem) { + parentItem->setExpanded(true); + parentItem = parentItem->parent(); } - // fall through - case TreeItemMode::ExpandItem: - if(!parent) { - if(item->parent()->isExpanded()) - item->setExpanded(true); - }else{ - App::DocumentObject *topParent = 0; - std::ostringstream ss; - item->getSubName(ss,topParent); - if(!topParent) { - if(parent!=obj.getObject()) - continue; - }else if(topParent!=parent) - continue; - showItem(item,false,true); - item->setExpanded(true); - } - break; - case TreeItemMode::CollapseItem: - item->setExpanded(false); - break; - case TreeItemMode::ToggleItem: - if (item->isExpanded()) - item->setExpanded(false); - else - item->setExpanded(true); - break; - - default: + item->setExpanded(true); break; } - if(item->isExpanded()) - populateItem(item); - if(parent) - return; + // fall through + case TreeItemMode::ExpandItem: + if (!parent) { + if (item->parent()->isExpanded()) + item->setExpanded(true); + } + else { + App::DocumentObject* topParent = 0; + std::ostringstream ss; + item->getSubName(ss, topParent); + if (!topParent) { + if (parent != obj.getObject()) + continue; + } + else if (topParent != parent) + continue; + showItem(item, false, true); + item->setExpanded(true); + } + break; + case TreeItemMode::CollapseItem: + item->setExpanded(false); + break; + case TreeItemMode::ToggleItem: + if (item->isExpanded()) + item->setExpanded(false); + else + item->setExpanded(true); + break; + + default: + break; + } + if (item->isExpanded()) + populateItem(item); + if (parent) + return; END_FOREACH_ITEM } void DocumentItem::slotScrollToObject(const Gui::ViewProviderDocumentObject& obj) { - if(!obj.getObject() || !obj.getObject()->getNameInDocument()) + if (!obj.getObject() || !obj.getObject()->getNameInDocument()) return; auto it = ObjectMap.find(obj.getObject()); - if(it == ObjectMap.end() || it->second->items.empty()) + if (it == ObjectMap.end() || it->second->items.empty()) return; auto item = it->second->rootItem; - if(!item) + if (!item) item = *it->second->items.begin(); getTree()->_updateStatus(false); getTree()->scrollToItem(item); } -void DocumentItem::slotRecomputedObject(const App::DocumentObject &obj) { - if(obj.isValid()) +void DocumentItem::slotRecomputedObject(const App::DocumentObject& obj) { + if (obj.isValid()) return; - slotRecomputed(*obj.getDocument(), {const_cast(&obj)}); + slotRecomputed(*obj.getDocument(), { const_cast(&obj) }); } -void DocumentItem::slotRecomputed(const App::Document &, const std::vector &objs) { +void DocumentItem::slotRecomputed(const App::Document&, const std::vector& objs) { auto tree = getTree(); - for(auto obj : objs) { - if(!obj->isValid()) + for (auto obj : objs) { + if (!obj->isValid()) tree->ChangedObjects[obj].set(TreeWidget::CS_Error); } - if(tree->ChangedObjects.size()) + if (tree->ChangedObjects.size()) tree->_updateStatus(); } @@ -4312,11 +4115,11 @@ Gui::Document* DocumentItem::document() const void DocumentItem::testStatus(void) { - for(const auto &v : ObjectMap) + for (const auto& v : ObjectMap) v.second->testStatus(); } -void DocumentItem::setData (int column, int role, const QVariant & value) +void DocumentItem::setData(int column, int role, const QVariant& value) { if (role == Qt::EditRole) { QString label = value.toString(); @@ -4326,93 +4129,94 @@ void DocumentItem::setData (int column, int role, const QVariant & value) QTreeWidgetItem::setData(column, role, value); } -void DocumentItem::clearSelection(DocumentObjectItem *exclude) +void DocumentItem::clearSelection(DocumentObjectItem* exclude) { // Block signals here otherwise we get a recursion and quadratic runtime bool ok = treeWidget()->blockSignals(true); FOREACH_ITEM_ALL(item); - if(item==exclude) { - if(item->selected>0) - item->selected = -1; - else - item->selected = 0; - updateItemSelection(item); - }else{ + if (item == exclude) { + if (item->selected > 0) + item->selected = -1; + else item->selected = 0; - item->mySubs.clear(); - item->setSelected(false); - item->setCheckState(false); - } + updateItemSelection(item); + } + else { + item->selected = 0; + item->mySubs.clear(); + item->setSelected(false); + item->setCheckState(false); + } END_FOREACH_ITEM; treeWidget()->blockSignals(ok); } -void DocumentItem::updateSelection(QTreeWidgetItem *ti, bool unselect) { - for(int i=0,count=ti->childCount();ichildCount(); i < count; ++i) { auto child = ti->child(i); - if(child && child->type()==TreeWidget::ObjectType) { + if (child && child->type() == TreeWidget::ObjectType) { auto childItem = static_cast(child); if (unselect) { childItem->setSelected(false); childItem->setCheckState(false); } updateItemSelection(childItem); - if(unselect && childItem->isGroup()) { + if (unselect && childItem->isGroup()) { // If the child item being force unselected by its group parent // is itself a group, propagate the unselection to its own // children - updateSelection(childItem,true); + updateSelection(childItem, true); } } } - if(unselect) return; - for(int i=0,count=ti->childCount();ichildCount(); i < count; ++i) updateSelection(ti->child(i)); } -void DocumentItem::updateItemSelection(DocumentObjectItem *item) { +void DocumentItem::updateItemSelection(DocumentObjectItem* item) { bool selected = item->isSelected(); bool checked = item->checkState(0) == Qt::Checked; - if(selected && !checked) + if (selected && !checked) item->setCheckState(true); - if(!selected && checked) + if (!selected && checked) item->setCheckState(false); - if((selected && item->selected>0) || (!selected && !item->selected)) { + if ((selected && item->selected > 0) || (!selected && !item->selected)) { return; } - if(item->selected != -1) + if (item->selected != -1) item->mySubs.clear(); item->selected = selected; auto obj = item->object()->getObject(); - if(!obj || !obj->getNameInDocument()) + if (!obj || !obj->getNameInDocument()) return; std::ostringstream str; - App::DocumentObject *topParent = 0; - item->getSubName(str,topParent); - if(topParent) { - if(topParent->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId())) { + App::DocumentObject* topParent = 0; + item->getSubName(str, topParent); + if (topParent) { + if (topParent->hasExtension(App::GeoFeatureGroupExtension::getExtensionClassTypeId())) { // remove legacy selection, i.e. those without subname Gui::Selection().rmvSelection(obj->getDocument()->getName(), - obj->getNameInDocument(),0); + obj->getNameInDocument(), 0); } - if(!obj->redirectSubName(str,topParent,0)) + if (!obj->redirectSubName(str, topParent, 0)) str << obj->getNameInDocument() << '.'; obj = topParent; } - const char *objname = obj->getNameInDocument(); - const char *docname = obj->getDocument()->getName(); - const auto &subname = str.str(); + const char* objname = obj->getNameInDocument(); + const char* docname = obj->getDocument()->getName(); + const auto& subname = str.str(); - if(subname.size()) { + if (subname.size()) { auto parentItem = item->getParentItem(); assert(parentItem); - if(selected && parentItem->selected) { + if (selected && parentItem->selected) { // When a group item is selected, all its children objects are // highlighted in the 3D view. So, when an item of some group is // newly selected, we must force unselect its parent in order to @@ -4429,7 +4233,7 @@ void DocumentItem::updateItemSelection(DocumentObjectItem *item) { } } - if(selected && item->isGroup()) { + if (selected && item->isGroup()) { // Same reasoning as above. When a group item is newly selected, We // choose to force unselect all its children to void messing up the // selection highlight @@ -4440,20 +4244,20 @@ void DocumentItem::updateItemSelection(DocumentObjectItem *item) { // updateSelection(item,true); } - if(!selected) { - Gui::Selection().rmvSelection(docname,objname,subname.c_str()); + if (!selected) { + Gui::Selection().rmvSelection(docname, objname, subname.c_str()); return; } selected = false; - if(item->mySubs.size()) { - for(auto &sub : item->mySubs) { - if(Gui::Selection().addSelection(docname,objname,(subname+sub).c_str())) + if (item->mySubs.size()) { + for (auto& sub : item->mySubs) { + if (Gui::Selection().addSelection(docname, objname, (subname + sub).c_str())) selected = true; } } - if(!selected) { + if (!selected) { item->mySubs.clear(); - if(!Gui::Selection().addSelection(docname,objname,subname.c_str())) { + if (!Gui::Selection().addSelection(docname, objname, subname.c_str())) { item->selected = 0; item->setSelected(false); item->setCheckState(false); @@ -4463,97 +4267,97 @@ void DocumentItem::updateItemSelection(DocumentObjectItem *item) { getTree()->syncView(item->object()); } -App::DocumentObject *DocumentItem::getTopParent(App::DocumentObject *obj, std::string &subname) { +App::DocumentObject* DocumentItem::getTopParent(App::DocumentObject* obj, std::string& subname) { auto it = ObjectMap.find(obj); - if(it == ObjectMap.end() || it->second->items.empty()) + if (it == ObjectMap.end() || it->second->items.empty()) return 0; // already a top parent - if(it->second->rootItem) + if (it->second->rootItem) return obj; - for(auto item : it->second->items) { + for (auto item : it->second->items) { // non group object do not provide a coordinate system, hence its // claimed child is still in the global coordinate space, so the // child can still be considered a top level object - if(!item->isParentGroup()) + if (!item->isParentGroup()) return obj; } // If no top level item, find an item that is closest to the top level - std::multimap items; - for(auto item : it->second->items) { - int i=0; - for(auto parent=item->parent();parent;++i,parent=parent->parent()) { - if(parent->isHidden()) + std::multimap items; + for (auto item : it->second->items) { + int i = 0; + for (auto parent = item->parent(); parent; ++i, parent = parent->parent()) { + if (parent->isHidden()) i += 1000; ++i; } - items.emplace(i,item); + items.emplace(i, item); } - App::DocumentObject *topParent = 0; + App::DocumentObject* topParent = 0; std::ostringstream ss; - items.begin()->second->getSubName(ss,topParent); - if(!topParent) { + items.begin()->second->getSubName(ss, topParent); + if (!topParent) { // this shouldn't happen FC_WARN("No top parent for " << obj->getFullName() << '.' << subname); return obj; } ss << obj->getNameInDocument() << '.' << subname; FC_LOG("Subname correction " << obj->getFullName() << '.' << subname - << " -> " << topParent->getFullName() << '.' << ss.str()); + << " -> " << topParent->getFullName() << '.' << ss.str()); subname = ss.str(); return topParent; } -DocumentObjectItem *DocumentItem::findItemByObject( - bool sync, App::DocumentObject *obj, const char *subname, bool select) +DocumentObjectItem* DocumentItem::findItemByObject( + bool sync, App::DocumentObject* obj, const char* subname, bool select) { - if(!subname) + if (!subname) subname = ""; auto it = ObjectMap.find(obj); - if(it == ObjectMap.end() || it->second->items.empty()) + if (it == ObjectMap.end() || it->second->items.empty()) return 0; // prefer top level item of this object - if(it->second->rootItem) - return findItem(sync,it->second->rootItem,subname,select); + if (it->second->rootItem) + return findItem(sync, it->second->rootItem, subname, select); - for(auto item : it->second->items) { + for (auto item : it->second->items) { // non group object do not provide a coordinate system, hence its // claimed child is still in the global coordinate space, so the // child can still be considered a top level object - if(!item->isParentGroup()) - return findItem(sync,item,subname,select); + if (!item->isParentGroup()) + return findItem(sync, item, subname, select); } // If no top level item, find an item that is closest to the top level - std::multimap items; - for(auto item : it->second->items) { - int i=0; - for(auto parent=item->parent();parent;++i,parent=parent->parent()) + std::multimap items; + for (auto item : it->second->items) { + int i = 0; + for (auto parent = item->parent(); parent; ++i, parent = parent->parent()) ++i; - items.emplace(i,item); + items.emplace(i, item); } - for(auto &v : items) { - auto item = findItem(sync,v.second,subname,select); - if(item) + for (auto& v : items) { + auto item = findItem(sync, v.second, subname, select); + if (item) return item; } return 0; } -DocumentObjectItem *DocumentItem::findItem( - bool sync, DocumentObjectItem *item, const char *subname, bool select) +DocumentObjectItem* DocumentItem::findItem( + bool sync, DocumentObjectItem* item, const char* subname, bool select) { - if(item->isHidden()) + if (item->isHidden()) item->setHidden(false); - if(!subname || *subname==0) { - if(select) { - item->selected+=2; + if (!subname || *subname == 0) { + if (select) { + item->selected += 2; item->mySubs.clear(); } return item; @@ -4562,113 +4366,114 @@ DocumentObjectItem *DocumentItem::findItem( TREE_TRACE("find next " << subname); // try to find the next level object name - const char *nextsub = 0; - const char *dot = 0; - if((dot=strchr(subname,'.'))) - nextsub = dot+1; + const char* nextsub = 0; + const char* dot = 0; + if ((dot = strchr(subname, '.'))) + nextsub = dot + 1; else { - if(select) { - item->selected+=2; - if(std::find(item->mySubs.begin(),item->mySubs.end(),subname)==item->mySubs.end()) + if (select) { + item->selected += 2; + if (std::find(item->mySubs.begin(), item->mySubs.end(), subname) == item->mySubs.end()) item->mySubs.push_back(subname); } return item; } - std::string name(subname,nextsub-subname); + std::string name(subname, nextsub - subname); auto obj = item->object()->getObject(); auto subObj = obj->getSubObject(name.c_str()); - if(!subObj || subObj==obj) { - if(!subObj && !getTree()->searchDoc) + if (!subObj || subObj == obj) { + if (!subObj && !getTree()->searchDoc) TREE_LOG("sub object not found " << item->getName() << '.' << name.c_str()); - if(select) { + if (select) { item->selected += 2; - if(std::find(item->mySubs.begin(),item->mySubs.end(),subname)==item->mySubs.end()) + if (std::find(item->mySubs.begin(), item->mySubs.end(), subname) == item->mySubs.end()) item->mySubs.push_back(subname); } return item; } - if(select) + if (select) item->mySubs.clear(); - if(!item->populated && sync) { + if (!item->populated && sync) { //force populate the item item->populated = true; - populateItem(item,true); + populateItem(item, true); } - for(int i=0,count=item->childCount();ichildCount(); i < count; ++i) { auto ti = item->child(i); - if(!ti || ti->type()!=TreeWidget::ObjectType) continue; + if (!ti || ti->type() != TreeWidget::ObjectType) continue; auto child = static_cast(ti); - if(child->object()->getObject() == subObj) - return findItem(sync,child,nextsub,select); + if (child->object()->getObject() == subObj) + return findItem(sync, child, nextsub, select); } // The sub object is not found. This could happen for geo group, since its // children may be in more than one hierarchy down. bool found = false; - DocumentObjectItem *res=0; + DocumentObjectItem* res = 0; auto it = ObjectMap.find(subObj); - if(it != ObjectMap.end()) { - for(auto child : it->second->items) { - if(child->isChildOfItem(item)) { + if (it != ObjectMap.end()) { + for (auto child : it->second->items) { + if (child->isChildOfItem(item)) { found = true; - res = findItem(sync,child,nextsub,select); - if(!select) + res = findItem(sync, child, nextsub, select); + if (!select) return res; } } } - if(select && !found) { + if (select && !found) { // The sub object is still not found. Maybe it is a non-object sub-element. // Select the current object instead. TREE_TRACE("element " << subname << " not found"); - item->selected+=2; - if(std::find(item->mySubs.begin(),item->mySubs.end(),subname)==item->mySubs.end()) + item->selected += 2; + if (std::find(item->mySubs.begin(), item->mySubs.end(), subname) == item->mySubs.end()) item->mySubs.push_back(subname); } return res; } void DocumentItem::selectItems(SelectionReason reason) { - const auto &sels = Selection().getSelection(pDocument->getDocument()->getName(),false); + const auto& sels = Selection().getSelection(pDocument->getDocument()->getName(), false); - bool sync = (sels.size()>50 || reason==SR_SELECT)?false:true; + bool sync = (sels.size() > 50 || reason == SR_SELECT) ? false : true; - for(const auto &sel : sels) - findItemByObject(sync,sel.pObject,sel.SubName,true); + for (const auto& sel : sels) + findItemByObject(sync, sel.pObject, sel.SubName, true); - DocumentObjectItem *newSelect = 0; - DocumentObjectItem *oldSelect = 0; + DocumentObjectItem* newSelect = 0; + DocumentObjectItem* oldSelect = 0; FOREACH_ITEM_ALL(item) - if(item->selected == 1) { + if (item->selected == 1) { // this means it is the old selection and is not in the current // selection item->selected = 0; item->mySubs.clear(); item->setSelected(false); item->setCheckState(false); - }else if(item->selected) { - if(sync) { - if(item->selected==2 && showItem(item,false,reason==SR_FORCE_EXPAND)) { + } + else if (item->selected) { + if (sync) { + if (item->selected == 2 && showItem(item, false, reason == SR_FORCE_EXPAND)) { // This means newly selected and can auto expand - if(!newSelect) + if (!newSelect) newSelect = item; } - if(!newSelect && !oldSelect && !item->isHidden()) { + if (!newSelect && !oldSelect && !item->isHidden()) { bool visible = true; - for(auto parent=item->parent();parent;parent=parent->parent()) { - if(!parent->isExpanded() || parent->isHidden()) { + for (auto parent = item->parent(); parent; parent = parent->parent()) { + if (!parent->isExpanded() || parent->isHidden()) { visible = false; break; } } - if(visible) + if (visible) oldSelect = item; } } @@ -4678,38 +4483,38 @@ void DocumentItem::selectItems(SelectionReason reason) { } END_FOREACH_ITEM; - if(sync) { - if(!newSelect) + if (sync) { + if (!newSelect) newSelect = oldSelect; else getTree()->syncView(newSelect->object()); - if(newSelect) + if (newSelect) getTree()->scrollToItem(newSelect); } } -void DocumentItem::populateParents(const ViewProvider *vp, ViewParentMap &parentMap) { +void DocumentItem::populateParents(const ViewProvider* vp, ViewParentMap& parentMap) { auto it = parentMap.find(vp); - if(it == parentMap.end()) return; - for(auto parent : it->second) { + if (it == parentMap.end()) return; + for (auto parent : it->second) { auto it = ObjectMap.find(parent->getObject()); - if(it==ObjectMap.end()) + if (it == ObjectMap.end()) continue; - populateParents(parent,parentMap); - for(auto item : it->second->items) { - if(!item->isHidden() && !item->populated) { + populateParents(parent, parentMap); + for (auto item : it->second->items) { + if (!item->isHidden() && !item->populated) { item->populated = true; - populateItem(item,true); + populateItem(item, true); } } } } -void DocumentItem::selectAllInstances(const ViewProviderDocumentObject &vpd) { +void DocumentItem::selectAllInstances(const ViewProviderDocumentObject& vpd) { ViewParentMap parentMap; auto pObject = vpd.getObject(); - if(ObjectMap.find(pObject) == ObjectMap.end()) + if (ObjectMap.find(pObject) == ObjectMap.end()) return; bool lock = getTree()->blockConnection(true); @@ -4718,27 +4523,27 @@ void DocumentItem::selectAllInstances(const ViewProviderDocumentObject &vpd) { // provider, i.e. all appearance of the object inside all its parent items // // Build a map of object to all its parent - for(auto &v : ObjectMap) { - if(v.second->viewObject == &vpd) continue; - for(auto child : v.second->viewObject->claimChildren()) { + for (auto& v : ObjectMap) { + if (v.second->viewObject == &vpd) continue; + for (auto child : v.second->viewObject->claimChildren()) { auto vp = getViewProvider(child); - if(!vp) continue; + if (!vp) continue; parentMap[vp].push_back(v.second->viewObject); } } // now make sure all parent items are populated. In order to do that, we // need to populate the oldest parent first - populateParents(&vpd,parentMap); + populateParents(&vpd, parentMap); - DocumentObjectItem *first = 0; - FOREACH_ITEM(item,vpd); - if(showItem(item,true) && !first) - first = item; + DocumentObjectItem* first = 0; + FOREACH_ITEM(item, vpd); + if (showItem(item, true) && !first) + first = item; END_FOREACH_ITEM; getTree()->blockConnection(lock); - if(first) { + if (first) { treeWidget()->scrollToItem(first); updateSelection(); } @@ -4752,44 +4557,45 @@ void DocumentItem::setShowHidden(bool show) { pDocument->getDocument()->ShowHidden.setValue(show); } -bool DocumentItem::showItem(DocumentObjectItem *item, bool select, bool force) { +bool DocumentItem::showItem(DocumentObjectItem* item, bool select, bool force) { auto parent = item->parent(); - if(item->isHidden()) { - if(!force) + if (item->isHidden()) { + if (!force) return false; item->setHidden(false); } - if(parent->type()==TreeWidget::ObjectType) { - if(!showItem(static_cast(parent),false)) + if (parent->type() == TreeWidget::ObjectType) { + if (!showItem(static_cast(parent), false)) return false; auto pitem = static_cast(parent); - if(force || !pitem->object()->getObject()->testStatus(App::NoAutoExpand)) + if (force || !pitem->object()->getObject()->testStatus(App::NoAutoExpand)) parent->setExpanded(true); - else if(!select) + else if (!select) return false; - }else + } + else parent->setExpanded(true); - if(select) { + if (select) { item->setSelected(true); item->setCheckState(true); } return true; } -void DocumentItem::updateItemsVisibility(QTreeWidgetItem *item, bool show) { - if(item->type() == TreeWidget::ObjectType) { +void DocumentItem::updateItemsVisibility(QTreeWidgetItem* item, bool show) { + if (item->type() == TreeWidget::ObjectType) { auto objitem = static_cast(item); objitem->setHidden(!show && !objitem->object()->showInTree()); } - for(int i=0;ichildCount();++i) - updateItemsVisibility(item->child(i),show); + for (int i = 0; i < item->childCount(); ++i) + updateItemsVisibility(item->child(i), show); } void DocumentItem::updateSelection() { bool lock = getTree()->blockConnection(true); - updateSelection(this,false); + updateSelection(this, false); getTree()->blockConnection(lock); } @@ -4797,9 +4603,9 @@ void DocumentItem::updateSelection() { static int countItems; -DocumentObjectItem::DocumentObjectItem(DocumentItem *ownerDocItem, DocumentObjectDataPtr data) +DocumentObjectItem::DocumentObjectItem(DocumentItem* ownerDocItem, DocumentObjectDataPtr data) : QTreeWidgetItem(TreeWidget::ObjectType) - , myOwner(ownerDocItem), myData(data), previousStatus(-1),selected(0),populated(false) + , myOwner(ownerDocItem), myData(data), previousStatus(-1), selected(0), populated(false) { setFlags(flags() | Qt::ItemIsEditable | Qt::ItemIsUserCheckable); setCheckState(false); @@ -4814,17 +4620,17 @@ DocumentObjectItem::~DocumentObjectItem() --countItems; TREE_LOG("Delete item: " << countItems << ", " << object()->getObject()->getFullName()); auto it = myData->items.find(this); - if(it == myData->items.end()) + if (it == myData->items.end()) assert(0); else myData->items.erase(it); - if(myData->rootItem == this) + if (myData->rootItem == this) myData->rootItem = 0; - if(myOwner && myData->items.empty()) { + if (myOwner && myData->items.empty()) { auto it = myOwner->_ParentMap.find(object()->getObject()); - if(it!=myOwner->_ParentMap.end() && it->second.size()) { + if (it != myOwner->_ParentMap.end() && it->second.size()) { myOwner->PopulateObjects.push_back(*it->second.begin()); myOwner->getTree()->_updateStatus(); } @@ -4832,12 +4638,12 @@ DocumentObjectItem::~DocumentObjectItem() } void DocumentObjectItem::restoreBackground() { - this->setBackground(0,this->bgBrush); + this->setBackground(0, this->bgBrush); } void DocumentObjectItem::setHighlight(bool set, Gui::HighlightMode high) { QFont f = this->font(0); - auto highlight = [=](const QColor& col){ + auto highlight = [=](const QColor& col) { if (set) this->setBackground(0, col); else @@ -4859,27 +4665,27 @@ void DocumentObjectItem::setHighlight(bool set, Gui::HighlightMode high) { f.setOverline(set); break; case HighlightMode::Blue: - highlight(QColor(200,200,255)); + highlight(QColor(200, 200, 255)); break; case HighlightMode::LightBlue: - highlight(QColor(230,230,255)); + highlight(QColor(230, 230, 255)); break; case HighlightMode::UserDefined: { - QColor color(230,230,255); + QColor color(230, 230, 255); if (set) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/TreeView"); - bool bold = hGrp->GetBool("TreeActiveBold",true); - bool italic = hGrp->GetBool("TreeActiveItalic",false); - bool underlined = hGrp->GetBool("TreeActiveUnderlined",false); - bool overlined = hGrp->GetBool("TreeActiveOverlined",false); + bool bold = hGrp->GetBool("TreeActiveBold", true); + bool italic = hGrp->GetBool("TreeActiveItalic", false); + bool underlined = hGrp->GetBool("TreeActiveUnderlined", false); + bool overlined = hGrp->GetBool("TreeActiveOverlined", false); f.setBold(bold); f.setItalic(italic); f.setUnderline(underlined); f.setOverline(overlined); - unsigned long col = hGrp->GetUnsigned("TreeActiveColor",3873898495); - color = QColor((col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff); + unsigned long col = hGrp->GetUnsigned("TreeActiveColor", 3873898495); + color = QColor((col >> 24) & 0xff, (col >> 16) & 0xff, (col >> 8) & 0xff); } else { f.setBold(false); @@ -4892,10 +4698,10 @@ void DocumentObjectItem::setHighlight(bool set, Gui::HighlightMode high) { default: break; } - this->setFont(0,f); + this->setFont(0, f); } -const char *DocumentObjectItem::getTreeName() const +const char* DocumentObjectItem::getTreeName() const { return myData->getTreeName(); } @@ -4907,30 +4713,30 @@ Gui::ViewProviderDocumentObject* DocumentObjectItem::object() const void DocumentObjectItem::testStatus(bool resetStatus) { - QIcon icon,icon2; - testStatus(resetStatus,icon,icon2); + QIcon icon, icon2; + testStatus(resetStatus, icon, icon2); } -void DocumentObjectItem::testStatus(bool resetStatus, QIcon &icon1, QIcon &icon2) +void DocumentObjectItem::testStatus(bool resetStatus, QIcon& icon1, QIcon& icon2) { App::DocumentObject* pObject = object()->getObject(); int visible = -1; auto parentItem = getParentItem(); - if(parentItem) { + if (parentItem) { Timing(testStatus1); auto parent = parentItem->object()->getObject(); - auto ext = parent->getExtensionByType(true,false); - if(!ext) + auto ext = parent->getExtensionByType(true, false); + if (!ext) visible = parent->isElementVisible(pObject->getNameInDocument()); else { // We are dealing with a plain group. It has special handling when // linked, which allows it to have indpenedent visibility control. // We need to go up the hierarchy and see if there is any link to // it. - for(auto pp=parentItem->getParentItem();pp;pp=pp->getParentItem()) { + for (auto pp = parentItem->getParentItem(); pp; pp = pp->getParentItem()) { auto obj = pp->object()->getObject(); - if(!obj->hasExtension(App::GroupExtension::getExtensionClassTypeId(),false)) { + if (!obj->hasExtension(App::GroupExtension::getExtensionClassTypeId(), false)) { visible = pp->object()->getObject()->isElementVisible(pObject->getNameInDocument()); break; } @@ -4940,27 +4746,27 @@ void DocumentObjectItem::testStatus(bool resetStatus, QIcon &icon1, QIcon &icon2 Timing(testStatus2); - if(visible<0) - visible = object()->isShow()?1:0; + if (visible < 0) + visible = object()->isShow() ? 1 : 0; auto obj = object()->getObject(); auto linked = obj->getLinkedObject(false); - bool external = object()->getDocument()!=getOwnerDocument()->document() || - (linked && linked->getDocument()!=obj->getDocument()); + bool external = object()->getDocument() != getOwnerDocument()->document() || + (linked && linked->getDocument() != obj->getDocument()); int currentStatus = - ((external?0:1)<<4) | + ((external ? 0 : 1) << 4) | ((object()->showInTree() ? 0 : 1) << 3) | - ((pObject->isError() ? 1 : 0) << 2) | - ((pObject->isTouched()||pObject->mustExecute()== 1 ? 1 : 0) << 1) | - (visible ? 1 : 0); + ((pObject->isError() ? 1 : 0) << 2) | + ((pObject->isTouched() || pObject->mustExecute() == 1 ? 1 : 0) << 1) | + (visible ? 1 : 0); TimingStop(testStatus2); - if (!resetStatus && previousStatus==currentStatus) + if (!resetStatus && previousStatus == currentStatus) return; - _Timing(1,testStatus3); + _Timing(1, testStatus3); previousStatus = currentStatus; @@ -4974,29 +4780,29 @@ void DocumentObjectItem::testStatus(bool resetStatus, QIcon &icon1, QIcon &icon2 // to black which will lead to unreadable text if the system background // hss already a dark color. // However, it works if we set the appropriate role to an empty QVariant(). - this->setData(0, Qt::ForegroundRole,QVariant()); + this->setData(0, Qt::ForegroundRole, QVariant()); } else { // invisible QStyleOptionViewItem opt; // it can happen that a tree item is not attached to the tree widget (#0003025) if (this->treeWidget()) opt.initFrom(this->treeWidget()); - this->setForeground(0, opt.palette.color(QPalette::Disabled,QPalette::Text)); + this->setForeground(0, opt.palette.color(QPalette::Disabled, QPalette::Text)); mode = QIcon::Disabled; } - _TimingStop(1,testStatus3); + _TimingStop(1, testStatus3); - QIcon &icon = mode==QIcon::Normal?icon1:icon2; + QIcon& icon = mode == QIcon::Normal ? icon1 : icon2; - if(icon.isNull()) { + if (icon.isNull()) { Timing(getIcon); QPixmap px; if (currentStatus & 4) { static QPixmap pxError; - if(pxError.isNull()) { - // object is in error state - const char * const feature_error_xpm[]={ + if (pxError.isNull()) { + // object is in error state + const char* const feature_error_xpm[] = { "9 9 3 1", ". c None", "# c #ff0000", @@ -5009,16 +4815,16 @@ void DocumentObjectItem::testStatus(bool resetStatus, QIcon &icon1, QIcon &icon2 "#########", ".##aaa##.", ".##aaa##.", - "...###..."}; + "...###..." }; pxError = QPixmap(feature_error_xpm); } px = pxError; } else if (currentStatus & 2) { static QPixmap pxRecompute; - if(pxRecompute.isNull()) { + if (pxRecompute.isNull()) { // object must be recomputed - const char * const feature_recompute_xpm[]={ + const char* const feature_recompute_xpm[] = { "9 9 3 1", ". c None", "# c #0000ff", @@ -5031,7 +4837,7 @@ void DocumentObjectItem::testStatus(bool resetStatus, QIcon &icon1, QIcon &icon2 "#aaaa####", ".#aa####.", ".#######.", - "...###..."}; + "...###..." }; pxRecompute = QPixmap(feature_recompute_xpm); } px = pxRecompute; @@ -5042,23 +4848,24 @@ void DocumentObjectItem::testStatus(bool resetStatus, QIcon &icon1, QIcon &icon2 int w = getTree()->viewOptions().decorationSize.width(); - QPixmap pxOn,pxOff; + QPixmap pxOn, pxOff; // if needed show small pixmap inside if (!px.isNull()) { pxOff = BitmapFactory().merge(icon_org.pixmap(w, w, mode, QIcon::Off), - px,BitmapFactoryInst::TopRight); - pxOn = BitmapFactory().merge(icon_org.pixmap(w, w, mode, QIcon::On ), - px,BitmapFactoryInst::TopRight); - } else { + px, BitmapFactoryInst::TopRight); + pxOn = BitmapFactory().merge(icon_org.pixmap(w, w, mode, QIcon::On), + px, BitmapFactoryInst::TopRight); + } + else { pxOff = icon_org.pixmap(w, w, mode, QIcon::Off); pxOn = icon_org.pixmap(w, w, mode, QIcon::On); } - if(currentStatus & 8) {// hidden item + if (currentStatus & 8) {// hidden item static QPixmap pxHidden; - if(pxHidden.isNull()) { - const char * const feature_hidden_xpm[]={ + if (pxHidden.isNull()) { + const char* const feature_hidden_xpm[] = { "9 7 3 1", ". c None", "# c #000000", @@ -5069,17 +4876,17 @@ void DocumentObjectItem::testStatus(bool resetStatus, QIcon &icon1, QIcon &icon2 "#aa###aa#", ".#a###a#.", "..#aaa#..", - "...###..."}; + "...###..." }; pxHidden = QPixmap(feature_hidden_xpm); } pxOff = BitmapFactory().merge(pxOff, pxHidden, BitmapFactoryInst::TopLeft); pxOn = BitmapFactory().merge(pxOn, pxHidden, BitmapFactoryInst::TopLeft); } - if(external) {// external item + if (external) {// external item static QPixmap pxExternal; - if(pxExternal.isNull()) { - const char * const feature_external_xpm[]={ + if (pxExternal.isNull()) { + const char* const feature_external_xpm[] = { "7 7 3 1", ". c None", "# c #000000", @@ -5090,7 +4897,7 @@ void DocumentObjectItem::testStatus(bool resetStatus, QIcon &icon1, QIcon &icon2 "..##aa#", "..#aa##", ".#aa##.", - "..###.."}; + "..###.." }; pxExternal = QPixmap(feature_external_xpm); } pxOff = BitmapFactory().merge(pxOff, pxExternal, BitmapFactoryInst::BottomRight); @@ -5104,7 +4911,7 @@ void DocumentObjectItem::testStatus(bool resetStatus, QIcon &icon1, QIcon &icon2 } - _Timing(2,setIcon); + _Timing(2, setIcon); this->setIcon(0, icon); } @@ -5118,12 +4925,12 @@ void DocumentObjectItem::displayStatusInfo() info += TreeWidget::tr(" (but must be executed)"); QString status = TreeWidget::tr("%1, Internal name: %2") - .arg(info, QString::fromLatin1(Obj->getNameInDocument())); + .arg(info, QString::fromLatin1(Obj->getNameInDocument())); if (!Obj->isError()) getMainWindow()->showMessage(status); else { - getMainWindow()->showStatus(MainWindow::Err,status); + getMainWindow()->showStatus(MainWindow::Err, status); QTreeWidget* tree = this->treeWidget(); QPoint pos = tree->visualItemRect(this).topRight(); QToolTip::showText(tree->mapToGlobal(pos), info); @@ -5132,20 +4939,20 @@ void DocumentObjectItem::displayStatusInfo() void DocumentObjectItem::setExpandedStatus(bool on) { - if(getOwnerDocument()->document() == object()->getDocument()) + if (getOwnerDocument()->document() == object()->getDocument()) object()->getObject()->setStatus(App::Expand, on); } -void DocumentObjectItem::setData (int column, int role, const QVariant & value) +void DocumentObjectItem::setData(int column, int role, const QVariant& value) { QVariant myValue(value); - if (role == Qt::EditRole && column<=1) { + if (role == Qt::EditRole && column <= 1) { auto obj = object()->getObject(); - auto &label = column?obj->Label2:obj->Label; + auto& label = column ? obj->Label2 : obj->Label; std::ostringstream ss; ss << "Change " << getName() << '.' << label.getName(); App::AutoTransaction committer(ss.str().c_str()); - label.setValue((const char *)value.toString().toUtf8()); + label.setValue((const char*)value.toString().toUtf8()); myValue = QString::fromUtf8(label.getValue()); } QTreeWidgetItem::setData(column, role, myValue); @@ -5153,26 +4960,26 @@ void DocumentObjectItem::setData (int column, int role, const QVariant & value) bool DocumentObjectItem::isChildOfItem(DocumentObjectItem* item) { - for(auto pitem=parent();pitem;pitem=pitem->parent()) - if(pitem == item) + for (auto pitem = parent(); pitem; pitem = pitem->parent()) + if (pitem == item) return true; return false; } -bool DocumentObjectItem::requiredAtRoot(bool excludeSelf) const{ - if(myData->rootItem || object()->getDocument()!=getOwnerDocument()->document()) +bool DocumentObjectItem::requiredAtRoot(bool excludeSelf) const { + if (myData->rootItem || object()->getDocument() != getOwnerDocument()->document()) return false; bool checkMap = true; - for(auto item : myData->items) { - if(excludeSelf && item == this) continue; + for (auto item : myData->items) { + if (excludeSelf && item == this) continue; auto pi = item->getParentItem(); - if(!pi || pi->myData->removeChildrenFromRoot) + if (!pi || pi->myData->removeChildrenFromRoot) return false; checkMap = false; } - if(checkMap && myOwner) { + if (checkMap && myOwner) { auto it = myOwner->_ParentMap.find(object()->getObject()); - if(it!=myOwner->_ParentMap.end()) { + if (it != myOwner->_ParentMap.end()) { // Reaching here means all items of this corresponding object is // going to be deleted, but the object itself is not deleted and // still being referred to by some parent item that is not expanded @@ -5185,8 +4992,8 @@ bool DocumentObjectItem::requiredAtRoot(bool excludeSelf) const{ // expand its parent item. It only causes minor problems, such as, // tree scroll to object command won't work properly. - for(auto parent : it->second) { - if(getOwnerDocument()->populateObject(parent)) + for (auto parent : it->second) { + if (getOwnerDocument()->populateObject(parent)) return false; } } @@ -5197,7 +5004,7 @@ bool DocumentObjectItem::requiredAtRoot(bool excludeSelf) const{ bool DocumentObjectItem::isLink() const { auto obj = object()->getObject(); auto linked = obj->getLinkedObject(false); - return linked && obj!=linked; + return linked && obj != linked; } bool DocumentObjectItem::isLinkFinal() const { @@ -5222,33 +5029,33 @@ enum GroupType { int DocumentObjectItem::isGroup() const { auto obj = object()->getObject(); auto linked = obj->getLinkedObject(true); - if(linked && linked->hasExtension( - App::GeoFeatureGroupExtension::getExtensionClassTypeId())) + if (linked && linked->hasExtension( + App::GeoFeatureGroupExtension::getExtensionClassTypeId())) return PartGroup; - if(obj->hasChildElement()) + if (obj->hasChildElement()) return LinkGroup; - if(obj->hasExtension(App::GroupExtension::getExtensionClassTypeId(),false)) { - for(auto parent=getParentItem();parent;parent=parent->getParentItem()) { + if (obj->hasExtension(App::GroupExtension::getExtensionClassTypeId(), false)) { + for (auto parent = getParentItem(); parent; parent = parent->getParentItem()) { auto pobj = parent->object()->getObject(); - if(pobj->hasExtension(App::GroupExtension::getExtensionClassTypeId(),false)) + if (pobj->hasExtension(App::GroupExtension::getExtensionClassTypeId(), false)) continue; - if(pobj->isElementVisible(obj->getNameInDocument())>=0) + if (pobj->isElementVisible(obj->getNameInDocument()) >= 0) return LinkGroup; } } return NotGroup; } -bool DocumentItem::isObjectShowable(App::DocumentObject *obj) { +bool DocumentItem::isObjectShowable(App::DocumentObject* obj) { auto itParents = _ParentMap.find(obj); - if(itParents == _ParentMap.end() || itParents->second.empty()) + if (itParents == _ParentMap.end() || itParents->second.empty()) return true; bool showable = true; - for(auto parent : itParents->second) { - if(parent->getDocument() != obj->getDocument()) + for (auto parent : itParents->second) { + if (parent->getDocument() != obj->getDocument()) continue; - if(!parent->hasChildElement() - && parent->getLinkedObject(false)==parent) + if (!parent->hasChildElement() + && parent->getLinkedObject(false) == parent) return true; showable = false; } @@ -5257,64 +5064,30 @@ bool DocumentItem::isObjectShowable(App::DocumentObject *obj) { int DocumentObjectItem::isParentGroup() const { auto pi = getParentItem(); - return pi?pi->isGroup():0; + return pi ? pi->isGroup() : 0; } -DocumentObjectItem *DocumentObjectItem::getParentItem() const{ - if(parent()->type()!=TreeWidget::ObjectType) +DocumentObjectItem* DocumentObjectItem::getParentItem() const { + if (parent()->type() != TreeWidget::ObjectType) return 0; return static_cast(parent()); } -DocumentObjectItem *DocumentObjectItem::getNextSibling() const -{ - QTreeWidgetItem *parent = this->parent(); - if (parent) { - int index = parent->indexOfChild(const_cast(this)); - if (index >= 0) { - while (++index < parent->childCount()) { - QTreeWidgetItem *sibling = parent->child(index); - if (sibling->type() == TreeWidget::ObjectType) { - return static_cast(sibling); - } - } - } - } - - return 0; +const char* DocumentObjectItem::getName() const { + const char* name = object()->getObject()->getNameInDocument(); + return name ? name : ""; } -DocumentObjectItem *DocumentObjectItem::getPreviousSibling() const -{ - QTreeWidgetItem *parent = this->parent(); - if (parent) { - int index = parent->indexOfChild(const_cast(this)); - while (index > 0) { - QTreeWidgetItem *sibling = parent->child(--index); - if (sibling->type() == TreeWidget::ObjectType) { - return static_cast(sibling); - } - } - } - - return 0; -} - -const char *DocumentObjectItem::getName() const { - const char *name = object()->getObject()->getNameInDocument(); - return name?name:""; -} - -int DocumentObjectItem::getSubName(std::ostringstream &str, App::DocumentObject *&topParent) const +int DocumentObjectItem::getSubName(std::ostringstream& str, App::DocumentObject*& topParent) const { auto parent = getParentItem(); - if(!parent) + if (!parent) return NotGroup; - int ret = parent->getSubName(str,topParent); - if(ret != SuperGroup) { + int ret = parent->getSubName(str, topParent); + if (ret != SuperGroup) { int group = parent->isGroup(); - if(group == NotGroup) { - if(ret!=PartGroup) { + if (group == NotGroup) { + if (ret != PartGroup) { // Handle this situation, // // LinkGroup @@ -5334,74 +5107,74 @@ int DocumentObjectItem::getSubName(std::ostringstream &str, App::DocumentObject } auto obj = parent->object()->getObject(); - if(!obj || !obj->getNameInDocument()) { + if (!obj || !obj->getNameInDocument()) { topParent = 0; str.str(""); return NotGroup; } - if(!topParent) + if (!topParent) topParent = obj; - else if(!obj->redirectSubName(str,topParent,object()->getObject())) + else if (!obj->redirectSubName(str, topParent, object()->getObject())) str << obj->getNameInDocument() << '.'; return ret; } -App::DocumentObject *DocumentObjectItem::getFullSubName( - std::ostringstream &str, DocumentObjectItem *parent) const +App::DocumentObject* DocumentObjectItem::getFullSubName( + std::ostringstream& str, DocumentObjectItem* parent) const { auto pi = getParentItem(); - if(this==parent || !pi || (!parent && !pi->isGroup())) + if (this == parent || !pi || (!parent && !pi->isGroup())) return object()->getObject(); - auto ret = pi->getFullSubName(str,parent); + auto ret = pi->getFullSubName(str, parent); str << getName() << '.'; return ret; } -App::DocumentObject *DocumentObjectItem::getRelativeParent( - std::ostringstream &str, DocumentObjectItem *cousin, - App::DocumentObject **topParent, std::string *topSubname) const +App::DocumentObject* DocumentObjectItem::getRelativeParent( + std::ostringstream& str, DocumentObjectItem* cousin, + App::DocumentObject** topParent, std::string* topSubname) const { std::ostringstream str2; - App::DocumentObject *top=0,*top2=0; - getSubName(str,top); - if(topParent) + App::DocumentObject* top = 0, * top2 = 0; + getSubName(str, top); + if (topParent) *topParent = top; - if(!top) + if (!top) return 0; - if(topSubname) + if (topSubname) *topSubname = str.str() + getName() + '.'; - cousin->getSubName(str2,top2); - if(top!=top2) { + cousin->getSubName(str2, top2); + if (top != top2) { str << getName() << '.'; return top; } auto subname = str.str(); auto subname2 = str2.str(); - const char *sub = subname.c_str(); - const char *sub2 = subname2.c_str(); - while(1) { - const char *dot = strchr(sub,'.'); - if(!dot) { + const char* sub = subname.c_str(); + const char* sub2 = subname2.c_str(); + while (1) { + const char* dot = strchr(sub, '.'); + if (!dot) { str.str(""); return 0; } - const char *dot2 = strchr(sub2,'.'); - if(!dot2 || dot-sub!=dot2-sub2 || strncmp(sub,sub2,dot-sub)!=0) { - auto substr = subname.substr(0,dot-subname.c_str()+1); + const char* dot2 = strchr(sub2, '.'); + if (!dot2 || dot - sub != dot2 - sub2 || strncmp(sub, sub2, dot - sub) != 0) { + auto substr = subname.substr(0, dot - subname.c_str() + 1); auto ret = top->getSubObject(substr.c_str()); - if(!top) { + if (!top) { FC_ERR("invalid subname " << top->getFullName() << '.' << substr); str.str(""); return 0; } str.str(""); - str << dot+1 << getName() << '.'; + str << dot + 1 << getName() << '.'; return ret; } - sub = dot+1; - sub2 = dot2+1; + sub = dot + 1; + sub2 = dot2 + 1; } str.str(""); return 0; @@ -5414,34 +5187,16 @@ void DocumentObjectItem::setCheckState(bool checked) { setData(0, Qt::CheckStateRole, QVariant()); } -DocumentItem *DocumentObjectItem::getParentDocument() const { +DocumentItem* DocumentObjectItem::getParentDocument() const { return getTree()->getDocumentItem(object()->getDocument()); } -DocumentItem *DocumentObjectItem::getOwnerDocument() const { +DocumentItem* DocumentObjectItem::getOwnerDocument() const { return myOwner; } -TreeWidget *DocumentObjectItem::getTree() const{ +TreeWidget* DocumentObjectItem::getTree() const { return static_cast(treeWidget()); } -void DocumentObjectItem::getExpandedSnapshot(std::vector &snapshot) const -{ - snapshot.push_back(isExpanded()); - - for (int i = 0; i < childCount(); ++i) { - static_cast(child(i))->getExpandedSnapshot(snapshot); - } -} - -void DocumentObjectItem::applyExpandedSnapshot(const std::vector &snapshot, std::vector::const_iterator &from) -{ - setExpanded(*from++); - - for (int i = 0; i < childCount(); ++i) { - static_cast(child(i))->applyExpandedSnapshot(snapshot, from); - } -} - #include "moc_Tree.cpp" diff --git a/src/Gui/Tree.h b/src/Gui/Tree.h index 6997af4456..d2b4575757 100644 --- a/src/Gui/Tree.h +++ b/src/Gui/Tree.h @@ -28,7 +28,6 @@ #include #include #include -#include #include #include @@ -132,12 +131,7 @@ public: void synchronizeSelectionCheckBoxes(); - bool getSelectedSiblingObjectItems(std::vector &items) const; - - bool allowMoveUpInGroup(const std::vector &items, DocumentObjectItem **preceding = 0) const; - bool allowMoveDownInGroup(const std::vector &items, DocumentObjectItem **succeeding = 0) const; - - static bool moveSiblings(const std::vector &items, DocumentObjectItem *pivot, int direction); + QList childrenOfItem(const QTreeWidgetItem &item) const; protected: /// Observer message from the Selection @@ -163,7 +157,6 @@ protected: protected: void showEvent(QShowEvent *) override; void hideEvent(QHideEvent *) override; - void focusInEvent(QFocusEvent *event) override; void leaveEvent(QEvent *) override; void _updateStatus(bool delay=true); @@ -192,7 +185,6 @@ private Q_SLOTS: void onItemEntered(QTreeWidgetItem * item); void onItemCollapsed(QTreeWidgetItem * item); void onItemExpanded(QTreeWidgetItem * item); - void onTabifiedDockWidgetActivated(QDockWidget *dockWidget); void onUpdateStatus(void); Q_SIGNALS: @@ -222,9 +214,6 @@ private: bool CheckForDependents(); void addDependentToSelection(App::Document* doc, App::DocumentObject* docObject); - typedef bool (*DocumentObjectItemComparator)(const DocumentObjectItem *a, const DocumentObjectItem *b); - bool sortObjectItems(QTreeWidgetItem *node, DocumentObjectItemComparator comparator); - private: QAction* createGroupAction; QAction* relabelObjectAction; @@ -263,8 +252,6 @@ private: std::unordered_map > NewObjects; - std::set ReorderedObjects; - static std::set Instances; std::string myName; // for debugging purpose @@ -356,7 +343,7 @@ protected: QTreeWidgetItem *parent=0, int index=-1, DocumentObjectDataPtr ptrs = DocumentObjectDataPtr()); - int findRootIndex(const ViewProviderDocumentObject *childObj) const; + int findRootIndex(App::DocumentObject *childObj); DocumentObjectItem *findItemByObject(bool sync, App::DocumentObject *obj, const char *subname, bool select=false); @@ -456,14 +443,8 @@ public: int isParentGroup() const; DocumentObjectItem *getParentItem() const; - DocumentObjectItem *getNextSibling() const; - DocumentObjectItem *getPreviousSibling() const; - TreeWidget *getTree() const; - void getExpandedSnapshot(std::vector &snapshot) const; - void applyExpandedSnapshot(const std::vector &snapshot, std::vector::const_iterator &from); - private: void setCheckState(bool checked); diff --git a/src/Gui/View3DInventor.cpp b/src/Gui/View3DInventor.cpp index 2b378dfb2f..ab8e284b10 100644 --- a/src/Gui/View3DInventor.cpp +++ b/src/Gui/View3DInventor.cpp @@ -162,6 +162,7 @@ View3DInventor::View3DInventor(Gui::Document* pcDocument, QWidget* parent, // apply the user settings OnChange(*hGrp,"EyeDistance"); OnChange(*hGrp,"CornerCoordSystem"); + OnChange(*hGrp,"CornerCoordSystemSize"); OnChange(*hGrp,"ShowAxisCross"); OnChange(*hGrp,"UseAutoRotation"); OnChange(*hGrp,"Gradient"); @@ -224,7 +225,7 @@ View3DInventor::~View3DInventor() } if (_viewerPy) { - static_cast(_viewerPy)->_view = 0; + Base::PyGILStateLocker lock; Py_DECREF(_viewerPy); } @@ -378,6 +379,9 @@ void View3DInventor::OnChange(ParameterGrp::SubjectType &rCaller,ParameterGrp::M else if (strcmp(Reason,"CornerCoordSystem") == 0) { _viewer->setFeedbackVisibility(rGrp.GetBool("CornerCoordSystem",true)); } + else if (strcmp(Reason,"CornerCoordSystemSize") == 0) { + _viewer->setFeedbackSize(rGrp.GetInt("CornerCoordSystemSize",10)); + } else if (strcmp(Reason,"ShowAxisCross") == 0) { _viewer->setAxisCross(rGrp.GetBool("ShowAxisCross",false)); } diff --git a/src/Gui/View3DInventorViewer.cpp b/src/Gui/View3DInventorViewer.cpp index bedc3c6d22..7befb531d8 100644 --- a/src/Gui/View3DInventorViewer.cpp +++ b/src/Gui/View3DInventorViewer.cpp @@ -150,6 +150,8 @@ #include "ViewProviderLink.h" +#include "CornerCrossLetters.h" + FC_LOG_LEVEL_INIT("3DViewer",true,true) //#define FC_LOGGING_CB @@ -3266,13 +3268,6 @@ void View3DInventorViewer::setViewing(SbBool enable) inherited::setViewing(enable); } -//**************************************************************************** - -// Bitmap representations of an "X", a "Y" and a "Z" for the axis cross. -static GLubyte xbmp[] = { 0x11,0x11,0x0a,0x04,0x0a,0x11,0x11 }; -static GLubyte ybmp[] = { 0x04,0x04,0x04,0x04,0x0a,0x11,0x11 }; -static GLubyte zbmp[] = { 0x1f,0x10,0x08,0x04,0x02,0x01,0x1f }; - void View3DInventorViewer::drawAxisCross(void) { // FIXME: convert this to a superimposition scenegraph instead of @@ -3429,12 +3424,15 @@ void View3DInventorViewer::drawAxisCross(void) else glColor3fv(SbVec3f(0.0f, 0.0f, 0.0f).getValue()); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glPixelZoom((float)axiscrossSize/30, (float)axiscrossSize/30); // 30 = 3 (character pixmap ratio) * 10 (default axiscrossSize) glRasterPos2d(xpos[0], xpos[1]); - glBitmap(8, 7, 0, 0, 0, 0, xbmp); + glDrawPixels(XPM_WIDTH, XPM_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, XPM_PIXEL_DATA); glRasterPos2d(ypos[0], ypos[1]); - glBitmap(8, 7, 0, 0, 0, 0, ybmp); + glDrawPixels(YPM_WIDTH, YPM_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, YPM_PIXEL_DATA); glRasterPos2d(zpos[0], zpos[1]); - glBitmap(8, 7, 0, 0, 0, 0, zbmp); + glDrawPixels(ZPM_WIDTH, ZPM_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, ZPM_PIXEL_DATA); glPixelStorei(GL_UNPACK_ALIGNMENT, unpack); glPopMatrix(); @@ -3453,11 +3451,33 @@ void View3DInventorViewer::drawAxisCross(void) // Draw an arrow for the axis representation directly through OpenGL. void View3DInventorViewer::drawArrow(void) { - glBegin(GL_LINES); - glVertex3f(0.0f, 0.0f, 0.0f); - glVertex3f(1.0f, 0.0f, 0.0f); - glEnd(); glDisable(GL_CULL_FACE); + glBegin(GL_QUADS); + glVertex3f(0.0f, -0.02f, 0.02f); + glVertex3f(0.0f, 0.02f, 0.02f); + glVertex3f(1.0f - 1.0f / 3.0f, 0.02f, 0.02f); + glVertex3f(1.0f - 1.0f / 3.0f, -0.02f, 0.02f); + + glVertex3f(0.0f, -0.02f, -0.02f); + glVertex3f(0.0f, 0.02f, -0.02f); + glVertex3f(1.0f - 1.0f / 3.0f, 0.02f, -0.02f); + glVertex3f(1.0f - 1.0f / 3.0f, -0.02f, -0.02f); + + glVertex3f(0.0f, -0.02f, 0.02f); + glVertex3f(0.0f, -0.02f, -0.02f); + glVertex3f(1.0f - 1.0f / 3.0f, -0.02f, -0.02f); + glVertex3f(1.0f - 1.0f / 3.0f, -0.02f, 0.02f); + + glVertex3f(0.0f, 0.02f, 0.02f); + glVertex3f(0.0f, 0.02f, -0.02f); + glVertex3f(1.0f - 1.0f / 3.0f, 0.02f, -0.02f); + glVertex3f(1.0f - 1.0f / 3.0f, 0.02f, 0.02f); + + glVertex3f(0.0f, 0.02f, 0.02f); + glVertex3f(0.0f, 0.02f, -0.02f); + glVertex3f(0.0f, -0.02f, -0.02f); + glVertex3f(0.0f, -0.02f, 0.02f); + glEnd(); glBegin(GL_TRIANGLES); glVertex3f(1.0f, 0.0f, 0.0f); glVertex3f(1.0f - 1.0f / 3.0f, +0.5f / 4.0f, 0.0f); diff --git a/src/Gui/View3DPy.cpp b/src/Gui/View3DPy.cpp index 0576eae5da..a5eae1992d 100644 --- a/src/Gui/View3DPy.cpp +++ b/src/Gui/View3DPy.cpp @@ -81,7 +81,6 @@ void View3DInventorPy::init_type() behaviors().supportGetattr(); behaviors().supportSetattr(); - add_varargs_method("message",&View3DInventorPy::message,"message()"); add_varargs_method("fitAll",&View3DInventorPy::fitAll,"fitAll()"); add_keyword_method("boxZoom",&View3DInventorPy::boxZoom,"boxZoom()"); @@ -208,10 +207,19 @@ void View3DInventorPy::init_type() "hasClippingPlane(): check whether this clipping plane is active"); add_varargs_method("graphicsView",&View3DInventorPy::graphicsView, "graphicsView(): Access this view as QGraphicsView"); + add_varargs_method("setCornerCrossVisible",&View3DInventorPy::setCornerCrossVisible, + "setCornerCrossVisible(bool): Defines corner axis cross visibility"); + add_varargs_method("isCornerCrossVisible",&View3DInventorPy::isCornerCrossVisible, + "isCornerCrossVisible(): Returns current corner axis cross visibility"); + add_varargs_method("setCornerCrossSize",&View3DInventorPy::setCornerCrossSize, + "setCornerCrossSize(int): Defines corner axis cross size"); + add_varargs_method("getCornerCrossSize",&View3DInventorPy::getCornerCrossSize, + "getCornerCrossSize(): Returns current corner axis cross size"); + add_varargs_method("cast_to_base", &View3DInventorPy::cast_to_base, "cast_to_base() cast to MDIView class"); } View3DInventorPy::View3DInventorPy(View3DInventor *vi) - : _view(vi) + : base(vi) { } @@ -222,11 +230,16 @@ View3DInventorPy::~View3DInventorPy() Py_DECREF(*it); } +View3DInventor* View3DInventorPy::getView3DIventorPtr() +{ + return qobject_cast(base.getMDIViewPtr()); +} + Py::Object View3DInventorPy::repr() { std::string s; std::ostringstream s_out; - if (!_view) + if (!getView3DIventorPtr()) throw Py::RuntimeError("Cannot print representation of deleted object"); s_out << "View3DInventor"; return Py::String(s_out.str()); @@ -245,28 +258,57 @@ PyObject *View3DInventorPy::method_varargs_ext_handler(PyObject *_self_and_name_ catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } + catch (const Py::Exception&) { + throw; + } catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } } +// Since with PyCXX it's not possible to make a sub-class of MDIViewPy +// a trick is to use MDIViewPy as class member and override getattr() to +// join the attributes of both classes. This way all methods of MDIViewPy +// appear for SheetViewPy, too. +Py::Object View3DInventorPy::getattribute(const char * attr) +{ + if (!getView3DIventorPtr()) + throw Py::RuntimeError("Cannot print representation of deleted object"); + std::string name( attr ); + if (name == "__dict__" || name == "__class__") { + Py::Dict dict_self(BaseType::getattr("__dict__")); + Py::Dict dict_base(base.getattr("__dict__")); + for (auto it : dict_base) { + dict_self.setItem(it.first, it.second); + } + return dict_self; + } + + try { + return BaseType::getattr(attr); + } + catch (Py::AttributeError& e) { + e.clear(); + return base.getattr(attr); + } +} + Py::Object View3DInventorPy::getattr(const char * attr) { - if (!_view) { - std::string s; + if (!getView3DIventorPtr()) { std::ostringstream s_out; s_out << "Cannot access attribute '" << attr << "' of deleted object"; throw Py::RuntimeError(s_out.str()); } else { // see if an active object has the same name - App::DocumentObject *docObj = _view->getActiveObject(attr); + App::DocumentObject *docObj = getView3DIventorPtr()->getActiveObject(attr); if (docObj) { return Py::Object(docObj->getPyObject(),true); } else { // else looking for a method with the name and call it - Py::Object obj = Py::PythonExtension::getattr(attr); + Py::Object obj = getattribute(attr); if (PyCFunction_Check(obj.ptr())) { PyCFunctionObject* op = reinterpret_cast(obj.ptr()); if (op->m_ml->ml_flags == METH_VARARGS) { @@ -282,39 +324,17 @@ Py::Object View3DInventorPy::getattr(const char * attr) int View3DInventorPy::setattr(const char * attr, const Py::Object & value) { - if (!_view) { + if (!getView3DIventorPtr()) { std::string s; std::ostringstream s_out; s_out << "Cannot access attribute '" << attr << "' of deleted object"; throw Py::RuntimeError(s_out.str()); } else { - return Py::PythonExtension::setattr(attr, value); + return BaseType::setattr(attr, value); } } -Py::Object View3DInventorPy::message(const Py::Tuple& args) -{ - const char **ppReturn = 0; - char *psMsgStr; - if (!PyArg_ParseTuple(args.ptr(), "s;Message string needed (string)",&psMsgStr)) // convert args: Python->C - throw Py::Exception(); - - try { - _view->onMsg(psMsgStr,ppReturn); - } - catch (const Base::Exception& e) { - throw Py::RuntimeError(e.what()); - } - catch (const std::exception& e) { - throw Py::RuntimeError(e.what()); - } - catch (...) { - throw Py::RuntimeError("Unknown C++ exception"); - } - return Py::None(); -} - Py::Object View3DInventorPy::fitAll(const Py::Tuple& args) { double factor = 1.0; @@ -322,7 +342,7 @@ Py::Object View3DInventorPy::fitAll(const Py::Tuple& args) throw Py::Exception(); try { - _view->getViewer()->viewAll((float)factor); + getView3DIventorPtr()->getViewer()->viewAll((float)factor); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -345,7 +365,7 @@ Py::Object View3DInventorPy::boxZoom(const Py::Tuple& args, const Py::Dict& kwds throw Py::Exception(); SbBox2s box(xmin, ymin, xmax, ymax); - _view->getViewer()->boxZoom(box); + getView3DIventorPtr()->getViewer()->boxZoom(box); return Py::None(); } @@ -468,7 +488,7 @@ Py::Object View3DInventorPy::viewBottom(const Py::Tuple& args) throw Py::Exception(); try { - _view->getViewer()->setCameraOrientation(Camera::rotation(Camera::Bottom)); + getView3DIventorPtr()->getViewer()->setCameraOrientation(Camera::rotation(Camera::Bottom)); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -489,7 +509,7 @@ Py::Object View3DInventorPy::viewFront(const Py::Tuple& args) throw Py::Exception(); try { - _view->getViewer()->setCameraOrientation(Camera::rotation(Camera::Front)); + getView3DIventorPtr()->getViewer()->setCameraOrientation(Camera::rotation(Camera::Front)); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -510,7 +530,7 @@ Py::Object View3DInventorPy::viewLeft(const Py::Tuple& args) throw Py::Exception(); try { - _view->getViewer()->setCameraOrientation(Camera::rotation(Camera::Left)); + getView3DIventorPtr()->getViewer()->setCameraOrientation(Camera::rotation(Camera::Left)); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -531,7 +551,7 @@ Py::Object View3DInventorPy::viewRear(const Py::Tuple& args) throw Py::Exception(); try { - _view->getViewer()->setCameraOrientation(Camera::rotation(Camera::Rear)); + getView3DIventorPtr()->getViewer()->setCameraOrientation(Camera::rotation(Camera::Rear)); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -552,7 +572,7 @@ Py::Object View3DInventorPy::viewRight(const Py::Tuple& args) throw Py::Exception(); try { - _view->getViewer()->setCameraOrientation(Camera::rotation(Camera::Right)); + getView3DIventorPtr()->getViewer()->setCameraOrientation(Camera::rotation(Camera::Right)); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -573,7 +593,7 @@ Py::Object View3DInventorPy::viewTop(const Py::Tuple& args) throw Py::Exception(); try { - _view->getViewer()->setCameraOrientation(Camera::rotation(Camera::Top)); + getView3DIventorPtr()->getViewer()->setCameraOrientation(Camera::rotation(Camera::Top)); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -594,7 +614,7 @@ Py::Object View3DInventorPy::viewIsometric(const Py::Tuple& args) throw Py::Exception(); try { - _view->getViewer()->setCameraOrientation(Camera::rotation(Camera::Isometric)); + getView3DIventorPtr()->getViewer()->setCameraOrientation(Camera::rotation(Camera::Isometric)); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -615,7 +635,7 @@ Py::Object View3DInventorPy::viewDimetric(const Py::Tuple& args) throw Py::Exception(); try { - _view->getViewer()->setCameraOrientation(Camera::rotation(Camera::Dimetric)); + getView3DIventorPtr()->getViewer()->setCameraOrientation(Camera::rotation(Camera::Dimetric)); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -636,7 +656,7 @@ Py::Object View3DInventorPy::viewTrimetric(const Py::Tuple& args) throw Py::Exception(); try { - _view->getViewer()->setCameraOrientation(Camera::rotation(Camera::Trimetric)); + getView3DIventorPtr()->getViewer()->setCameraOrientation(Camera::rotation(Camera::Trimetric)); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -705,7 +725,7 @@ Py::Object View3DInventorPy::viewDefaultOrientation(const Py::Tuple& args) rot.setValue(q0, q1, q2, q3); } - SoCamera* cam = _view->getViewer()->getCamera(); + SoCamera* cam = getView3DIventorPtr()->getViewer()->getCamera(); cam->orientation = rot; if (scale < 0.0){ @@ -748,7 +768,7 @@ Py::Object View3DInventorPy::viewRotateLeft(const Py::Tuple& args) throw Py::Exception(); try { - SoCamera* cam = _view->getViewer()->getSoRenderManager()->getCamera(); + SoCamera* cam = getView3DIventorPtr()->getViewer()->getSoRenderManager()->getCamera(); SbRotation rot = cam->orientation.getValue(); SbVec3f vdir(0, 0, -1); rot.multVec(vdir, vdir); @@ -774,7 +794,7 @@ Py::Object View3DInventorPy::viewRotateRight(const Py::Tuple& args) throw Py::Exception(); try { - SoCamera* cam = _view->getViewer()->getSoRenderManager()->getCamera(); + SoCamera* cam = getView3DIventorPtr()->getViewer()->getSoRenderManager()->getCamera(); SbRotation rot = cam->orientation.getValue(); SbVec3f vdir(0, 0, -1); rot.multVec(vdir, vdir); @@ -800,7 +820,7 @@ Py::Object View3DInventorPy::zoomIn(const Py::Tuple& args) throw Py::Exception(); try { - _view->getViewer()->navigationStyle()->zoomIn(); + getView3DIventorPtr()->getViewer()->navigationStyle()->zoomIn(); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -821,7 +841,7 @@ Py::Object View3DInventorPy::zoomOut(const Py::Tuple& args) throw Py::Exception(); try { - _view->getViewer()->navigationStyle()->zoomOut(); + getView3DIventorPtr()->getViewer()->navigationStyle()->zoomOut(); } catch (const Base::Exception& e) { throw Py::RuntimeError(e.what()); @@ -850,13 +870,13 @@ Py::Object View3DInventorPy::setCameraOrientation(const Py::Tuple& args) float q1 = (float)Py::Float(tuple[1]); float q2 = (float)Py::Float(tuple[2]); float q3 = (float)Py::Float(tuple[3]); - _view->getViewer()->setCameraOrientation(SbRotation(q0, q1, q2, q3), PyObject_IsTrue(m)); + getView3DIventorPtr()->getViewer()->setCameraOrientation(SbRotation(q0, q1, q2, q3), PyObject_IsTrue(m)); } else if (PyObject_TypeCheck(o, &Base::RotationPy::Type)) { Base::Rotation r = (Base::Rotation)Py::Rotation(o,false); double q0, q1, q2, q3; r.getValue(q0, q1, q2, q3); - _view->getViewer()->setCameraOrientation(SbRotation((float)q0, (float)q1, (float)q2, (float)q3), PyObject_IsTrue(m)); + getView3DIventorPtr()->getViewer()->setCameraOrientation(SbRotation((float)q0, (float)q1, (float)q2, (float)q3), PyObject_IsTrue(m)); } else { throw Py::ValueError("Neither tuple nor rotation object"); @@ -882,7 +902,7 @@ Py::Object View3DInventorPy::getCameraOrientation(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - SbRotation rot = _view->getViewer()->getCameraOrientation(); + SbRotation rot = getView3DIventorPtr()->getViewer()->getCameraOrientation(); float q0,q1,q2,q3; rot.getValue(q0,q1,q2,q3); return Py::Rotation(Base::Rotation(q0,q1,q2,q3)); @@ -902,12 +922,12 @@ Py::Object View3DInventorPy::viewPosition(const Py::Tuple& args) Base::Vector3d pos = plm->getPosition(); double q0,q1,q2,q3; rot.getValue(q0,q1,q2,q3); - _view->getViewer()->moveCameraTo( + getView3DIventorPtr()->getViewer()->moveCameraTo( SbRotation((float)q0, (float)q1, (float)q2, (float)q3), SbVec3f((float)pos.x, (float)pos.y, (float)pos.z), steps, ms); } - SoCamera* cam = _view->getViewer()->getSoRenderManager()->getCamera(); + SoCamera* cam = getView3DIventorPtr()->getViewer()->getSoRenderManager()->getCamera(); if (!cam) return Py::None(); SbRotation rot = cam->orientation.getValue(); @@ -926,7 +946,7 @@ Py::Object View3DInventorPy::startAnimating(const Py::Tuple& args) float velocity; if (!PyArg_ParseTuple(args.ptr(), "ffff", &x,&y,&z,&velocity)) throw Py::Exception(); - _view->getViewer()->startAnimating(SbVec3f(x,y,z),velocity); + getView3DIventorPtr()->getViewer()->startAnimating(SbVec3f(x,y,z),velocity); return Py::None(); } @@ -934,7 +954,7 @@ Py::Object View3DInventorPy::stopAnimating(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - _view->getViewer()->stopAnimating(); + getView3DIventorPtr()->getViewer()->stopAnimating(); return Py::None(); } @@ -943,7 +963,7 @@ Py::Object View3DInventorPy::setAnimationEnabled(const Py::Tuple& args) int ok; if (!PyArg_ParseTuple(args.ptr(), "i", &ok)) throw Py::Exception(); - _view->getViewer()->setAnimationEnabled(ok!=0); + getView3DIventorPtr()->getViewer()->setAnimationEnabled(ok!=0); return Py::None(); } @@ -951,7 +971,7 @@ Py::Object View3DInventorPy::isAnimationEnabled(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - SbBool ok = _view->getViewer()->isAnimationEnabled(); + SbBool ok = getView3DIventorPtr()->getViewer()->isAnimationEnabled(); return Py::Boolean(ok ? true : false); } @@ -960,7 +980,7 @@ Py::Object View3DInventorPy::setPopupMenuEnabled(const Py::Tuple& args) int ok; if (!PyArg_ParseTuple(args.ptr(), "i", &ok)) throw Py::Exception(); - _view->getViewer()->setPopupMenuEnabled(ok!=0); + getView3DIventorPtr()->getViewer()->setPopupMenuEnabled(ok!=0); return Py::None(); } @@ -968,7 +988,7 @@ Py::Object View3DInventorPy::isPopupMenuEnabled(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - SbBool ok = _view->getViewer()->isPopupMenuEnabled(); + SbBool ok = getView3DIventorPtr()->getViewer()->isPopupMenuEnabled(); return Py::Boolean(ok ? true : false); } @@ -996,10 +1016,10 @@ Py::Object View3DInventorPy::saveImage(const Py::Tuple& args) bg.setNamedColor(colname); QImage img; - _view->getViewer()->savePicture(w, h, s, bg, img); + getView3DIventorPtr()->getViewer()->savePicture(w, h, s, bg, img); SoFCOffscreenRenderer& renderer = SoFCOffscreenRenderer::instance(); - SoCamera* cam = _view->getViewer()->getSoRenderManager()->getCamera(); + SoCamera* cam = getView3DIventorPtr()->getViewer()->getSoRenderManager()->getCamera(); renderer.writeToImageFile(encodedName.c_str(), cComment, cam->getViewVolume().getMatrix(), img); return Py::None(); @@ -1040,11 +1060,11 @@ Py::Object View3DInventorPy::saveVectorGraphic(const Py::Tuple& args) QColor bg; QString colname = QString::fromLatin1(name); if (colname.compare(QLatin1String("Current"), Qt::CaseInsensitive) == 0) - bg = _view->getViewer()->backgroundColor(); + bg = getView3DIventorPtr()->getViewer()->backgroundColor(); else bg.setNamedColor(colname); - _view->getViewer()->saveGraphic(ps,bg,vo.get()); + getView3DIventorPtr()->getViewer()->saveGraphic(ps,bg,vo.get()); out->closeFile(); return Py::None(); } @@ -1055,7 +1075,7 @@ Py::Object View3DInventorPy::getCameraNode(const Py::Tuple& args) throw Py::Exception(); try { - SoNode* camera = _view->getViewer()->getSoRenderManager()->getCamera(); + SoNode* camera = getView3DIventorPtr()->getViewer()->getSoRenderManager()->getCamera(); PyObject* proxy = 0; std::string type; type = "So"; // seems that So prefix is missing in camera node @@ -1081,7 +1101,7 @@ Py::Object View3DInventorPy::getCamera(const Py::Tuple& args) try { SoWriteAction wa(&out); - SoCamera * cam = _view->getViewer()->getSoRenderManager()->getCamera(); + SoCamera * cam = getView3DIventorPtr()->getViewer()->getSoRenderManager()->getCamera(); if (cam) wa.apply(cam); else buffer[0] = '\0'; return Py::String(buffer); @@ -1102,7 +1122,7 @@ Py::Object View3DInventorPy::getViewDirection(const Py::Tuple& args) if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); try { - SbVec3f dvec = _view->getViewer()->getViewDirection(); + SbVec3f dvec = getView3DIventorPtr()->getViewer()->getViewDirection(); return Py::Vector(Base::Vector3f(dvec[0], dvec[1], dvec[2])); } catch (const Base::Exception& e) { @@ -1132,7 +1152,7 @@ Py::Object View3DInventorPy::setViewDirection(const Py::Tuple& args) dir.setValue((float)x, (float)y, (float)z); if (dir.length() < 0.001f) throw Py::ValueError("Null vector cannot be used to set direction"); - _view->getViewer()->setViewDirection(dir); + getView3DIventorPtr()->getViewer()->setViewDirection(dir); return Py::None(); } } @@ -1161,7 +1181,7 @@ Py::Object View3DInventorPy::setCamera(const Py::Tuple& args) throw Py::Exception(); try { - _view->setCamera(buffer); + getView3DIventorPtr()->setCamera(buffer); return Py::None(); } catch (const Base::Exception& e) { @@ -1183,7 +1203,7 @@ Py::Object View3DInventorPy::getCameraType(const Py::Tuple& args) if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - SoCamera* cam = _view->getViewer()->getSoRenderManager()->getCamera(); + SoCamera* cam = getView3DIventorPtr()->getViewer()->getSoRenderManager()->getCamera(); if (!cam) { throw Py::RuntimeError("No camera set!"); } @@ -1224,9 +1244,9 @@ Py::Object View3DInventorPy::setCameraType(const Py::Tuple& args) if (cameratype < 0 || cameratype > 1) throw Py::IndexError("Out of range"); if (cameratype==0) - _view->getViewer()->setCameraType(SoOrthographicCamera::getClassTypeId()); + getView3DIventorPtr()->getViewer()->setCameraType(SoOrthographicCamera::getClassTypeId()); else - _view->getViewer()->setCameraType(SoPerspectiveCamera::getClassTypeId()); + getView3DIventorPtr()->getViewer()->setCameraType(SoPerspectiveCamera::getClassTypeId()); return Py::None(); } @@ -1261,7 +1281,7 @@ Py::Object View3DInventorPy::dump(const Py::Tuple& args) throw Py::Exception(); try { - _view->dump(filename, PyObject_IsTrue(onlyVisible)); + getView3DIventorPtr()->dump(filename, PyObject_IsTrue(onlyVisible)); return Py::None(); } catch (const Base::Exception& e) { @@ -1322,7 +1342,7 @@ Py::Object View3DInventorPy::setStereoType(const Py::Tuple& args) if (stereomode < 0 || stereomode > 4) throw Py::IndexError("Out of range"); Quarter::SoQTQuarterAdaptor::StereoMode mode = Quarter::SoQTQuarterAdaptor::StereoMode(stereomode); - _view->getViewer()->setStereoMode(mode); + getView3DIventorPtr()->getViewer()->setStereoMode(mode); return Py::None(); } catch (const Base::Exception& e) { @@ -1342,7 +1362,7 @@ Py::Object View3DInventorPy::getStereoType(const Py::Tuple& args) throw Py::Exception(); try { - int mode = (int)(_view->getViewer()->stereoMode()); + int mode = (int)(getView3DIventorPtr()->getViewer()->stereoMode()); return Py::String(StereoTypeEnums[mode]); } catch (const Base::Exception& e) { @@ -1385,10 +1405,10 @@ Py::Object View3DInventorPy::getCursorPos(const Py::Tuple& args) if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); try { - QPoint pos = _view->mapFromGlobal(QCursor::pos()); + QPoint pos = getView3DIventorPtr()->mapFromGlobal(QCursor::pos()); Py::Tuple tuple(2); tuple.setItem(0, Py::Int(pos.x())); - tuple.setItem(1, Py::Int(_view->height()-pos.y()-1)); + tuple.setItem(1, Py::Int(getView3DIventorPtr()->height()-pos.y()-1)); return tuple; } catch (const Py::Exception&) { @@ -1399,7 +1419,7 @@ Py::Object View3DInventorPy::getCursorPos(const Py::Tuple& args) Py::Object View3DInventorPy::getObjectInfo(const Py::Tuple& args) { PyObject* object; - float r = _view->getViewer()->getPickRadius(); + float r = getView3DIventorPtr()->getViewer()->getPickRadius(); if (!PyArg_ParseTuple(args.ptr(), "O|f", &object, &r)) throw Py::Exception(); @@ -1417,10 +1437,10 @@ Py::Object View3DInventorPy::getObjectInfo(const Py::Tuple& args) // graph traversal we must not use a second SoHandleEventAction as // we will get Coin warnings because of multiple scene graph traversals // which is regarded as error-prone. - SoRayPickAction action(_view->getViewer()->getSoRenderManager()->getViewportRegion()); + SoRayPickAction action(getView3DIventorPtr()->getViewer()->getSoRenderManager()->getViewportRegion()); action.setPoint(SbVec2s((long)x,(long)y)); action.setRadius(r); - action.apply(_view->getViewer()->getSoRenderManager()->getSceneGraph()); + action.apply(getView3DIventorPtr()->getViewer()->getSoRenderManager()->getSceneGraph()); SoPickedPoint *Point = action.getPickedPoint(); Py::Object ret = Py::None(); @@ -1431,9 +1451,9 @@ Py::Object View3DInventorPy::getObjectInfo(const Py::Tuple& args) dict.setItem("y", Py::Float(pt[1])); dict.setItem("z", Py::Float(pt[2])); - Gui::Document* doc = _view->getViewer()->getDocument(); + Gui::Document* doc = getView3DIventorPtr()->getViewer()->getDocument(); ViewProvider *vp = doc ? doc->getViewProviderByPathFromHead(Point->getPath()) - : _view->getViewer()->getViewProviderByPath(Point->getPath()); + : getView3DIventorPtr()->getViewer()->getViewProviderByPath(Point->getPath()); if (vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { if (!vp->isSelectable()) return ret; @@ -1507,7 +1527,7 @@ Py::Object View3DInventorPy::getObjectInfo(const Py::Tuple& args) Py::Object View3DInventorPy::getObjectsInfo(const Py::Tuple& args) { PyObject* object; - float r = _view->getViewer()->getPickRadius(); + float r = getView3DIventorPtr()->getViewer()->getPickRadius(); if (!PyArg_ParseTuple(args.ptr(), "O|f", &object, &r)) throw Py::Exception(); @@ -1525,14 +1545,14 @@ Py::Object View3DInventorPy::getObjectsInfo(const Py::Tuple& args) // graph traversal we must not use a second SoHandleEventAction as // we will get Coin warnings because of multiple scene graph traversals // which is regarded as error-prone. - SoRayPickAction action(_view->getViewer()->getSoRenderManager()->getViewportRegion()); + SoRayPickAction action(getView3DIventorPtr()->getViewer()->getSoRenderManager()->getViewportRegion()); action.setPickAll(true); action.setRadius(r); action.setPoint(SbVec2s((long)x,(long)y)); - action.apply(_view->getViewer()->getSoRenderManager()->getSceneGraph()); + action.apply(getView3DIventorPtr()->getViewer()->getSoRenderManager()->getSceneGraph()); const SoPickedPointList& pp = action.getPickedPointList(); - Gui::Document* doc = _view->getViewer()->getDocument(); + Gui::Document* doc = getView3DIventorPtr()->getViewer()->getDocument(); Py::Object ret = Py::None(); if (pp.getLength() > 0) { Py::List list; @@ -1545,7 +1565,7 @@ Py::Object View3DInventorPy::getObjectsInfo(const Py::Tuple& args) dict.setItem("z", Py::Float(pt[2])); ViewProvider *vp = doc ? doc->getViewProviderByPathFromHead(point->getPath()) - : _view->getViewer()->getViewProviderByPath(point->getPath()); + : getView3DIventorPtr()->getViewer()->getViewProviderByPath(point->getPath()); if(vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { if(!vp->isSelectable()) continue; @@ -1623,7 +1643,7 @@ Py::Object View3DInventorPy::getSize(const Py::Tuple& args) if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); try { - SbVec2s size = _view->getViewer()->getSoRenderManager()->getSize(); + SbVec2s size = getView3DIventorPtr()->getViewer()->getSoRenderManager()->getSize(); Py::Tuple tuple(2); tuple.setItem(0, Py::Int(size[0])); tuple.setItem(1, Py::Int(size[1])); @@ -1644,7 +1664,7 @@ Py::Object View3DInventorPy::getPoint(const Py::Tuple& args) y = (int)Py::Int(t[1]); } try { - SbVec3f pt = _view->getViewer()->getPointOnScreen(SbVec2s(x,y)); + SbVec3f pt = getView3DIventorPtr()->getViewer()->getPointOnScreen(SbVec2s(x,y)); return Py::Vector(Base::Vector3f(pt[0], pt[1], pt[2])); } catch (const Base::Exception& e) { @@ -1673,11 +1693,11 @@ Py::Object View3DInventorPy::getPointOnScreen(const Py::Tuple& args) } try { - const SbViewportRegion& vp = _view->getViewer()->getSoRenderManager()->getViewportRegion(); + const SbViewportRegion& vp = getView3DIventorPtr()->getViewer()->getSoRenderManager()->getViewportRegion(); float fRatio = vp.getViewportAspectRatio(); const SbVec2s& sp = vp.getViewportSizePixels(); //float dX, dY; vp.getViewportSize().getValue(dX, dY); - SbViewVolume vv = _view->getViewer()->getSoRenderManager()->getCamera()->getViewVolume(fRatio); + SbViewVolume vv = getView3DIventorPtr()->getViewer()->getSoRenderManager()->getCamera()->getViewVolume(fRatio); SbVec3f pt(vx,vy,vz); vv.projectToScreen(pt, pt); @@ -1718,7 +1738,7 @@ Py::Object View3DInventorPy::listNavigationTypes(const Py::Tuple&) Py::Object View3DInventorPy::getNavigationType(const Py::Tuple&) { - std::string name = _view->getViewer()->navigationStyle()->getTypeId().getName(); + std::string name = getView3DIventorPtr()->getViewer()->navigationStyle()->getTypeId().getName(); return Py::String(name); } @@ -1728,7 +1748,7 @@ Py::Object View3DInventorPy::setNavigationType(const Py::Tuple& args) if (!PyArg_ParseTuple(args.ptr(), "s", &style)) throw Py::Exception(); Base::Type type = Base::Type::fromName(style); - _view->getViewer()->setNavigationType(type); + getView3DIventorPtr()->getViewer()->setNavigationType(type); return Py::None(); } @@ -2109,7 +2129,7 @@ Py::Object View3DInventorPy::addEventCallback(const Py::Tuple& args) throw Py::TypeError(s_out.str()); } - _view->getViewer()->addEventCallback(eventId, View3DInventorPy::eventCallback, method); + getView3DIventorPtr()->getViewer()->addEventCallback(eventId, View3DInventorPy::eventCallback, method); callbacks.push_back(method); Py_INCREF(method); return Py::Callable(method, false); @@ -2137,7 +2157,7 @@ Py::Object View3DInventorPy::removeEventCallback(const Py::Tuple& args) throw Py::TypeError(s_out.str()); } - _view->getViewer()->removeEventCallback(eventId, View3DInventorPy::eventCallback, method); + getView3DIventorPtr()->getViewer()->removeEventCallback(eventId, View3DInventorPy::eventCallback, method); callbacks.remove(method); Py_DECREF(method); return Py::None(); @@ -2162,7 +2182,7 @@ Py::Object View3DInventorPy::setAnnotation(const Py::Tuple& args) throw Py::RuntimeError(e.what()); } - _view->getGuiDocument()->setAnnotationViewProvider(psAnnoName, view); + getView3DIventorPtr()->getGuiDocument()->setAnnotationViewProvider(psAnnoName, view); return Py::None(); } @@ -2172,9 +2192,9 @@ Py::Object View3DInventorPy::removeAnnotation(const Py::Tuple& args) if (!PyArg_ParseTuple(args.ptr(), "s", &psAnnoName)) throw Py::Exception(); ViewProvider* view = 0; - view = _view->getGuiDocument()->getAnnotationViewProvider(psAnnoName); + view = getView3DIventorPtr()->getGuiDocument()->getAnnotationViewProvider(psAnnoName); if (view) { - _view->getGuiDocument()->removeAnnotationViewProvider(psAnnoName); + getView3DIventorPtr()->getGuiDocument()->removeAnnotationViewProvider(psAnnoName); return Py::None(); } else { @@ -2191,7 +2211,7 @@ Py::Object View3DInventorPy::getSceneGraph(const Py::Tuple& args) throw Py::Exception(); try { - SoNode* scene = _view->getViewer()->getSceneGraph(); + SoNode* scene = getView3DIventorPtr()->getViewer()->getSceneGraph(); PyObject* proxy = 0; proxy = Base::Interpreter().createSWIGPointerObj("pivy.coin", "SoSeparator *", (void*)scene, 1); scene->ref(); @@ -2207,7 +2227,7 @@ Py::Object View3DInventorPy::getViewer(const Py::Tuple& args) if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - View3DInventorViewer* viewer = _view->getViewer(); + View3DInventorViewer* viewer = getView3DIventorPtr()->getViewer(); return Py::Object(viewer->getPyObject(), true); } @@ -2311,7 +2331,7 @@ Py::Object View3DInventorPy::addEventCallbackPivy(const Py::Tuple& args) SoEventCallbackCB* callback = (ex == 1 ? View3DInventorPy::eventCallbackPivyEx : View3DInventorPy::eventCallbackPivy); - _view->getViewer()->addEventCallback(*eventId, callback, method); + getView3DIventorPtr()->getViewer()->addEventCallback(*eventId, callback, method); callbacks.push_back(method); Py_INCREF(method); return Py::Callable(method, false); @@ -2353,7 +2373,7 @@ Py::Object View3DInventorPy::removeEventCallbackPivy(const Py::Tuple& args) SoEventCallbackCB* callback = (ex == 1 ? View3DInventorPy::eventCallbackPivyEx : View3DInventorPy::eventCallbackPivy); - _view->getViewer()->removeEventCallback(*eventId, callback, method); + getView3DIventorPtr()->getViewer()->removeEventCallback(*eventId, callback, method); callbacks.remove(method); Py_DECREF(method); return Py::Callable(method, false); @@ -2368,7 +2388,7 @@ Py::Object View3DInventorPy::setAxisCross(const Py::Tuple& args) int ok; if (!PyArg_ParseTuple(args.ptr(), "i", &ok)) throw Py::Exception(); - _view->getViewer()->setAxisCross(ok!=0); + getView3DIventorPtr()->getViewer()->setAxisCross(ok!=0); return Py::None(); } @@ -2376,7 +2396,7 @@ Py::Object View3DInventorPy::hasAxisCross(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - SbBool ok = _view->getViewer()->hasAxisCross(); + SbBool ok = getView3DIventorPtr()->getViewer()->hasAxisCross(); return Py::Boolean(ok ? true : false); } @@ -2520,13 +2540,13 @@ Py::Object View3DInventorPy::setActiveObject(const Py::Tuple& args) throw Py::Exception(); if (docObject == Py_None) { - _view->setActiveObject(0, name); + getView3DIventorPtr()->setActiveObject(0, name); } else { if (!PyObject_TypeCheck(docObject, &App::DocumentObjectPy::Type)) throw Py::TypeError("Expect the second argument to be a document object or None"); App::DocumentObject* obj = static_cast(docObject)->getDocumentObjectPtr(); - _view->setActiveObject(obj, name, subname); + getView3DIventorPtr()->setActiveObject(obj, name, subname); } return Py::None(); @@ -2541,7 +2561,7 @@ Py::Object View3DInventorPy::getActiveObject(const Py::Tuple& args) App::DocumentObject *parent = 0; std::string subname; - App::DocumentObject* obj = _view->getActiveObject(name,&parent,&subname); + App::DocumentObject* obj = getView3DIventorPtr()->getActiveObject(name,&parent,&subname); if (!obj) return Py::None(); @@ -2560,7 +2580,7 @@ Py::Object View3DInventorPy::getViewProvidersOfType(const Py::Tuple& args) if (!PyArg_ParseTuple(args.ptr(), "s", &name)) throw Py::Exception(); - std::vector vps = _view->getViewer()->getViewProvidersOfType(Base::Type::fromName(name)); + std::vector vps = getView3DIventorPtr()->getViewer()->getViewProvidersOfType(Base::Type::fromName(name)); Py::List list; for (std::vector::iterator it = vps.begin(); it != vps.end(); ++it) { list.append(Py::asObject((*it)->getPyObject())); @@ -2573,7 +2593,7 @@ Py::Object View3DInventorPy::redraw(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - _view->getViewer()->redraw(); + getView3DIventorPtr()->getViewer()->redraw(); return Py::None(); } @@ -2584,7 +2604,7 @@ Py::Object View3DInventorPy::setName(const Py::Tuple& args) throw Py::Exception(); try { - _view->setWindowTitle(QString::fromUtf8(buffer)); + getView3DIventorPtr()->setWindowTitle(QString::fromUtf8(buffer)); return Py::None(); } catch (const Base::Exception& e) { @@ -2612,7 +2632,7 @@ Py::Object View3DInventorPy::toggleClippingPlane(const Py::Tuple& args, const Py Base::Placement pla; if(pyPla!=Py_None) pla = *static_cast(pyPla)->getPlacementPtr(); - _view->getViewer()->toggleClippingPlane(toggle,PyObject_IsTrue(beforeEditing), + getView3DIventorPtr()->getViewer()->toggleClippingPlane(toggle,PyObject_IsTrue(beforeEditing), PyObject_IsTrue(noManip),pla); return Py::None(); } @@ -2621,7 +2641,7 @@ Py::Object View3DInventorPy::hasClippingPlane(const Py::Tuple& args) { if (!PyArg_ParseTuple(args.ptr(), "")) throw Py::Exception(); - return Py::Boolean(_view->getViewer()->hasClippingPlane()); + return Py::Boolean(getView3DIventorPtr()->getViewer()->hasClippingPlane()); } Py::Object View3DInventorPy::graphicsView(const Py::Tuple& args) @@ -2631,5 +2651,46 @@ Py::Object View3DInventorPy::graphicsView(const Py::Tuple& args) PythonWrapper wrap; wrap.loadWidgetsModule(); - return wrap.fromQWidget(_view->getViewer(), "QGraphicsView"); + return wrap.fromQWidget(getView3DIventorPtr()->getViewer(), "QGraphicsView"); +} + +Py::Object View3DInventorPy::setCornerCrossVisible(const Py::Tuple& args) +{ + int ok; + if (!PyArg_ParseTuple(args.ptr(), "i", &ok)) + throw Py::Exception(); + getView3DIventorPtr()->getViewer()->setFeedbackVisibility(ok!=0); + getView3DIventorPtr()->getViewer()->redraw(); // added because isViewing() returns False when focus is in Python Console + return Py::None(); +} + +Py::Object View3DInventorPy::isCornerCrossVisible(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + bool ok = getView3DIventorPtr()->getViewer()->isFeedbackVisible(); + return Py::Boolean(ok ? true : false); +} + +Py::Object View3DInventorPy::setCornerCrossSize(const Py::Tuple& args) +{ + int size=0; + if (!PyArg_ParseTuple(args.ptr(), "i", &size)) + throw Py::Exception(); + getView3DIventorPtr()->getViewer()->setFeedbackSize(size); + getView3DIventorPtr()->getViewer()->redraw(); // added because isViewing() returns False when focus is in Python Console + return Py::None(); +} + +Py::Object View3DInventorPy::getCornerCrossSize(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + int size = getView3DIventorPtr()->getViewer()->getFeedbackSize(); + return Py::Int(size); +} + +Py::Object View3DInventorPy::cast_to_base(const Py::Tuple&) +{ + return Gui::MDIViewPy::create(getView3DIventorPtr()); } diff --git a/src/Gui/View3DPy.h b/src/Gui/View3DPy.h index d84c073c34..49076c6645 100644 --- a/src/Gui/View3DPy.h +++ b/src/Gui/View3DPy.h @@ -26,6 +26,8 @@ #include #include +#include +#include class SoEventCallback; class SoDragger; @@ -56,16 +58,18 @@ public: class View3DInventorPy : public Py::PythonExtension { public: - static void init_type(void); // announce properties and methods + using BaseType = Py::PythonExtension; + static void init_type(); // announce properties and methods View3DInventorPy(View3DInventor *vi); ~View3DInventorPy(); + View3DInventor* getView3DIventorPtr(); Py::Object repr(); Py::Object getattr(const char *); int setattr(const char *, const Py::Object &); + Py::Object cast_to_base(const Py::Tuple&); - Py::Object message(const Py::Tuple&); Py::Object fitAll(const Py::Tuple&); Py::Object boxZoom(const Py::Tuple&, const Py::Dict&); Py::Object viewBottom(const Py::Tuple&); @@ -135,8 +139,10 @@ public: Py::Object toggleClippingPlane(const Py::Tuple& args, const Py::Dict &); Py::Object hasClippingPlane(const Py::Tuple& args); Py::Object graphicsView(const Py::Tuple& args); - - View3DInventor* getView3DIventorPtr() {return _view;} + Py::Object setCornerCrossVisible(const Py::Tuple& args); + Py::Object isCornerCrossVisible(const Py::Tuple& args); + Py::Object setCornerCrossSize(const Py::Tuple& args); + Py::Object getCornerCrossSize(const Py::Tuple& args); private: static void eventCallback(void * ud, SoEventCallback * n); @@ -148,11 +154,11 @@ private: typedef PyObject* (*method_varargs_handler)(PyObject *_self, PyObject *_args); static method_varargs_handler pycxx_handler; static PyObject *method_varargs_ext_handler(PyObject *_self, PyObject *_args); + Py::Object getattribute(const char *); private: + Gui::MDIViewPy base; std::list callbacks; - View3DInventor* _view; - friend class View3DInventor; }; } // namespace Gui diff --git a/src/Gui/ViewProviderDocumentObject.cpp b/src/Gui/ViewProviderDocumentObject.cpp index 208b18b1b8..fc7923a3bf 100644 --- a/src/Gui/ViewProviderDocumentObject.cpp +++ b/src/Gui/ViewProviderDocumentObject.cpp @@ -66,8 +66,6 @@ FC_LOG_LEVEL_INIT("Gui",true,true) using namespace Gui; -int ViewProviderDocumentObject::lastTreeRank = 0; - PROPERTY_SOURCE(Gui::ViewProviderDocumentObject, Gui::ViewProvider) ViewProviderDocumentObject::ViewProviderDocumentObject() @@ -92,8 +90,6 @@ ViewProviderDocumentObject::ViewProviderDocumentObject() "Element: On top only if some sub-element of the object is selected"); OnTopWhenSelected.setEnums(OnTopEnum); - ADD_PROPERTY_TYPE(TreeRank, (0), dogroup, App::PropertyType(App::Prop_Hidden|App::Prop_NoPersist), "Tree view item ordering key"); - sPixmap = "Feature"; } @@ -226,14 +222,6 @@ void ViewProviderDocumentObject::onChanged(const App::Property* prop) ? SoFCSelectionRoot::Box : SoFCSelectionRoot::Full; } } - else if (prop == &TreeRank) { - if (this->TreeRank.getValue() <= 0) { - this->TreeRank.setValue(++ViewProviderDocumentObject::lastTreeRank); - } - else if (this->TreeRank.getValue() > ViewProviderDocumentObject::lastTreeRank) { - ViewProviderDocumentObject::lastTreeRank = this->TreeRank.getValue(); - } - } if (prop && !prop->testStatus(App::Property::NoModify) && pcDocument @@ -690,15 +678,3 @@ std::string ViewProviderDocumentObject::getFullName() const { return pcObject->getFullName() + ".ViewObject"; return std::string("?"); } - -bool ViewProviderDocumentObject::allowTreeOrderSwap(const App::DocumentObject *child1, const App::DocumentObject *child2) const -{ - std::vector extensions = getExtensionsDerivedFromType(); - for (ViewProviderExtension *ext : extensions) { - if (!ext->extensionAllowTreeOrderSwap(child1, child2)) { - return false; - } - } - - return true; -} diff --git a/src/Gui/ViewProviderDocumentObject.h b/src/Gui/ViewProviderDocumentObject.h index 4d5a54df1d..3af4173d13 100644 --- a/src/Gui/ViewProviderDocumentObject.h +++ b/src/Gui/ViewProviderDocumentObject.h @@ -64,9 +64,6 @@ public: App::PropertyEnumeration OnTopWhenSelected; App::PropertyEnumeration SelectionStyle; - // Hidden properties - App::PropertyInteger TreeRank; - virtual void attach(App::DocumentObject *pcObject); virtual void reattach(App::DocumentObject *); virtual void update(const App::Property*) override; @@ -158,8 +155,6 @@ public: void setShowable(bool enable); bool isShowable() const; - virtual bool allowTreeOrderSwap(const App::DocumentObject *child1, const App::DocumentObject *child2) const; - protected: /*! Get the active mdi view of the document this view provider is part of. @note The returned mdi view doesn't need to be a 3d view but can be e.g. @@ -212,8 +207,6 @@ protected: App::DocumentObject *pcObject; Gui::Document* pcDocument; - static int lastTreeRank; - private: bool _Showable = true; diff --git a/src/Gui/ViewProviderExtension.h b/src/Gui/ViewProviderExtension.h index fa96fd3c2d..27af83db83 100644 --- a/src/Gui/ViewProviderExtension.h +++ b/src/Gui/ViewProviderExtension.h @@ -110,8 +110,6 @@ public: virtual bool extensionGetElementPicked(const SoPickedPoint *, std::string &) const {return false;} virtual bool extensionGetDetailPath(const char *, SoFullPath *, SoDetail *&) const {return false;} - virtual bool extensionAllowTreeOrderSwap(const App::DocumentObject *, const App::DocumentObject *) const { return true; } - private: bool m_ignoreOverlayIcon = false; //Gui::ViewProviderDocumentObject* m_viewBase = nullptr; diff --git a/src/Gui/ViewProviderGroupExtension.cpp b/src/Gui/ViewProviderGroupExtension.cpp index fd251d108f..5d2ff145a5 100644 --- a/src/Gui/ViewProviderGroupExtension.cpp +++ b/src/Gui/ViewProviderGroupExtension.cpp @@ -163,7 +163,7 @@ void ViewProviderGroupExtension::extensionHide(void) { bool ViewProviderGroupExtension::extensionOnDelete(const std::vector< std::string >& ) { auto* group = getExtendedViewProvider()->getObject()->getExtensionByType(); - // If the group is nonempty ask the user if he wants to delete its content + // If the group is nonempty ask the user if they want to delete its content if (group->Group.getSize() > 0) { QMessageBox::StandardButton choice = QMessageBox::question(getMainWindow(), QObject::tr ( "Delete group content?" ), diff --git a/src/Gui/ViewProviderLink.cpp b/src/Gui/ViewProviderLink.cpp index 2ebc20ec6d..4a2701c538 100644 --- a/src/Gui/ViewProviderLink.cpp +++ b/src/Gui/ViewProviderLink.cpp @@ -2506,7 +2506,7 @@ ViewProvider *ViewProviderLink::startEditing(int mode) { } // TODO: the 0x8000 mask here is for caller to disambiguate the intention - // here, whether he wants to, say transform the link itself or the linked + // here, whether they want to, say transform the link itself or the linked // object. Use of a mask here will allow forwarding those editing modes that // are supported by both the link and the linked object, such as transform // and set color. We need to find a better place to declare this constant. diff --git a/src/Gui/ViewProviderOrigin.h b/src/Gui/ViewProviderOrigin.h index e46bdad450..e9fd870706 100644 --- a/src/Gui/ViewProviderOrigin.h +++ b/src/Gui/ViewProviderOrigin.h @@ -74,8 +74,6 @@ public: return false; } - virtual bool allowTreeOrderSwap(const App::DocumentObject *, const App::DocumentObject *) const { return false; } - /// Returns default size. Use this if it is not possible to determine appropriate size by other means static double defaultSize(); protected: diff --git a/src/Gui/ViewProviderOriginGroupExtension.h b/src/Gui/ViewProviderOriginGroupExtension.h index 8e8abe38e0..1a28f7fe4e 100644 --- a/src/Gui/ViewProviderOriginGroupExtension.h +++ b/src/Gui/ViewProviderOriginGroupExtension.h @@ -48,8 +48,6 @@ public: void updateOriginSize(); - virtual bool extensionAllowTreeOrderSwap(const App::DocumentObject *, const App::DocumentObject *) const override { return false; } - protected: void slotChangedObjectApp ( const App::DocumentObject& obj ); void slotChangedObjectGui ( const Gui::ViewProviderDocumentObject& obj ); diff --git a/src/Gui/WidgetFactory.cpp b/src/Gui/WidgetFactory.cpp index 241c35f655..5ca34a3aa5 100644 --- a/src/Gui/WidgetFactory.cpp +++ b/src/Gui/WidgetFactory.cpp @@ -25,6 +25,8 @@ #ifndef _PreComp_ # include # include +# include +# include #endif #ifdef FC_OS_WIN32 diff --git a/src/Gui/Widgets.cpp b/src/Gui/Widgets.cpp index cf59f11167..e3c1c87147 100644 --- a/src/Gui/Widgets.cpp +++ b/src/Gui/Widgets.cpp @@ -56,6 +56,7 @@ #include "DlgExpressionInput.h" #include "QuantitySpinBox_p.h" #include "Tools.h" +#include "ui_DlgTreeWidget.h" using namespace Gui; using namespace App; @@ -526,8 +527,9 @@ void ClearLineEdit::updateClearButton(const QString& text) */ CheckListDialog::CheckListDialog( QWidget* parent, Qt::WindowFlags fl ) : QDialog( parent, fl ) + , ui(new Ui_DlgTreeWidget) { - ui.setupUi(this); + ui->setupUi(this); } /** @@ -544,7 +546,7 @@ CheckListDialog::~CheckListDialog() void CheckListDialog::setCheckableItems( const QStringList& items ) { for ( QStringList::ConstIterator it = items.begin(); it != items.end(); ++it ) { - QTreeWidgetItem* item = new QTreeWidgetItem(ui.treeWidget); + QTreeWidgetItem* item = new QTreeWidgetItem(ui->treeWidget); item->setText(0, *it); item->setCheckState(0, Qt::Unchecked); } @@ -557,7 +559,7 @@ void CheckListDialog::setCheckableItems( const QStringList& items ) void CheckListDialog::setCheckableItems( const QList& items ) { for ( QList::ConstIterator it = items.begin(); it != items.end(); ++it ) { - QTreeWidgetItem* item = new QTreeWidgetItem(ui.treeWidget); + QTreeWidgetItem* item = new QTreeWidgetItem(ui->treeWidget); item->setText(0, (*it).first); item->setCheckState(0, ( (*it).second ? Qt::Checked : Qt::Unchecked)); } @@ -576,7 +578,7 @@ QStringList CheckListDialog::getCheckedItems() const */ void CheckListDialog::accept () { - QTreeWidgetItemIterator it(ui.treeWidget, QTreeWidgetItemIterator::Checked); + QTreeWidgetItemIterator it(ui->treeWidget, QTreeWidgetItemIterator::Checked); while (*it) { checked.push_back((*it)->text(0)); ++it; @@ -1662,4 +1664,34 @@ void ExpLineEdit::keyPressEvent(QKeyEvent *event) QLineEdit::keyPressEvent(event); } +// -------------------------------------------------------------------- + +ButtonGroup::ButtonGroup(QObject *parent) + : QButtonGroup(parent) + , _exclusive(true) +{ + QButtonGroup::setExclusive(false); + + connect(this, QOverload::of(&QButtonGroup::buttonClicked), + [=](QAbstractButton *button) { + if (exclusive()) { + for (auto btn : buttons()) { + if (btn && btn != button && btn->isCheckable()) + btn->setChecked(false); + } + } + }); +} + +void ButtonGroup::setExclusive(bool on) +{ + _exclusive = on; +} + +bool ButtonGroup::exclusive() const +{ + return _exclusive; +} + + #include "moc_Widgets.cpp" diff --git a/src/Gui/Widgets.h b/src/Gui/Widgets.h index c4064eb50e..c9aacb80be 100644 --- a/src/Gui/Widgets.h +++ b/src/Gui/Widgets.h @@ -24,7 +24,8 @@ #ifndef GUI_WIDGETS_H #define GUI_WIDGETS_H -#include +#include +#include #include #include #include @@ -36,7 +37,16 @@ #include #include #include "ExpressionBinding.h" -#include "Base/Parameter.h" +#include +#include +#include + + +class QGridLayout; +class QVBoxLayout; +class QTreeWidget; +class QTreeWidgetItem; +class QSpacerItem; namespace Gui { class PrefCheckBox; @@ -164,6 +174,7 @@ private: // ------------------------------------------------------------------------------ typedef QPair CheckListItem; +class Ui_DlgTreeWidget; /** * The CheckListDialog class provides a dialog with a QListView with @@ -187,7 +198,7 @@ public: private: QStringList checked; - Ui_DlgTreeWidget ui; + std::unique_ptr ui; }; // ------------------------------------------------------------------------------ @@ -562,6 +573,25 @@ private: bool autoClose; }; +/*! + * \brief The ButtonGroup class + * Unlike Qt's QButtonGroup this class allows it that in exclusive mode + * all buttons can be unchecked. + */ +class GuiExport ButtonGroup : public QButtonGroup +{ + Q_OBJECT + +public: + ButtonGroup(QObject *parent = nullptr); + + void setExclusive(bool on); + bool exclusive() const; + +private: + bool _exclusive; +}; + } // namespace Gui #endif // GUI_WIDGETS_H diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index 58c68ed01d..4abc96ada3 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -56,7 +56,7 @@ using namespace Gui; FreeCAD provides the possibility to have one or more workbenches for a module. A workbench changes the appearance of the main window in that way that it defines toolbars, items in the toolbox, menus or the context menu and dockable windows that are shown to the user. - The idea behind this concept is that the user should see only the functions that are required for the task that he is doing at this moment and not to show dozens of unneeded functions which the user never uses. + The idea behind this concept is that the user should see only the functions that are required for the task that they are doing at this moment and not to show dozens of unneeded functions which the user never uses. \section stepbystep Step by step Here follows a short description of how your own workbench can be added to a module. @@ -181,8 +181,8 @@ using namespace Gui; * At startup FreeCAD scans all module directories and invokes InitGui.py. So an item for a workbench gets created. If the user * clicks on such an item the matching module gets loaded, the C++ workbench gets registered and activated. * - * The user is able to modify a workbench (Edit|Customize). E.g. he can add new toolbars or items for the toolbox and add his preferred - * functions to them. But he has only full control over "his" toolbars, the default workbench items cannot be modified or even removed. + * The user is able to modify a workbench (Edit|Customize). E.g. they can add new toolbars or items for the toolbox and add their preferred + * functions to them. But the user only has full control over "their" own toolbars, the default workbench items cannot be modified or even removed. * * FreeCAD provides also the possibility to define pure Python workbenches. Such workbenches are temporarily only and are lost after exiting * the FreeCAD session. But if you want to keep your Python workbench you can write a macro and attach it with a user defined button or just diff --git a/src/Mod/AddonManager/Resources/AddonManager.qrc b/src/Mod/AddonManager/Resources/AddonManager.qrc index 2c53e1db0a..312e646a45 100644 --- a/src/Mod/AddonManager/Resources/AddonManager.qrc +++ b/src/Mod/AddonManager/Resources/AddonManager.qrc @@ -58,7 +58,6 @@ icons/Silk_workbench_icon.svg icons/timber_workbench_icon.svg icons/ThreadProfile_workbench_icon.svg - icons/VendorParts_workbench_icon.svg icons/WebTools_workbench_icon.svg icons/workfeature_workbench_icon.svg icons/yaml-workspace_workbench_icon.svg diff --git a/src/Mod/AddonManager/Resources/icons/VendorParts_workbench_icon.svg b/src/Mod/AddonManager/Resources/icons/VendorParts_workbench_icon.svg deleted file mode 100644 index ce06f586b4..0000000000 --- a/src/Mod/AddonManager/Resources/icons/VendorParts_workbench_icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/Mod/Assembly/App/opendcm/core/property.hpp b/src/Mod/Assembly/App/opendcm/core/property.hpp index 62dafc99a1..6b67eb04f2 100644 --- a/src/Mod/Assembly/App/opendcm/core/property.hpp +++ b/src/Mod/Assembly/App/opendcm/core/property.hpp @@ -302,7 +302,7 @@ struct pts { //property type sequence /** * @brief Type traits to detect if the property has a default value * - * If the user want to provide a default value for a property than he adds a default_value static function. + * If the user wants to provide a default value for a property than they add a default_value static function. * To check if the this function is available we add a type traits which searches for this special function. */ BOOST_MPL_HAS_XXX_TRAIT_DEF(default_value) @@ -312,7 +312,7 @@ BOOST_MPL_HAS_XXX_TRAIT_DEF(default_value) /** * @brief Functor to assign default values to property * - * This functor holds a pointer to the PropertyOwner in question. The operator() get the properties which + * This functor holds a pointer to the PropertyOwner in question. The operator() gets the properties which * hold a default value and assigns this value to the property the owner holds. */ template diff --git a/src/Mod/Draft/App/DraftDxf.cpp b/src/Mod/Draft/App/DraftDxf.cpp index 230fe392fb..88a585ee17 100644 --- a/src/Mod/Draft/App/DraftDxf.cpp +++ b/src/Mod/Draft/App/DraftDxf.cpp @@ -163,6 +163,9 @@ void DraftDxfRead::OnReadInsert(const double* point, const double* scale, const std::string prefix = "BLOCKS "; prefix += name; prefix += " "; + auto checkScale = [=](double v) { + return v != 0.0 ? v : 1.0; + }; for(std::map > ::const_iterator i = layers.begin(); i != layers.end(); ++i) { std::string k = i->first; if(k.substr(0, prefix.size()) == prefix) { @@ -178,7 +181,7 @@ void DraftDxfRead::OnReadInsert(const double* point, const double* scale, const if (!comp.IsNull()) { Part::TopoShape* pcomp = new Part::TopoShape(comp); Base::Matrix4D mat; - mat.scale(scale[0],scale[1],scale[2]); + mat.scale(checkScale(scale[0]),checkScale(scale[1]),checkScale(scale[2])); mat.rotZ(rotation); mat.move(point[0]*optionScaling,point[1]*optionScaling,point[2]*optionScaling); pcomp->transformShape(mat,true); diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 992fb7c418..f3b8e45d4c 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -120,9 +120,11 @@ from draftutils.gui_utils import (dim_symbol, dim_dash, dimDash) -from draftutils.groups import (get_group_names, +from draftutils.groups import (is_group, + get_group_names, getGroupNames, ungroup, + get_windows, get_group_contents, getGroupContents, get_movable_children, diff --git a/src/Mod/Draft/draftguitools/gui_setstyle.py b/src/Mod/Draft/draftguitools/gui_setstyle.py index 1d3046ea99..cce17d785e 100644 --- a/src/Mod/Draft/draftguitools/gui_setstyle.py +++ b/src/Mod/Draft/draftguitools/gui_setstyle.py @@ -123,7 +123,7 @@ class Draft_SetStyle_TaskPanel: return QtGui.QColor.fromRgbF(r,g,b) def getValues(self): - + preset = {} preset["LineColor"] = self.form.LineColor.property("color").rgb()<<8 preset["LineWidth"] = self.form.LineWidth.value() @@ -143,7 +143,7 @@ class Draft_SetStyle_TaskPanel: return preset def setValues(self,preset): - + from PySide import QtCore,QtGui self.form.LineColor.setProperty("color",self.getColor(preset.get("LineColor",255))) self.form.LineWidth.setValue(preset.get("LineWidth",1)) @@ -169,6 +169,8 @@ class Draft_SetStyle_TaskPanel: FreeCAD.ParamGet(self.p+"View").SetUnsigned("DefaultShapeLineColor",self.form.LineColor.property("color").rgb()<<8) FreeCAD.ParamGet(self.p+"View").SetInt("DefaultShapeLineWidth",self.form.LineWidth.value()) + FreeCAD.ParamGet(self.p+"View").SetUnsigned("DefaultShapeVertexColor",self.form.LineColor.property("color").rgb()<<8) + FreeCAD.ParamGet(self.p+"View").SetInt("DefaultShapePointSize",self.form.LineWidth.value()) FreeCAD.ParamGet(self.p+"Mod/Draft").SetInt("DefaultDrawStyle",self.form.DrawStyle.currentIndex()) FreeCAD.ParamGet(self.p+"Mod/Draft").SetInt("DefaultDisplayMode",self.form.DisplayMode.currentIndex()) FreeCAD.ParamGet(self.p+"View").SetUnsigned("DefaultShapeColor",self.form.ShapeColor.property("color").rgb()<<8) @@ -195,18 +197,17 @@ class Draft_SetStyle_TaskPanel: vobj.LineColor = self.form.LineColor.property("color").getRgbF() if "LineWidth" in vobj.PropertiesList: vobj.LineWidth = self.form.LineWidth.value() + if "PointColor" in vobj.PropertiesList: + vobj.PointColor = self.form.LineColor.property("color").getRgbF() + if "PointSize" in vobj.PropertiesList: + vobj.PointSize = self.form.LineWidth.value() if "DrawStyle" in vobj.PropertiesList: vobj.DrawStyle = ["Solid","Dashed","Dotted","Dashdot"][self.form.DrawStyle.currentIndex()] if "DisplayMode" in vobj.PropertiesList: dmodes = ["Flat Lines","Wireframe","Shaded","Points"] dm = dmodes[self.form.DisplayMode.currentIndex()] - if hasattr(vobj,"Proxy") and hasattr(vobj.Proxy,"getDisplayModes"): - dmodes = vobj.Proxy.getDisplayModes(vobj) - if dm in dmodes: - try: - vobj.DisplayMode = dm - except Exception: - pass + if dm in vobj.getEnumerationsOfProperty("DisplayMode"): + vobj.DisplayMode = dm if "ShapeColor" in vobj.PropertiesList: vobj.ShapeColor = self.form.ShapeColor.property("color").getRgbF() if "Transparency" in vobj.PropertiesList: @@ -233,7 +234,7 @@ class Draft_SetStyle_TaskPanel: vobj.LineSpacing = self.form.LineSpacing.value() def onApplyDim(self,index): - + import Draft objs = FreeCAD.ActiveDocument.Objects dims = Draft.getObjectsOfType(objs,"LinearDimension") diff --git a/src/Mod/Draft/draftguitools/gui_shapestrings.py b/src/Mod/Draft/draftguitools/gui_shapestrings.py index c8ff4528be..5795b3f5ca 100644 --- a/src/Mod/Draft/draftguitools/gui_shapestrings.py +++ b/src/Mod/Draft/draftguitools/gui_shapestrings.py @@ -64,7 +64,6 @@ class ShapeString(gui_base_original.Creator): """Set icon, menu and tooltip.""" d = {'Pixmap': 'Draft_ShapeString', - 'Accel': "S, S", 'MenuText': QT_TRANSLATE_NOOP("Draft_ShapeString", "Shape from text"), 'ToolTip': QT_TRANSLATE_NOOP("Draft_ShapeString", "Creates a shape from a text string by choosing a specific font and a placement.\nThe closed shapes can be used for extrusions and boolean operations.")} return d diff --git a/src/Mod/Draft/draftguitools/gui_snapper.py b/src/Mod/Draft/draftguitools/gui_snapper.py index b396e7fdb1..25d10812b0 100644 --- a/src/Mod/Draft/draftguitools/gui_snapper.py +++ b/src/Mod/Draft/draftguitools/gui_snapper.py @@ -83,7 +83,7 @@ class Snapper: def __init__(self): self.activeview = None - self.lastObj = [None, None] + self.lastObj = [] self.maxEdges = 0 self.radius = 0 self.constraintAxis = None @@ -265,10 +265,6 @@ class Snapper: # Setup trackers if needed self.setTrackers() - # Show the grid if it's off (new view, for ex) - if self.grid and Draft.getParam("grid", True): - self.grid.on() - # Get current snap radius self.radius = self.getScreenDist(Draft.getParam("snapRange", 8), screenpos) @@ -475,16 +471,16 @@ class Snapper: # snap to corners of section planes snaps.extend(self.snapToEndpoints(obj.Shape)) + # updating last objects list + if obj.Name in self.lastObj: + self.lastObj.remove(obj.Name) + self.lastObj.append(obj.Name) + if len(self.lastObj) > 8: + self.lastObj = self.lastObj[-8:] + if not snaps: return None - # updating last objects list - if not self.lastObj[1]: - self.lastObj[1] = obj.Name - elif self.lastObj[1] != obj.Name: - self.lastObj[0] = self.lastObj[1] - self.lastObj[1] = obj.Name - # calculating the nearest snap point shortest = 1000000000000000000 origin = App.Vector(self.snapInfo['x'], @@ -614,9 +610,9 @@ class Snapper: self.setCursor(tsnap[1]) return tsnap[2], eline - for o in (self.lastObj[1], self.lastObj[0]): - if o and (self.isEnabled('Extension') - or self.isEnabled('Parallel')): + for o in self.lastObj: + if (self.isEnabled('Extension') + or self.isEnabled('Parallel')): ob = App.ActiveDocument.getObject(o) if not ob: continue @@ -634,10 +630,10 @@ class Snapper: if DraftGeomUtils.geomType(e) != "Line": continue np = self.getPerpendicular(e,point) - if DraftGeomUtils.isPtOnEdge(np,e): - continue if (np.sub(point)).Length < self.radius: if self.isEnabled('Extension'): + if DraftGeomUtils.isPtOnEdge(np,e): + continue if np != e.Vertexes[0].Point: p0 = e.Vertexes[0].Point if self.tracker and not self.selectMode: @@ -668,20 +664,19 @@ class Snapper: self.lastExtensions[1] = self.lastExtensions[0] self.lastExtensions[0] = ne return np,ne - else: - if self.isEnabled('Parallel'): - if last: - ve = DraftGeomUtils.vec(e) - if not DraftVecUtils.isNull(ve): - de = Part.LineSegment(last,last.add(ve)).toShape() - np = self.getPerpendicular(de,point) - if (np.sub(point)).Length < self.radius: - if self.tracker and not self.selectMode: - self.tracker.setCoords(np) - self.tracker.setMarker(self.mk['parallel']) - self.tracker.on() - self.setCursor('parallel') - return np,de + elif self.isEnabled('Parallel'): + if last: + ve = DraftGeomUtils.vec(e) + if not DraftVecUtils.isNull(ve): + de = Part.LineSegment(last,last.add(ve)).toShape() + np = self.getPerpendicular(de,point) + if (np.sub(point)).Length < self.radius: + if self.tracker and not self.selectMode: + self.tracker.setCoords(np) + self.tracker.setMarker(self.mk['parallel']) + self.tracker.on() + self.setCursor('parallel') + return np,de return point,eline @@ -999,8 +994,8 @@ class Snapper: snaps = [] if self.isEnabled("Intersection"): # get the stored objects to calculate intersections - if self.lastObj[0]: - obj = App.ActiveDocument.getObject(self.lastObj[0]) + for o in self.lastObj[:-1]: + obj = App.ActiveDocument.getObject(o) if obj: if obj.isDerivedFrom("Part::Feature") or (Draft.getType(obj) == "Axis"): if (not self.maxEdges) or (len(obj.Shape.Edges) <= self.maxEdges): @@ -1230,6 +1225,7 @@ class Snapper: self.selectMode = False self.running = False self.holdPoints = [] + self.lastObj = [] def setSelectMode(self, mode): diff --git a/src/Mod/Draft/draftobjects/draftlink.py b/src/Mod/Draft/draftobjects/draftlink.py index 5437a67dbf..d230977acd 100644 --- a/src/Mod/Draft/draftobjects/draftlink.py +++ b/src/Mod/Draft/draftobjects/draftlink.py @@ -187,8 +187,9 @@ class DraftLink(DraftObject): "from '{}'\n".format(obj.Label, obj.Base.Label)) raise RuntimeError(_err_msg) else: - shape = shape.copy() - shape.Placement = App.Placement() + # Resetting the Placement of the copied shape does not work for + # Part_Vertex and Draft_Point objects, we need to transform: + shape = shape.transformGeometry(shape.Placement.Matrix.inverse()) base = [] for i, pla in enumerate(pls): vis = getattr(obj, 'VisibilityList', []) diff --git a/src/Mod/Draft/draftviewproviders/view_layer.py b/src/Mod/Draft/draftviewproviders/view_layer.py index 1f88700105..337a0e595a 100644 --- a/src/Mod/Draft/draftviewproviders/view_layer.py +++ b/src/Mod/Draft/draftviewproviders/view_layer.py @@ -38,7 +38,7 @@ import FreeCAD as App import FreeCADGui as Gui from draftutils.messages import _msg -from draftutils.translate import _tr +from draftutils.translate import translate from draftobjects.layer import Layer @@ -239,15 +239,17 @@ class ViewProviderLayer: # and then sets the target property accordingly if hasattr(target_vobj, prop): setattr(target_vobj, prop, getattr(vobj, prop)) - else: - continue - # Use the line color for the text color if it exists + # Use the line color for the point color and text color if prop == "LineColor": + if hasattr(target_vobj, "PointColor"): + target_vobj.PointColor = vobj.LineColor if hasattr(target_vobj, "TextColor"): target_vobj.TextColor = vobj.LineColor - if hasattr(target_vobj, "FontColor"): - target_vobj.FontColor = vobj.LineColor + # Use the line width for the point size + elif prop == "LineWidth": + if hasattr(target_vobj, "PointSize"): + target_vobj.PointSize = vobj.LineWidth def onChanged(self, vobj, prop): """Execute when a view property is changed.""" @@ -356,13 +358,13 @@ class ViewProviderLayer: def setupContextMenu(self, vobj, menu): """Set up actions to perform in the context menu.""" action1 = QtGui.QAction(QtGui.QIcon(":/icons/button_right.svg"), - _tr("Activate this layer"), + translate("draft", "Activate this layer"), menu) action1.triggered.connect(self.activate) menu.addAction(action1) action2 = QtGui.QAction(QtGui.QIcon(":/icons/Draft_SelectGroup.svg"), - _tr("Select layer contents"), + translate("draft", "Select layer contents"), menu) action2.triggered.connect(self.select_contents) menu.addAction(action2) @@ -400,12 +402,12 @@ class ViewProviderLayerContainer: def setupContextMenu(self, vobj, menu): """Set up actions to perform in the context menu.""" action1 = QtGui.QAction(QtGui.QIcon(":/icons/Draft_Layer.svg"), - _tr("Merge layer duplicates"), + translate("draft", "Merge layer duplicates"), menu) action1.triggered.connect(self.merge_by_name) menu.addAction(action1) action2 = QtGui.QAction(QtGui.QIcon(":/icons/Draft_NewLayer.svg"), - _tr("Add new layer"), + translate("draft", "Add new layer"), menu) action2.triggered.connect(self.add_layer) menu.addAction(action2) @@ -416,7 +418,7 @@ class ViewProviderLayerContainer: return doc = App.ActiveDocument - doc.openTransaction(_tr("Merge layer duplicates")) + doc.openTransaction(translate("draft", "Merge layer duplicates")) layer_container = self.Object layers = [] @@ -448,12 +450,12 @@ class ViewProviderLayerContainer: base.Group = base_group to_delete.append(layer) elif layer.Label != base_label: - _msg(_tr("Relabeling layer:") + _msg(translate("draft", "Relabeling layer:") + " '{}' -> '{}'".format(layer.Label, base_label)) layer.Label = base_label for layer in to_delete: - _msg(_tr("Merging layer:") + " '{}'".format(layer.Label)) + _msg(translate("draft", "Merging layer:") + " '{}'".format(layer.Label)) doc.removeObject(layer.Name) doc.recompute() @@ -464,7 +466,7 @@ class ViewProviderLayerContainer: import Draft doc = App.ActiveDocument - doc.openTransaction(_tr("Add new layer")) + doc.openTransaction(translate("draft", "Add new layer")) Draft.make_layer() diff --git a/src/Mod/Drawing/Gui/DrawingView.cpp b/src/Mod/Drawing/Gui/DrawingView.cpp index bca5828799..69bcded867 100644 --- a/src/Mod/Drawing/Gui/DrawingView.cpp +++ b/src/Mod/Drawing/Gui/DrawingView.cpp @@ -681,7 +681,7 @@ void DrawingView::viewAll() PyObject* DrawingView::getPyObject() { - Py_Return; + return Gui::MDIView::getPyObject(); } #include "moc_DrawingView.cpp" diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.cpp b/src/Mod/Fem/Gui/TaskPostBoxes.cpp index 641ea09da7..f8c173ae5f 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.cpp +++ b/src/Mod/Fem/Gui/TaskPostBoxes.cpp @@ -1138,7 +1138,7 @@ void TaskPostWarpVector::on_Max_valueChanged(double) { /* * problem, if warp_factor is 2000 one would like to input 4000 as max, one starts to input 4 - * immediately the warp_factor is changed to 4 because 4 < 2000, but one has just input one character of his 4000 + * immediately the warp_factor is changed to 4 because 4 < 2000, but one has just input one character of their 4000 * I do not know how to solve this, but the code to set slider and spinbox is fine thus I leave it ... * * mhh it works if "apply changes to pipeline directly" button is deactivated, still it really confuses if diff --git a/src/Mod/Fem/femmesh/gmshtools.py b/src/Mod/Fem/femmesh/gmshtools.py index f51b3092f8..fad41d007a 100644 --- a/src/Mod/Fem/femmesh/gmshtools.py +++ b/src/Mod/Fem/femmesh/gmshtools.py @@ -1011,7 +1011,7 @@ for len in max_mesh_sizes: """ TODO class GmshTools should be splittet in two classes -one class should only collect the mesh parameter from mesh object and his childs +one class should only collect the mesh parameter from mesh object and its childs a second class only uses the collected parameter, writes the input file runs gmsh reads back the unv and returns a FemMesh gmsh binary will be collected in the second class diff --git a/src/Mod/Fem/femmesh/meshtools.py b/src/Mod/Fem/femmesh/meshtools.py index 3f04ec7c0c..3cf89b77c0 100644 --- a/src/Mod/Fem/femmesh/meshtools.py +++ b/src/Mod/Fem/femmesh/meshtools.py @@ -1015,7 +1015,7 @@ def get_ref_edgenodes_table( nodecount += 1 if nodecount > 1: refedge_fem_faceelements.append(elem) - # for every refedge_fem_faceelement look which of his nodes is in + # for every refedge_fem_faceelement look which of its nodes is in # refedge_nodes --> add all these nodes to edge_table for elem in refedge_fem_faceelements: fe_refedge_nodes = [] diff --git a/src/Mod/Fem/femsolver/calculix/writer.py b/src/Mod/Fem/femsolver/calculix/writer.py index 89d560306a..08d37d4cf8 100644 --- a/src/Mod/Fem/femsolver/calculix/writer.py +++ b/src/Mod/Fem/femsolver/calculix/writer.py @@ -70,7 +70,7 @@ from femtools import constants units_information = """*********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/box_frequency.inp b/src/Mod/Fem/femtest/data/calculix/box_frequency.inp index 00c7073376..c6f624721c 100644 --- a/src/Mod/Fem/femtest/data/calculix/box_frequency.inp +++ b/src/Mod/Fem/femtest/data/calculix/box_frequency.inp @@ -471,7 +471,7 @@ S, E *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/box_static.inp b/src/Mod/Fem/femtest/data/calculix/box_static.inp index 195a5630cd..f5ad252b4c 100644 --- a/src/Mod/Fem/femtest/data/calculix/box_static.inp +++ b/src/Mod/Fem/femtest/data/calculix/box_static.inp @@ -599,7 +599,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_buckling_flexuralbuckling.inp b/src/Mod/Fem/femtest/data/calculix/ccx_buckling_flexuralbuckling.inp index 22f722e722..10c99f02ab 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_buckling_flexuralbuckling.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_buckling_flexuralbuckling.inp @@ -818,7 +818,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_circle.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_circle.inp index 9c70c8229e..683406edf8 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_circle.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_circle.inp @@ -113,7 +113,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_pipe.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_pipe.inp index 4f9f744756..7b88d0cd62 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_pipe.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_pipe.inp @@ -113,7 +113,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_rect.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_rect.inp index 65338dbab6..edb546f88c 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_rect.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_beam_rect.inp @@ -113,7 +113,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_hexa20.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_hexa20.inp index e7d58efd79..8196de6c7e 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_hexa20.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_hexa20.inp @@ -453,7 +453,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad4.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad4.inp index 3105e03a28..aa88c6e3a1 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad4.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad4.inp @@ -139,7 +139,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad8.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad8.inp index 6e27d94ff0..01817ff723 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad8.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_quad8.inp @@ -129,7 +129,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg2.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg2.inp index 2d5115f6f4..08e6809a2a 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg2.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg2.inp @@ -255,7 +255,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg3.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg3.inp index 8884cec1f4..c9d196b933 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg3.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_seg3.inp @@ -113,7 +113,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria3.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria3.inp index 7a99b20451..22b20b22a7 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria3.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria3.inp @@ -1620,7 +1620,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria6.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria6.inp index 31a6b19e5c..1cac5e0ac2 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria6.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_ele_tria6.inp @@ -347,7 +347,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_faceload.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_faceload.inp index 815e66a119..8f13ca6fac 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_faceload.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_faceload.inp @@ -419,7 +419,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_nodeload.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_nodeload.inp index 964b40f707..bd7dbec4cd 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_nodeload.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_nodeload.inp @@ -416,7 +416,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_prescribeddisplacement.inp b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_prescribeddisplacement.inp index c3d571db8c..9d9d464b72 100644 --- a/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_prescribeddisplacement.inp +++ b/src/Mod/Fem/femtest/data/calculix/ccx_cantilever_prescribeddisplacement.inp @@ -426,7 +426,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_centrif.inp b/src/Mod/Fem/femtest/data/calculix/constraint_centrif.inp index 47a3e0b109..a2faa25ee5 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_centrif.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_centrif.inp @@ -18792,7 +18792,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K 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 963dec0633..9e75a79e40 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 @@ -38424,7 +38424,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K 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 71d055cc73..61e7a9a212 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 @@ -5356,7 +5356,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_sectionprint.inp b/src/Mod/Fem/femtest/data/calculix/constraint_sectionprint.inp index 2eea3f4a01..253c38210c 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_sectionprint.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_sectionprint.inp @@ -3470,7 +3470,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K 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 613c03c7fc..4fda9cf903 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_selfweight_cantilever.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_selfweight_cantilever.inp @@ -2199,7 +2199,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp b/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp index ac07fd6743..ea73b8caa3 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_tie.inp @@ -18662,7 +18662,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_transform_beam_hinged.inp b/src/Mod/Fem/femtest/data/calculix/constraint_transform_beam_hinged.inp index 433461358a..92bd75d962 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_transform_beam_hinged.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_transform_beam_hinged.inp @@ -3800,7 +3800,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/constraint_transform_torque.inp b/src/Mod/Fem/femtest/data/calculix/constraint_transform_torque.inp index 4e0ba5f9b6..8f95976602 100644 --- a/src/Mod/Fem/femtest/data/calculix/constraint_transform_torque.inp +++ b/src/Mod/Fem/femtest/data/calculix/constraint_transform_torque.inp @@ -13173,7 +13173,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/frequency_beamsimple.inp b/src/Mod/Fem/femtest/data/calculix/frequency_beamsimple.inp index 3203aa1474..b0a20a53a7 100644 --- a/src/Mod/Fem/femtest/data/calculix/frequency_beamsimple.inp +++ b/src/Mod/Fem/femtest/data/calculix/frequency_beamsimple.inp @@ -17084,7 +17084,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fiveboxes.inp b/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fiveboxes.inp index 53ba2564cb..c324cb241d 100644 --- a/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fiveboxes.inp +++ b/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fiveboxes.inp @@ -29200,7 +29200,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fivefaces.inp b/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fivefaces.inp index 4976a607f9..2f4da096c8 100644 --- a/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fivefaces.inp +++ b/src/Mod/Fem/femtest/data/calculix/material_multiple_bendingbeam_fivefaces.inp @@ -2711,7 +2711,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/material_multiple_tensionrod_twoboxes.inp b/src/Mod/Fem/femtest/data/calculix/material_multiple_tensionrod_twoboxes.inp index 6ba8538f1c..ab2d0d6c2a 100644 --- a/src/Mod/Fem/femtest/data/calculix/material_multiple_tensionrod_twoboxes.inp +++ b/src/Mod/Fem/femtest/data/calculix/material_multiple_tensionrod_twoboxes.inp @@ -1292,7 +1292,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/material_nonlinear.inp b/src/Mod/Fem/femtest/data/calculix/material_nonlinear.inp index a2d2f5190d..4e1e0c5b30 100644 --- a/src/Mod/Fem/femtest/data/calculix/material_nonlinear.inp +++ b/src/Mod/Fem/femtest/data/calculix/material_nonlinear.inp @@ -20129,7 +20129,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_edgeforces.inp b/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_edgeforces.inp index 22fef3842b..c16882fd25 100644 --- a/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_edgeforces.inp +++ b/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_edgeforces.inp @@ -2674,7 +2674,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_nodeforces.inp b/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_nodeforces.inp index 099ef3bea0..e439f04309 100644 --- a/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_nodeforces.inp +++ b/src/Mod/Fem/femtest/data/calculix/square_pipe_end_twisted_nodeforces.inp @@ -2786,7 +2786,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/thermomech_bimetall.inp b/src/Mod/Fem/femtest/data/calculix/thermomech_bimetall.inp index 5d20a88b1d..dd1f031e31 100644 --- a/src/Mod/Fem/femtest/data/calculix/thermomech_bimetall.inp +++ b/src/Mod/Fem/femtest/data/calculix/thermomech_bimetall.inp @@ -8258,7 +8258,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/thermomech_flow1D.inp b/src/Mod/Fem/femtest/data/calculix/thermomech_flow1D.inp index 22a885456a..d8b7b7b0e2 100644 --- a/src/Mod/Fem/femtest/data/calculix/thermomech_flow1D.inp +++ b/src/Mod/Fem/femtest/data/calculix/thermomech_flow1D.inp @@ -168,7 +168,7 @@ MF, PS *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtest/data/calculix/thermomech_spine.inp b/src/Mod/Fem/femtest/data/calculix/thermomech_spine.inp index 290687e1c6..7cc0b503a3 100644 --- a/src/Mod/Fem/femtest/data/calculix/thermomech_spine.inp +++ b/src/Mod/Fem/femtest/data/calculix/thermomech_spine.inp @@ -205,7 +205,7 @@ RF *********************************************************** ** About units: ** See ccx manual, ccx does not know about any unit. -** Golden rule: The user must make sure that the numbers he provides have consistent units. +** Golden rule: The user must make sure that the numbers they provide have consistent units. ** The user is the FreeCAD calculix writer module ;-) ** ** The unit system which is used at Guido Dhondt's company: mm, N, s, K diff --git a/src/Mod/Fem/femtools/checksanalysis.py b/src/Mod/Fem/femtools/checksanalysis.py index b16412e450..50624ed13b 100644 --- a/src/Mod/Fem/femtools/checksanalysis.py +++ b/src/Mod/Fem/femtools/checksanalysis.py @@ -344,7 +344,7 @@ def check_member_for_solver_calculix(analysis, solver, mesh, member): ): message += ( "Mesh without geometry link. " - "The mesh needs to know his geometry for the beam rotations.\n" + "The mesh needs to know its geometry for the beam rotations.\n" ) if len(member.geos_beamrotation) > 1: message += ( diff --git a/src/Mod/Image/Gui/AppImageGui.cpp b/src/Mod/Image/Gui/AppImageGui.cpp index 3c4050a813..5c1300a167 100644 --- a/src/Mod/Image/Gui/AppImageGui.cpp +++ b/src/Mod/Image/Gui/AppImageGui.cpp @@ -18,6 +18,7 @@ #include #include #include +#include "ImageView.h" #include "Workbench.h" #include "ViewProviderImagePlane.h" @@ -50,6 +51,7 @@ PyMOD_INIT_FUNC(ImageGui) // instantiating the commands CreateImageCommands(); + ImageGui::ImageView::init(); ImageGui::ViewProviderImagePlane::init(); ImageGui::Workbench::init(); diff --git a/src/Mod/Image/Gui/ImageView.cpp b/src/Mod/Image/Gui/ImageView.cpp index 36e7d5dc81..e1345c64df 100644 --- a/src/Mod/Image/Gui/ImageView.cpp +++ b/src/Mod/Image/Gui/ImageView.cpp @@ -36,6 +36,8 @@ using namespace ImageGui; /* TRANSLATOR ImageGui::ImageView */ +TYPESYSTEM_SOURCE_ABSTRACT(ImageGui::ImageView, Gui::MDIView) + ImageView::ImageView(QWidget* parent) : MDIView(0, parent), _ignoreCloseEvent(false) { diff --git a/src/Mod/Image/Gui/ImageView.h b/src/Mod/Image/Gui/ImageView.h index 7bc34f8e21..ef14bebd75 100644 --- a/src/Mod/Image/Gui/ImageView.h +++ b/src/Mod/Image/Gui/ImageView.h @@ -40,6 +40,8 @@ class ImageGuiExport ImageView : public Gui::MDIView { Q_OBJECT + TYPESYSTEM_HEADER(); + public: ImageView(QWidget* parent); virtual ~ImageView(); diff --git a/src/Mod/Import/App/ImpExpDxf.cpp b/src/Mod/Import/App/ImpExpDxf.cpp index dcd88ef138..e5cc1b632d 100644 --- a/src/Mod/Import/App/ImpExpDxf.cpp +++ b/src/Mod/Import/App/ImpExpDxf.cpp @@ -320,6 +320,9 @@ void ImpExpDxfRead::OnReadInsert(const double* point, const double* scale, const std::string prefix = "BLOCKS "; prefix += name; prefix += " "; + auto checkScale = [=](double v) { + return v != 0.0 ? v : 1.0; + }; for(std::map > ::const_iterator i = layers.begin(); i != layers.end(); ++i) { std::string k = i->first; if(k.substr(0, prefix.size()) == prefix) { @@ -335,7 +338,7 @@ void ImpExpDxfRead::OnReadInsert(const double* point, const double* scale, const if (!comp.IsNull()) { Part::TopoShape* pcomp = new Part::TopoShape(comp); Base::Matrix4D mat; - mat.scale(scale[0],scale[1],scale[2]); + mat.scale(checkScale(scale[0]),checkScale(scale[1]),checkScale(scale[2])); mat.rotZ(rotation); mat.move(point[0]*optionScaling,point[1]*optionScaling,point[2]*optionScaling); pcomp->transformShape(mat,true); diff --git a/src/Mod/Inspection/Gui/ViewProviderInspection.cpp b/src/Mod/Inspection/Gui/ViewProviderInspection.cpp index f04cc0c6fe..a5099038ca 100644 --- a/src/Mod/Inspection/Gui/ViewProviderInspection.cpp +++ b/src/Mod/Inspection/Gui/ViewProviderInspection.cpp @@ -24,6 +24,7 @@ #include "PreCompiled.h" #ifndef _PreComp_ +# include # include # include #endif diff --git a/src/Mod/Mesh/App/Core/SetOperations.h b/src/Mod/Mesh/App/Core/SetOperations.h index d83cc4f362..d2943bf994 100644 --- a/src/Mod/Mesh/App/Core/SetOperations.h +++ b/src/Mod/Mesh/App/Core/SetOperations.h @@ -76,7 +76,7 @@ protected: float _minDistanceToPoint; /** Minimal distance to facet corner points */ private: - // Helper class cutting edge to his two attached facets + // Helper class cutting edge to its two attached facets class Edge { public: diff --git a/src/Mod/Mesh/Gui/Workbench.cpp b/src/Mod/Mesh/Gui/Workbench.cpp index 198a02fb73..822d6cdbfc 100644 --- a/src/Mod/Mesh/Gui/Workbench.cpp +++ b/src/Mod/Mesh/Gui/Workbench.cpp @@ -45,6 +45,7 @@ using namespace MeshGui; qApp->translate("Workbench", "Analyze"); qApp->translate("Workbench", "Boolean"); qApp->translate("Workbench", "&Meshes"); + qApp->translate("Workbench", "Cutting"); qApp->translate("Workbench", "Mesh tools"); #endif diff --git a/src/Mod/MeshPart/Gui/MeshFlatteningCommand.py b/src/Mod/MeshPart/Gui/MeshFlatteningCommand.py index 8d09f86c82..3b4a910de4 100644 --- a/src/Mod/MeshPart/Gui/MeshFlatteningCommand.py +++ b/src/Mod/MeshPart/Gui/MeshFlatteningCommand.py @@ -26,6 +26,8 @@ import FreeCADGui as Gui import Part import MeshPartGui +from PySide.QtCore import QT_TRANSLATE_NOOP # for translations + class BaseCommand(object): def __init__(self): pass @@ -41,7 +43,9 @@ class CreateFlatMesh(BaseCommand): """create flat wires from a meshed face""" def GetResources(self): - return {'Pixmap': 'MeshPart_CreateFlatMesh.svg', 'MenuText': 'Unwrap Mesh', 'ToolTip': 'find a flat representation of a mesh'} + return {'Pixmap': 'MeshPart_CreateFlatMesh.svg', + 'MenuText': QT_TRANSLATE_NOOP("MeshPart_FlatteningCommand", "Unwrap Mesh"), + 'ToolTip': QT_TRANSLATE_NOOP("MeshPart_FlatteningCommand", "Find a flat representation of a mesh.")} def Activated(self): import numpy as np @@ -69,10 +73,12 @@ class CreateFlatMesh(BaseCommand): class CreateFlatFace(BaseCommand): """create a flat face from a single face only full faces are supported right now""" - + def GetResources(self): - return {'Pixmap': 'MeshPart_CreateFlatFace.svg', 'MenuText': 'Unwrap Face', 'ToolTip': 'find a flat representation of a mesh'} - + return {'Pixmap': 'MeshPart_CreateFlatFace.svg', + 'MenuText': QT_TRANSLATE_NOOP("MeshPart_FlatteningCommand", "Unwrap Face"), + 'ToolTip': QT_TRANSLATE_NOOP("MeshPart_FlatteningCommand", "Find a flat representation of a mesh.")} + def Activated(self): import numpy as np import flatmesh @@ -96,12 +102,14 @@ class CreateFlatFace(BaseCommand): bs.setPole(u + 1, v + 1, App.Vector(poles[i])) i += 1 Part.show(bs.toShape()) - + def IsActive(self): assert(super(CreateFlatFace, self).IsActive()) assert(isinstance(Gui.Selection.getSelectionEx()[0].SubObjects[0], Part.Face)) return True + +# Test if pybind11 dependency is available try: import flatmesh Gui.addCommand('MeshPart_CreateFlatMesh', CreateFlatMesh()) diff --git a/src/Mod/Part/App/TopoShapeFacePy.xml b/src/Mod/Part/App/TopoShapeFacePy.xml index c3809f047f..cc6c1655f6 100644 --- a/src/Mod/Part/App/TopoShapeFacePy.xml +++ b/src/Mod/Part/App/TopoShapeFacePy.xml @@ -30,6 +30,11 @@ Returns Compound of Wires. Deprecated - use makeOffset2D instead.
+ + + Profile along the spine + + Get the list of (u,v) nodes of the tessellation diff --git a/src/Mod/Part/App/TopoShapeFacePyImp.cpp b/src/Mod/Part/App/TopoShapeFacePyImp.cpp index 63e8bead74..089857d117 100644 --- a/src/Mod/Part/App/TopoShapeFacePyImp.cpp +++ b/src/Mod/Part/App/TopoShapeFacePyImp.cpp @@ -70,6 +70,7 @@ # include # include #endif // _PreComp +#include #include #include @@ -99,6 +100,10 @@ using namespace Part; +namespace Part { + extern Py::Object shape2pyshape(const TopoDS_Shape &shape); +} + // returns a string which represent the object e.g. when printed in python std::string TopoShapeFacePy::representation(void) const { @@ -438,6 +443,66 @@ PyObject* TopoShapeFacePy::makeOffset(PyObject *args) return new TopoShapePy(new TopoShape(mkOffset.Shape())); } +/* +import PartEnums +v = App.Vector +profile = Part.makePolygon([v(0.,0.,0.), v(-60.,-60.,-100.), v(-60.,-60.,-140.)]) +spine = Part.Face(Part.makePolygon([v(0.,0.,0.), v(100.,0.,0.), v(100.,100.,0.), v(0.,100.,0.), v(0.,0.,0.)])) +evolve = spine.makeEvolved(Profile=profile, Join=PartEnums.JoinType.Arc) +*/ +PyObject* TopoShapeFacePy::makeEvolved(PyObject *args, PyObject *kwds) +{ + PyObject* Profile; + PyObject* AxeProf = Py_True; + PyObject* Solid = Py_False; + PyObject* ProfOnSpine = Py_False; + int JoinType = int(GeomAbs_Arc); + double Tolerance = 0.0000001; + + static char* kwds_evolve[] = {"Profile", "Join", "AxeProf", "Solid", "ProfOnSpine", "Tolerance", nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|iO!O!O!d", kwds_evolve, + &TopoShapeWirePy::Type, &Profile, &JoinType, + &PyBool_Type, &AxeProf, &PyBool_Type, &Solid, + &PyBool_Type, &ProfOnSpine, &Tolerance)) + return nullptr; + + const TopoDS_Face& spine = TopoDS::Face(getTopoShapePtr()->getShape()); + BRepBuilderAPI_FindPlane findPlane(spine); + if (!findPlane.Found()) { + PyErr_SetString(PartExceptionOCCError, "No planar face"); + return nullptr; + } + + const TopoDS_Wire& profile = TopoDS::Wire(static_cast(Profile)->getTopoShapePtr()->getShape()); + + GeomAbs_JoinType joinType; + switch (JoinType) { + case GeomAbs_Tangent: + joinType = GeomAbs_Tangent; + break; + case GeomAbs_Intersection: + joinType = GeomAbs_Intersection; + break; + default: + joinType = GeomAbs_Arc; + break; + } + + try { + BRepOffsetAPI_MakeEvolved evolved(spine, profile, joinType, + PyObject_IsTrue(AxeProf) ? Standard_True : Standard_False, + PyObject_IsTrue(Solid) ? Standard_True : Standard_False, + PyObject_IsTrue(ProfOnSpine) ? Standard_True : Standard_False, + Tolerance); + TopoDS_Shape shape = evolved.Shape(); + return Py::new_reference_to(shape2pyshape(shape)); + } + catch (Standard_Failure& e) { + PyErr_SetString(PartExceptionOCCError, e.GetMessageString()); + return nullptr; + } +} + PyObject* TopoShapeFacePy::valueAt(PyObject *args) { double u,v; diff --git a/src/Mod/Part/App/TopoShapeWirePy.xml b/src/Mod/Part/App/TopoShapeWirePy.xml index ab033e2064..04959c1d03 100644 --- a/src/Mod/Part/App/TopoShapeWirePy.xml +++ b/src/Mod/Part/App/TopoShapeWirePy.xml @@ -58,6 +58,11 @@ Transition can be 0 (default), 1 (right corners) or 2 (rounded corners). + + + Profile along the spine + + Approximate B-Spline-curve from this wire diff --git a/src/Mod/Part/App/TopoShapeWirePyImp.cpp b/src/Mod/Part/App/TopoShapeWirePyImp.cpp index a1f32d6d03..c48b6046b0 100644 --- a/src/Mod/Part/App/TopoShapeWirePyImp.cpp +++ b/src/Mod/Part/App/TopoShapeWirePyImp.cpp @@ -45,6 +45,7 @@ # include # include #endif +#include #include #include @@ -307,6 +308,66 @@ PyObject* TopoShapeWirePy::makePipeShell(PyObject *args) return 0; } +/* +import PartEnums +v = App.Vector +profile = Part.makePolygon([v(0.,0.,0.), v(-60.,-60.,-100.), v(-60.,-60.,-140.)]) +spine = Part.makePolygon([v(0.,0.,0.), v(100.,0.,0.), v(100.,100.,0.), v(0.,100.,0.), v(0.,0.,0.)]) +evolve = spine.makeEvolved(Profile=profile, Join=PartEnums.JoinType.Arc) +*/ +PyObject* TopoShapeWirePy::makeEvolved(PyObject *args, PyObject *kwds) +{ + PyObject* Profile; + PyObject* AxeProf = Py_True; + PyObject* Solid = Py_False; + PyObject* ProfOnSpine = Py_False; + int JoinType = int(GeomAbs_Arc); + double Tolerance = 0.0000001; + + static char* kwds_evolve[] = {"Profile", "Join", "AxeProf", "Solid", "ProfOnSpine", "Tolerance", nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|iO!O!O!d", kwds_evolve, + &TopoShapeWirePy::Type, &Profile, &JoinType, + &PyBool_Type, &AxeProf, &PyBool_Type, &Solid, + &PyBool_Type, &ProfOnSpine, &Tolerance)) + return nullptr; + + const TopoDS_Wire& spine = TopoDS::Wire(getTopoShapePtr()->getShape()); + BRepBuilderAPI_FindPlane findPlane(spine); + if (!findPlane.Found()) { + PyErr_SetString(PartExceptionOCCError, "No planar wire"); + return nullptr; + } + + const TopoDS_Wire& profile = TopoDS::Wire(static_cast(Profile)->getTopoShapePtr()->getShape()); + + GeomAbs_JoinType joinType; + switch (JoinType) { + case GeomAbs_Tangent: + joinType = GeomAbs_Tangent; + break; + case GeomAbs_Intersection: + joinType = GeomAbs_Intersection; + break; + default: + joinType = GeomAbs_Arc; + break; + } + + try { + BRepOffsetAPI_MakeEvolved evolved(spine, profile, joinType, + PyObject_IsTrue(AxeProf) ? Standard_True : Standard_False, + PyObject_IsTrue(Solid) ? Standard_True : Standard_False, + PyObject_IsTrue(ProfOnSpine) ? Standard_True : Standard_False, + Tolerance); + TopoDS_Shape shape = evolved.Shape(); + return Py::new_reference_to(shape2pyshape(shape)); + } + catch (Standard_Failure& e) { + PyErr_SetString(PartExceptionOCCError, e.GetMessageString()); + return nullptr; + } +} + PyObject* TopoShapeWirePy::makeHomogenousWires(PyObject *args) { PyObject* wire; diff --git a/src/Mod/Part/BasicShapes/CommandShapes.py b/src/Mod/Part/BasicShapes/CommandShapes.py index 1b2601f2f5..6b5639e244 100644 --- a/src/Mod/Part/BasicShapes/CommandShapes.py +++ b/src/Mod/Part/BasicShapes/CommandShapes.py @@ -53,6 +53,9 @@ class CommandTube: tube = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Tube") Shapes.TubeFeature(tube) vp = ViewProviderShapes.ViewProviderTube(tube.ViewObject) + activePart = FreeCADGui.activeView().getActiveObject('part') + if activePart: + activePart.addObject(tube) FreeCAD.ActiveDocument.recompute() vp.startDefaultEditMode(tube.ViewObject) diff --git a/src/Mod/Part/CMakeLists.txt b/src/Mod/Part/CMakeLists.txt index e231288c21..8497099ca8 100644 --- a/src/Mod/Part/CMakeLists.txt +++ b/src/Mod/Part/CMakeLists.txt @@ -8,6 +8,7 @@ set(Part_Scripts Init.py JoinFeatures.py MakeBottle.py + PartEnums.py TestPartApp.py ) diff --git a/src/Mod/Part/Gui/CommandParametric.cpp b/src/Mod/Part/Gui/CommandParametric.cpp index 7cb297458b..9196add7ae 100644 --- a/src/Mod/Part/Gui/CommandParametric.cpp +++ b/src/Mod/Part/Gui/CommandParametric.cpp @@ -30,9 +30,29 @@ # include #endif +#include #include #include #include +#include + +//=========================================================================== +// Utils +//=========================================================================== +namespace { +QString getAutoGroupCommandStr() +// Helper function to get the python code to add the newly created object to the active Part object if present +{ + App::Part* activePart = Gui::Application::Instance->activeView()->getActiveObject("part"); + if (activePart) { + QString activePartName = QString::fromLatin1(activePart->getNameInDocument()); + return QString::fromLatin1("App.ActiveDocument.getObject('%1\')." + "addObject(App.ActiveDocument.ActiveObject)\n") + .arg(activePartName); + } + return QString::fromLatin1("# Object created at document root."); +} +} //=========================================================================== // Part_Cylinder @@ -62,6 +82,7 @@ void CmdPartCylinder::activated(int iMsg) cmd = QString::fromLatin1("App.ActiveDocument.ActiveObject.Label = \"%1\"") .arg(qApp->translate("CmdPartCylinder","Cylinder")); runCommand(Doc,cmd.toUtf8()); + runCommand(Doc, getAutoGroupCommandStr().toUtf8()); commitCommand(); updateActive(); runCommand(Gui, "Gui.SendMsgToActiveView(\"ViewFit\")"); @@ -103,6 +124,7 @@ void CmdPartBox::activated(int iMsg) cmd = QString::fromLatin1("App.ActiveDocument.ActiveObject.Label = \"%1\"") .arg(qApp->translate("CmdPartBox","Cube")); runCommand(Doc,cmd.toUtf8()); + runCommand(Doc, getAutoGroupCommandStr().toUtf8()); commitCommand(); updateActive(); runCommand(Gui, "Gui.SendMsgToActiveView(\"ViewFit\")"); @@ -144,6 +166,7 @@ void CmdPartSphere::activated(int iMsg) cmd = QString::fromLatin1("App.ActiveDocument.ActiveObject.Label = \"%1\"") .arg(qApp->translate("CmdPartSphere","Sphere")); runCommand(Doc,cmd.toUtf8()); + runCommand(Doc, getAutoGroupCommandStr().toUtf8()); commitCommand(); updateActive(); runCommand(Gui, "Gui.SendMsgToActiveView(\"ViewFit\")"); @@ -185,6 +208,7 @@ void CmdPartCone::activated(int iMsg) cmd = QString::fromLatin1("App.ActiveDocument.ActiveObject.Label = \"%1\"") .arg(qApp->translate("CmdPartCone","Cone")); runCommand(Doc,cmd.toUtf8()); + runCommand(Doc, getAutoGroupCommandStr().toUtf8()); commitCommand(); updateActive(); runCommand(Gui, "Gui.SendMsgToActiveView(\"ViewFit\")"); @@ -226,6 +250,7 @@ void CmdPartTorus::activated(int iMsg) cmd = QString::fromLatin1("App.ActiveDocument.ActiveObject.Label = \"%1\"") .arg(qApp->translate("CmdPartTorus","Torus")); runCommand(Doc,cmd.toUtf8()); + runCommand(Doc, getAutoGroupCommandStr().toUtf8()); commitCommand(); updateActive(); runCommand(Gui, "Gui.SendMsgToActiveView(\"ViewFit\")"); diff --git a/src/Mod/Part/Gui/DlgPrimitives.cpp b/src/Mod/Part/Gui/DlgPrimitives.cpp index 3e428146ad..ee3193748b 100644 --- a/src/Mod/Part/Gui/DlgPrimitives.cpp +++ b/src/Mod/Part/Gui/DlgPrimitives.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -64,6 +65,20 @@ using namespace PartGui; namespace PartGui { + QString getAutoGroupCommandStr(QString objectName) + // Helper function to get the python code to add the newly created object to the active Part object if present + { + App::Part* activePart = Gui::Application::Instance->activeView()->getActiveObject("part"); + if (activePart) { + QString activeObjectName = QString::fromLatin1(activePart->getNameInDocument()); + return QString::fromLatin1("App.ActiveDocument.getObject('%1\')." + "addObject(App.ActiveDocument.getObject('%2\'))\n") + .arg(activeObjectName) + .arg(objectName); + } + return QString::fromLatin1("# Object %1 created at document root").arg(objectName); + } + const char* gce_ErrorStatusText(gce_ErrorType et) { switch (et) @@ -1107,6 +1122,7 @@ void DlgPrimitives::createPrimitive(const QString& placement) QString prim = tr("Create %1").arg(ui->PrimitiveTypeCB->currentText()); Gui::Application::Instance->activeDocument()->openCommand(prim.toUtf8()); Gui::Command::runCommand(Gui::Command::Doc, cmd.toUtf8()); + Gui::Command::runCommand(Gui::Command::Doc, getAutoGroupCommandStr(name).toUtf8()); Gui::Application::Instance->activeDocument()->commitCommand(); Gui::Command::runCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); Gui::Command::runCommand(Gui::Command::Gui, "Gui.SendMsgToActiveView(\"ViewFit\")"); diff --git a/src/Mod/Part/Gui/ViewProviderBoolean.h b/src/Mod/Part/Gui/ViewProviderBoolean.h index ac97433d90..3c379309ab 100644 --- a/src/Mod/Part/Gui/ViewProviderBoolean.h +++ b/src/Mod/Part/Gui/ViewProviderBoolean.h @@ -44,9 +44,6 @@ public: QIcon getIcon(void) const; void updateData(const App::Property*); bool onDelete(const std::vector &); - - virtual bool allowTreeOrderSwap(const App::DocumentObject *, const App::DocumentObject *) const { return false; } - }; /// ViewProvider for the MultiFuse feature diff --git a/src/Mod/Part/Gui/ViewProviderMirror.h b/src/Mod/Part/Gui/ViewProviderMirror.h index 1664528a41..1753a66291 100644 --- a/src/Mod/Part/Gui/ViewProviderMirror.h +++ b/src/Mod/Part/Gui/ViewProviderMirror.h @@ -126,8 +126,6 @@ public: /// grouping handling std::vector claimChildren(void)const; bool onDelete(const std::vector &); - - virtual bool allowTreeOrderSwap(const App::DocumentObject *, const App::DocumentObject *) const { return false; } }; class ViewProviderSweep : public ViewProviderPart @@ -143,8 +141,6 @@ public: /// grouping handling std::vector claimChildren(void)const; bool onDelete(const std::vector &); - - virtual bool allowTreeOrderSwap(const App::DocumentObject *, const App::DocumentObject *) const { return false; } }; class ViewProviderOffset : public ViewProviderPart @@ -192,8 +188,6 @@ public: void setupContextMenu(QMenu*, QObject*, const char*); bool onDelete(const std::vector &); - virtual bool allowTreeOrderSwap(const App::DocumentObject *, const App::DocumentObject *) const { return false; } - protected: virtual bool setEdit(int ModNum); virtual void unsetEdit(int ModNum); diff --git a/src/Mod/Part/PartEnums.py b/src/Mod/Part/PartEnums.py new file mode 100644 index 0000000000..dbd983c908 --- /dev/null +++ b/src/Mod/Part/PartEnums.py @@ -0,0 +1,65 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2021 Werner Mayer * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +__title__ = "PartEnums module" +__author__ = "Werner Mayer" +__url__ = "http://www.freecadweb.org" +__doc__ = "Enum types" + +from enum import IntEnum + +class JoinType(IntEnum): + Arc = 0 + Tangent = 1 + Intersection = 2 + +class Shape(IntEnum): + C0 = 0 + G1 = 1 + C1 = 2 + G2 = 3 + C2 = 4 + C3 = 5 + CN = 6 + +class FillingStyle(IntEnum): + StretchStyle = 0 + CoonsStyle = 1 + CurvedStyle = 2 + +class Orientation(IntEnum): + FORWARD = 0 + REVERSED = 1 + INTERNAL = 2 + EXTERNAL = 3 + +class ShapeEnum(IntEnum): + COMPOUND = 0 + COMPSOLID = 1 + SOLID = 2 + SHELL= 3 + FACE = 4 + WIRE = 5 + EDGE = 6 + VERTEX = 7 + SHAPE = 8 + diff --git a/src/Mod/PartDesign/App/AppPartDesign.cpp b/src/Mod/PartDesign/App/AppPartDesign.cpp index ce39347034..f130609fc0 100644 --- a/src/Mod/PartDesign/App/AppPartDesign.cpp +++ b/src/Mod/PartDesign/App/AppPartDesign.cpp @@ -103,6 +103,7 @@ PyMOD_INIT_FUNC(_PartDesign) PartDesign::MultiTransform ::init(); PartDesign::Hole ::init(); PartDesign::Body ::init(); + PartDesign::FeatureExtrude ::init(); PartDesign::Pad ::init(); PartDesign::Pocket ::init(); PartDesign::Fillet ::init(); diff --git a/src/Mod/PartDesign/App/CMakeLists.txt b/src/Mod/PartDesign/App/CMakeLists.txt index a623563930..d74a77c1d5 100644 --- a/src/Mod/PartDesign/App/CMakeLists.txt +++ b/src/Mod/PartDesign/App/CMakeLists.txt @@ -87,6 +87,8 @@ SET(FeaturesDressUp_SRCS SOURCE_GROUP("DressUpFeatures" FILES ${FeaturesDressUp_SRCS}) SET(FeaturesSketchBased_SRCS + FeatureExtrude.cpp + FeatureExtrude.h FeatureSketchBased.cpp FeatureSketchBased.h FeaturePad.cpp diff --git a/src/Mod/PartDesign/App/FeatureDressUp.cpp b/src/Mod/PartDesign/App/FeatureDressUp.cpp index 430eac64ba..6068f48df4 100644 --- a/src/Mod/PartDesign/App/FeatureDressUp.cpp +++ b/src/Mod/PartDesign/App/FeatureDressUp.cpp @@ -116,7 +116,7 @@ void DressUp::getContinuousEdges(Part::TopoShape TopShape, std::vector< std::str { std::string aSubName = static_cast(SubNames.at(i)); - if (aSubName.size() > 4 && aSubName.substr(0,4) == "Edge") { + if (aSubName.compare(0, 4, "Edge") == 0) { TopoDS_Edge edge = TopoDS::Edge(TopShape.getSubShape(aSubName.c_str())); const TopTools_ListOfShape& los = mapEdgeFace.FindFromKey(edge); @@ -138,7 +138,7 @@ void DressUp::getContinuousEdges(Part::TopoShape TopShape, std::vector< std::str i++; } - else if(aSubName.size() > 4 && aSubName.substr(0,4) == "Face") { + else if(aSubName.compare(0, 4, "Face") == 0) { TopoDS_Face face = TopoDS::Face(TopShape.getSubShape(aSubName.c_str())); TopTools_IndexedMapOfShape mapOfFaces; diff --git a/src/Mod/PartDesign/App/FeatureExtrude.cpp b/src/Mod/PartDesign/App/FeatureExtrude.cpp new file mode 100644 index 0000000000..765ea8ae69 --- /dev/null +++ b/src/Mod/PartDesign/App/FeatureExtrude.cpp @@ -0,0 +1,131 @@ +/*************************************************************************** + * Copyright (c) 2010 Juergen Riegel * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include + +#include "FeatureExtrude.h" + +using namespace PartDesign; + + +PROPERTY_SOURCE(PartDesign::FeatureExtrude, PartDesign::ProfileBased) + +FeatureExtrude::FeatureExtrude() +{ +} + +short FeatureExtrude::mustExecute() const +{ + if (Placement.isTouched() || + Type.isTouched() || + Length.isTouched() || + Length2.isTouched() || + UseCustomVector.isTouched() || + Direction.isTouched() || + ReferenceAxis.isTouched() || + AlongSketchNormal.isTouched() || + Offset.isTouched() || + UpToFace.isTouched()) + return 1; + return ProfileBased::mustExecute(); +} + +Base::Vector3d FeatureExtrude::computeDirection(const Base::Vector3d& sketchVector) +{ + Base::Vector3d extrudeDirection; + + if (!UseCustomVector.getValue()) { + if (!ReferenceAxis.getValue()) { + // use sketch's normal vector for direction + extrudeDirection = sketchVector; + AlongSketchNormal.setReadOnly(true); + } + else { + // update Direction from ReferenceAxis + App::DocumentObject* pcReferenceAxis = ReferenceAxis.getValue(); + const std::vector& subReferenceAxis = ReferenceAxis.getSubValues(); + Base::Vector3d base; + Base::Vector3d dir; + getAxis(pcReferenceAxis, subReferenceAxis, base, dir, ForbiddenAxis::NotPerpendicularWithNormal); + switch (addSubType) { + case Type::Additive: + extrudeDirection = dir; + break; + case Type::Subtractive: + extrudeDirection = -dir; + break; + } + } + } + else { + // use the given vector + // if null vector, use sketchVector + if ((fabs(Direction.getValue().x) < Precision::Confusion()) + && (fabs(Direction.getValue().y) < Precision::Confusion()) + && (fabs(Direction.getValue().z) < Precision::Confusion())) { + Direction.setValue(sketchVector); + } + extrudeDirection = Direction.getValue(); + } + + // disable options of UseCustomVector + Direction.setReadOnly(!UseCustomVector.getValue()); + ReferenceAxis.setReadOnly(UseCustomVector.getValue()); + // UseCustomVector allows AlongSketchNormal but !UseCustomVector does not forbid it + if (UseCustomVector.getValue()) + AlongSketchNormal.setReadOnly(false); + + // explicitly set the Direction so that the dialog shows also the used direction + // if the sketch's normal vector was used + Direction.setValue(extrudeDirection); + return extrudeDirection; +} diff --git a/src/Mod/PartDesign/App/FeatureExtrude.h b/src/Mod/PartDesign/App/FeatureExtrude.h new file mode 100644 index 0000000000..cfa1522de8 --- /dev/null +++ b/src/Mod/PartDesign/App/FeatureExtrude.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (c) 2010 Juergen Riegel * + * * + * 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 PARTDESIGN_FEATURE_EXTRUDE_H +#define PARTDESIGN_FEATURE_EXTRUDE_H + +#include +#include +#include + +#include "FeatureSketchBased.h" + +namespace PartDesign +{ + +class PartDesignExport FeatureExtrude : public ProfileBased +{ + PROPERTY_HEADER(PartDesign::FeatureExtrude); + +public: + FeatureExtrude(); + + App::PropertyEnumeration Type; + App::PropertyLength Length; + App::PropertyLength Length2; + App::PropertyBool UseCustomVector; + App::PropertyVector Direction; + App::PropertyBool AlongSketchNormal; + App::PropertyLength Offset; + App::PropertyLinkSub ReferenceAxis; + + /** @name methods override feature */ + //@{ + short mustExecute() const; + //@} + +protected: + Base::Vector3d computeDirection(const Base::Vector3d& sketchVector); +}; + +} //namespace PartDesign + + +#endif // PARTDESIGN_FEATURE_EXTRUDE_H diff --git a/src/Mod/PartDesign/App/FeatureGroove.cpp b/src/Mod/PartDesign/App/FeatureGroove.cpp index 2b4ffc00ba..bb0822ef11 100644 --- a/src/Mod/PartDesign/App/FeatureGroove.cpp +++ b/src/Mod/PartDesign/App/FeatureGroove.cpp @@ -208,7 +208,7 @@ void Groove::updateAxis(void) const std::vector &subReferenceAxis = ReferenceAxis.getSubValues(); Base::Vector3d base; Base::Vector3d dir; - getAxis(pcReferenceAxis, subReferenceAxis, base, dir); + getAxis(pcReferenceAxis, subReferenceAxis, base, dir, ForbiddenAxis::NotParallelWithNormal); if (dir.Length() > Precision::Confusion()) { Base.setValue(base.x,base.y,base.z); diff --git a/src/Mod/PartDesign/App/FeatureHelix.cpp b/src/Mod/PartDesign/App/FeatureHelix.cpp index a6fdedac22..7fb90f15cf 100644 --- a/src/Mod/PartDesign/App/FeatureHelix.cpp +++ b/src/Mod/PartDesign/App/FeatureHelix.cpp @@ -63,11 +63,9 @@ # include "FeatureHelix.h" -const double PI = 3.14159265359; - using namespace PartDesign; -const char* Helix::ModeEnums[] = {"pitch-height-angle", "pitch-turns-angle", "height-turns-angle", "height-turns-growth", NULL}; +const char* Helix::ModeEnums[] = { "pitch-height-angle", "pitch-turns-angle", "height-turns-angle", "height-turns-growth", NULL }; PROPERTY_SOURCE(PartDesign::Helix, PartDesign::ProfileBased) @@ -108,41 +106,48 @@ short Helix::mustExecute() const return ProfileBased::mustExecute(); } -App::DocumentObjectExecReturn *Helix::execute(void) +App::DocumentObjectExecReturn* Helix::execute(void) { - // Validate and normalize parameters + // Validate and normalize parameters HelixMode mode = static_cast(Mode.getValue()); if (mode == HelixMode::pitch_height_angle) { if (Pitch.getValue() < Precision::Confusion()) return new App::DocumentObjectExecReturn("Error: Pitch too small"); if (Height.getValue() < Precision::Confusion()) return new App::DocumentObjectExecReturn("Error: height too small!"); - Turns.setValue(Height.getValue()/Pitch.getValue()); - } else if (mode == HelixMode::pitch_turns_angle) { + Turns.setValue(Height.getValue() / Pitch.getValue()); + } + else if (mode == HelixMode::pitch_turns_angle) { if (Pitch.getValue() < Precision::Confusion()) return new App::DocumentObjectExecReturn("Error: pitch too small!"); if (Turns.getValue() < Precision::Confusion()) return new App::DocumentObjectExecReturn("Error: turns too small!"); - Height.setValue(Turns.getValue()*Pitch.getValue()); - } else if (mode == HelixMode::height_turns_angle) { + Height.setValue(Turns.getValue() * Pitch.getValue()); + } + else if (mode == HelixMode::height_turns_angle) { if (Height.getValue() < Precision::Confusion()) return new App::DocumentObjectExecReturn("Error: height too small!"); if (Turns.getValue() < Precision::Confusion()) - return new App::DocumentObjectExecReturn("Error turns too small!"); - Pitch.setValue(Height.getValue()/Turns.getValue()); - } else if (mode == HelixMode::height_turns_growth) { + return new App::DocumentObjectExecReturn("Error: turns too small!"); + Pitch.setValue(Height.getValue() / Turns.getValue()); + } + else if (mode == HelixMode::height_turns_growth) { if (Turns.getValue() < Precision::Confusion()) - return new App::DocumentObjectExecReturn("Error turns too small!"); - Pitch.setValue(Height.getValue()/Turns.getValue()); - } else { + return new App::DocumentObjectExecReturn("Error: turns too small!"); + if ((Height.getValue() < Precision::Confusion()) + && (abs(Growth.getValue()) < Precision::Confusion())) + return new App::DocumentObjectExecReturn("Error: either height or growth must not be zero!"); + Pitch.setValue(Height.getValue() / Turns.getValue()); + } + else { return new App::DocumentObjectExecReturn("Error: unsupported mode"); } - TopoDS_Shape sketchshape; try { sketchshape = getVerifiedFace(); - } catch (const Base::Exception& e) { + } + catch (const Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); } @@ -163,7 +168,8 @@ App::DocumentObjectExecReturn *Helix::execute(void) TopoDS_Shape base; try { base = getBaseShape(); - } catch (const Base::Exception&) { + } + catch (const Base::Exception&) { // fall back to support (for legacy features) base = TopoDS_Shape(); } @@ -171,7 +177,8 @@ App::DocumentObjectExecReturn *Helix::execute(void) // update Axis from ReferenceAxis try { updateAxis(); - } catch (const Base::Exception& e) { + } + catch (const Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); } @@ -187,18 +194,19 @@ App::DocumentObjectExecReturn *Helix::execute(void) std::vector wires; try { wires = getProfileWires(); - } catch (const Base::Exception& e) { + } + catch (const Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); } std::vector> wiresections; - for(TopoDS_Wire& wire : wires) + for (TopoDS_Wire& wire : wires) wiresections.emplace_back(1, wire); //build all shells std::vector shells; std::vector frontwires, backwires; - for(std::vector& wires : wiresections) { + for (std::vector& wires : wiresections) { BRepOffsetAPI_MakePipeShell mkPS(TopoDS::Wire(path)); @@ -206,7 +214,7 @@ App::DocumentObjectExecReturn *Helix::execute(void) mkPS.SetTransitionMode(BRepBuilderAPI_Transformed); mkPS.SetMode(true); //This is for frenet - for(TopoDS_Wire& wire : wires) { + for (TopoDS_Wire& wire : wires) { wire.Move(invObjLoc); mkPS.Add(wire); } @@ -231,19 +239,20 @@ App::DocumentObjectExecReturn *Helix::execute(void) if (!frontwires.empty()) { // build the end faces, sew the shell and build the final solid TopoDS_Shape front = Part::FaceMakerCheese::makeFace(frontwires); - TopoDS_Shape back = Part::FaceMakerCheese::makeFace(backwires); + TopoDS_Shape back = Part::FaceMakerCheese::makeFace(backwires); BRepBuilderAPI_Sewing sewer; sewer.SetTolerance(Precision::Confusion()); sewer.Add(front); sewer.Add(back); - for(TopoDS_Shape& s : shells) + for (TopoDS_Shape& s : shells) sewer.Add(s); sewer.Perform(); mkSolid.Add(TopoDS::Shell(sewer.SewedShape())); - } else { + } + else { // shells are already closed - add them directly for (TopoDS_Shape& s : shells) { mkSolid.Add(TopoDS::Shell(s)); @@ -305,7 +314,8 @@ App::DocumentObjectExecReturn *Helix::execute(void) return new App::DocumentObjectExecReturn("Error: Intersecting the helix failed"); boolOp = this->getSolid(mkCom.Shape()); - } else { + } + else { BRepAlgoAPI_Cut mkCut(base, result); if (!mkCut.IsDone()) return new App::DocumentObjectExecReturn("Error: Subtracting the helix failed"); @@ -341,14 +351,14 @@ App::DocumentObjectExecReturn *Helix::execute(void) void Helix::updateAxis(void) { - App::DocumentObject *pcReferenceAxis = ReferenceAxis.getValue(); - const std::vector &subReferenceAxis = ReferenceAxis.getSubValues(); + App::DocumentObject* pcReferenceAxis = ReferenceAxis.getValue(); + const std::vector& subReferenceAxis = ReferenceAxis.getSubValues(); Base::Vector3d base; Base::Vector3d dir; - getAxis(pcReferenceAxis, subReferenceAxis, base, dir, false); + getAxis(pcReferenceAxis, subReferenceAxis, base, dir, ForbiddenAxis::NoCheck); - Base.setValue(base.x,base.y,base.z); - Axis.setValue(dir.x,dir.y,dir.z); + Base.setValue(base.x, base.y, base.z); + Axis.setValue(dir.x, dir.y, dir.z); } TopoDS_Shape Helix::generateHelixPath(void) @@ -365,24 +375,38 @@ TopoDS_Shape Helix::generateHelixPath(void) // get revolve axis Base::Vector3d b = Base.getValue(); - gp_Pnt pnt(b.x,b.y,b.z); + gp_Pnt pnt(b.x, b.y, b.z); Base::Vector3d v = Axis.getValue(); - gp_Dir dir(v.x,v.y,v.z); + gp_Dir dir(v.x, v.y, v.z); Base::Vector3d normal = getProfileNormal(); Base::Vector3d start = v.Cross(normal); // pointing towards the desired helix start point. + + // if our axis is (nearly) aligned with the profile's normal, we're only interested in the "twist" + // of the helix. The actual starting point, and thus the radius, isn't important as long as it's + // somewhere in the profile's plane: an arbitrary vector perpendicular to the normal. + if (start.IsNull()) { + auto hopefullyNotParallel = Base::Vector3d(1.0, 2.0, 3.0); + start = normal.Cross(hopefullyNotParallel); + if (start.IsNull()) { + // bad luck + hopefullyNotParallel = Base::Vector3d(3.0, 2.0, 1.0); + start = normal.Cross(hopefullyNotParallel); + } + } + gp_Dir dir_start(start.x, start.y, start.z); // Find out in what quadrant relative to the axis the profile is located, and the exact position. Base::Vector3d profileCenter = getProfileCenterPoint(); - double axisOffset = profileCenter*start - b*start; - double startOffset = profileCenter*v - b*v; + double axisOffset = profileCenter * start - b * start; + double startOffset = profileCenter * v - b * v; double radius = std::fabs(axisOffset); bool turned = axisOffset < 0; - if (radius < Precision::Confusion()) { + if (radius < Precision::Confusion()) { // in this case ensure that axis is not in the sketch plane - if (std::fabs(v*normal) < Precision::Confusion()) + if (std::fabs(v * normal) < Precision::Confusion()) throw Base::ValueError("Error: Result is self intersecting"); radius = 1.0; //fallback to radius 1 } @@ -390,7 +414,7 @@ TopoDS_Shape Helix::generateHelixPath(void) bool growthMode = std::string(Mode.getValueAsString()).find("growth") != std::string::npos; double radiusTop; if (growthMode) - radiusTop = radius + turns*growth; + radiusTop = radius + turns * growth; else radiusTop = radius + height * tan(Base::toRadians(angle)); @@ -414,19 +438,19 @@ TopoDS_Shape Helix::generateHelixPath(void) if (reversed) { - mov.SetRotation(gp_Ax1(origo, dir_axis2), PI); + mov.SetRotation(gp_Ax1(origo, dir_axis2), M_PI); TopLoc_Location loc(mov); path.Move(loc); } if (abs(startOffset) > 0) { // translate the helix so that the starting point aligns with the profile - mov.SetTranslation(startOffset*gp_Vec(dir_axis1)); + mov.SetTranslation(startOffset * gp_Vec(dir_axis1)); TopLoc_Location loc(mov); path.Move(loc); } if (turned) { // turn the helix so that the starting point aligns with the profile - mov.SetRotation(gp_Ax1(origo, dir_axis1), PI); + mov.SetRotation(gp_Ax1(origo, dir_axis1), M_PI); TopLoc_Location loc(mov); path.Move(loc); } @@ -447,32 +471,36 @@ TopoDS_Shape Helix::generateHelixPath(void) // this function calculates self intersection safe pitch based on the profile bounding box. double Helix::safePitch() { - // Below is an approximation. It is possible to do the general way by solving for the pitch - // where the helix is self intersecting. - - double angle = Angle.getValue()/180.0*PI; + Base::Vector3d axisVec = Axis.getValue(); + Base::Vector3d startVec = axisVec.Cross(getProfileNormal()); // pointing towards the helix start point + if (startVec.IsNull()) + return Precision::Confusion(); // if the axis orthogonal to the profile, any pitch > 0 is safe + double angle = Angle.getValue() / 180.0 * M_PI; + gp_Dir direction(axisVec.x, axisVec.y, axisVec.z); + gp_Dir directionStart(startVec.x, startVec.y, startVec.z); TopoDS_Shape sketchshape = getVerifiedFace(); - Bnd_Box bb; - BRepBndLib::Add(sketchshape, bb); + Bnd_Box boundingBox; + BRepBndLib::Add(sketchshape, boundingBox); + // get boundary and dimensions of boundingBox double Xmin, Ymin, Zmin, Xmax, Ymax, Zmax; - bb.Get(Xmin, Ymin, Zmin, Xmax, Ymax, Zmax); - + boundingBox.Get(Xmin, Ymin, Zmin, Xmax, Ymax, Zmax); double X = Xmax - Xmin, Y = Ymax - Ymin, Z = Zmax - Zmin; + gp_Vec boundingBoxVec(X, Y, Z); - Base::Vector3d v = Axis.getValue(); - gp_Dir dir(v.x,v.y,v.z); - gp_Vec bbvec(X, Y, Z); + // Below is an approximation becaue since we take the bounding box it is + // impossible to calculate it precisely. For example a circle has as bounding + // box a square and thus results in a larger pitch than really necessary - double p0 = bbvec*dir; // safe pitch if angle=0 + // minimal safe pitch if the angle is 0 + double p0 = boundingBoxVec * direction; - Base::Vector3d n = getProfileNormal(); - Base::Vector3d s = v.Cross(n); // pointing towards the desired helix start point. - gp_Dir dir_s(s.x, s.y, s.z); - - if (tan(abs(angle))*p0 > abs(bbvec*dir_s)) - return abs(bbvec*dir_s)/tan(abs(angle)); + // if the angle is so large that the distange perpendicular to p0 + // between two turns is larger than the bounding box size in this direction + // the pitch can be smaller than p0 + if (tan(abs(angle)) * p0 > abs(boundingBoxVec * directionStart)) + return abs(boundingBoxVec * directionStart) / tan(abs(angle)); else return p0; } @@ -488,7 +516,7 @@ void Helix::proposeParameters(bool force) double pitch = 1.1 * sqrt(bb.SquareExtent()); Pitch.setValue(pitch); - Height.setValue(pitch*3.0); + Height.setValue(pitch * 3.0); HasBeenEdited.setValue(1); } } @@ -502,7 +530,7 @@ Base::Vector3d Helix::getProfileCenterPoint() box.SetGap(0.0); double xmin, ymin, zmin, xmax, ymax, zmax; box.Get(xmin, ymin, zmin, xmax, ymax, zmax); - return Base::Vector3d(0.5*(xmin+xmax), 0.5*(ymin+ymax), 0.5*(zmin+zmax)); + return Base::Vector3d(0.5 * (xmin + xmax), 0.5 * (ymin + ymax), 0.5 * (zmin + zmax)); } void Helix::handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop) @@ -514,6 +542,13 @@ void Helix::handleChangedPropertyType(Base::XMLReader& reader, const char* TypeN TurnsProperty.Restore(reader); Turns.setValue(TurnsProperty.getValue()); } + // property Growth had the App::PropertyLength and was changed to App::PropertyDistance + else if (prop == &Growth && strcmp(TypeName, "App::PropertyLength") == 0) { + App::PropertyLength GrowthProperty; + // restore the PropertyLength to be able to set its value + GrowthProperty.Restore(reader); + Growth.setValue(GrowthProperty.getValue()); + } else { ProfileBased::handleChangedPropertyType(reader, TypeName, prop); } diff --git a/src/Mod/PartDesign/App/FeatureHelix.h b/src/Mod/PartDesign/App/FeatureHelix.h index 7e9823a0ef..904caf96cc 100644 --- a/src/Mod/PartDesign/App/FeatureHelix.h +++ b/src/Mod/PartDesign/App/FeatureHelix.h @@ -52,7 +52,7 @@ public: App::PropertyFloatConstraint Turns; App::PropertyBool LeftHanded; App::PropertyAngle Angle; - App::PropertyLength Growth; + App::PropertyDistance Growth; App::PropertyEnumeration Mode; App::PropertyBool Outside; App::PropertyBool HasBeenEdited; @@ -64,7 +64,7 @@ public: /** @name methods override feature */ //@{ - App::DocumentObjectExecReturn *execute(void); + App::DocumentObjectExecReturn* execute(void); short mustExecute() const; /// returns the type name of the view provider const char* getViewProviderName(void) const { diff --git a/src/Mod/PartDesign/App/FeatureLinearPattern.cpp b/src/Mod/PartDesign/App/FeatureLinearPattern.cpp index 08ffa89d5b..7d571d66cf 100644 --- a/src/Mod/PartDesign/App/FeatureLinearPattern.cpp +++ b/src/Mod/PartDesign/App/FeatureLinearPattern.cpp @@ -98,12 +98,12 @@ const std::list LinearPattern::getTransformations(const std::vectorgetAxis(Part::Part2DObject::V_Axis); else if (subStrings[0] == "N_Axis") axis = refSketch->getAxis(Part::Part2DObject::N_Axis); - else if (subStrings[0].size() > 4 && subStrings[0].substr(0,4) == "Axis") { + else if (subStrings[0].compare(0, 4, "Axis") == 0) { int AxId = std::atoi(subStrings[0].substr(4,4000).c_str()); if (AxId >= 0 && AxId < refSketch->getAxisCount()) axis = refSketch->getAxis(AxId); } - else if (subStrings[0].substr(0,4) == "Edge") { + else if (subStrings[0].compare(0, 4, "Edge") == 0) { Part::TopoShape refShape = refSketch->Shape.getShape(); TopoDS_Shape ref = refShape.getSubShape(subStrings[0].c_str()); TopoDS_Edge refEdge = TopoDS::Edge(ref); diff --git a/src/Mod/PartDesign/App/FeatureLoft.cpp b/src/Mod/PartDesign/App/FeatureLoft.cpp index 811fd972eb..20fd6c23ec 100644 --- a/src/Mod/PartDesign/App/FeatureLoft.cpp +++ b/src/Mod/PartDesign/App/FeatureLoft.cpp @@ -74,17 +74,48 @@ short Loft::mustExecute() const App::DocumentObjectExecReturn *Loft::execute(void) { + auto getSectionShape = + [](App::DocumentObject* feature, const std::vector &subs) -> TopoDS_Shape { + if (!feature || + !feature->isDerivedFrom(Part::Feature::getClassTypeId())) + throw Base::TypeError("Loft: Invalid profile/section"); + + auto subName = subs.empty() ? "" : subs.front(); + + // only take the entire shape when we have a sketch selected, but + // not a point of the sketch + if (feature->isDerivedFrom(Part::Part2DObject::getClassTypeId()) && + subName.compare(0, 6, "Vertex") != 0) + return static_cast(feature)->Shape.getValue(); + else { + if(subName.empty()) + throw Base::ValueError("No valid subelement linked in Part::Feature"); + return static_cast(feature)->Shape.getShape().getSubShape(subName.c_str()); + } + }; + + auto addWiresToWireSections = + [](TopoDS_Shape& section, + std::vector>& wiresections) -> size_t { + TopExp_Explorer ex; + size_t i=0; + bool initialWireSectionsEmpty = wiresections.empty(); + for (ex.Init(section, TopAbs_WIRE); ex.More(); ex.Next(), ++i) { + // if profile was just a point then this is where we can first set our list + if (i>=wiresections.size()) { + if (initialWireSectionsEmpty) + wiresections.emplace_back(1, ex.Current()); + else + throw Base::ValueError("Loft: Sections need to have the same amount of inner wires (except profile and last section, which can be points)"); + } + else + wiresections[i].push_back(TopoDS::Wire(ex.Current())); + } + return i; + }; std::vector wires; - try { - wires = getProfileWires(); - } catch (const Base::Exception& e) { - return new App::DocumentObjectExecReturn(e.what()); - } - - TopoDS_Shape sketchshape = getVerifiedFace(); - if (sketchshape.IsNull()) - return new App::DocumentObjectExecReturn("Loft: Creating a face from sketch failed"); + TopoDS_Shape profilePoint; // if the Base property has a valid shape, fuse the pipe into it TopoDS_Shape base; @@ -102,80 +133,109 @@ App::DocumentObjectExecReturn *Loft::execute(void) base.Move(invObjLoc); // build up multisections - auto multisections = Sections.getValues(); + auto multisections = Sections.getSubListValues(); if (multisections.empty()) return new App::DocumentObjectExecReturn("Loft: At least one section is needed"); - std::vector> wiresections; - for (TopoDS_Wire& wire : wires) - wiresections.emplace_back(1, wire); + TopoDS_Shape profileShape = getSectionShape(Profile.getValue(), + Profile.getSubValues()); + if (profileShape.IsNull()) + return new App::DocumentObjectExecReturn("Loft: Could not obtain profile shape"); - for (auto obj : multisections) { - if (!obj->isDerivedFrom(Part::Feature::getClassTypeId())) - return new App::DocumentObjectExecReturn("Loft: All sections need to be part features"); - - // if the section is an object's face then take just the face - TopoDS_Shape shape; - if (obj->isDerivedFrom(Part::Part2DObject::getClassTypeId())) - shape = static_cast(obj)->Shape.getValue(); - else { - auto subValues = Sections.getSubValues(obj); - if (subValues.empty()) - throw Base::ValueError("Loft: No valid subelement linked in Part::Feature"); - - shape = static_cast(obj)->Shape.getShape().getSubShape(subValues[0].c_str()); - } + std::vector> wiresections; + size_t numWires = addWiresToWireSections(profileShape, wiresections); + if (numWires == 0) { + // profileShape had no wires so only other valid option is point section TopExp_Explorer ex; - size_t i=0; - for (ex.Init(shape, TopAbs_WIRE); ex.More(); ex.Next(), ++i) { - if (i>=wiresections.size()) - return new App::DocumentObjectExecReturn("Loft: Sections need to have the same amount of inner wires as the base section"); - wiresections[i].push_back(TopoDS::Wire(ex.Current())); + size_t i = 0; + for (ex.Init(profileShape, TopAbs_VERTEX); ex.More(); ex.Next(), ++i) { + profilePoint = ex.Current(); } - if (i 1) + return new App::DocumentObjectExecReturn("Loft: When using points for profile/sections, the sketch should have a single point"); + } + bool isLastSectionVertex = false; + + for (auto &subSet : multisections) { + if (!subSet.first->isDerivedFrom(Part::Feature::getClassTypeId())) + return new App::DocumentObjectExecReturn("Loft: All sections need to be part features"); + + // if the selected subvalue is a point, pick that even if we have a sketch + TopoDS_Shape shape = getSectionShape(subSet.first, subSet.second); + if (shape.IsNull()) + return new App::DocumentObjectExecReturn("Loft: Could not obtain section shape"); + + size_t numWiresAdded = addWiresToWireSections(shape, wiresections); + if (numWiresAdded == 0) { + TopExp_Explorer ex; + size_t j = 0; + for (ex.Init(shape, TopAbs_VERTEX); ex.More(); ex.Next(), ++j) { + if (isLastSectionVertex) + return new App::DocumentObjectExecReturn("Loft: Only the profile and last section can be vertices"); + isLastSectionVertex = true; + for (auto &wires : wiresections) + wires.push_back(ex.Current()); + } + if (j > 1) + return new App::DocumentObjectExecReturn("Loft: When using points for profile/sections, the sketch should have a single point"); + } + if (!isLastSectionVertex && numWiresAdded < wiresections.size()) + return new App::DocumentObjectExecReturn("Loft: Sections need to have the same amount of inner wires as the base section"); } // build all shells std::vector shells; - for (std::vector& wires : wiresections) { + TopoDS_Shape copyProfilePoint(profilePoint); + if (!profilePoint.IsNull()) + copyProfilePoint.Move(invObjLoc); + + for (auto& wires : wiresections) { BRepOffsetAPI_ThruSections mkTS(false, Ruled.getValue(), Precision::Confusion()); - for (TopoDS_Wire& wire : wires) { - wire.Move(invObjLoc); - mkTS.AddWire(wire); + if (!profilePoint.IsNull()) + mkTS.AddVertex(TopoDS::Vertex(copyProfilePoint)); + + for (auto& shape : wires) { + shape.Move(invObjLoc); + if (shape.ShapeType() == TopAbs_VERTEX) + mkTS.AddVertex(TopoDS::Vertex(shape)); + else + mkTS.AddWire(TopoDS::Wire(shape)); } mkTS.Build(); if (!mkTS.IsDone()) return new App::DocumentObjectExecReturn("Loft could not be built"); - //build the shell use simulate to get the top and bottom wires in an easy way + // build the shell use simulate to get the top and bottom wires in an easy way shells.push_back(mkTS.Shape()); } - //build the top and bottom face, sew the shell and build the final solid - TopoDS_Shape front = getVerifiedFace(); - front.Move(invObjLoc); - std::vector backwires; - for (std::vector& wires : wiresections) - backwires.push_back(wires.back()); - - TopoDS_Shape back = Part::FaceMakerCheese::makeFace(backwires); - + // build the top and bottom faces (where possible), sew the shell, + // and build the final solid BRepBuilderAPI_Sewing sewer; sewer.SetTolerance(Precision::Confusion()); - sewer.Add(front); - sewer.Add(back); + if (profilePoint.IsNull()) { + TopoDS_Shape front = getVerifiedFace(); + front.Move(invObjLoc); + sewer.Add(front); + } + if (!isLastSectionVertex) { + std::vector backwires; + for (auto& wires : wiresections) + backwires.push_back(TopoDS::Wire(wires.back())); + TopoDS_Shape back = Part::FaceMakerCheese::makeFace(backwires); + sewer.Add(back); + } for (TopoDS_Shape& s : shells) sewer.Add(s); sewer.Perform(); - //build the solid + // build the solid BRepBuilderAPI_MakeSolid mkSolid; mkSolid.Add(TopoDS::Shell(sewer.SewedShape())); if (!mkSolid.IsDone()) diff --git a/src/Mod/PartDesign/App/FeatureLoft.h b/src/Mod/PartDesign/App/FeatureLoft.h index 270cdfbda7..ad4658e566 100644 --- a/src/Mod/PartDesign/App/FeatureLoft.h +++ b/src/Mod/PartDesign/App/FeatureLoft.h @@ -40,7 +40,7 @@ class PartDesignExport Loft : public ProfileBased public: Loft(); - App::PropertyXLinkSubList Sections; + App::PropertyLinkSubList Sections; App::PropertyBool Ruled; App::PropertyBool Closed; diff --git a/src/Mod/PartDesign/App/FeatureMirrored.cpp b/src/Mod/PartDesign/App/FeatureMirrored.cpp index 5aa3e76495..b6e9d43694 100644 --- a/src/Mod/PartDesign/App/FeatureMirrored.cpp +++ b/src/Mod/PartDesign/App/FeatureMirrored.cpp @@ -77,7 +77,7 @@ const std::list Mirrored::getTransformations(const std::vectorgetAxis(Part::Part2DObject::H_Axis); else if (subStrings[0] == "") axis = refSketch->getAxis(Part::Part2DObject::N_Axis); - else if (subStrings[0].size() > 4 && subStrings[0].substr(0,4) == "Axis") { + else if (subStrings[0].compare(0, 4, "Axis") == 0) { int AxId = std::atoi(subStrings[0].substr(4,4000).c_str()); if (AxId >= 0 && AxId < refSketch->getAxisCount()) { axis = refSketch->getAxis(AxId); diff --git a/src/Mod/PartDesign/App/FeaturePad.cpp b/src/Mod/PartDesign/App/FeaturePad.cpp index e88fc4eebe..8b58918fa0 100644 --- a/src/Mod/PartDesign/App/FeaturePad.cpp +++ b/src/Mod/PartDesign/App/FeaturePad.cpp @@ -58,7 +58,7 @@ using namespace PartDesign; const char* Pad::TypeEnums[]= {"Length", "UpToLast", "UpToFirst", "UpToFace", "TwoLengths", NULL}; -PROPERTY_SOURCE(PartDesign::Pad, PartDesign::ProfileBased) +PROPERTY_SOURCE(PartDesign::Pad, PartDesign::FeatureExtrude) Pad::Pad() { @@ -82,23 +82,7 @@ Pad::Pad() Length2.setConstraints(nullptr); } -short Pad::mustExecute() const -{ - if (Placement.isTouched() || - Type.isTouched() || - Length.isTouched() || - Length2.isTouched() || - UseCustomVector.isTouched() || - Direction.isTouched() || - ReferenceAxis.isTouched() || - AlongSketchNormal.isTouched() || - Offset.isTouched() || - UpToFace.isTouched()) - return 1; - return ProfileBased::mustExecute(); -} - -App::DocumentObjectExecReturn *Pad::execute(void) +App::DocumentObjectExecReturn *Pad::execute() { // Validate parameters double L = Length.getValue(); @@ -133,8 +117,6 @@ App::DocumentObjectExecReturn *Pad::execute(void) base = TopoDS_Shape(); } - // get the Sketch plane - Base::Placement SketchPos = obj->Placement.getValue(); // get the normal vector of the sketch Base::Vector3d SketchVector = getProfileNormal(); @@ -144,53 +126,11 @@ App::DocumentObjectExecReturn *Pad::execute(void) base.Move(invObjLoc); - Base::Vector3d paddingDirection; - - if (!UseCustomVector.getValue()) { - if (!ReferenceAxis.getValue()) { - // use sketch's normal vector for direction - paddingDirection = SketchVector; - AlongSketchNormal.setReadOnly(true); - } - else { - // update Direction from ReferenceAxis - try { - App::DocumentObject* pcReferenceAxis = ReferenceAxis.getValue(); - const std::vector& subReferenceAxis = ReferenceAxis.getSubValues(); - Base::Vector3d base; - Base::Vector3d dir; - getAxis(pcReferenceAxis, subReferenceAxis, base, dir, false); - paddingDirection = dir; - } - catch (const Base::Exception& e) { - return new App::DocumentObjectExecReturn(e.what()); - } - } - } - else { - // use the given vector - // if null vector, use SketchVector - if ( (fabs(Direction.getValue().x) < Precision::Confusion()) - && (fabs(Direction.getValue().y) < Precision::Confusion()) - && (fabs(Direction.getValue().z) < Precision::Confusion()) ) { - Direction.setValue(SketchVector); - } - paddingDirection = Direction.getValue(); - } - - // disable options of UseCustomVector - Direction.setReadOnly(!UseCustomVector.getValue()); - ReferenceAxis.setReadOnly(UseCustomVector.getValue()); - // UseCustomVector allows AlongSketchNormal but !UseCustomVector does not forbid it - if (UseCustomVector.getValue()) - AlongSketchNormal.setReadOnly(false); + Base::Vector3d paddingDirection = computeDirection(SketchVector); // create vector in padding direction with length 1 gp_Dir dir(paddingDirection.x, paddingDirection.y, paddingDirection.z); - // store the finally used direction to display it in the dialog - Direction.setValue(dir.X(), dir.Y(), dir.Z()); - // The length of a gp_Dir is 1 so the resulting pad would have // the length L in the direction of dir. But we want to have its height in the // direction of the normal vector. @@ -212,10 +152,6 @@ App::DocumentObjectExecReturn *Pad::execute(void) L2 = L2 / factor; } - // explicitly set the Direction so that the dialog shows also the used direction - // if the sketch's normal vector was used - Direction.setValue(paddingDirection); - dir.Transform(invObjLoc.Transformation()); if (sketchshape.IsNull()) @@ -243,65 +179,6 @@ App::DocumentObjectExecReturn *Pad::execute(void) // TODO: Write our own PrismMaker which does not depend on a solid base shape if (base.IsNull()) { - // This implementation suffers from some problems: - // * it explicitly checks for planes only but e.g. a B-spline may work too - // * The extracted surface passed to GeomAPI_ProjectPointOnSurf may lack of - // its placement and thus computes a wrong result - // * the direction computed by base and projection point must not be transformed -#if 0 - // Workaround because BRepFeat_MakePrism requires the base face located on a solid to be able to extrude up to a face - // Handle special case of extruding up to a face or plane parallel to the base face - BRepAdaptor_Surface adapt(upToFace); - if (adapt.GetType() != GeomAbs_Plane) - return new App::DocumentObjectExecReturn("Pad: Extruding up to a face or plane is only possible if the sketch is located on a face"); - - double angle = dir.Angle(adapt.Plane().Axis().Direction()); - if (angle > Precision::Confusion()) - return new App::DocumentObjectExecReturn("Pad: Extruding up to a face is only possible if the sketch plane is parallel to it"); - - // Project basepoint of sketch onto the UpToFace to determine distance and direction - gp_Pnt basePoint(SketchPos.getPosition().x, SketchPos.getPosition().y, SketchPos.getPosition().z); - GeomAPI_ProjectPointOnSurf prj(basePoint, adapt.Surface().Surface()); - if (prj.NbPoints() != 1) - return new App::DocumentObjectExecReturn("Pad: Extruding up to a face failed to find extrusion direction"); - // Distance - double length = prj.Distance(1) + Offset.getValue(); - if (length < Precision::Confusion()) - return new App::DocumentObjectExecReturn("Pad: Extruding up to a face failed because of zero height"); - - // Direction (the distance is always positive) - gp_Pnt prjP = prj.NearestPoint(); - dir = gp_Dir(gp_Vec(basePoint, prjP)); - dir.Transform(invObjLoc.Transformation()); -#else - /*TopLoc_Location upToFaceLoc; - Handle(Geom_Surface) surf = BRep_Tool::Surface(upToFace, upToFaceLoc); - GeomLib_IsPlanarSurface checkSurface(surf); - if (surf.IsNull() || !checkSurface.IsPlanar()) - return new App::DocumentObjectExecReturn("Pad: Extruding up to a face or plane is only possible if the sketch is located on a face"); - - gp_Pln upToPlane = checkSurface.Plan().Transformed(upToFaceLoc); - gp_Dir planeNorm = upToPlane.Axis().Direction(); - gp_Pnt planeBase = upToPlane.Location(); - double angle = dir.Angle(planeNorm); - if (angle > Precision::Confusion()) - return new App::DocumentObjectExecReturn("Pad: Extruding up to a face is only possible if the sketch plane is parallel to it"); - - // Project basepoint of sketch onto the UpToFace to determine distance and direction - gp_Pnt basePoint(SketchPos.getPosition().x, SketchPos.getPosition().y, SketchPos.getPosition().z); - Standard_Real pn = planeBase.XYZ().Dot(planeNorm.XYZ()); - Standard_Real qn = basePoint.XYZ().Dot(planeNorm.XYZ()); - gp_Pnt projPoint = basePoint.Translated(planeNorm.XYZ().Multiplied(pn-qn)); - - // Distance - double length = projPoint.Distance(basePoint) + Offset.getValue(); - if (length < Precision::Confusion()) - return new App::DocumentObjectExecReturn("Pad: Extruding up to a face failed because of zero height"); - - // Direction (the distance is always positive) - dir = gp_Dir(gp_Vec(basePoint, projPoint));*/ -#endif - //generatePrism(prism, sketchshape, "Length", dir, length, 0.0, false, false); base = sketchshape; supportface = TopoDS::Face(sketchshape); @@ -309,20 +186,11 @@ App::DocumentObjectExecReturn *Pad::execute(void) if (!Ex.More()) supportface = TopoDS_Face(); -#if 0 - BRepFeat_MakePrism PrismMaker; - PrismMaker.Init(base, sketchshape, supportface, dir, 2, 1); - PrismMaker.Perform(upToFace); - - if (!PrismMaker.IsDone()) - return new App::DocumentObjectExecReturn("Pad: Up to face: Could not extrude the sketch!"); - prism = PrismMaker.Shape(); -#else PrismMode mode = PrismMode::None; generatePrism(prism, method, base, sketchshape, supportface, upToFace, dir, mode, Standard_True); -#endif base.Nullify(); - } else { + } + else { // A support object is always required and we need to use BRepFeat_MakePrism // Problem: For Pocket/UpToFirst (or an equivalent Pocket/UpToFace) the resulting shape is invalid // because the feature does not add any material. This only happens with the "2" option, though @@ -335,20 +203,11 @@ App::DocumentObjectExecReturn *Pad::execute(void) TopExp_Explorer Ex(supportface,TopAbs_WIRE); if (!Ex.More()) supportface = TopoDS_Face(); -#if 0 - BRepFeat_MakePrism PrismMaker; - PrismMaker.Init(base, sketchshape, supportface, dir, 2, 1); - PrismMaker.Perform(upToFace); - - if (!PrismMaker.IsDone()) - return new App::DocumentObjectExecReturn("Pad: Up to face: Could not extrude the sketch!"); - prism = PrismMaker.Shape(); -#else PrismMode mode = PrismMode::None; generatePrism(prism, method, base, sketchshape, supportface, upToFace, dir, mode, Standard_True); -#endif } - } else { + } + else { generatePrism(prism, sketchshape, method, dir, L, L2, hasMidplane, hasReversed); } @@ -361,8 +220,6 @@ App::DocumentObjectExecReturn *Pad::execute(void) this->AddSubShape.setValue(prism); if (!base.IsNull()) { -// auto obj = getDocument()->addObject("Part::Feature", "prism"); -// static_cast(obj)->Shape.setValue(getSolid(prism)); // Let's call algorithm computing a fuse operation: BRepAlgoAPI_Fuse mkFuse(base, prism); // Let's check if the fusion has been successful @@ -382,7 +239,8 @@ App::DocumentObjectExecReturn *Pad::execute(void) solRes = refineShapeIfActive(solRes); this->Shape.setValue(getSolid(solRes)); - } else { + } + else { int solidCount = countSolids(prism); if (solidCount > 1) { return new App::DocumentObjectExecReturn("Pad: Result has multiple solids. This is not supported at this time."); @@ -394,7 +252,6 @@ App::DocumentObjectExecReturn *Pad::execute(void) return App::DocumentObject::StdReturn; } catch (Standard_Failure& e) { - if (std::string(e.GetMessageString()) == "TopoDS::Face") return new App::DocumentObjectExecReturn("Could not create face from sketch.\n" "Intersecting sketch entities or multiple faces in a sketch are not allowed."); diff --git a/src/Mod/PartDesign/App/FeaturePad.h b/src/Mod/PartDesign/App/FeaturePad.h index 775102f1b4..381571e4f5 100644 --- a/src/Mod/PartDesign/App/FeaturePad.h +++ b/src/Mod/PartDesign/App/FeaturePad.h @@ -24,31 +24,18 @@ #ifndef PARTDESIGN_Pad_H #define PARTDESIGN_Pad_H -#include -#include -#include - -#include "FeatureSketchBased.h" +#include "FeatureExtrude.h" namespace PartDesign { -class PartDesignExport Pad : public ProfileBased +class PartDesignExport Pad : public FeatureExtrude { PROPERTY_HEADER(PartDesign::Pad); public: Pad(); - App::PropertyEnumeration Type; - App::PropertyLength Length; - App::PropertyLength Length2; - App::PropertyBool UseCustomVector; - App::PropertyVector Direction; - App::PropertyBool AlongSketchNormal; - App::PropertyLength Offset; - App::PropertyLinkSub ReferenceAxis; - /** @name methods override feature */ //@{ /** Recalculate the feature @@ -64,17 +51,15 @@ public: * If Reversed is true then the direction of revolution will be reversed. * The created material will be fused with the sketch support (if there is one) */ - App::DocumentObjectExecReturn *execute(void); - short mustExecute() const; + App::DocumentObjectExecReturn *execute(); /// returns the type name of the view provider - const char* getViewProviderName(void) const { + const char* getViewProviderName() const { return "PartDesignGui::ViewProviderPad"; } //@} private: static const char* TypeEnums[]; - //static const char* SideEnums[]; }; } //namespace PartDesign diff --git a/src/Mod/PartDesign/App/FeaturePipe.cpp b/src/Mod/PartDesign/App/FeaturePipe.cpp index 77976627c3..023a8d10bc 100644 --- a/src/Mod/PartDesign/App/FeaturePipe.cpp +++ b/src/Mod/PartDesign/App/FeaturePipe.cpp @@ -71,8 +71,8 @@ using namespace PartDesign; -const char* Pipe::TypeEnums[] = {"FullPath","UpToFace",NULL}; -const char* Pipe::TransitionEnums[] = {"Transformed","Right corner", "Round corner",NULL}; +const char* Pipe::TypeEnums[] = {"FullPath", "UpToFace", NULL}; +const char* Pipe::TransitionEnums[] = {"Transformed", "Right corner", "Round corner", NULL}; const char* Pipe::ModeEnums[] = {"Standard", "Fixed", "Frenet", "Auxiliary", "Binormal", NULL}; const char* Pipe::TransformEnums[] = {"Constant", "Multisection", "Linear", "S-shape", "Interpolation", NULL}; @@ -112,26 +112,54 @@ short Pipe::mustExecute() const App::DocumentObjectExecReturn *Pipe::execute(void) { - std::vector wires; - try { - wires = getProfileWires(); - } catch (const Base::Exception& e) { - return new App::DocumentObjectExecReturn(e.what()); - } + auto getSectionShape = + [](App::DocumentObject* feature, const std::vector &subs) -> TopoDS_Shape { + if (!feature || + !feature->isDerivedFrom(Part::Feature::getClassTypeId())) + throw Base::TypeError("Pipe: Invalid profile/section"); - TopoDS_Shape sketchshape = getVerifiedFace(); - if (sketchshape.IsNull()) - return new App::DocumentObjectExecReturn("Pipe: No valid sketch or face as first section"); - else { - //TODO: currently we only allow planar faces. the reason for this is that with other faces in front, we could - //not use the current simulate approach and build the start and end face from the wires. As the shell - //begins always at the spine and not the profile, the sketchshape cannot be used directly as front face. - //We would need a method to translate the front shape to match the shell starting position somehow... - TopoDS_Face face = TopoDS::Face(sketchshape); - BRepAdaptor_Surface adapt(face); - if (adapt.GetType() != GeomAbs_Plane) - return new App::DocumentObjectExecReturn("Pipe: Only planar faces supported"); - } + auto subName = subs.empty() ? "" : subs.front(); + + // only take the entire shape when we have a sketch selected, but + // not a point of the sketch + if (feature->isDerivedFrom(Part::Part2DObject::getClassTypeId()) && + subName.compare(0, 6, "Vertex") != 0) + return static_cast(feature)->Shape.getValue(); + else { + if(subName.empty()) + throw Base::ValueError("Pipe: No valid subelement linked in Part::Feature"); + return static_cast(feature)->Shape.getShape().getSubShape(subName.c_str()); + } + }; + + auto addWiresToWireSections = + [](TopoDS_Shape& section, + std::vector>& wiresections) -> size_t { + TopExp_Explorer ex; + size_t i=0; + bool initialWireSectionsEmpty = wiresections.empty(); + for (ex.Init(section, TopAbs_WIRE); ex.More(); ex.Next(), ++i) { + // if profile was just a point then this is where we can first set our list + if (i>=wiresections.size()) { + if (initialWireSectionsEmpty) + wiresections.emplace_back(1, ex.Current()); + else + throw Base::ValueError("Pipe: Sections need to have the same amount of inner wires (except profile and last section, which can be points)"); + } + else + wiresections[i].push_back(TopoDS::Wire(ex.Current())); + } + return i; + }; + + // TODO: currently we can only allow planar faces, so add that check. + // The reason for this is that with other faces in front, we could not use the + // current simulate approach and build the start and end face from the wires. + // As the shell begins always at the spine and not the profile, the sketchshape + // cannot be used directly as front face. We would need a method to translate + // the front shape to match the shell starting position somehow... + std::vector wires; + TopoDS_Shape profilePoint; // if the Base property has a valid shape, fuse the pipe into it TopoDS_Shape base; @@ -142,16 +170,22 @@ App::DocumentObjectExecReturn *Pipe::execute(void) } try { - //setup the location + // setup the location this->positionByPrevious(); TopLoc_Location invObjLoc = this->getLocation().Inverted(); if (!base.IsNull()) base.Move(invObjLoc); - //build the paths + // setup the profile section + TopoDS_Shape profileShape = getSectionShape(Profile.getValue(), + Profile.getSubValues()); + if (profileShape.IsNull()) + return new App::DocumentObjectExecReturn("Pipe: Could not obtain profile shape"); + + // build the paths App::DocumentObject* spine = Spine.getValue(); if (!(spine && spine->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId()))) - return new App::DocumentObjectExecReturn("No spine linked."); + return new App::DocumentObjectExecReturn("No spine linked"); std::vector subedge = Spine.getSubValues(); TopoDS_Shape path; @@ -159,7 +193,6 @@ App::DocumentObjectExecReturn *Pipe::execute(void) buildPipePath(shape, subedge, path); path.Move(invObjLoc); - // auxiliary TopoDS_Shape auxpath; if (Mode.getValue()==3) { @@ -173,47 +206,57 @@ App::DocumentObjectExecReturn *Pipe::execute(void) auxpath.Move(invObjLoc); } - //build up multisections - auto multisections = Sections.getValues(); - std::vector> wiresections; - for (TopoDS_Wire& wire : wires) - wiresections.emplace_back(1, wire); - //maybe we need a sacling law + // build up multisections + auto multisections = Sections.getSubListValues(); + std::vector> wiresections; + + size_t numWires = addWiresToWireSections(profileShape, wiresections); + if (numWires == 0) { + // profileShape had no wires so only other valid option is single point section + TopExp_Explorer ex; + size_t i=0; + for (ex.Init(profileShape, TopAbs_VERTEX); ex.More(); ex.Next(), ++i) + profilePoint = ex.Current(); + if (i > 1) + return new App::DocumentObjectExecReturn("Pipe: Only one isolated point is needed if using a sketch with isolated points for section"); + } + + if (!profilePoint.IsNull() && + (Transformation.getValue() != 1 || multisections.empty())) + return new App::DocumentObjectExecReturn("Pipe: At least one section is needed when using a single point for profile"); + + // maybe we need a scaling law Handle(Law_Function) scalinglaw; - //see if we shall use multiple sections + bool isLastSectionVertex = false; + + // see if we shall use multiple sections if (Transformation.getValue() == 1) { - - //TODO: we need to order the sections to prevent occ from crahsing, as makepieshell connects - //the sections in the order of adding - - for (App::DocumentObject* obj : multisections) { - if (!obj->isDerivedFrom(Part::Feature::getClassTypeId())) - return new App::DocumentObjectExecReturn("All sections need to be part features"); + // TODO: we need to order the sections to prevent occ from crashing, + // as makepipeshell connects the sections in the order of adding + for (auto &subSet : multisections) { + if (!subSet.first->isDerivedFrom(Part::Feature::getClassTypeId())) + return new App::DocumentObjectExecReturn("Pipe: All sections need to be part features"); // if the section is an object's face then take just the face - TopoDS_Shape shape; - if (obj->isDerivedFrom(Part::Part2DObject::getClassTypeId())) - shape = static_cast(obj)->Shape.getValue(); - else { - auto subValues = Sections.getSubValues(obj); - if (subValues.empty()) - throw Base::ValueError("Pipe: No valid subelement in multisection"); + TopoDS_Shape shape = getSectionShape(subSet.first, subSet.second); + if (shape.IsNull()) + return new App::DocumentObjectExecReturn("Pipe: Could not obtain section shape"); - shape = static_cast(obj)->Shape.getShape(). getSubShape(subValues[0].c_str()); + size_t nWiresAdded = addWiresToWireSections(shape, wiresections); + if (nWiresAdded == 0) { + TopExp_Explorer ex; + size_t i=0; + for (ex.Init(shape, TopAbs_VERTEX); ex.More(); ex.Next(), ++i) { + if (isLastSectionVertex) + return new App::DocumentObjectExecReturn("Pipe: Only the profile and last section can be vertices"); + isLastSectionVertex = true; + for (auto &wires : wiresections) + wires.push_back(ex.Current()); + } } - TopExp_Explorer ex; - size_t i=0; - for (ex.Init(shape, TopAbs_WIRE); ex.More(); ex.Next()) { - if (i>=wiresections.size()) - return new App::DocumentObjectExecReturn("Multisections need to have the same amount of inner wires as the base section"); - wiresections[i].push_back(TopoDS::Wire(ex.Current())); - - ++i; - } - - if (iSet(0,1,1,ScalingData[0].x); + lin->Set(0, 1, 1, ScalingData[0].x); scalinglaw = lin; } @@ -232,7 +275,7 @@ App::DocumentObjectExecReturn *Pipe::execute(void) return new App::DocumentObjectExecReturn("No valid data given for S-shape scaling mode"); Handle(Law_S) s = new Law_S(); - s->Set(0,1,ScalingData[0].y, 1, ScalingData[0].x, ScalingData[0].z); + s->Set(0, 1, ScalingData[0].y, 1, ScalingData[0].x, ScalingData[0].z); scalinglaw = s; }*/ @@ -243,20 +286,30 @@ App::DocumentObjectExecReturn *Pipe::execute(void) // build all shells std::vector shells; - std::vector frontwires, backwires; - for (std::vector& wires : wiresections) { + TopoDS_Shape copyProfilePoint(profilePoint); + if (!profilePoint.IsNull()) + copyProfilePoint.Move(invObjLoc); + + std::vector frontwires, backwires; + for (auto& wires : wiresections) { BRepOffsetAPI_MakePipeShell mkPS(TopoDS::Wire(path)); setupAlgorithm(mkPS, auxpath); if (!scalinglaw) { - for (TopoDS_Wire& wire : wires) { + if (!profilePoint.IsNull()) + mkPS.Add(copyProfilePoint); + + for (auto& wire : wires) { wire.Move(invObjLoc); mkPS.Add(wire); } } else { - for (TopoDS_Wire& wire : wires) { + if (!profilePoint.IsNull()) + mkPS.SetLaw(copyProfilePoint, scalinglaw); + + for (auto& wire : wires) { wire.Move(invObjLoc); mkPS.SetLaw(wire, scalinglaw); } @@ -272,6 +325,10 @@ App::DocumentObjectExecReturn *Pipe::execute(void) TopTools_ListOfShape sim; mkPS.Simulate(2, sim); + // Note that while we call them front and back, these sections + // appear to correspond to the front or back of the path. When one + // or both ends of the pipe are points, one or both of these wires + // (and eventually faces) will be null. frontwires.push_back(TopoDS::Wire(sim.First())); backwires.push_back(TopoDS::Wire(sim.Last())); } @@ -279,16 +336,19 @@ App::DocumentObjectExecReturn *Pipe::execute(void) BRepBuilderAPI_MakeSolid mkSolid; - if (!frontwires.empty()) { - // build the end faces, sew the shell and build the final solid - TopoDS_Shape front = Part::FaceMakerCheese::makeFace(frontwires); - TopoDS_Shape back = Part::FaceMakerCheese::makeFace(backwires); - + if (!frontwires.empty() || !backwires.empty()) { BRepBuilderAPI_Sewing sewer; sewer.SetTolerance(Precision::Confusion()); - sewer.Add(front); - sewer.Add(back); + // build the end faces, sew the shell and build the final solid + if (!frontwires.empty()) { + TopoDS_Shape front = Part::FaceMakerCheese::makeFace(frontwires); + sewer.Add(front); + } + if (!backwires.empty()) { + TopoDS_Shape back = Part::FaceMakerCheese::makeFace(backwires); + sewer.Add(back); + } for (TopoDS_Shape& s : shells) sewer.Add(s); @@ -393,7 +453,7 @@ void Pipe::setupAlgorithm(BRepOffsetAPI_MakePipeShell& mkPipeShell, TopoDS_Shape const Base::Vector3d& bVec = Binormal.getValue(); switch(Mode.getValue()) { case 1: - mkPipeShell.SetMode(gp_Ax2(gp_Pnt(0,0,0), gp_Dir(0,0,1), gp_Dir(1,0,0))); + mkPipeShell.SetMode(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1), gp_Dir(1, 0, 0))); break; case 2: mkPipeShell.SetMode(true); @@ -402,13 +462,13 @@ void Pipe::setupAlgorithm(BRepOffsetAPI_MakePipeShell& mkPipeShell, TopoDS_Shape auxiliary = true; break; case 4: - mkPipeShell.SetMode(gp_Dir(bVec.x,bVec.y,bVec.z)); + mkPipeShell.SetMode(gp_Dir(bVec.x, bVec.y, bVec.z)); break; } if (auxiliary) { mkPipeShell.SetMode(TopoDS::Wire(auxshape), AuxilleryCurvelinear.getValue()); - //mkPipeShell.SetMode(TopoDS::Wire(auxshape), AuxilleryCurvelinear.getValue(), BRepFill_ContactOnBorder); + // mkPipeShell.SetMode(TopoDS::Wire(auxshape), AuxilleryCurvelinear.getValue(), BRepFill_ContactOnBorder); } } @@ -430,7 +490,7 @@ void Pipe::getContinuousEdges(Part::TopoShape /*TopShape*/, std::vector< std::st { std::string aSubName = static_cast(SubNames.at(i)); - if (aSubName.size() > 4 && aSubName.substr(0,4) == "Edge") { + if (aSubName.compare(0, 4, "Edge") == 0) { TopoDS_Edge edge = TopoDS::Edge(TopShape.getSubShape(aSubName.c_str())); const TopTools_ListOfShape& los = mapEdgeEdge.FindFromKey(edge); diff --git a/src/Mod/PartDesign/App/FeaturePipe.h b/src/Mod/PartDesign/App/FeaturePipe.h index e173cc3830..7e8a7802c7 100644 --- a/src/Mod/PartDesign/App/FeaturePipe.h +++ b/src/Mod/PartDesign/App/FeaturePipe.h @@ -47,7 +47,7 @@ public: App::PropertyVector Binormal; App::PropertyEnumeration Transition; App::PropertyEnumeration Transformation; - App::PropertyXLinkSubList Sections; + App::PropertyLinkSubList Sections; App::DocumentObjectExecReturn *execute(void); short mustExecute() const; diff --git a/src/Mod/PartDesign/App/FeaturePocket.cpp b/src/Mod/PartDesign/App/FeaturePocket.cpp index d77390069e..2263a0613a 100644 --- a/src/Mod/PartDesign/App/FeaturePocket.cpp +++ b/src/Mod/PartDesign/App/FeaturePocket.cpp @@ -57,7 +57,7 @@ using namespace PartDesign; const char* Pocket::TypeEnums[]= {"Length","ThroughAll","UpToFirst","UpToFace","TwoLengths",NULL}; -PROPERTY_SOURCE(PartDesign::Pocket, PartDesign::ProfileBased) +PROPERTY_SOURCE(PartDesign::Pocket, PartDesign::FeatureExtrude) Pocket::Pocket() { @@ -81,23 +81,7 @@ Pocket::Pocket() Length2.setConstraints(nullptr); } -short Pocket::mustExecute() const -{ - if (Placement.isTouched() || - Type.isTouched() || - Length.isTouched() || - Length2.isTouched() || - Offset.isTouched() || - UseCustomVector.isTouched() || - Direction.isTouched() || - ReferenceAxis.isTouched() || - AlongSketchNormal.isTouched() || - UpToFace.isTouched()) - return 1; - return ProfileBased::mustExecute(); -} - -App::DocumentObjectExecReturn *Pocket::execute(void) +App::DocumentObjectExecReturn *Pocket::execute() { // Handle legacy features, these typically have Type set to 3 (previously NULL, now UpToFace), // empty FaceName (because it didn't exist) and a value for Length @@ -136,10 +120,8 @@ App::DocumentObjectExecReturn *Pocket::execute(void) return new App::DocumentObjectExecReturn(text); } - // get the Sketch plane - Base::Placement SketchPos = obj->Placement.getValue(); // get the normal vector of the sketch - Base::Vector3d SketchVector = getProfileNormal(); + Base::Vector3d SketchVector = getProfileNormal(); // turn around for pockets SketchVector *= -1; @@ -150,53 +132,11 @@ App::DocumentObjectExecReturn *Pocket::execute(void) base.Move(invObjLoc); - Base::Vector3d pocketDirection; - - if (!UseCustomVector.getValue()) { - if (!ReferenceAxis.getValue()) { - // use sketch's normal vector for direction - pocketDirection = SketchVector; - AlongSketchNormal.setReadOnly(true); - } - else { - // update Direction from ReferenceAxis - try { - App::DocumentObject* pcReferenceAxis = ReferenceAxis.getValue(); - const std::vector& subReferenceAxis = ReferenceAxis.getSubValues(); - Base::Vector3d base; - Base::Vector3d dir; - getAxis(pcReferenceAxis, subReferenceAxis, base, dir, false); - pocketDirection = -dir; - } - catch (const Base::Exception& e) { - return new App::DocumentObjectExecReturn(e.what()); - } - } - } - else { - // use the given vector - // if null vector, use SketchVector - if ((fabs(Direction.getValue().x) < Precision::Confusion()) - && (fabs(Direction.getValue().y) < Precision::Confusion()) - && (fabs(Direction.getValue().z) < Precision::Confusion())) { - Direction.setValue(SketchVector); - } - pocketDirection = Direction.getValue(); - } - - // disable options of UseCustomVector - Direction.setReadOnly(!UseCustomVector.getValue()); - ReferenceAxis.setReadOnly(UseCustomVector.getValue()); - // UseCustomVector allows AlongSketchNormal but !UseCustomVector does not forbid it - if (UseCustomVector.getValue()) - AlongSketchNormal.setReadOnly(false); + Base::Vector3d pocketDirection = computeDirection(SketchVector); // create vector in pocketing direction with length 1 gp_Dir dir(pocketDirection.x, pocketDirection.y, pocketDirection.z); - // store the finally used direction to display it in the dialog - Direction.setValue(dir.X(), dir.Y(), dir.Z()); - // The length of a gp_Dir is 1 so the resulting pocket would have // the length L in the direction of dir. But we want to have its height in the // direction of the normal vector. @@ -218,10 +158,6 @@ App::DocumentObjectExecReturn *Pocket::execute(void) L2 = L2 / factor; } - // explicitly set the Direction so that the dialog shows also the used direction - // if the sketch's normal vector was used - Direction.setValue(pocketDirection); - dir.Transform(invObjLoc.Transformation()); if (profileshape.IsNull()) @@ -259,19 +195,9 @@ App::DocumentObjectExecReturn *Pocket::execute(void) TopExp_Explorer Ex(supportface,TopAbs_WIRE); if (!Ex.More()) supportface = TopoDS_Face(); -#if 0 - BRepFeat_MakePrism PrismMaker; - PrismMaker.Init(base, profileshape, supportface, dir, 0, 1); - PrismMaker.Perform(upToFace); - - if (!PrismMaker.IsDone()) - return new App::DocumentObjectExecReturn("Pocket: Up to face: Could not extrude the sketch!"); - TopoDS_Shape prism = PrismMaker.Shape(); -#else TopoDS_Shape prism; PrismMode mode = PrismMode::CutFromBase; generatePrism(prism, method, base, profileshape, supportface, upToFace, dir, mode, Standard_True); -#endif // And the really expensive way to get the SubShape... BRepAlgoAPI_Cut mkCut(base, prism); @@ -287,7 +213,8 @@ App::DocumentObjectExecReturn *Pocket::execute(void) } this->Shape.setValue(getSolid(prism)); - } else { + } + else { TopoDS_Shape prism; generatePrism(prism, profileshape, method, dir, L, L2, Midplane.getValue(), Reversed.getValue()); @@ -322,7 +249,6 @@ App::DocumentObjectExecReturn *Pocket::execute(void) return App::DocumentObject::StdReturn; } catch (Standard_Failure& e) { - if (std::string(e.GetMessageString()) == "TopoDS::Face" && (std::string(Type.getValueAsString()) == "UpToFirst" || std::string(Type.getValueAsString()) == "UpToFace")) return new App::DocumentObjectExecReturn("Could not create face from sketch.\n" diff --git a/src/Mod/PartDesign/App/FeaturePocket.h b/src/Mod/PartDesign/App/FeaturePocket.h index 9fb580f4cf..3c83e7becf 100644 --- a/src/Mod/PartDesign/App/FeaturePocket.h +++ b/src/Mod/PartDesign/App/FeaturePocket.h @@ -24,28 +24,18 @@ #ifndef PARTDESIGN_Pocket_H #define PARTDESIGN_Pocket_H -#include -#include "FeatureSketchBased.h" +#include "FeatureExtrude.h" namespace PartDesign { -class PartDesignExport Pocket : public ProfileBased +class PartDesignExport Pocket : public FeatureExtrude { PROPERTY_HEADER(PartDesign::Pocket); public: Pocket(); - App::PropertyEnumeration Type; - App::PropertyLength Length; - App::PropertyLength Length2; - App::PropertyLength Offset; - App::PropertyBool UseCustomVector; - App::PropertyVector Direction; - App::PropertyBool AlongSketchNormal; - App::PropertyLinkSub ReferenceAxis; - /** @name methods override feature */ //@{ /** Recalculate the feature @@ -58,8 +48,7 @@ public: * If Midplane is true, then the extrusion will extend for half of the length on both sides of the sketch plane * The created material will be cut out of the sketch support */ - App::DocumentObjectExecReturn *execute(void); - short mustExecute() const; + App::DocumentObjectExecReturn *execute(); /// returns the type name of the view provider const char* getViewProviderName(void) const { return "PartDesignGui::ViewProviderPocket"; @@ -67,7 +56,6 @@ public: //@} private: static const char* TypeEnums[]; - }; } //namespace PartDesign diff --git a/src/Mod/PartDesign/App/FeaturePolarPattern.cpp b/src/Mod/PartDesign/App/FeaturePolarPattern.cpp index 646216564a..5da35a4ee2 100644 --- a/src/Mod/PartDesign/App/FeaturePolarPattern.cpp +++ b/src/Mod/PartDesign/App/FeaturePolarPattern.cpp @@ -115,7 +115,7 @@ const std::list PolarPattern::getTransformations(const std::vectorgetAxis(Part::Part2DObject::V_Axis); else if (subStrings[0] == "N_Axis") axis = refSketch->getAxis(Part::Part2DObject::N_Axis); - else if (subStrings[0].size() > 4 && subStrings[0].substr(0,4) == "Axis") { + else if (subStrings[0].compare(0, 4, "Axis") == 0) { int AxId = std::atoi(subStrings[0].substr(4,4000).c_str()); if (AxId >= 0 && AxId < refSketch->getAxisCount()) axis = refSketch->getAxis(AxId); diff --git a/src/Mod/PartDesign/App/FeatureRevolution.cpp b/src/Mod/PartDesign/App/FeatureRevolution.cpp index 419dc5b817..f5ec892e74 100644 --- a/src/Mod/PartDesign/App/FeatureRevolution.cpp +++ b/src/Mod/PartDesign/App/FeatureRevolution.cpp @@ -204,7 +204,7 @@ void Revolution::updateAxis(void) const std::vector &subReferenceAxis = ReferenceAxis.getSubValues(); Base::Vector3d base; Base::Vector3d dir; - getAxis(pcReferenceAxis, subReferenceAxis, base, dir); + getAxis(pcReferenceAxis, subReferenceAxis, base, dir, ForbiddenAxis::NotParallelWithNormal); Base.setValue(base.x,base.y,base.z); Axis.setValue(dir.x,dir.y,dir.z); diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp index 497f3ea6be..d3a215be10 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp +++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp @@ -65,7 +65,6 @@ # include # include # include -# include # include # include # include @@ -843,13 +842,13 @@ void ProfileBased::remapSupportShape(const TopoDS_Shape& newShape) for (std::vector::iterator it = subValues.begin(); it != subValues.end(); ++it) { std::string shapetype; - if (it->size() > 4 && it->substr(0,4) == "Face") { + if (it->compare(0, 4, "Face") == 0) { shapetype = "Face"; } - else if (it->size() > 4 && it->substr(0,4) == "Edge") { + else if (it->compare(0, 4, "Edge") == 0) { shapetype = "Edge"; } - else if (it->size() > 6 && it->substr(0,6) == "Vertex") { + else if (it->compare(0, 6, "Vertex") == 0) { shapetype = "Vertex"; } else { @@ -1041,10 +1040,49 @@ double ProfileBased::getReversedAngle(const Base::Vector3d &b, const Base::Vecto } void ProfileBased::getAxis(const App::DocumentObject *pcReferenceAxis, const std::vector &subReferenceAxis, - Base::Vector3d& base, Base::Vector3d& dir, bool checkPerpendicular) + Base::Vector3d& base, Base::Vector3d& dir, ProfileBased::ForbiddenAxis checkAxis) { + auto verifyAxisFunc = [](ProfileBased::ForbiddenAxis checkAxis, const gp_Pln& sketchplane, const gp_Dir& dir) { + switch (checkAxis) { + case ForbiddenAxis::NotPerpendicularWithNormal: + // If perpendicular to the normal then it's parallel to the plane + if (sketchplane.Axis().Direction().IsNormal(dir, Precision::Angular())) + throw Base::ValueError("Axis must not be parallel to the sketch plane"); + break; + case ForbiddenAxis::NotParallelWithNormal: + // If parallel with the normal then it's perpendicular to the plane + if (sketchplane.Axis().Direction().IsParallel(dir, Precision::Angular())) + throw Base::ValueError("Axis must not be perpendicular to the sketch plane"); + break; + default: + break; + } + }; + + auto getAxisFromEdge = [](const TopoDS_Edge& refEdge, Base::Vector3d& base, Base::Vector3d& dir) { + if (refEdge.IsNull()) + throw Base::ValueError("Failed to extract rotation edge"); + BRepAdaptor_Curve adapt(refEdge); + gp_Pnt b; + gp_Dir d; + if (adapt.GetType() == GeomAbs_Line) { + b = adapt.Line().Location(); + d = adapt.Line().Direction(); + } + else if (adapt.GetType() == GeomAbs_Circle) { + b = adapt.Circle().Location(); + d = adapt.Circle().Axis().Direction(); + } + else { + throw Base::TypeError("Edge must be a straight line, circle or arc of circle"); + } + + base = Base::Vector3d(b.X(), b.Y(), b.Z()); + dir = Base::Vector3d(d.X(), d.Y(), d.Z()); + }; + dir = Base::Vector3d(0,0,0); // If unchanged signals that no valid axis was found - if (pcReferenceAxis == NULL) + if (!pcReferenceAxis) return; App::DocumentObject* profile = Profile.getValue(); @@ -1074,7 +1112,7 @@ void ProfileBased::getAxis(const App::DocumentObject *pcReferenceAxis, const std hasValidAxis = true; axis = sketch->getAxis(Part::Part2DObject::N_Axis); } - else if (subReferenceAxis[0].size() > 4 && subReferenceAxis[0].substr(0, 4) == "Axis") { + else if (subReferenceAxis[0].compare(0, 4, "Axis") == 0) { int AxId = std::atoi(subReferenceAxis[0].substr(4, 4000).c_str()); if (AxId >= 0 && AxId < sketch->getAxisCount()) { hasValidAxis = true; @@ -1103,9 +1141,7 @@ void ProfileBased::getAxis(const App::DocumentObject *pcReferenceAxis, const std base = line->getBasePoint(); dir = line->getDirection(); - // Check that axis is perpendicular with sketch plane! - if (checkPerpendicular && sketchplane.Axis().Direction().IsParallel(gp_Dir(dir.x, dir.y, dir.z), Precision::Angular())) - throw Base::ValueError("Rotation axis must not be perpendicular with the sketch plane"); + verifyAxisFunc(checkAxis, sketchplane, gp_Dir(dir.x, dir.y, dir.z)); return; } @@ -1114,9 +1150,7 @@ void ProfileBased::getAxis(const App::DocumentObject *pcReferenceAxis, const std base = Base::Vector3d(0,0,0); line->Placement.getValue().multVec(Base::Vector3d (1,0,0), dir); - // Check that axis is perpendicular with sketch plane! - if (checkPerpendicular && sketchplane.Axis().Direction().IsParallel(gp_Dir(dir.x, dir.y, dir.z), Precision::Angular())) - throw Base::ValueError("Rotation axis must not be perpendicular with the sketch plane"); + verifyAxisFunc(checkAxis, sketchplane, gp_Dir(dir.x, dir.y, dir.z)); return; } @@ -1135,34 +1169,13 @@ void ProfileBased::getAxis(const App::DocumentObject *pcReferenceAxis, const std } if (ref.ShapeType() == TopAbs_EDGE) { - TopoDS_Edge refEdge = TopoDS::Edge(ref); - if (refEdge.IsNull()) - throw Base::ValueError("Failed to extract rotation edge"); - BRepAdaptor_Curve adapt(refEdge); - gp_Pnt b; - gp_Dir d; - if (adapt.GetType() == GeomAbs_Line) { - b = adapt.Line().Location(); - d = adapt.Line().Direction(); - } else if (adapt.GetType() == GeomAbs_Circle) { - b = adapt.Circle().Location(); - d = adapt.Circle().Axis().Direction(); - } else { - throw Base::TypeError("Rotation edge must be a straight line, circle or arc of circle"); - } - base = Base::Vector3d(b.X(), b.Y(), b.Z()); - dir = Base::Vector3d(d.X(), d.Y(), d.Z()); - // Check that axis is co-planar with sketch plane! - // Check that axis is perpendicular with sketch plane! - if (sketchplane.Axis().Direction().IsParallel(d, Precision::Angular())) - throw Base::ValueError("Rotation axis must not be perpendicular with the sketch plane"); + getAxisFromEdge(TopoDS::Edge(ref), base, dir); + verifyAxisFunc(checkAxis, sketchplane, gp_Dir(dir.x, dir.y, dir.z)); return; - } else { - throw Base::TypeError("Rotation reference must be an edge"); } } - throw Base::TypeError("Rotation axis reference is invalid"); + throw Base::TypeError("Unsupported geometry type to get reference axis"); } Base::Vector3d ProfileBased::getProfileNormal() const { @@ -1180,18 +1193,21 @@ Base::Vector3d ProfileBased::getProfileNormal() const { } else { TopoDS_Shape shape = getVerifiedFace(true); - if (shape == TopoDS_Shape()) + if (shape.IsNull()) return SketchVector; - if (shape.ShapeType() == TopAbs_FACE) { - BRepAdaptor_Surface adapt(TopoDS::Face(shape)); + // the shape can be a single face or a compound of faces, only consider the first face + TopExp_Explorer ex(shape, TopAbs_FACE); + if (ex.More()) { + TopoDS_Face face = TopoDS::Face(ex.Current()); + BRepAdaptor_Surface adapt(face); double u = adapt.FirstUParameter() + (adapt.LastUParameter() - adapt.FirstUParameter())/2.; double v = adapt.FirstVParameter() + (adapt.LastVParameter() - adapt.FirstVParameter())/2.; BRepLProp_SLProps prop(adapt,u,v,2,Precision::Confusion()); if(prop.IsNormalDefined()) { gp_Pnt pnt; gp_Vec vec; // handles the orientation state of the shape - BRepGProp_Face(TopoDS::Face(shape)).Normal(u,v,pnt,vec); + BRepGProp_Face(face).Normal(u,v,pnt,vec); SketchVector = Base::Vector3d(vec.X(), vec.Y(), vec.Z()); } } diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.h b/src/Mod/PartDesign/App/FeatureSketchBased.h index 52e1b12f17..3e3522e156 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.h +++ b/src/Mod/PartDesign/App/FeatureSketchBased.h @@ -42,6 +42,11 @@ class PartDesignExport ProfileBased : public PartDesign::FeatureAddSub PROPERTY_HEADER(PartDesign::SketchBased); public: + enum class ForbiddenAxis { + NoCheck = 0, + NotPerpendicularWithNormal = 1, + NotParallelWithNormal = 2 + }; ProfileBased(); // Common properties for all sketch based features @@ -182,7 +187,7 @@ protected: double getReversedAngle(const Base::Vector3d& b, const Base::Vector3d& v); /// get Axis from ReferenceAxis void getAxis(const App::DocumentObject* pcReferenceAxis, const std::vector& subReferenceAxis, - Base::Vector3d& base, Base::Vector3d& dir, bool checkPerpendicular=true); + Base::Vector3d& base, Base::Vector3d& dir, ForbiddenAxis checkAxis); void onChanged(const App::Property* prop); private: diff --git a/src/Mod/PartDesign/Gui/CMakeLists.txt b/src/Mod/PartDesign/Gui/CMakeLists.txt index 01c0ad1cc0..e3964bcaf2 100644 --- a/src/Mod/PartDesign/Gui/CMakeLists.txt +++ b/src/Mod/PartDesign/Gui/CMakeLists.txt @@ -147,6 +147,8 @@ SET(PartDesignGuiTaskDlgs_SRCS TaskFeatureParameters.h TaskSketchBasedParameters.cpp TaskSketchBasedParameters.h + TaskExtrudeParameters.cpp + TaskExtrudeParameters.h TaskPadParameters.ui TaskPadParameters.cpp TaskPadParameters.h @@ -224,6 +226,7 @@ SET(PartDesignGuiModule_SRCS Command.cpp CommandPrimitive.cpp CommandBody.cpp + EnumFlags.h Resources/PartDesign.qrc PreCompiled.cpp PreCompiled.h diff --git a/src/Mod/PartDesign/Gui/Command.cpp b/src/Mod/PartDesign/Gui/Command.cpp index 144e9a0494..1e0271e234 100644 --- a/src/Mod/PartDesign/Gui/Command.cpp +++ b/src/Mod/PartDesign/Gui/Command.cpp @@ -996,7 +996,7 @@ void prepareProfileBased(PartDesign::Body *pcActiveBody, Gui::Command* cmd, cons if (feature->isTouched()) feature->recomputeFeature(); - std::string FeatName = cmd->getUniqueObjectName(which.c_str(),pcActiveBody); + std::string FeatName = cmd->getUniqueObjectName(which.c_str(), pcActiveBody); Gui::Command::openCommand((std::string("Make ") + which).c_str()); @@ -1004,18 +1004,39 @@ void prepareProfileBased(PartDesign::Body *pcActiveBody, Gui::Command* cmd, cons auto Feat = pcActiveBody->getDocument()->getObject(FeatName.c_str()); auto objCmd = Gui::Command::getObjectCmd(feature); - if (feature->isDerivedFrom(Part::Part2DObject::getClassTypeId()) || subs.empty()) { - FCMD_OBJ_CMD(Feat,"Profile = " << objCmd); - } - else { - std::ostringstream ss; - for (auto &s : subs) - ss << "'" << s << "',"; - FCMD_OBJ_CMD(Feat,"Profile = (" << objCmd << ", [" << ss.str() << "])"); - } - //for additive and subtractive lofts allow the user to preselect the sections - if (which.compare("AdditiveLoft") == 0 || which.compare("SubtractiveLoft") == 0) { + // run the command in console to set the profile (without selected subelements) + auto runProfileCmd = + [=]() { + FCMD_OBJ_CMD(Feat,"Profile = " << objCmd); + }; + + // run the command in console to set the profile with selected subelements + // useful to set, say, a face of a solid as the "profile" + auto runProfileCmdWithSubs = + [=]() { + std::ostringstream ss; + for (auto &s : subs) + ss << "'" << s << "',"; + FCMD_OBJ_CMD(Feat,"Profile = (" << objCmd << ", [" << ss.str() << "])"); + }; + + if (which.compare("AdditiveLoft") == 0 || + which.compare("SubtractiveLoft") == 0) { + // for additive and subtractive lofts set subvalues even for sketches + // when a vertex is first selected + auto subName = subs.empty() ? "" : subs.front(); + + // `ProfileBased::getProfileShape()` and other methods will return + // just the sub-shapes if they are set. So when whole sketches are + // desired, do not set sub-values. + if (feature->isDerivedFrom(Part::Part2DObject::getClassTypeId()) && + subName.compare(0, 6, "Vertex") != 0) + runProfileCmd(); + else + runProfileCmdWithSubs(); + + // for additive and subtractive lofts allow the user to preselect the sections std::vector selection = cmd->getSelection().getSelectionEx(); if (selection.size() > 1) { //treat additional selected objects as sections for (std::vector::size_type ii = 1; ii < selection.size(); ii++) { @@ -1028,9 +1049,22 @@ void prepareProfileBased(PartDesign::Body *pcActiveBody, Gui::Command* cmd, cons } } } + else if (which.compare("AdditivePipe") == 0 || + which.compare("SubtractivePipe") == 0) { + // for additive and subtractive pipes set subvalues even for sketches + // to support point sections + auto subName = subs.empty() ? "" : subs.front(); - // for additive and subtractive pipes allow the user to preselect the spines - if (which.compare("AdditivePipe") == 0 || which.compare("SubtractivePipe") == 0) { + // `ProfileBased::getProfileShape()` and other methods will return + // just the sub-shapes if they are set. So when whole sketches are + // desired, don not set sub-values. + if (feature->isDerivedFrom(Part::Part2DObject::getClassTypeId()) && + subName.compare(0, 6, "Vertex") != 0) + runProfileCmd(); + else + runProfileCmdWithSubs(); + + // for additive and subtractive pipes allow the user to preselect the spines std::vector selection = cmd->getSelection().getSelectionEx(); if (selection.size() == 2) { //treat additional selected object as spine std::vector subnames = selection[1].getSubNames(); @@ -1048,6 +1082,12 @@ void prepareProfileBased(PartDesign::Body *pcActiveBody, Gui::Command* cmd, cons } } } + else { + if (feature->isDerivedFrom(Part::Part2DObject::getClassTypeId()) || subs.empty()) + runProfileCmd(); + else + runProfileCmdWithSubs(); + } func(static_cast(feature), Feat); }; @@ -1143,8 +1183,8 @@ void prepareProfileBased(PartDesign::Body *pcActiveBody, Gui::Command* cmd, cons // a Part container and if not an error was raised and the function aborted. // First of all, for the user this wasn't obvious because the error message // was quite confusing (and thus the user may have done the wrong thing since - // he may have assumed the that the sketch was meant) and second there is no need - // that the body must be inside a Part container. + // they may have assumed the that the sketch was meant) and + // Second, there is no need that the body must be inside a Part container. // For more details see: https://forum.freecadweb.org/viewtopic.php?f=19&t=32164 // The function has been modified not to expect the body to be in the Part // and it now directly invokes the 'makeCopy' dialog. @@ -1883,15 +1923,15 @@ void finishDressupFeature(const Gui::Command* cmd, const std::string& which, } str << "])"; - std::string FeatName = cmd->getUniqueObjectName(which.c_str(),base); + std::string FeatName = cmd->getUniqueObjectName(which.c_str(), base); - auto body = PartDesignGui::getBodyFor(base,false); + auto body = PartDesignGui::getBodyFor(base, false); if (!body) return; cmd->openCommand((std::string("Make ") + which).c_str()); FCMD_OBJ_CMD(body,"newObject('PartDesign::"<getDocument()->getObject(FeatName.c_str()); FCMD_OBJ_CMD(Feat,"Base = " << str.str()); - cmd->doCommand(cmd->Gui,"Gui.Selection.clearSelection()"); + cmd->doCommand(cmd->Gui, "Gui.Selection.clearSelection()"); finishFeature(cmd, Feat, base); App::DocumentObject* baseFeature = static_cast(Feat)->Base.getValue(); @@ -2008,7 +2048,7 @@ void CmdPartDesignDraft::activated(int iMsg) { std::string aSubName = static_cast(SubNames.at(i)); - if (aSubName.size() > 4 && aSubName.substr(0,4) == "Face") { + if (aSubName.compare(0, 4, "Face") == 0) { // Check for valid face types TopoDS_Face face = TopoDS::Face(TopShape.getSubShape(aSubName.c_str())); BRepAdaptor_Surface sf(face); @@ -2064,7 +2104,7 @@ void CmdPartDesignThickness::activated(int iMsg) { std::string aSubName = static_cast(SubNames.at(i)); - if (aSubName.size() > 4 && aSubName.substr(0,4) != "Face") { + if (aSubName.compare(0, 4, "Face") != 0) { // empty name or any other sub-element SubNames.erase(SubNames.begin()+i); } diff --git a/src/Mod/PartDesign/Gui/CommandBody.cpp b/src/Mod/PartDesign/Gui/CommandBody.cpp index a73bd62630..d3c9f45c26 100644 --- a/src/Mod/PartDesign/Gui/CommandBody.cpp +++ b/src/Mod/PartDesign/Gui/CommandBody.cpp @@ -941,7 +941,7 @@ void CmdPartDesignMoveFeatureInTree::activated(int iMsg) } // If the selected objects have been moved after the current tip then ask the - // user if he wants the last object to be the new tip. + // user if they want the last object to be the new tip. // Only do this for features that can hold a tip (not for e.g. datums) if ( lastObject && body->Tip.getValue() == target && lastObject->isDerivedFrom(PartDesign::Feature::getClassTypeId()) ) { diff --git a/src/Mod/PartDesign/Gui/DlgActiveBody.cpp b/src/Mod/PartDesign/Gui/DlgActiveBody.cpp index fb4841d94f..07bcd2ad02 100644 --- a/src/Mod/PartDesign/Gui/DlgActiveBody.cpp +++ b/src/Mod/PartDesign/Gui/DlgActiveBody.cpp @@ -31,29 +31,28 @@ #include #include +#include "ui_DlgActiveBody.h" #include "DlgActiveBody.h" #include "ReferenceSelection.h" #include "Utils.h" -Q_DECLARE_METATYPE(App::DocumentObject*); +Q_DECLARE_METATYPE(App::DocumentObject*) using namespace PartDesignGui; -DlgActiveBody::DlgActiveBody(QWidget *parent, App::Document*& doc, - const QString& infoText) - : QDialog(parent), - ui(new Ui_DlgActiveBody), - _doc(doc), - activeBody(nullptr) +DlgActiveBody::DlgActiveBody(QWidget *parent, App::Document*& doc, const QString& infoText) + : QDialog(parent) + , ui(new Ui_DlgActiveBody) + , _doc(doc) + , activeBody(nullptr) { ui->setupUi(this); - QObject::connect(ui->bodySelect, SIGNAL(itemDoubleClicked(QListWidgetItem *)), - this, SLOT(accept())); + connect(ui->bodySelect, SIGNAL(itemDoubleClicked(QListWidgetItem *)), + this, SLOT(accept())); - if(!infoText.isEmpty()) { - ui->label->setText(infoText + QString::fromUtf8("\n\n") + - QObject::tr("Please select")); + if (!infoText.isEmpty()) { + ui->label->setText(infoText + QString::fromUtf8("\n\n") + tr("Please select")); } auto bodies = _doc->getObjectsOfType(PartDesign::Body::getClassTypeId()); @@ -85,6 +84,10 @@ DlgActiveBody::DlgActiveBody(QWidget *parent, App::Document*& doc, } } +DlgActiveBody::~DlgActiveBody() +{ +} + void DlgActiveBody::accept() { auto selectedItems = ui->bodySelect->selectedItems(); diff --git a/src/Mod/PartDesign/Gui/DlgActiveBody.h b/src/Mod/PartDesign/Gui/DlgActiveBody.h index c73f3c8bcd..d0ccf61139 100644 --- a/src/Mod/PartDesign/Gui/DlgActiveBody.h +++ b/src/Mod/PartDesign/Gui/DlgActiveBody.h @@ -26,13 +26,15 @@ #ifndef PARTDESIGNGUI_DLGACTIVEBODY_H #define PARTDESIGNGUI_DLGACTIVEBODY_H +#include +#include #include #include +#include -// TODO: Apparently this header can be avoided. See ./Command.cpp:74. -#include "ui_DlgActiveBody.h" namespace PartDesignGui { +class Ui_DlgActiveBody; /** Dialog box to ask user to pick a Part Design body to make active * or make a new one @@ -44,9 +46,10 @@ class PartDesignGuiExport DlgActiveBody : public QDialog public: DlgActiveBody(QWidget* parent, App::Document*& doc, const QString& infoText=QString()); + ~DlgActiveBody(); void accept() override; - PartDesign::Body* getActiveBody() { return activeBody; }; + PartDesign::Body* getActiveBody() const { return activeBody; } private: std::unique_ptr ui; diff --git a/src/Mod/PartDesign/Gui/EnumFlags.h b/src/Mod/PartDesign/Gui/EnumFlags.h new file mode 100644 index 0000000000..ecd410a6b5 --- /dev/null +++ b/src/Mod/PartDesign/Gui/EnumFlags.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * Copyright (c) 2021 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 PARTDESIGNGUI_ENUMFLAGS_H +#define PARTDESIGNGUI_ENUMFLAGS_H + +#include + +namespace PartDesignGui { + +// https://wiggling-bits.net/using-enum-classes-as-type-safe-bitmasks/ +// https://www.boost.org/doc/libs/1_66_0/boost/detail/bitmask.hpp +// https://stackoverflow.com/questions/1448396/how-to-use-enums-as-flags-in-c + +enum class AllowSelection { + NONE = 0, + EDGE = 1 << 0, /**< Allow picking edges */ + FACE = 1 << 1, /**< Allow picking faces */ + PLANAR = 1 << 2, /**< Allow only linear edges and planar faces */ + CIRCLE = 1 << 3, /**< Allow picking circular edges (incl arcs) */ + POINT = 1 << 4, /**< Allow picking datum points */ + OTHERBODY = 1 << 5, /**< Allow picking objects from another body in the same part */ + WHOLE = 1 << 6 /**< Allow whole object selection */ +}; +Q_DECLARE_FLAGS(AllowSelectionFlags, AllowSelection) + +} //namespace PartDesignGui + +Q_DECLARE_OPERATORS_FOR_FLAGS(PartDesignGui::AllowSelectionFlags) + +#endif // PARTDESIGNGUI_ENUMFLAGS_H diff --git a/src/Mod/PartDesign/Gui/ReferenceSelection.cpp b/src/Mod/PartDesign/Gui/ReferenceSelection.cpp index 920ef2bdbb..55feb77d9e 100644 --- a/src/Mod/PartDesign/Gui/ReferenceSelection.cpp +++ b/src/Mod/PartDesign/Gui/ReferenceSelection.cpp @@ -60,86 +60,35 @@ using namespace Gui; bool ReferenceSelection::allow(App::Document* pDoc, App::DocumentObject* pObj, const char* sSubName) { - // TODO review this function (2015-09-04, Fat-Zer) - PartDesign::Body *body; - App::DocumentObject* originGroupObject = nullptr; - - if ( support ) { - body = PartDesign::Body::findBodyOf (support); - } else { - body = PartDesignGui::getBody (false); - } - - if ( body ) { // Search for Part of the body - originGroupObject = App::OriginGroupExtension::getGroupOfObject ( body ) ; - } else if ( support ) { // if no body search part for support - originGroupObject = App::OriginGroupExtension::getGroupOfObject ( support ) ; - } else { // fallback to active part - originGroupObject = PartDesignGui::getActivePart ( ); - } - - App::OriginGroupExtension* originGroup = nullptr; - if(originGroupObject) - originGroup = originGroupObject->getExtensionByType(); + PartDesign::Body *body = getBody(); + App::OriginGroupExtension* originGroup = getOriginGroupExtension(body); // Don't allow selection in other document - if ( support && pDoc != support->getDocument() ) { + if (support && pDoc != support->getDocument()) { return false; } // Enable selection from origin of current part/ - if ( pObj->getTypeId().isDerivedFrom(App::OriginFeature::getClassTypeId()) ) { - bool fits = false; - if ( plane && pObj->getTypeId().isDerivedFrom(App::Plane::getClassTypeId()) ) { - fits = true; - } else if ( edge && pObj->getTypeId().isDerivedFrom(App::Line::getClassTypeId()) ) { - fits = true; - } - - if (fits) { // check that it is actually belongs to the chosen body or part - try { // here are some throwers - if (body) { - if (body->getOrigin ()->hasObject (pObj) ) { - return true; - } - } else if (originGroup ) { - if ( originGroup->getOrigin ()->hasObject (pObj) ) { - return true; - } - } - } catch (const Base::Exception&) - { } - } - return false; // The Plane/Axis doesn't fits our needs + if (pObj->getTypeId().isDerivedFrom(App::OriginFeature::getClassTypeId())) { + return allowOrigin(body, originGroup, pObj); } if (pObj->getTypeId().isDerivedFrom(Part::Datum::getClassTypeId())) { - - if (!body) { // Allow selecting Part::Datum features from the active Body - return false; - } else if (!allowOtherBody && !body->hasObject(pObj)) { - return false; - } - - if (plane && (pObj->getTypeId().isDerivedFrom(PartDesign::Plane::getClassTypeId()))) - return true; - if (edge && (pObj->getTypeId().isDerivedFrom(PartDesign::Line::getClassTypeId()))) - return true; - if (point && (pObj->getTypeId().isDerivedFrom(PartDesign::Point::getClassTypeId()))) - return true; - - return false; + return allowDatum(body, pObj); } - if (!allowOtherBody) { + // The flag was used to be set. So, this block will never be treated and doesn't make really sense anyway +#if 0 + if (!type.testFlag(AllowSelection::OTHERBODY)) { if (support == NULL) return false; if (pObj != support) return false; } +#endif // Handle selection of geometry elements if (!sSubName || sSubName[0] == '\0') - return whole; + return type.testFlag(AllowSelection::WHOLE); // resolve links if needed if (!pObj->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) { @@ -147,51 +96,172 @@ bool ReferenceSelection::allow(App::Document* pDoc, App::DocumentObject* pObj, c } if (pObj && pObj->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) { - std::string subName(sSubName); - if (edge && subName.size() > 4 && subName.substr(0,4) == "Edge") { - const Part::TopoShape &shape = static_cast(pObj)->Shape.getValue(); - TopoDS_Shape sh = shape.getSubShape(subName.c_str()); - const TopoDS_Edge& edgeShape = TopoDS::Edge(sh); - if (!edgeShape.IsNull()) { - if (planar) { - BRepAdaptor_Curve adapt(edgeShape); - if (adapt.GetType() == GeomAbs_Line) - return true; - } else { - return true; - } - } - } - if (plane && subName.size() > 4 && subName.substr(0,4) == "Face") { - const Part::TopoShape &shape = static_cast(pObj)->Shape.getValue(); - TopoDS_Shape sh = shape.getSubShape(subName.c_str()); - const TopoDS_Face& face = TopoDS::Face(sh); - if (!face.IsNull()) { - if (planar) { - BRepAdaptor_Surface adapt(face); - if (adapt.GetType() == GeomAbs_Plane) - return true; - } else { - return true; - } - } - } - if (point && subName.size() > 6 && subName.substr(0,6) == "Vertex") { - return true; - } - if (circle && subName.size() > 4 && subName.substr(0,4) == "Edge") { - const Part::TopoShape &shape = static_cast(pObj)->Shape.getValue(); - TopoDS_Shape sh = shape.getSubShape(subName.c_str()); - const TopoDS_Edge& edgeShape = TopoDS::Edge(sh); - BRepAdaptor_Curve adapt(edgeShape); - if (adapt.GetType() == GeomAbs_Circle) { - return true; - } - } + return allowPartFeature(pObj, sSubName); } + return false; } +PartDesign::Body* ReferenceSelection::getBody() const +{ + PartDesign::Body *body; + if (support) { + body = PartDesign::Body::findBodyOf (support); + } + else { + body = PartDesignGui::getBody(false); + } + + return body; +} + +App::OriginGroupExtension* ReferenceSelection::getOriginGroupExtension(PartDesign::Body *body) const +{ + App::DocumentObject* originGroupObject = nullptr; + + if (body) { // Search for Part of the body + originGroupObject = App::OriginGroupExtension::getGroupOfObject(body); + } + else if (support) { // if no body search part for support + originGroupObject = App::OriginGroupExtension::getGroupOfObject(support); + } + else { // fallback to active part + originGroupObject = PartDesignGui::getActivePart(); + } + + App::OriginGroupExtension* originGroup = nullptr; + if (originGroupObject) + originGroup = originGroupObject->getExtensionByType(); + + return originGroup; +} + +bool ReferenceSelection::allowOrigin(PartDesign::Body *body, App::OriginGroupExtension* originGroup, App::DocumentObject* pObj) const +{ + bool fits = false; + if (type.testFlag(AllowSelection::FACE) && pObj->getTypeId().isDerivedFrom(App::Plane::getClassTypeId())) { + fits = true; + } + else if (type.testFlag(AllowSelection::EDGE) && pObj->getTypeId().isDerivedFrom(App::Line::getClassTypeId())) { + fits = true; + } + + if (fits) { // check that it actually belongs to the chosen body or part + try { // here are some throwers + if (body) { + if (body->getOrigin ()->hasObject (pObj) ) { + return true; + } + } + else if (originGroup ) { + if (originGroup->getOrigin()->hasObject(pObj)) { + return true; + } + } + } + catch (const Base::Exception&) { + } + } + return false; // The Plane/Axis doesn't fits our needs +} + +bool ReferenceSelection::allowDatum(PartDesign::Body *body, App::DocumentObject* pObj) const +{ + if (!body) { // Allow selecting Part::Datum features from the active Body + return false; + } + else if (!type.testFlag(AllowSelection::OTHERBODY) && !body->hasObject(pObj)) { + return false; + } + + if (type.testFlag(AllowSelection::FACE) && (pObj->getTypeId().isDerivedFrom(PartDesign::Plane::getClassTypeId()))) + return true; + if (type.testFlag(AllowSelection::EDGE) && (pObj->getTypeId().isDerivedFrom(PartDesign::Line::getClassTypeId()))) + return true; + if (type.testFlag(AllowSelection::POINT) && (pObj->getTypeId().isDerivedFrom(PartDesign::Point::getClassTypeId()))) + return true; + + return false; +} + +bool ReferenceSelection::allowPartFeature(App::DocumentObject* pObj, const char* sSubName) const +{ + std::string subName(sSubName); + if (type.testFlag(AllowSelection::POINT) && subName.compare(0, 6, "Vertex") == 0) { + return true; + } + + if (type.testFlag(AllowSelection::EDGE) && subName.compare(0, 4, "Edge") == 0) { + if (isEdge(pObj, sSubName)) + return true; + } + + if (type.testFlag(AllowSelection::CIRCLE) && subName.compare(0, 4, "Edge") == 0) { + if (isCircle(pObj, sSubName)) + return true; + } + + if (type.testFlag(AllowSelection::FACE) && subName.compare(0, 4, "Face") == 0) { + if (isFace(pObj, sSubName)) + return true; + } + + return false; +} + +bool ReferenceSelection::isEdge(App::DocumentObject* pObj, const char* sSubName) const +{ + const Part::TopoShape &shape = static_cast(pObj)->Shape.getValue(); + TopoDS_Shape sh = shape.getSubShape(sSubName); + const TopoDS_Edge& edgeShape = TopoDS::Edge(sh); + if (!edgeShape.IsNull()) { + if (type.testFlag(AllowSelection::PLANAR)) { + BRepAdaptor_Curve adapt(edgeShape); + if (adapt.GetType() == GeomAbs_Line) + return true; + } + else { + return true; + } + } + + return false; +} + +bool ReferenceSelection::isFace(App::DocumentObject* pObj, const char* sSubName) const +{ + const Part::TopoShape &shape = static_cast(pObj)->Shape.getValue(); + TopoDS_Shape sh = shape.getSubShape(sSubName); + const TopoDS_Face& face = TopoDS::Face(sh); + if (!face.IsNull()) { + if (type.testFlag(AllowSelection::PLANAR)) { + BRepAdaptor_Surface adapt(face); + if (adapt.GetType() == GeomAbs_Plane) + return true; + } + else { + return true; + } + } + + return false; +} + +bool ReferenceSelection::isCircle(App::DocumentObject* pObj, const char* sSubName) const +{ + const Part::TopoShape &shape = static_cast(pObj)->Shape.getValue(); + TopoDS_Shape sh = shape.getSubShape(sSubName); + const TopoDS_Edge& edgeShape = TopoDS::Edge(sh); + BRepAdaptor_Curve adapt(edgeShape); + if (adapt.GetType() == GeomAbs_Circle) { + return true; + } + + return false; +} + +// ---------------------------------------------------------------------------- + bool NoDependentsSelection::allow(App::Document* /*pDoc*/, App::DocumentObject* pObj, const char* /*sSubName*/) { if (support && support->testIfLinkDAGCompatible(pObj)) { diff --git a/src/Mod/PartDesign/Gui/ReferenceSelection.h b/src/Mod/PartDesign/Gui/ReferenceSelection.h index 42490a75ce..ce662c9c77 100644 --- a/src/Mod/PartDesign/Gui/ReferenceSelection.h +++ b/src/Mod/PartDesign/Gui/ReferenceSelection.h @@ -25,33 +25,27 @@ #define GUI_ReferenceSelection_H #include +#include +namespace App { +class OriginGroupExtension; +} +namespace PartDesign { +class Body; +} namespace PartDesignGui { class ReferenceSelection : public Gui::SelectionFilterGate { - // TODO Replace this set of bools with bitwice enum (2015-09-04, Fat-Zer) const App::DocumentObject* support; - // If set to true, allow picking edges or planes or both - bool edge, plane; - // If set to true, allow only linear edges and planar faces - bool planar; - // If set to true, allow picking datum points - bool point; - // If set to true, allow picking objects from another body in the same part - bool allowOtherBody; - // Allow whole object selection - bool whole; - // Allow picking circular edges (incl arcs) - bool circle; + AllowSelectionFlags type; public: ReferenceSelection(const App::DocumentObject* support_, - const bool edge_, const bool plane_, const bool planar_, - const bool point_ = false, bool whole_ = false, bool circle_ = false) - : Gui::SelectionFilterGate((Gui::SelectionFilter*)0), - support(support_), edge(edge_), plane(plane_), - planar(planar_), point(point_), allowOtherBody(true), whole(whole_), circle(circle_) + AllowSelectionFlags type) + : Gui::SelectionFilterGate(static_cast(nullptr)) + , support(support_) + , type(type) { } /** @@ -59,6 +53,16 @@ public: * Optionally restrict the selection to planar edges/faces */ bool allow(App::Document* pDoc, App::DocumentObject* pObj, const char* sSubName); + +private: + PartDesign::Body* getBody() const; + App::OriginGroupExtension* getOriginGroupExtension(PartDesign::Body *body) const; + bool allowOrigin(PartDesign::Body *body, App::OriginGroupExtension* originGroup, App::DocumentObject* pObj) const; + bool allowDatum(PartDesign::Body *body, App::DocumentObject* pObj) const; + bool allowPartFeature(App::DocumentObject* pObj, const char* sSubName) const; + bool isEdge(App::DocumentObject* pObj, const char* sSubName) const; + bool isFace(App::DocumentObject* pObj, const char* sSubName) const; + bool isCircle(App::DocumentObject* pObj, const char* sSubName) const; }; class NoDependentsSelection : public Gui::SelectionFilterGate diff --git a/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp b/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp index 48a5a36054..afbb520963 100644 --- a/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskDraftParameters.cpp @@ -212,7 +212,9 @@ void TaskDraftParameters::onButtonPlane(bool checked) hideObject(); selectionMode = plane; Gui::Selection().clearSelection(); - Gui::Selection().addSelectionGate(new ReferenceSelection(this->getBase(), true, true, true)); + Gui::Selection().addSelectionGate(new ReferenceSelection(this->getBase(), AllowSelection::EDGE | + AllowSelection::FACE | + AllowSelection::PLANAR)); } } @@ -223,7 +225,8 @@ void TaskDraftParameters::onButtonLine(bool checked) hideObject(); selectionMode = line; Gui::Selection().clearSelection(); - Gui::Selection().addSelectionGate(new ReferenceSelection(this->getBase(), true, false, true)); + Gui::Selection().addSelectionGate(new ReferenceSelection(this->getBase(), AllowSelection::EDGE | + AllowSelection::PLANAR)); } } diff --git a/src/Mod/PartDesign/Gui/TaskDressUpParameters.cpp b/src/Mod/PartDesign/Gui/TaskDressUpParameters.cpp index 79f63a2b95..880393589f 100644 --- a/src/Mod/PartDesign/Gui/TaskDressUpParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskDressUpParameters.cpp @@ -145,8 +145,11 @@ void TaskDressUpParameters::onButtonRefAdd(bool checked) clearButtons(refAdd); hideObject(); selectionMode = refAdd; + AllowSelectionFlags allow; + allow.setFlag(AllowSelection::EDGE, allowEdges); + allow.setFlag(AllowSelection::FACE, allowFaces); Gui::Selection().clearSelection(); - Gui::Selection().addSelectionGate(new ReferenceSelection(this->getBase(), allowEdges, allowFaces, false)); + Gui::Selection().addSelectionGate(new ReferenceSelection(this->getBase(), allow)); DressUpView->highlightReferences(true); } else { exitSelectionMode(); @@ -160,8 +163,11 @@ void TaskDressUpParameters::onButtonRefRemove(const bool checked) clearButtons(refRemove); hideObject(); selectionMode = refRemove; - Gui::Selection().clearSelection(); - Gui::Selection().addSelectionGate(new ReferenceSelection(this->getBase(), allowEdges, allowFaces, false)); + AllowSelectionFlags allow; + allow.setFlag(AllowSelection::EDGE, allowEdges); + allow.setFlag(AllowSelection::FACE, allowFaces); + Gui::Selection().clearSelection(); + Gui::Selection().addSelectionGate(new ReferenceSelection(this->getBase(), allow)); DressUpView->highlightReferences(true); } else { diff --git a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp new file mode 100644 index 0000000000..b5cb476111 --- /dev/null +++ b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.cpp @@ -0,0 +1,758 @@ +/*************************************************************************** + * Copyright (c) 2011 Juergen Riegel * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +# include +# include +# include +#endif + +#include "ui_TaskPadParameters.h" +#include "TaskExtrudeParameters.h" +#include +#include +#include "ReferenceSelection.h" +#include + +using namespace PartDesignGui; +using namespace Gui; + +/* TRANSLATOR PartDesignGui::TaskExtrudeParameters */ + +TaskExtrudeParameters::TaskExtrudeParameters(ViewProviderSketchBased *SketchBasedView, QWidget *parent, + const std::string& pixmapname, const QString& parname) + : TaskSketchBasedParameters(SketchBasedView, parent, pixmapname, parname) + , ui(new Ui_TaskPadParameters) +{ + // we need a separate container widget to add all controls to + proxy = new QWidget(this); + ui->setupUi(proxy); + ui->lineFaceName->setPlaceholderText(tr("No face selected")); + + Gui::ButtonGroup* group = new Gui::ButtonGroup(this); + group->addButton(ui->checkBoxMidplane); + group->addButton(ui->checkBoxReversed); + group->setExclusive(true); + + this->groupLayout()->addWidget(proxy); +} + +TaskExtrudeParameters::~TaskExtrudeParameters() +{ +} + +void TaskExtrudeParameters::setupDialog() +{ + // Get the feature data + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + Base::Quantity l = extrude->Length.getQuantityValue(); + Base::Quantity l2 = extrude->Length2.getQuantityValue(); + Base::Quantity off = extrude->Offset.getQuantityValue(); + + bool alongNormal = extrude->AlongSketchNormal.getValue(); + bool useCustom = extrude->UseCustomVector.getValue(); + + double xs = extrude->Direction.getValue().x; + double ys = extrude->Direction.getValue().y; + double zs = extrude->Direction.getValue().z; + + bool midplane = extrude->Midplane.getValue(); + bool reversed = extrude->Reversed.getValue(); + + int index = extrude->Type.getValue(); // must extract value here, clear() kills it! + App::DocumentObject* obj = extrude->UpToFace.getValue(); + std::vector subStrings = extrude->UpToFace.getSubValues(); + std::string upToFace; + int faceId = -1; + if (obj && !subStrings.empty()) { + upToFace = subStrings.front(); + if (upToFace.compare(0, 4, "Face") == 0) + faceId = std::atoi(&upToFace[4]); + } + + // set decimals for the direction edits + // do this here before the edits are filed to avoid rounding mistakes + int UserDecimals = Base::UnitsApi::getDecimals(); + ui->XDirectionEdit->setDecimals(UserDecimals); + ui->YDirectionEdit->setDecimals(UserDecimals); + ui->ZDirectionEdit->setDecimals(UserDecimals); + + // Fill data into dialog elements + // the direction combobox is later filled in updateUI() + ui->lengthEdit->setValue(l); + ui->lengthEdit2->setValue(l2); + ui->offsetEdit->setValue(off); + + ui->checkBoxAlongDirection->setChecked(alongNormal); + ui->checkBoxDirection->setChecked(useCustom); + onDirectionToggled(useCustom); + + // disable to change the direction if not custom + if (!useCustom) { + ui->XDirectionEdit->setEnabled(false); + ui->YDirectionEdit->setEnabled(false); + ui->ZDirectionEdit->setEnabled(false); + } + ui->XDirectionEdit->setValue(xs); + ui->YDirectionEdit->setValue(ys); + ui->ZDirectionEdit->setValue(zs); + + // Bind input fields to properties + ui->lengthEdit->bind(extrude->Length); + ui->lengthEdit2->bind(extrude->Length2); + ui->offsetEdit->bind(extrude->Offset); + ui->XDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, std::string("Direction.x"))); + ui->YDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, std::string("Direction.y"))); + ui->ZDirectionEdit->bind(App::ObjectIdentifier::parse(extrude, std::string("Direction.z"))); + + ui->checkBoxMidplane->setChecked(midplane); + // According to bug #0000521 the reversed option + // shouldn't be de-activated if the pad has a support face + ui->checkBoxReversed->setChecked(reversed); + + // Set object labels + if (obj && PartDesign::Feature::isDatum(obj)) { + ui->lineFaceName->setText(QString::fromUtf8(obj->Label.getValue())); + ui->lineFaceName->setProperty("FeatureName", QByteArray(obj->getNameInDocument())); + } + else if (obj && faceId >= 0) { + ui->lineFaceName->setText(QString::fromLatin1("%1:%2%3") + .arg(QString::fromUtf8(obj->Label.getValue())) + .arg(tr("Face")) + .arg(faceId)); + ui->lineFaceName->setProperty("FeatureName", QByteArray(obj->getNameInDocument())); + } + else { + ui->lineFaceName->clear(); + ui->lineFaceName->setProperty("FeatureName", QVariant()); + } + + ui->lineFaceName->setProperty("FaceName", QByteArray(upToFace.c_str())); + + translateModeList(index); + + connectSlots(); + + this->propReferenceAxis = &(extrude->ReferenceAxis); + + // Due to signals attached after changes took took into effect we should update the UI now. + updateUI(index); +} + +void TaskExtrudeParameters::readValuesFromHistory() +{ + ui->lengthEdit->setToLastUsedValue(); + ui->lengthEdit->selectNumber(); + ui->lengthEdit2->setToLastUsedValue(); + ui->lengthEdit2->selectNumber(); + ui->offsetEdit->setToLastUsedValue(); + ui->offsetEdit->selectNumber(); +} + +void TaskExtrudeParameters::connectSlots() +{ + QMetaObject::connectSlotsByName(this); + + connect(ui->lengthEdit, SIGNAL(valueChanged(double)), + this, SLOT(onLengthChanged(double))); + connect(ui->lengthEdit2, SIGNAL(valueChanged(double)), + this, SLOT(onLength2Changed(double))); + connect(ui->offsetEdit, SIGNAL(valueChanged(double)), + this, SLOT(onOffsetChanged(double))); + connect(ui->directionCB, SIGNAL(activated(int)), + this, SLOT(onDirectionCBChanged(int))); + connect(ui->checkBoxAlongDirection, SIGNAL(toggled(bool)), + this, SLOT(onAlongSketchNormalChanged(bool))); + connect(ui->checkBoxDirection, SIGNAL(toggled(bool)), + this, SLOT(onDirectionToggled(bool))); + connect(ui->XDirectionEdit, SIGNAL(valueChanged(double)), + this, SLOT(onXDirectionEditChanged(double))); + connect(ui->YDirectionEdit, SIGNAL(valueChanged(double)), + this, SLOT(onYDirectionEditChanged(double))); + connect(ui->ZDirectionEdit, SIGNAL(valueChanged(double)), + this, SLOT(onZDirectionEditChanged(double))); + connect(ui->checkBoxMidplane, SIGNAL(toggled(bool)), + this, SLOT(onMidplaneChanged(bool))); + connect(ui->checkBoxReversed, SIGNAL(toggled(bool)), + this, SLOT(onReversedChanged(bool))); + connect(ui->changeMode, SIGNAL(currentIndexChanged(int)), + this, SLOT(onModeChanged(int))); + connect(ui->buttonFace, SIGNAL(clicked()), + this, SLOT(onButtonFace())); + connect(ui->lineFaceName, SIGNAL(textEdited(QString)), + this, SLOT(onFaceName(QString))); + connect(ui->checkBoxUpdateView, SIGNAL(toggled(bool)), + this, SLOT(onUpdateView(bool))); +} + +void TaskExtrudeParameters::tryRecomputeFeature() +{ + try { + // recompute and update the direction + recomputeFeature(); + } + catch (const Base::Exception& e) { + e.ReportException(); + } +} + +void TaskExtrudeParameters::onSelectionChanged(const Gui::SelectionChanges& msg) +{ + if (msg.Type == Gui::SelectionChanges::AddSelection) { + // if we have an edge selection for the extrude direction + if (!selectionFace) { + selectedReferenceAxis(msg); + } + // if we have a selection of a face + else { + QString refText = onAddSelection(msg); + if (refText.length() > 0) { + QSignalBlocker block(ui->lineFaceName); + ui->lineFaceName->setText(refText); + ui->lineFaceName->setProperty("FeatureName", QByteArray(msg.pObjectName)); + ui->lineFaceName->setProperty("FaceName", QByteArray(msg.pSubName)); + // Turn off reference selection mode + onButtonFace(false); + } + else { + clearFaceName(); + } + } + } + else if (msg.Type == Gui::SelectionChanges::ClrSelection && selectionFace) { + clearFaceName(); + } +} + +void TaskExtrudeParameters::selectedReferenceAxis(const Gui::SelectionChanges& msg) +{ + std::vector edge; + App::DocumentObject* selObj; + if (getReferencedSelection(vp->getObject(), msg, selObj, edge) && selObj) { + exitSelectionMode(); + propReferenceAxis->setValue(selObj, edge); + tryRecomputeFeature(); + // update direction combobox + fillDirectionCombo(); + } +} + +void TaskExtrudeParameters::clearFaceName() +{ + QSignalBlocker block(ui->lineFaceName); + ui->lineFaceName->clear(); + ui->lineFaceName->setProperty("FeatureName", QVariant()); + ui->lineFaceName->setProperty("FaceName", QVariant()); + +} + +void TaskExtrudeParameters::onLengthChanged(double len) +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + extrude->Length.setValue(len); + tryRecomputeFeature(); +} + +void TaskExtrudeParameters::onLength2Changed(double len) +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + extrude->Length2.setValue(len); + tryRecomputeFeature(); +} + +void TaskExtrudeParameters::onOffsetChanged(double len) +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + extrude->Offset.setValue(len); + tryRecomputeFeature(); +} + +bool TaskExtrudeParameters::hasProfileFace(PartDesign::ProfileBased* profile) const +{ + try { + Part::Feature* pcFeature = profile->getVerifiedObject(); + Base::Vector3d SketchVector = profile->getProfileNormal(); + Q_UNUSED(pcFeature) + Q_UNUSED(SketchVector) + return true; + } + catch (const Base::Exception&) { + } + + return false; +} + +void TaskExtrudeParameters::fillDirectionCombo() +{ + bool oldVal_blockUpdate = blockUpdate; + blockUpdate = true; + + if (axesInList.empty()) { + bool hasFace = false; + ui->directionCB->clear(); + // we can have sketches or faces + // for sketches just get the sketch normal + PartDesign::ProfileBased* pcFeat = static_cast(vp->getObject()); + Part::Part2DObject* pcSketch = dynamic_cast(pcFeat->Profile.getValue()); + // for faces we test if it is verified and if we can get its normal + if (!pcSketch) { + hasFace = hasProfileFace(pcFeat); + } + + if (pcSketch) + addAxisToCombo(pcSketch, "N_Axis", tr("Sketch normal")); + else if (hasFace) + addAxisToCombo(pcFeat->Profile.getValue(), std::string(), tr("Face normal"), false); + + // add the other entries + addAxisToCombo(0, std::string(), tr("Select reference...")); + + // we start with the sketch normal as proposal for the custom direction + if (pcSketch) + addAxisToCombo(pcSketch, "N_Axis", tr("Custom direction")); + else if (hasFace) + addAxisToCombo(pcFeat->Profile.getValue(), std::string(), tr("Custom direction"), false); + } + + // add current link, if not in list + // first, figure out the item number for current axis + int indexOfCurrent = -1; + App::DocumentObject* ax = propReferenceAxis->getValue(); + const std::vector& subList = propReferenceAxis->getSubValues(); + for (size_t i = 0; i < axesInList.size(); i++) { + if (ax == axesInList[i]->getValue() && subList == axesInList[i]->getSubValues()) { + indexOfCurrent = i; + break; + } + } + // if the axis is not yet listed in the combobox + if (indexOfCurrent == -1 && ax) { + assert(subList.size() <= 1); + std::string sub; + if (!subList.empty()) + sub = subList[0]; + addAxisToCombo(ax, sub, getRefStr(ax, subList)); + indexOfCurrent = axesInList.size() - 1; + // the axis is not the normal, thus enable along direction + ui->checkBoxAlongDirection->setEnabled(true); + // we don't have custom direction thus disable its settings + ui->XDirectionEdit->setEnabled(false); + ui->YDirectionEdit->setEnabled(false); + ui->ZDirectionEdit->setEnabled(false); + } + + // highlight either current index or set custom direction + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + bool hasCustom = extrude->UseCustomVector.getValue(); + if (indexOfCurrent != -1 && !hasCustom) + ui->directionCB->setCurrentIndex(indexOfCurrent); + if (hasCustom) + ui->directionCB->setCurrentIndex(DirectionModes::Custom); + + blockUpdate = oldVal_blockUpdate; +} + +void TaskExtrudeParameters::addAxisToCombo(App::DocumentObject* linkObj, std::string linkSubname, + QString itemText, bool hasSketch) +{ + this->ui->directionCB->addItem(itemText); + this->axesInList.emplace_back(new App::PropertyLinkSub); + App::PropertyLinkSub& lnk = *(axesInList.back()); + // if we have a face, we leave the link empty since we cannot + // store the face normal as sublink + if (hasSketch) + lnk.setValue(linkObj, std::vector(1, linkSubname)); +} + +void TaskExtrudeParameters::onDirectionCBChanged(int num) +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + + if (axesInList.empty()) + return; + + // we use this scheme for 'num' + // 0: normal to sketch or face + // 1: selection mode + // 2: custom + // 3-x: edges selected in the 3D model + + // check the axis + // when the link is empty we are either in selection mode + // or we are normal to a face + App::PropertyLinkSub& lnk = *(axesInList[num]); + if (num == DirectionModes::Select) { + // enter reference selection mode + this->blockConnection(false); + // to distinguish that this is the direction selection + selectionFace = false; + setDirectionMode(num); + TaskSketchBasedParameters::onSelectReference(true, AllowSelection::EDGE | + AllowSelection::PLANAR | + AllowSelection::CIRCLE); + } + else { + if (lnk.getValue()) { + if (!extrude->getDocument()->isIn(lnk.getValue())) { + Base::Console().Error("Object was deleted\n"); + return; + } + propReferenceAxis->Paste(lnk); + } + + // in case the user is in selection mode, but changed his mind before selecting anything + exitSelectionMode(); + + setDirectionMode(num); + extrude->ReferenceAxis.setValue(lnk.getValue(), lnk.getSubValues()); + tryRecomputeFeature(); + updateDirectionEdits(); + } +} + +void TaskExtrudeParameters::onAlongSketchNormalChanged(bool on) +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + extrude->AlongSketchNormal.setValue(on); + tryRecomputeFeature(); +} + +void TaskExtrudeParameters::onDirectionToggled(bool on) +{ + if (on) + ui->groupBoxDirection->show(); + else + ui->groupBoxDirection->hide(); +} + +void TaskExtrudeParameters::onXDirectionEditChanged(double len) +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + extrude->Direction.setValue(len, extrude->Direction.getValue().y, extrude->Direction.getValue().z); + tryRecomputeFeature(); + // checking for case of a null vector is done in FeatureExtrude.cpp + // if there was a null vector, the normal vector of the sketch is used. + // therefore the vector component edits must be updated + updateDirectionEdits(); +} + +void TaskExtrudeParameters::onYDirectionEditChanged(double len) +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + extrude->Direction.setValue(extrude->Direction.getValue().x, len, extrude->Direction.getValue().z); + tryRecomputeFeature(); + updateDirectionEdits(); +} + +void TaskExtrudeParameters::onZDirectionEditChanged(double len) +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + extrude->Direction.setValue(extrude->Direction.getValue().x, extrude->Direction.getValue().y, len); + tryRecomputeFeature(); + updateDirectionEdits(); +} + +void TaskExtrudeParameters::updateDirectionEdits() +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + // we don't want to execute the onChanged edits, but just update their contents + QSignalBlocker xdir(ui->XDirectionEdit); + QSignalBlocker ydir(ui->YDirectionEdit); + QSignalBlocker zdir(ui->ZDirectionEdit); + ui->XDirectionEdit->setValue(extrude->Direction.getValue().x); + ui->YDirectionEdit->setValue(extrude->Direction.getValue().y); + ui->ZDirectionEdit->setValue(extrude->Direction.getValue().z); +} + +void TaskExtrudeParameters::setDirectionMode(int index) +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + // disable AlongSketchNormal when the direction is already normal + if (index == DirectionModes::Normal) + ui->checkBoxAlongDirection->setEnabled(false); + else + ui->checkBoxAlongDirection->setEnabled(true); + + // if custom direction is used, show it + if (index == DirectionModes::Custom) { + ui->checkBoxDirection->setChecked(true); + extrude->UseCustomVector.setValue(true); + } + else { + extrude->UseCustomVector.setValue(false); + } + + // if we dont use custom direction, only allow to show its direction + if (index != DirectionModes::Custom) { + ui->XDirectionEdit->setEnabled(false); + ui->YDirectionEdit->setEnabled(false); + ui->ZDirectionEdit->setEnabled(false); + } + else { + ui->XDirectionEdit->setEnabled(true); + ui->YDirectionEdit->setEnabled(true); + ui->ZDirectionEdit->setEnabled(true); + } + +} + +void TaskExtrudeParameters::onMidplaneChanged(bool on) +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + extrude->Midplane.setValue(on); + tryRecomputeFeature(); +} + +void TaskExtrudeParameters::onReversedChanged(bool on) +{ + PartDesign::FeatureExtrude* extrude = static_cast(vp->getObject()); + extrude->Reversed.setValue(on); + // update the direction + tryRecomputeFeature(); + updateDirectionEdits(); +} + +void TaskExtrudeParameters::getReferenceAxis(App::DocumentObject*& obj, std::vector& sub) const +{ + if (axesInList.empty()) + throw Base::RuntimeError("Not initialized!"); + + int num = ui->directionCB->currentIndex(); + const App::PropertyLinkSub& lnk = *(axesInList[num]); + if (!lnk.getValue()) { + // Note: It is possible that a face of an object is directly padded/pocketed without defining a profile shape + obj = nullptr; + sub.clear(); + } + else { + PartDesign::ProfileBased* pcDirection = static_cast(vp->getObject()); + if (!pcDirection->getDocument()->isIn(lnk.getValue())) + throw Base::RuntimeError("Object was deleted"); + + obj = lnk.getValue(); + sub = lnk.getSubValues(); + } +} + +void TaskExtrudeParameters::onButtonFace(const bool pressed) +{ + this->blockConnection(!pressed); + + // to distinguish that this is the direction selection + selectionFace = true; + + // only faces are allowed + TaskSketchBasedParameters::onSelectReference(pressed, AllowSelection::FACE); + + // Update button if onButtonFace() is called explicitly + ui->buttonFace->setChecked(pressed); +} + +void TaskExtrudeParameters::onFaceName(const QString& text) +{ + if (text.isEmpty()) { + // if user cleared the text field then also clear the properties + ui->lineFaceName->setProperty("FeatureName", QVariant()); + ui->lineFaceName->setProperty("FaceName", QVariant()); + } + else { + // expect that the label of an object is used + QStringList parts = text.split(QChar::fromLatin1(':')); + QString label = parts[0]; + QVariant name = objectNameByLabel(label, ui->lineFaceName->property("FeatureName")); + if (name.isValid()) { + parts[0] = name.toString(); + QString uptoface = parts.join(QString::fromLatin1(":")); + ui->lineFaceName->setProperty("FeatureName", name); + ui->lineFaceName->setProperty("FaceName", setUpToFace(uptoface)); + } + else { + ui->lineFaceName->setProperty("FeatureName", QVariant()); + ui->lineFaceName->setProperty("FaceName", QVariant()); + } + } +} + +void TaskExtrudeParameters::translateFaceName() +{ + ui->lineFaceName->setPlaceholderText(tr("No face selected")); + QVariant featureName = ui->lineFaceName->property("FeatureName"); + if (featureName.isValid()) { + QStringList parts = ui->lineFaceName->text().split(QChar::fromLatin1(':')); + QByteArray upToFace = ui->lineFaceName->property("FaceName").toByteArray(); + int faceId = -1; + bool ok = false; + if (upToFace.indexOf("Face") == 0) { + faceId = upToFace.remove(0,4).toInt(&ok); + } + + if (ok) { + ui->lineFaceName->setText(QString::fromLatin1("%1:%2%3") + .arg(parts[0]) + .arg(tr("Face")) + .arg(faceId)); + } + else { + ui->lineFaceName->setText(parts[0]); + } + } +} + +double TaskExtrudeParameters::getLength(void) const +{ + return ui->lengthEdit->value().getValue(); +} + +double TaskExtrudeParameters::getLength2(void) const +{ + return ui->lengthEdit2->value().getValue(); +} + +double TaskExtrudeParameters::getOffset(void) const +{ + return ui->offsetEdit->value().getValue(); +} + +bool TaskExtrudeParameters::getAlongSketchNormal(void) const +{ + return ui->checkBoxAlongDirection->isChecked(); +} + +bool TaskExtrudeParameters::getCustom(void) const +{ + return (ui->directionCB->currentIndex() == DirectionModes::Custom); +} + +std::string TaskExtrudeParameters::getReferenceAxis(void) const +{ + std::vector sub; + App::DocumentObject* obj; + getReferenceAxis(obj, sub); + return buildLinkSingleSubPythonStr(obj, sub); +} + +double TaskExtrudeParameters::getXDirection(void) const +{ + return ui->XDirectionEdit->value(); +} + +double TaskExtrudeParameters::getYDirection(void) const +{ + return ui->YDirectionEdit->value(); +} + +double TaskExtrudeParameters::getZDirection(void) const +{ + return ui->ZDirectionEdit->value(); +} + +bool TaskExtrudeParameters::getReversed(void) const +{ + return ui->checkBoxReversed->isChecked(); +} + +bool TaskExtrudeParameters::getMidplane(void) const +{ + return ui->checkBoxMidplane->isChecked(); +} + +int TaskExtrudeParameters::getMode(void) const +{ + return ui->changeMode->currentIndex(); +} + +QString TaskExtrudeParameters::getFaceName(void) const +{ + QVariant featureName = ui->lineFaceName->property("FeatureName"); + if (featureName.isValid()) { + QString faceName = ui->lineFaceName->property("FaceName").toString(); + return getFaceReference(featureName.toString(), faceName); + } + + return QString::fromLatin1("None"); +} + +void TaskExtrudeParameters::changeEvent(QEvent *e) +{ + TaskBox::changeEvent(e); + if (e->type() == QEvent::LanguageChange) { + QSignalBlocker length(ui->lengthEdit); + QSignalBlocker length2(ui->lengthEdit2); + QSignalBlocker offset(ui->offsetEdit); + QSignalBlocker xdir(ui->XDirectionEdit); + QSignalBlocker ydir(ui->YDirectionEdit); + QSignalBlocker zdir(ui->ZDirectionEdit); + QSignalBlocker dir(ui->directionCB); + QSignalBlocker face(ui->lineFaceName); + QSignalBlocker mode(ui->changeMode); + + // Save all items + QStringList items; + for (int i = 0; i < ui->directionCB->count(); i++) + items << ui->directionCB->itemText(i); + + // Translate direction items + int index = ui->directionCB->currentIndex(); + ui->retranslateUi(proxy); + + // Keep custom items + for (int i = 0; i < ui->directionCB->count(); i++) + items.pop_front(); + ui->directionCB->addItems(items); + ui->directionCB->setCurrentIndex(index); + + // Translate mode items + translateModeList(ui->changeMode->currentIndex()); + + translateFaceName(); + } +} + +void TaskExtrudeParameters::saveHistory(void) +{ + // save the user values to history + ui->lengthEdit->pushToHistory(); + ui->lengthEdit2->pushToHistory(); + ui->offsetEdit->pushToHistory(); +} + +void TaskExtrudeParameters::onModeChanged(int) +{ + // implement in sub-class +} + +void TaskExtrudeParameters::updateUI(int) +{ + // implement in sub-class +} + +void TaskExtrudeParameters::translateModeList(int) +{ + // implement in sub-class +} + +#include "moc_TaskExtrudeParameters.cpp" diff --git a/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h new file mode 100644 index 0000000000..ae85079720 --- /dev/null +++ b/src/Mod/PartDesign/Gui/TaskExtrudeParameters.h @@ -0,0 +1,128 @@ +/*************************************************************************** + * Copyright (c) 2011 Juergen Riegel * + * * + * 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 GUI_TASKVIEW_TaskExtrudeParameters_H +#define GUI_TASKVIEW_TaskExtrudeParameters_H + +#include +#include +#include + +#include "TaskSketchBasedParameters.h" +#include "ViewProviderSketchBased.h" + +class Ui_TaskPadParameters; + +namespace App { +class Property; +} + +namespace PartDesign { +class ProfileBased; +} + +namespace PartDesignGui { + + +class TaskExtrudeParameters : public TaskSketchBasedParameters +{ + Q_OBJECT + + enum DirectionModes { + Normal, + Select, + Custom, + Reference + }; + +public: + TaskExtrudeParameters(ViewProviderSketchBased *SketchBasedView, QWidget *parent, + const std::string& pixmapname, const QString& parname); + ~TaskExtrudeParameters(); + + virtual void saveHistory() override; + + void fillDirectionCombo(); + void addAxisToCombo(App::DocumentObject* linkObj, std::string linkSubname, QString itemText, + bool hasSketch = true); + +protected Q_SLOTS: + void onLengthChanged(double); + void onLength2Changed(double); + void onOffsetChanged(double); + void onDirectionCBChanged(int); + void onAlongSketchNormalChanged(bool); + void onDirectionToggled(bool); + void onXDirectionEditChanged(double); + void onYDirectionEditChanged(double); + void onZDirectionEditChanged(double); + void onMidplaneChanged(bool); + void onReversedChanged(bool); + void onButtonFace(const bool pressed = true); + void onFaceName(const QString& text); + virtual void onModeChanged(int); + +protected: + void setupDialog(); + void readValuesFromHistory(); + void changeEvent(QEvent *e) override; + App::PropertyLinkSub* propReferenceAxis; + void getReferenceAxis(App::DocumentObject*& obj, std::vector& sub) const; + + double getLength(void) const; + double getLength2(void) const; + bool getAlongSketchNormal(void) const; + bool getCustom(void) const; + std::string getReferenceAxis(void) const; + double getXDirection(void) const; + double getYDirection(void) const; + double getZDirection(void) const; + double getOffset(void) const; + bool getReversed(void) const; + bool getMidplane(void) const; + int getMode(void) const; + QString getFaceName(void) const; + void onSelectionChanged(const Gui::SelectionChanges& msg) override; + virtual void translateModeList(int index); + virtual void updateUI(int index); + void updateDirectionEdits(void); + void setDirectionMode(int index); + +private: + void tryRecomputeFeature(); + void translateFaceName(); + void connectSlots(); + bool hasProfileFace(PartDesign::ProfileBased*) const; + void selectedReferenceAxis(const Gui::SelectionChanges& msg); + void clearFaceName(); + +protected: + QWidget* proxy; + std::unique_ptr ui; + bool selectionFace; + std::vector> axesInList; +}; + +} //namespace PartDesignGui + +#endif // GUI_TASKVIEW_TaskExtrudeParameters_H diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp b/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp index 029a63c661..f6af96849b 100644 --- a/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.cpp @@ -61,13 +61,91 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskHelixParameters */ -TaskHelixParameters::TaskHelixParameters(PartDesignGui::ViewProviderHelix *HelixView, QWidget *parent) - : TaskSketchBasedParameters(HelixView, parent, "PartDesign_AdditiveHelix", tr("Helix parameters")), - ui (new Ui_TaskHelixParameters) +TaskHelixParameters::TaskHelixParameters(PartDesignGui::ViewProviderHelix* HelixView, QWidget* parent) + : TaskSketchBasedParameters(HelixView, parent, "PartDesign_AdditiveHelix", tr("Helix parameters")) + , ui(new Ui_TaskHelixParameters) { // we need a separate container widget to add all controls to proxy = new QWidget(this); ui->setupUi(proxy); + this->groupLayout()->addWidget(proxy); + + initializeHelix(); + + assignProperties(); + setValuesFromProperties(); + + updateUI(); + + // enable use of parametric expressions for the numerical fields + bindProperties(); + + connectSlots(); + setFocus(); + showCoordinateAxes(); +} + +void TaskHelixParameters::initializeHelix() +{ + PartDesign::Helix* helix = static_cast(vp->getObject()); + if (!(helix->HasBeenEdited).getValue()) { + helix->proposeParameters(); + recomputeFeature(); + } +} + +void TaskHelixParameters::assignProperties() +{ + PartDesign::Helix* helix = static_cast(vp->getObject()); + propAngle = &(helix->Angle); + propGrowth = &(helix->Growth); + propPitch = &(helix->Pitch); + propHeight = &(helix->Height); + propTurns = &(helix->Turns); + propReferenceAxis = &(helix->ReferenceAxis); + propLeftHanded = &(helix->LeftHanded); + propReversed = &(helix->Reversed); + propMode = &(helix->Mode); + propOutside = &(helix->Outside); +} + +void TaskHelixParameters::setValuesFromProperties() +{ + double pitch = propPitch->getValue(); + double height = propHeight->getValue(); + double turns = propTurns->getValue(); + double angle = propAngle->getValue(); + double growth = propGrowth->getValue(); + bool leftHanded = propLeftHanded->getValue(); + bool reversed = propReversed->getValue(); + int index = propMode->getValue(); + bool outside = propOutside->getValue(); + + ui->pitch->setValue(pitch); + ui->height->setValue(height); + ui->turns->setValue(turns); + ui->coneAngle->setValue(angle); + ui->coneAngle->setMinimum(propAngle->getMinimum()); + ui->coneAngle->setMaximum(propAngle->getMaximum()); + ui->growth->setValue(growth); + ui->checkBoxLeftHanded->setChecked(leftHanded); + ui->checkBoxReversed->setChecked(reversed); + ui->inputMode->setCurrentIndex(index); + ui->checkBoxOutside->setChecked(outside); +} + +void TaskHelixParameters::bindProperties() +{ + PartDesign::Helix* helix = static_cast(vp->getObject()); + ui->pitch->bind(helix->Pitch); + ui->height->bind(helix->Height); + ui->turns->bind(helix->Turns); + ui->coneAngle->bind(helix->Angle); + ui->growth->bind(helix->Growth); +} + +void TaskHelixParameters::connectSlots() +{ QMetaObject::connectSlotsByName(this); connect(ui->pitch, SIGNAL(valueChanged(double)), @@ -92,97 +170,23 @@ TaskHelixParameters::TaskHelixParameters(PartDesignGui::ViewProviderHelix *Helix this, SLOT(onModeChanged(int))); connect(ui->checkBoxOutside, SIGNAL(toggled(bool)), this, SLOT(onOutsideChanged(bool))); +} - this->groupLayout()->addWidget(proxy); - - // Temporarily prevent unnecessary feature recomputes - ui->axis->blockSignals(true); - ui->pitch->blockSignals(true); - ui->height->blockSignals(true); - ui->turns->blockSignals(true); - ui->coneAngle->blockSignals(true); - ui->growth->blockSignals(true); - ui->checkBoxLeftHanded->blockSignals(true); - ui->checkBoxReversed->blockSignals(true); - ui->checkBoxOutside->blockSignals(true); - - //bind property mirrors - PartDesign::ProfileBased* pcFeat = static_cast(vp->getObject()); - - PartDesign::Helix* rev = static_cast(vp->getObject()); - - if (!(rev->HasBeenEdited).getValue()) { - rev->proposeParameters(); - recomputeFeature(); - } - - this->propAngle = &(rev->Angle); - this->propGrowth = &(rev->Growth); - this->propPitch = &(rev->Pitch); - this->propHeight = &(rev->Height); - this->propTurns = &(rev->Turns); - this->propReferenceAxis = &(rev->ReferenceAxis); - this->propLeftHanded = &(rev->LeftHanded); - this->propReversed = &(rev->Reversed); - this->propMode = &(rev->Mode); - this->propOutside = &(rev->Outside); - - double pitch = propPitch->getValue(); - double height = propHeight->getValue(); - double turns = propTurns->getValue(); - double angle = propAngle->getValue(); - double growth = propGrowth->getValue(); - bool leftHanded = propLeftHanded->getValue(); - bool reversed = propReversed->getValue(); - int index = propMode->getValue(); - bool outside = propOutside->getValue(); - - ui->pitch->setValue(pitch); - ui->height->setValue(height); - ui->turns->setValue(turns); - ui->coneAngle->setValue(angle); - ui->coneAngle->setMinimum(propAngle->getMinimum()); - ui->coneAngle->setMaximum(propAngle->getMaximum()); - ui->growth->setValue(growth); - ui->checkBoxLeftHanded->setChecked(leftHanded); - ui->checkBoxReversed->setChecked(reversed); - ui->inputMode->setCurrentIndex(index); - ui->checkBoxOutside->setChecked(outside); - - blockUpdate = false; - updateUI(); - - // enable use of parametric expressions for the numerical fields - ui->pitch->bind(static_cast(pcFeat)->Pitch); - ui->height->bind(static_cast(pcFeat)->Height); - ui->turns->bind(static_cast(pcFeat)->Turns); - ui->coneAngle->bind(static_cast(pcFeat)->Angle); - ui->growth->bind(static_cast(pcFeat)->Growth); - - ui->axis->blockSignals(false); - ui->pitch->blockSignals(false); - ui->height->blockSignals(false); - ui->turns->blockSignals(false); - ui->coneAngle->blockSignals(false); - ui->growth->blockSignals(false); - ui->checkBoxLeftHanded->blockSignals(false); - ui->checkBoxReversed->blockSignals(false); - ui->checkBoxOutside->blockSignals(false); - - setFocus (); - +void TaskHelixParameters::showCoordinateAxes() +{ //show the parts coordinate system axis for selection - PartDesign::Body * body = PartDesign::Body::findBodyOf ( vp->getObject () ); - if(body) { + PartDesign::Body* body = PartDesign::Body::findBodyOf(vp->getObject()); + if (body) { try { - App::Origin *origin = body->getOrigin(); + App::Origin* origin = body->getOrigin(); ViewProviderOrigin* vpOrigin; vpOrigin = static_cast(Gui::Application::Instance->getViewProvider(origin)); vpOrigin->setTemporaryVisibility(true, false); - } catch (const Base::Exception &ex) { + } + catch (const Base::Exception& ex) { ex.ReportException(); } - } + } } void TaskHelixParameters::fillAxisCombo(bool forceRefill) @@ -193,101 +197,124 @@ void TaskHelixParameters::fillAxisCombo(bool forceRefill) if (axesInList.empty()) forceRefill = true;//not filled yet, full refill - if (forceRefill){ + if (forceRefill) { ui->axis->clear(); - this->axesInList.clear(); //add sketch axes - PartDesign::ProfileBased* pcFeat = static_cast(vp->getObject()); - Part::Part2DObject* pcSketch = dynamic_cast(pcFeat->Profile.getValue()); - if (pcSketch){ - addAxisToCombo(pcSketch,"V_Axis",QObject::tr("Vertical sketch axis")); - addAxisToCombo(pcSketch,"H_Axis",QObject::tr("Horizontal sketch axis")); - for (int i=0; i < pcSketch->getAxisCount(); i++) { - QString itemText = QObject::tr("Construction line %1").arg(i+1); - std::stringstream sub; - sub << "Axis" << i; - addAxisToCombo(pcSketch,sub.str(),itemText); - } - } + addSketchAxes(); //add part axes - PartDesign::Body * body = PartDesign::Body::findBodyOf ( pcFeat ); - if (body) { - try { - App::Origin* orig = body->getOrigin(); - addAxisToCombo(orig->getX(),"",tr("Base X axis")); - addAxisToCombo(orig->getY(),"",tr("Base Y axis")); - addAxisToCombo(orig->getZ(),"",tr("Base Z axis")); - } catch (const Base::Exception &ex) { - ex.ReportException(); - } - } + addPartAxes(); //add "Select reference" - addAxisToCombo(0,std::string(),tr("Select reference...")); - }//endif forceRefill - - //add current link, if not in list - //first, figure out the item number for current axis - int indexOfCurrent = -1; - App::DocumentObject* ax = propReferenceAxis->getValue(); - const std::vector &subList = propReferenceAxis->getSubValues(); - for (size_t i = 0; i < axesInList.size(); i++) { - if (ax == axesInList[i]->getValue() && subList == axesInList[i]->getSubValues()) { - indexOfCurrent = i; - break; - } - } - if (indexOfCurrent == -1 && ax) { - assert(subList.size() <= 1); - std::string sub; - if (!subList.empty()) - sub = subList[0]; - addAxisToCombo(ax, sub, getRefStr(ax, subList)); - indexOfCurrent = axesInList.size()-1; + addAxisToCombo(0, std::string(), tr("Select reference...")); } - //highlight current. + //add current link, if not in list and highlight it + int indexOfCurrent = addCurrentLink(); if (indexOfCurrent != -1) ui->axis->setCurrentIndex(indexOfCurrent); blockUpdate = oldVal_blockUpdate; } +void TaskHelixParameters::addSketchAxes() +{ + PartDesign::ProfileBased* pcFeat = static_cast(vp->getObject()); + Part::Part2DObject* pcSketch = dynamic_cast(pcFeat->Profile.getValue()); + if (pcSketch) { + addAxisToCombo(pcSketch, "N_Axis", tr("Normal sketch axis")); + addAxisToCombo(pcSketch, "V_Axis", tr("Vertical sketch axis")); + addAxisToCombo(pcSketch, "H_Axis", tr("Horizontal sketch axis")); + for (int i = 0; i < pcSketch->getAxisCount(); i++) { + QString itemText = tr("Construction line %1").arg(i + 1); + std::stringstream sub; + sub << "Axis" << i; + addAxisToCombo(pcSketch, sub.str(), itemText); + } + } +} + +void TaskHelixParameters::addPartAxes() +{ + PartDesign::ProfileBased* pcFeat = static_cast(vp->getObject()); + PartDesign::Body* body = PartDesign::Body::findBodyOf(pcFeat); + if (body) { + try { + App::Origin* orig = body->getOrigin(); + addAxisToCombo(orig->getX(), "", tr("Base X axis")); + addAxisToCombo(orig->getY(), "", tr("Base Y axis")); + addAxisToCombo(orig->getZ(), "", tr("Base Z axis")); + } + catch (const Base::Exception& ex) { + ex.ReportException(); + } + } +} + +int TaskHelixParameters::addCurrentLink() +{ + int indexOfCurrent = -1; + App::DocumentObject* ax = propReferenceAxis->getValue(); + const std::vector& subList = propReferenceAxis->getSubValues(); + for (size_t i = 0; i < axesInList.size(); i++) { + if (ax == axesInList[i]->getValue() && subList == axesInList[i]->getSubValues()) { + indexOfCurrent = i; + break; + } + } + + if (indexOfCurrent == -1 && ax) { + assert(subList.size() <= 1); + std::string sub; + if (!subList.empty()) + sub = subList[0]; + addAxisToCombo(ax, sub, getRefStr(ax, subList)); + indexOfCurrent = axesInList.size() - 1; + } + + return indexOfCurrent; +} + void TaskHelixParameters::addAxisToCombo(App::DocumentObject* linkObj, - std::string linkSubname, - QString itemText) + std::string linkSubname, + QString itemText) { this->ui->axis->addItem(itemText); this->axesInList.emplace_back(new App::PropertyLinkSub); - App::PropertyLinkSub &lnk = *(axesInList.back()); - lnk.setValue(linkObj,std::vector(1,linkSubname)); + App::PropertyLinkSub& lnk = *(axesInList.back()); + lnk.setValue(linkObj, std::vector(1, linkSubname)); +} + +void TaskHelixParameters::updateStatus() +{ + auto pcHelix = static_cast(vp->getObject()); + auto status = std::string(pcHelix->getStatusString()); + if (status.compare("Valid") == 0 || status.compare("Touched") == 0) { + if (pcHelix->safePitch() > pcHelix->Pitch.getValue()) + status = "Warning: helix might be self intersecting"; + else + status = ""; + } + ui->labelMessage->setText(QString::fromUtf8(status.c_str())); } void TaskHelixParameters::updateUI() { fillAxisCombo(); - auto pcHelix = static_cast(vp->getObject()); - auto status = std::string(pcHelix->getStatusString()); - if (status.compare("Valid")==0 || status.compare("Touched")==0) { - if (pcHelix->safePitch() > propPitch->getValue()) - status = "Warning: helix might be self intersecting"; - else - status = ""; - } - ui->labelMessage->setText(QString::fromUtf8(status.c_str())); + updateStatus(); - bool isPitchVisible = false; + bool isPitchVisible = false; bool isHeightVisible = false; - bool isTurnsVisible = false; + bool isTurnsVisible = false; bool isOutsideVisible = false; bool isAngleVisible = false; bool isGrowthVisible = false; - if(pcHelix->getAddSubType() == PartDesign::FeatureAddSub::Subtractive) + auto pcHelix = static_cast(vp->getObject()); + if (pcHelix->getAddSubType() == PartDesign::FeatureAddSub::Subtractive) isOutsideVisible = true; HelixMode mode = static_cast(propMode->getValue()); @@ -295,21 +322,24 @@ void TaskHelixParameters::updateUI() isPitchVisible = true; isHeightVisible = true; isAngleVisible = true; - } else if (mode == HelixMode::pitch_turns_angle) { + } + else if (mode == HelixMode::pitch_turns_angle) { isPitchVisible = true; isTurnsVisible = true; isAngleVisible = true; - } else if (mode == HelixMode::height_turns_angle) { + } + else if (mode == HelixMode::height_turns_angle) { isHeightVisible = true; isTurnsVisible = true; isAngleVisible = true; - } else if (mode == HelixMode::height_turns_growth) { + } + else if (mode == HelixMode::height_turns_growth) { isHeightVisible = true; isTurnsVisible = true; isGrowthVisible = true; - } else { - status = "Error: unsupported mode"; - ui->labelMessage->setText(QString::fromUtf8(status.c_str())); + } + else { + ui->labelMessage->setText(tr("Error: unsupported mode")); } ui->pitch->setVisible(isPitchVisible); @@ -328,7 +358,6 @@ void TaskHelixParameters::updateUI() ui->labelGrowth->setVisible(isGrowthVisible); ui->checkBoxOutside->setVisible(isOutsideVisible); - } void TaskHelixParameters::onSelectionChanged(const Gui::SelectionChanges& msg) @@ -388,31 +417,34 @@ void TaskHelixParameters::onAxisChanged(int num) if (axesInList.empty()) return; - App::DocumentObject *oldRefAxis = propReferenceAxis->getValue(); + App::DocumentObject* oldRefAxis = propReferenceAxis->getValue(); std::vector oldSubRefAxis = propReferenceAxis->getSubValues(); std::string oldRefName; if (!oldSubRefAxis.empty()) oldRefName = oldSubRefAxis.front(); - App::PropertyLinkSub &lnk = *(axesInList[num]); + App::PropertyLinkSub& lnk = *(axesInList[num]); if (lnk.getValue() == 0) { // enter reference selection mode - TaskSketchBasedParameters::onSelectReference(true, true, false, true, true); + TaskSketchBasedParameters::onSelectReference(true, AllowSelection::EDGE | + AllowSelection::PLANAR | + AllowSelection::CIRCLE); return; - } else { - if (!pcHelix->getDocument()->isIn(lnk.getValue())){ + } + else { + if (!pcHelix->getDocument()->isIn(lnk.getValue())) { Base::Console().Error("Object was deleted\n"); return; } propReferenceAxis->Paste(lnk); - // in case user is in selection mode, but changed his mind before selecting anything. + // in case user is in selection mode, but changed their mind before selecting anything. exitSelectionMode(); } try { - App::DocumentObject *newRefAxis = propReferenceAxis->getValue(); - const std::vector &newSubRefAxis = propReferenceAxis->getSubValues(); + App::DocumentObject* newRefAxis = propReferenceAxis->getValue(); + const std::vector& newSubRefAxis = propReferenceAxis->getSubValues(); std::string newRefName; if (!newSubRefAxis.empty()) newRefName = newSubRefAxis.front(); @@ -430,6 +462,7 @@ void TaskHelixParameters::onAxisChanged(int num) } recomputeFeature(); + updateStatus(); } catch (const Base::Exception& e) { e.ReportException(); @@ -438,12 +471,11 @@ void TaskHelixParameters::onAxisChanged(int num) void TaskHelixParameters::onModeChanged(int index) { - propMode->setValue(index); ui->pitch->setValue(propPitch->getValue()); ui->height->setValue(propHeight->getValue()); - ui->turns->setValue((propHeight->getValue())/(propPitch->getValue())); + ui->turns->setValue(propTurns->getValue()); recomputeFeature(); updateUI(); @@ -453,6 +485,7 @@ void TaskHelixParameters::onLeftHandedChanged(bool on) { propLeftHanded->setValue(on); recomputeFeature(); + updateUI(); } void TaskHelixParameters::onReversedChanged(bool on) @@ -474,24 +507,36 @@ TaskHelixParameters::~TaskHelixParameters() { try { //hide the parts coordinate system axis for selection - PartDesign::Body * body = vp ? PartDesign::Body::findBodyOf(vp->getObject()) : 0; + PartDesign::Body* body = vp ? PartDesign::Body::findBodyOf(vp->getObject()) : 0; if (body) { - App::Origin *origin = body->getOrigin(); + App::Origin* origin = body->getOrigin(); ViewProviderOrigin* vpOrigin; vpOrigin = static_cast(Gui::Application::Instance->getViewProvider(origin)); vpOrigin->resetTemporaryVisibility(); } - } catch (const Base::Exception &ex) { + } + catch (const Base::Exception& ex) { ex.ReportException(); } } -void TaskHelixParameters::changeEvent(QEvent *e) +void TaskHelixParameters::changeEvent(QEvent* e) { TaskBox::changeEvent(e); if (e->type() == QEvent::LanguageChange) { + // save current indexes + int axis = ui->axis->currentIndex(); + int mode = ui->inputMode->currentIndex(); ui->retranslateUi(proxy); + + // Axes added by the user cannot be restored + fillAxisCombo(true); + + // restore the indexes + if (axis < ui->axis->count()) + ui->axis->setCurrentIndex(axis); + ui->inputMode->setCurrentIndex(mode); } } @@ -501,12 +546,13 @@ void TaskHelixParameters::getReferenceAxis(App::DocumentObject*& obj, std::vecto throw Base::RuntimeError("Not initialized!"); int num = ui->axis->currentIndex(); - const App::PropertyLinkSub &lnk = *(axesInList[num]); + const App::PropertyLinkSub& lnk = *(axesInList.at(num)); if (lnk.getValue() == 0) { throw Base::RuntimeError("Still in reference selection mode; reference wasn't selected yet"); - } else { + } + else { PartDesign::ProfileBased* pcRevolution = static_cast(vp->getObject()); - if (!pcRevolution->getDocument()->isIn(lnk.getValue())){ + if (!pcRevolution->getDocument()->isIn(lnk.getValue())) { throw Base::RuntimeError("Object was deleted"); } @@ -562,15 +608,15 @@ void TaskHelixParameters::apply() getReferenceAxis(obj, sub); std::string axis = buildLinkSingleSubPythonStr(obj, sub); auto tobj = vp->getObject(); - FCMD_OBJ_CMD(tobj,"ReferenceAxis = " << axis); - FCMD_OBJ_CMD(tobj,"Mode = " << propMode->getValue()); - FCMD_OBJ_CMD(tobj,"Pitch = " << propPitch->getValue()); - FCMD_OBJ_CMD(tobj,"Height = " << propHeight->getValue()); - FCMD_OBJ_CMD(tobj,"Turns = " << propTurns->getValue()); - FCMD_OBJ_CMD(tobj,"Angle = " << propAngle->getValue()); - FCMD_OBJ_CMD(tobj,"Growth = " << propGrowth->getValue()); - FCMD_OBJ_CMD(tobj,"LeftHanded = " << (propLeftHanded->getValue() ? 1 : 0)); - FCMD_OBJ_CMD(tobj,"Reversed = " << (propReversed->getValue() ? 1 : 0)); + FCMD_OBJ_CMD(tobj, "ReferenceAxis = " << axis); + FCMD_OBJ_CMD(tobj, "Mode = " << propMode->getValue()); + FCMD_OBJ_CMD(tobj, "Pitch = " << propPitch->getValue()); + FCMD_OBJ_CMD(tobj, "Height = " << propHeight->getValue()); + FCMD_OBJ_CMD(tobj, "Turns = " << propTurns->getValue()); + FCMD_OBJ_CMD(tobj, "Angle = " << propAngle->getValue()); + FCMD_OBJ_CMD(tobj, "Growth = " << propGrowth->getValue()); + FCMD_OBJ_CMD(tobj, "LeftHanded = " << (propLeftHanded->getValue() ? 1 : 0)); + FCMD_OBJ_CMD(tobj, "Reversed = " << (propReversed->getValue() ? 1 : 0)); } @@ -578,7 +624,7 @@ void TaskHelixParameters::apply() //************************************************************************** // TaskDialog //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -TaskDlgHelixParameters::TaskDlgHelixParameters(ViewProviderHelix *HelixView) +TaskDlgHelixParameters::TaskDlgHelixParameters(ViewProviderHelix* HelixView) : TaskDlgSketchBasedParameters(HelixView) { assert(HelixView); diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.h b/src/Mod/PartDesign/Gui/TaskHelixParameters.h index 8b9b017299..02b9cdddd0 100644 --- a/src/Mod/PartDesign/Gui/TaskHelixParameters.h +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.h @@ -49,13 +49,14 @@ class TaskHelixParameters : public TaskSketchBasedParameters Q_OBJECT public: - TaskHelixParameters(ViewProviderHelix *HelixView,QWidget *parent = 0); + TaskHelixParameters(ViewProviderHelix* HelixView, QWidget* parent = 0); ~TaskHelixParameters(); virtual void apply() override; static bool showPreview(PartDesign::Helix*); +private: /** * @brief fillAxisCombo fills the combo and selects the item according to * current value of revolution object's axis reference. @@ -64,7 +65,10 @@ public: * list (if necessary), and selected. If the list is empty, it will be refilled anyway. */ void fillAxisCombo(bool forceRefill = false); - void addAxisToCombo(App::DocumentObject *linkObj, std::string linkSubname, QString itemText); + void addAxisToCombo(App::DocumentObject* linkObj, std::string linkSubname, QString itemText); + void addSketchAxes(); + void addPartAxes(); + int addCurrentLink(); private Q_SLOTS: void onPitchChanged(double); @@ -81,9 +85,9 @@ private Q_SLOTS: protected: void onSelectionChanged(const Gui::SelectionChanges& msg) override; - void changeEvent(QEvent *e) override; + void changeEvent(QEvent* e) override; bool updateView() const; - void getReferenceAxis(App::DocumentObject *&obj, std::vector &sub) const; + void getReferenceAxis(App::DocumentObject*& obj, std::vector& sub) const; void startReferenceSelection(App::DocumentObject* profile, App::DocumentObject* base) override; void finishReferenceSelection(App::DocumentObject* profile, App::DocumentObject* base) override; @@ -95,13 +99,20 @@ protected: App::PropertyBool* propReversed; App::PropertyLinkSub* propReferenceAxis; App::PropertyAngle* propAngle; - App::PropertyLength* propGrowth; + App::PropertyDistance* propGrowth; App::PropertyEnumeration* propMode; App::PropertyBool* propOutside; private: + void initializeHelix(); + void connectSlots(); void updateUI(); + void updateStatus(); + void assignProperties(); + void setValuesFromProperties(); + void bindProperties(); + void showCoordinateAxes(); private: QWidget* proxy; @@ -124,7 +135,7 @@ class TaskDlgHelixParameters : public TaskDlgSketchBasedParameters Q_OBJECT public: - TaskDlgHelixParameters(ViewProviderHelix *HelixView); + TaskDlgHelixParameters(ViewProviderHelix* HelixView); ViewProviderHelix* getHelixView() const { return static_cast(vp); } diff --git a/src/Mod/PartDesign/Gui/TaskHelixParameters.ui b/src/Mod/PartDesign/Gui/TaskHelixParameters.ui index 6d57f9d6a4..ff22ee13b6 100644 --- a/src/Mod/PartDesign/Gui/TaskHelixParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskHelixParameters.ui @@ -7,15 +7,14 @@ 0 0 278 - 193 + 330 Form - - + @@ -31,10 +30,9 @@ - + - - + @@ -45,6 +43,9 @@ + + Axis the helix winds around + Base X axis @@ -70,6 +71,11 @@ Vertical sketch axis + + + Normal sketch axis + + Select reference... @@ -79,8 +85,7 @@ - - + @@ -91,6 +96,9 @@ + + Parameter set defining the helix + Pitch-Height-Angle @@ -111,11 +119,10 @@ Height-Turns-Growth - + - @@ -127,6 +134,9 @@ + + Axial distance between two turns + false @@ -136,9 +146,6 @@ 0.000000000000000 - - 1.000000000000000 - 10.000000000000000 @@ -146,7 +153,6 @@ - @@ -158,6 +164,9 @@ + + Height of helix + false @@ -167,9 +176,6 @@ 0.000000000000000 - - 1.000000000000000 - 30.000000000000000 @@ -177,7 +183,6 @@ - @@ -189,23 +194,22 @@ + + Number of turns + false 0.000000000000000 - - 1.000000000000000 - - 3.0000000000000 + 3.000000000000000 - @@ -217,6 +221,9 @@ + + Angle of the cone that forms a hull around the helix. + false @@ -232,66 +239,65 @@ 5.000000000000000 - - 0.000000000000000 - - - Growth: + Radial growth: + + Radial growth of helix per turn + false mm - - 5.000000000000000 - - - 0.000000000000000 - - true + + Sets turning direction to left handed + Left handed - true + + Reverses the Axis direction + Reversed - + + the result will be the intersection of helix and the preexisting body + Remove outside of profile @@ -300,7 +306,6 @@ - @@ -308,7 +313,6 @@ - @@ -319,9 +323,6 @@ - - - diff --git a/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.cpp b/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.cpp index 1813f1ff00..34c05623f0 100644 --- a/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskLinearPatternParameters.cpp @@ -333,7 +333,7 @@ void TaskLinearPatternParameters::onDirectionChanged(int /*num*/) showBase(); selectionMode = reference; Gui::Selection().clearSelection(); - addReferenceSelectionGate(true, true); + addReferenceSelectionGate(AllowSelection::EDGE | AllowSelection::FACE | AllowSelection::PLANAR); } else { exitSelectionMode(); pcLinearPattern->Direction.Paste(dirLinks.getCurrentLink()); diff --git a/src/Mod/PartDesign/Gui/TaskLoftParameters.cpp b/src/Mod/PartDesign/Gui/TaskLoftParameters.cpp index 4b69d9313d..9511e0fb20 100644 --- a/src/Mod/PartDesign/Gui/TaskLoftParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskLoftParameters.cpp @@ -50,6 +50,8 @@ #include "TaskSketchBasedParameters.h" #include "ReferenceSelection.h" +Q_DECLARE_METATYPE(App::PropertyLinkSubList::SubSet); + using namespace PartDesignGui; using namespace Gui; @@ -103,17 +105,19 @@ TaskLoftParameters::TaskLoftParameters(ViewProviderLoft *LoftView, bool /*newObj if (profile) { Gui::Application::Instance->showViewProvider(profile); + // TODO: if it is a single vertex of a sketch, use that subshape's name QString label = make2DLabel(profile, loft->Profile.getSubValues()); ui->profileBaseEdit->setText(label); } - for (auto obj : loft->Sections.getValues()) { - Gui::Application::Instance->showViewProvider(obj); + for (auto &subSet : loft->Sections.getSubListValues()) { + Gui::Application::Instance->showViewProvider(subSet.first); - QString label = make2DLabel(obj, loft->Sections.getSubValues(obj)); + // TODO: if it is a single vertex of a sketch, use that subshape's name + QString label = make2DLabel(subSet.first, subSet.second); QListWidgetItem* item = new QListWidgetItem(); item->setText(label); - item->setData(Qt::UserRole, QByteArray(obj->getNameInDocument())); + item->setData(Qt::UserRole, QVariant::fromValue(subSet)); ui->listWidgetReferences->addItem(item); } @@ -150,6 +154,7 @@ void TaskLoftParameters::onSelectionChanged(const Gui::SelectionChanges& msg) App::Document* document = App::GetApplication().getDocument(msg.pDocName); App::DocumentObject* object = document ? document->getObject(msg.pObjectName) : nullptr; if (object) { + // TODO: if it is a single vertex of a sketch, use that subshape's name QString label = make2DLabel(object, {msg.pSubName}); if (selectionMode == refProfile) { ui->profileBaseEdit->setText(label); @@ -157,7 +162,8 @@ void TaskLoftParameters::onSelectionChanged(const Gui::SelectionChanges& msg) else if (selectionMode == refAdd) { QListWidgetItem* item = new QListWidgetItem(); item->setText(label); - item->setData(Qt::UserRole, QByteArray(msg.pObjectName)); + item->setData(Qt::UserRole, + QVariant::fromValue(std::make_pair(object, std::vector(1, msg.pSubName)))); ui->listWidgetReferences->addItem(item); } else if (selectionMode == refRemove) { @@ -243,7 +249,7 @@ void TaskLoftParameters::onDeleteSection() int row = ui->listWidgetReferences->currentRow(); QListWidgetItem* item = ui->listWidgetReferences->takeItem(row); if (item) { - QByteArray data = item->data(Qt::UserRole).toByteArray(); + QByteArray data(item->data(Qt::UserRole).value().first->getNameInDocument()); delete item; // search inside the list of sections @@ -270,17 +276,16 @@ void TaskLoftParameters::indexesMoved() return; PartDesign::Loft* loft = static_cast(vp->getObject()); - std::vector originals = loft->Sections.getValues(); + auto originals = loft->Sections.getSubListValues(); QByteArray name; int rows = model->rowCount(); for (int i = 0; i < rows; i++) { QModelIndex index = model->index(i, 0); - name = index.data(Qt::UserRole).toByteArray().constData(); - originals[i] = loft->getDocument()->getObject(name.constData()); + originals[i] = index.data(Qt::UserRole).value(); } - loft->Sections.setValues(originals); + loft->Sections.setSubListValues(originals); recomputeFeature(); updateUI(); } diff --git a/src/Mod/PartDesign/Gui/TaskMirroredParameters.cpp b/src/Mod/PartDesign/Gui/TaskMirroredParameters.cpp index 4f1bfce4ef..b2f1e92e12 100644 --- a/src/Mod/PartDesign/Gui/TaskMirroredParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskMirroredParameters.cpp @@ -246,7 +246,7 @@ void TaskMirroredParameters::onPlaneChanged(int /*num*/) showBase(); selectionMode = reference; Gui::Selection().clearSelection(); - addReferenceSelectionGate(false, true); + addReferenceSelectionGate(AllowSelection::FACE | AllowSelection::PLANAR); } else { exitSelectionMode(); pcMirrored->MirrorPlane.Paste(planeLinks.getCurrentLink()); diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp index b7615e2b1f..d3affd6c42 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.cpp @@ -32,23 +32,10 @@ #include "ui_TaskPadParameters.h" #include "TaskPadParameters.h" -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include -#include -#include #include #include -#include "TaskSketchBasedParameters.h" -#include "ReferenceSelection.h" using namespace PartDesignGui; using namespace Gui; @@ -56,160 +43,39 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskPadParameters */ TaskPadParameters::TaskPadParameters(ViewProviderPad *PadView, QWidget *parent, bool newObj) - : TaskSketchBasedParameters(PadView, parent, "PartDesign_Pad", tr("Pad parameters")) - , ui(new Ui_TaskPadParameters) + : TaskExtrudeParameters(PadView, parent, "PartDesign_Pad", tr("Pad parameters")) { - // we need a separate container widget to add all controls to - proxy = new QWidget(this); - ui->setupUi(proxy); -#if QT_VERSION >= 0x040700 - ui->lineFaceName->setPlaceholderText(tr("No face selected")); -#endif - - this->groupLayout()->addWidget(proxy); + ui->offsetEdit->setToolTip(tr("Offset from face at which pad will end")); + ui->checkBoxReversed->setToolTip(tr("Reverses pad direction")); // set the history path ui->lengthEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadLength")); ui->lengthEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadLength2")); ui->offsetEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PadOffset")); - // Get the feature data - PartDesign::Pad* pcPad = static_cast(vp->getObject()); - Base::Quantity l = pcPad->Length.getQuantityValue(); - Base::Quantity l2 = pcPad->Length2.getQuantityValue(); - bool alongNormal = pcPad->AlongSketchNormal.getValue(); - bool useCustom = pcPad->UseCustomVector.getValue(); - double xs = pcPad->Direction.getValue().x; - double ys = pcPad->Direction.getValue().y; - double zs = pcPad->Direction.getValue().z; - Base::Quantity off = pcPad->Offset.getQuantityValue(); - bool midplane = pcPad->Midplane.getValue(); - bool reversed = pcPad->Reversed.getValue(); - int index = pcPad->Type.getValue(); // must extract value here, clear() kills it! - App::DocumentObject* obj = pcPad->UpToFace.getValue(); - std::vector subStrings = pcPad->UpToFace.getSubValues(); - std::string upToFace; - int faceId = -1; - if ((obj != NULL) && !subStrings.empty()) { - upToFace = subStrings.front(); - if (upToFace.substr(0,4) == "Face") - faceId = std::atoi(&upToFace[4]); - } - - // set decimals for the direction edits - // do this here before the edits are filed to avoid rounding mistakes - int UserDecimals = Base::UnitsApi::getDecimals(); - ui->XDirectionEdit->setDecimals(UserDecimals); - ui->YDirectionEdit->setDecimals(UserDecimals); - ui->ZDirectionEdit->setDecimals(UserDecimals); - - // Fill data into dialog elements - // the direction combobox is later filled in updateUI() - ui->lengthEdit->setValue(l); - ui->lengthEdit2->setValue(l2); - ui->checkBoxAlongDirection->setChecked(alongNormal); - ui->checkBoxDirection->setChecked(useCustom); - onDirectionToggled(useCustom); - // disable to change the direction if not custom - if (!useCustom) { - ui->XDirectionEdit->setEnabled(false); - ui->YDirectionEdit->setEnabled(false); - ui->ZDirectionEdit->setEnabled(false); - } - ui->XDirectionEdit->setValue(xs); - ui->YDirectionEdit->setValue(ys); - ui->ZDirectionEdit->setValue(zs); - ui->offsetEdit->setValue(off); - - // Bind input fields to properties - ui->lengthEdit->bind(pcPad->Length); - ui->lengthEdit2->bind(pcPad->Length2); - ui->XDirectionEdit->bind(App::ObjectIdentifier::parse(pcPad, std::string("Direction.x"))); - ui->YDirectionEdit->bind(App::ObjectIdentifier::parse(pcPad, std::string("Direction.y"))); - ui->ZDirectionEdit->bind(App::ObjectIdentifier::parse(pcPad, std::string("Direction.z"))); - ui->offsetEdit->bind(pcPad->Offset); - - ui->checkBoxMidplane->setChecked(midplane); - // According to bug #0000521 the reversed option - // shouldn't be de-activated if the pad has a support face - ui->checkBoxReversed->setChecked(reversed); - - // Set object labels - if (obj && PartDesign::Feature::isDatum(obj)) { - ui->lineFaceName->setText(QString::fromUtf8(obj->Label.getValue())); - ui->lineFaceName->setProperty("FeatureName", QByteArray(obj->getNameInDocument())); - } - else if (obj && faceId >= 0) { - ui->lineFaceName->setText(QString::fromLatin1("%1:%2%3") - .arg(QString::fromUtf8(obj->Label.getValue())) - .arg(tr("Face")) - .arg(faceId)); - ui->lineFaceName->setProperty("FeatureName", QByteArray(obj->getNameInDocument())); - } - else { - ui->lineFaceName->clear(); - ui->lineFaceName->setProperty("FeatureName", QVariant()); - } - ui->lineFaceName->setProperty("FaceName", QByteArray(upToFace.c_str())); - - ui->changeMode->clear(); - ui->changeMode->insertItem(0, tr("Dimension")); - ui->changeMode->insertItem(1, tr("To last")); - ui->changeMode->insertItem(2, tr("To first")); - ui->changeMode->insertItem(3, tr("Up to face")); - ui->changeMode->insertItem(4, tr("Two dimensions")); - ui->changeMode->setCurrentIndex(index); - - QMetaObject::connectSlotsByName(this); - - connect(ui->lengthEdit, SIGNAL(valueChanged(double)), - this, SLOT(onLengthChanged(double))); - connect(ui->lengthEdit2, SIGNAL(valueChanged(double)), - this, SLOT(onLength2Changed(double))); - connect(ui->directionCB, SIGNAL(activated(int)), - this, SLOT(onDirectionCBChanged(int))); - connect(ui->checkBoxAlongDirection, SIGNAL(toggled(bool)), - this, SLOT(onAlongSketchNormalChanged(bool))); - connect(ui->checkBoxDirection, SIGNAL(toggled(bool)), - this, SLOT(onDirectionToggled(bool))); - connect(ui->XDirectionEdit, SIGNAL(valueChanged(double)), - this, SLOT(onXDirectionEditChanged(double))); - connect(ui->YDirectionEdit, SIGNAL(valueChanged(double)), - this, SLOT(onYDirectionEditChanged(double))); - connect(ui->ZDirectionEdit, SIGNAL(valueChanged(double)), - this, SLOT(onZDirectionEditChanged(double))); - connect(ui->offsetEdit, SIGNAL(valueChanged(double)), - this, SLOT(onOffsetChanged(double))); - connect(ui->checkBoxMidplane, SIGNAL(toggled(bool)), - this, SLOT(onMidplaneChanged(bool))); - connect(ui->checkBoxReversed, SIGNAL(toggled(bool)), - this, SLOT(onReversedChanged(bool))); - connect(ui->changeMode, SIGNAL(currentIndexChanged(int)), - this, SLOT(onModeChanged(int))); - connect(ui->buttonFace, SIGNAL(clicked()), - this, SLOT(onButtonFace())); - connect(ui->lineFaceName, SIGNAL(textEdited(QString)), - this, SLOT(onFaceName(QString))); - connect(ui->checkBoxUpdateView, SIGNAL(toggled(bool)), - this, SLOT(onUpdateView(bool))); - - this->propReferenceAxis = &(pcPad->ReferenceAxis); - - // Due to signals attached after changes took took into effect we should update the UI now. - updateUI(index); + setupDialog(); // if it is a newly created object use the last value of the history - // TODO: newObj doesn't supplied normally by any caller (2015-07-24, Fat-Zer) if (newObj) { - ui->lengthEdit->setToLastUsedValue(); - ui->lengthEdit->selectNumber(); - ui->lengthEdit2->setToLastUsedValue(); - ui->lengthEdit2->selectNumber(); - ui->offsetEdit->setToLastUsedValue(); - ui->offsetEdit->selectNumber(); + readValuesFromHistory(); } } +TaskPadParameters::~TaskPadParameters() +{ +} + +void TaskPadParameters::translateModeList(int index) +{ + ui->changeMode->clear(); + ui->changeMode->addItem(tr("Dimension")); + ui->changeMode->addItem(tr("To last")); + ui->changeMode->addItem(tr("To first")); + ui->changeMode->addItem(tr("Up to face")); + ui->changeMode->addItem(tr("Two dimensions")); + ui->changeMode->setCurrentIndex(index); +} + void TaskPadParameters::updateUI(int index) { // update direction combobox @@ -226,8 +92,9 @@ void TaskPadParameters::updateUI(int index) bool isReversedVisible = false; bool isFaceEditEnabled = false; - // dimension - if (index == 0) { + Modes mode = static_cast(index); + + if (mode == Modes::Dimension) { isLengthEditVisible = true; ui->lengthEdit->selectNumber(); // Make sure that the spin box has the focus to get key events @@ -240,14 +107,12 @@ void TaskPadParameters::updateUI(int index) isReversedEnabled = !ui->checkBoxMidplane->isChecked(); isReversedVisible = true; } - // up to first/last - else if (index == 1 || index == 2) { + else if (mode == Modes::ToLast || mode == Modes::ToFirst) { isOffsetEditVisible = true; isReversedEnabled = true; isReversedVisible = true; } - // up to face - else if (index == 3) { + else if (mode == Modes::ToFace) { isOffsetEditVisible = true; isFaceEditEnabled = true; QMetaObject::invokeMethod(ui->lineFaceName, "setFocus", Qt::QueuedConnection); @@ -257,8 +122,7 @@ void TaskPadParameters::updateUI(int index) isReversedEnabled = true; isReversedVisible = true; } - // two dimensions - else { + else if (mode == Modes::TwoDimensions) { isLengthEditVisible = true; isLengthEdit2Visible = true; isMidplaneEnabled = !ui->checkBoxReversed->isChecked(); @@ -293,543 +157,35 @@ void TaskPadParameters::updateUI(int index) } } -void TaskPadParameters::onSelectionChanged(const Gui::SelectionChanges& msg) -{ - if (msg.Type == Gui::SelectionChanges::AddSelection) { - // if we have an edge selection for the pad direction - if (!selectionFace) { - std::vector edge; - App::DocumentObject* selObj; - if (getReferencedSelection(vp->getObject(), msg, selObj, edge) && selObj) { - exitSelectionMode(); - propReferenceAxis->setValue(selObj, edge); - recomputeFeature(); - // update direction combobox - fillDirectionCombo(); - } - } - else { // if we have a selection of a face - QString refText = onAddSelection(msg); - if (refText.length() > 0) { - ui->lineFaceName->blockSignals(true); - ui->lineFaceName->setText(refText); - ui->lineFaceName->setProperty("FeatureName", QByteArray(msg.pObjectName)); - ui->lineFaceName->setProperty("FaceName", QByteArray(msg.pSubName)); - ui->lineFaceName->blockSignals(false); - // Turn off reference selection mode - onButtonFace(false); - } - else { - ui->lineFaceName->blockSignals(true); - ui->lineFaceName->clear(); - ui->lineFaceName->setProperty("FeatureName", QVariant()); - ui->lineFaceName->setProperty("FaceName", QVariant()); - ui->lineFaceName->blockSignals(false); - } - } - } else if (msg.Type == Gui::SelectionChanges::ClrSelection && selectionFace) { - ui->lineFaceName->blockSignals(true); - ui->lineFaceName->clear(); - ui->lineFaceName->setProperty("FeatureName", QVariant()); - ui->lineFaceName->setProperty("FaceName", QVariant()); - ui->lineFaceName->blockSignals(false); - } -} - -void TaskPadParameters::onLengthChanged(double len) -{ - PartDesign::Pad* pcPad = static_cast(vp->getObject()); - pcPad->Length.setValue(len); - recomputeFeature(); -} - -void TaskPadParameters::onLength2Changed(double len) -{ - PartDesign::Pad* pcPad = static_cast(vp->getObject()); - pcPad->Length2.setValue(len); - recomputeFeature(); -} - -void TaskPadParameters::fillDirectionCombo() -{ - bool oldVal_blockUpdate = blockUpdate; - blockUpdate = true; - - if (axesInList.empty()) { - bool hasFace = false; - ui->directionCB->clear(); - // we can have sketches or faces - // for sketches just get the sketch normal - PartDesign::ProfileBased* pcFeat = static_cast(vp->getObject()); - Part::Part2DObject* pcSketch = dynamic_cast(pcFeat->Profile.getValue()); - // for faces we test if it is verified and if we can get its normal - if (!pcSketch) { - try { - Part::Feature* pcFeature = pcFeat->getVerifiedObject(); - Base::Vector3d SketchVector = pcFeat->getProfileNormal(); - Q_UNUSED(pcFeature) - Q_UNUSED(SketchVector) - hasFace = true; - } - catch (const Base::Exception& e) { - new App::DocumentObjectExecReturn(e.what()); - } - } - if (pcSketch) - addAxisToCombo(pcSketch, "N_Axis", tr("Sketch normal")); - else if (hasFace) - addAxisToCombo(pcFeat->Profile.getValue(), std::string(), tr("Face normal"), false); - // add the other entries - addAxisToCombo(0, std::string(), tr("Select reference...")); - // we start with the sketch normal as proposal for the custom direction - if (pcSketch) - addAxisToCombo(pcSketch, "N_Axis", tr("Custom direction")); - else if (hasFace) - addAxisToCombo(pcFeat->Profile.getValue(), std::string(), tr("Custom direction"), false); - } - - // add current link, if not in list - // first, figure out the item number for current axis - int indexOfCurrent = -1; - App::DocumentObject* ax = propReferenceAxis->getValue(); - const std::vector& subList = propReferenceAxis->getSubValues(); - for (size_t i = 0; i < axesInList.size(); i++) { - if (ax == axesInList[i]->getValue() && subList == axesInList[i]->getSubValues()) { - indexOfCurrent = i; - break; - } - } - // if the axis is not yet listed in the combobox - if (indexOfCurrent == -1 && ax) { - assert(subList.size() <= 1); - std::string sub; - if (!subList.empty()) - sub = subList[0]; - addAxisToCombo(ax, sub, getRefStr(ax, subList)); - indexOfCurrent = axesInList.size() - 1; - // the axis is not the normal, thus enable along direction - ui->checkBoxAlongDirection->setEnabled(true); - // we don't have custom direction thus disable its settings - ui->XDirectionEdit->setEnabled(false); - ui->YDirectionEdit->setEnabled(false); - ui->ZDirectionEdit->setEnabled(false); - } - - // highlight either current index or set custom direction - PartDesign::Pad* pcPad = static_cast(vp->getObject()); - bool hasCustom = pcPad->UseCustomVector.getValue(); - if (indexOfCurrent != -1 && !hasCustom) - ui->directionCB->setCurrentIndex(indexOfCurrent); - if (hasCustom) - ui->directionCB->setCurrentIndex(2); - - blockUpdate = oldVal_blockUpdate; -} - -void TaskPadParameters::addAxisToCombo(App::DocumentObject* linkObj, - std::string linkSubname, QString itemText, bool hasSketch) -{ - this->ui->directionCB->addItem(itemText); - this->axesInList.emplace_back(new App::PropertyLinkSub); - App::PropertyLinkSub& lnk = *(axesInList.back()); - // if we have a face, we leave the link empty since we cannot - // store the face normal as sublink - if (hasSketch) - lnk.setValue(linkObj, std::vector(1, linkSubname)); -} - -void TaskPadParameters::onDirectionCBChanged(int num) -{ - PartDesign::Pad* pcPad = static_cast(vp->getObject()); - - if (axesInList.empty()) - return; - - // we use this scheme for 'num' - // 0: normal to sketch or face - // 1: selection mode - // 2: custom - // 3-x: edges selected in the 3D model - - // check the axis - // when the link is empty we are either in selection mode - // or we are normal to a face - App::PropertyLinkSub& lnk = *(axesInList[num]); - if (num == 1) { - // enter reference selection mode - this->blockConnection(false); - // to distinguish that this is the direction selection - selectionFace = false; - TaskSketchBasedParameters::onSelectReference(true, true, false, true, true); - return; - } - else if (lnk.getValue() != 0) { - if (!pcPad->getDocument()->isIn(lnk.getValue())) { - Base::Console().Error("Object was deleted\n"); - return; - } - propReferenceAxis->Paste(lnk); - } - - // in case the user is in selection mode, but changed his mind before selecting anything - exitSelectionMode(); - - try { - recomputeFeature(); - } - catch (const Base::Exception& e) { - e.ReportException(); - } - - // disable AlongSketchNormal when the direction is already normal - if (num == 0) - ui->checkBoxAlongDirection->setEnabled(false); - else - ui->checkBoxAlongDirection->setEnabled(true); - // if custom direction is used, show it - if (num == 2) { - ui->checkBoxDirection->setChecked(true); - pcPad->UseCustomVector.setValue(true); - } - else { - ui->checkBoxDirection->setChecked(false); - pcPad->UseCustomVector.setValue(false); - } - // if we dont use custom direction, only allow to show its direction - if (num != 2) { - ui->XDirectionEdit->setEnabled(false); - ui->YDirectionEdit->setEnabled(false); - ui->ZDirectionEdit->setEnabled(false); - } - else { - ui->XDirectionEdit->setEnabled(true); - ui->YDirectionEdit->setEnabled(true); - ui->ZDirectionEdit->setEnabled(true); - } - // recompute and update the direction - recomputeFeature(); - updateDirectionEdits(); -} - -void TaskPadParameters::onAlongSketchNormalChanged(bool on) -{ - PartDesign::Pad* pcPad = static_cast(vp->getObject()); - pcPad->AlongSketchNormal.setValue(on); - recomputeFeature(); -} - -void TaskPadParameters::onDirectionToggled(bool on) -{ - if (on) - ui->groupBoxDirection->show(); - else - ui->groupBoxDirection->hide(); -} - -void TaskPadParameters::onXDirectionEditChanged(double len) -{ - PartDesign::Pad* pcPad = static_cast(vp->getObject()); - pcPad->Direction.setValue(len, pcPad->Direction.getValue().y, pcPad->Direction.getValue().z); - recomputeFeature(); - // checking for case of a null vector is done in FeaturePad.cpp - // if there was a null vector, the normal vector of the sketch is used. - // therefore the vector component edits must be updated - updateDirectionEdits(); -} - -void TaskPadParameters::onYDirectionEditChanged(double len) -{ - PartDesign::Pad* pcPad = static_cast(vp->getObject()); - pcPad->Direction.setValue(pcPad->Direction.getValue().x, len, pcPad->Direction.getValue().z); - recomputeFeature(); - updateDirectionEdits(); -} - -void TaskPadParameters::onZDirectionEditChanged(double len) -{ - PartDesign::Pad* pcPad = static_cast(vp->getObject()); - pcPad->Direction.setValue(pcPad->Direction.getValue().x, pcPad->Direction.getValue().y, len); - recomputeFeature(); - updateDirectionEdits(); -} - -void TaskPadParameters::updateDirectionEdits(void) -{ - PartDesign::Pad* pcPad = static_cast(vp->getObject()); - // we don't want to execute the onChanged edits, but just update their contents - ui->XDirectionEdit->blockSignals(true); - ui->YDirectionEdit->blockSignals(true); - ui->ZDirectionEdit->blockSignals(true); - ui->XDirectionEdit->setValue(pcPad->Direction.getValue().x); - ui->YDirectionEdit->setValue(pcPad->Direction.getValue().y); - ui->ZDirectionEdit->setValue(pcPad->Direction.getValue().z); - ui->XDirectionEdit->blockSignals(false); - ui->YDirectionEdit->blockSignals(false); - ui->ZDirectionEdit->blockSignals(false); -} - -void TaskPadParameters::onOffsetChanged(double len) -{ - PartDesign::Pad* pcPad = static_cast(vp->getObject()); - pcPad->Offset.setValue(len); - recomputeFeature(); -} - -void TaskPadParameters::onMidplaneChanged(bool on) -{ - PartDesign::Pad* pcPad = static_cast(vp->getObject()); - pcPad->Midplane.setValue(on); - // reversed is not sensible when midplane - ui->checkBoxReversed->setEnabled(!on); - recomputeFeature(); -} - -void TaskPadParameters::onReversedChanged(bool on) -{ - PartDesign::Pad* pcPad = static_cast(vp->getObject()); - pcPad->Reversed.setValue(on); - // midplane is not sensible when reversed - ui->checkBoxMidplane->setEnabled(!on); - recomputeFeature(); - // update the direction - updateDirectionEdits(); -} - void TaskPadParameters::onModeChanged(int index) { PartDesign::Pad* pcPad = static_cast(vp->getObject()); - switch (index) { - case 0: - pcPad->Type.setValue("Length"); - // Avoid error message - if (ui->lengthEdit->value() < Base::Quantity(Precision::Confusion(), Base::Unit::Length)) - ui->lengthEdit->setValue(5.0); - break; - case 1: pcPad->Type.setValue("UpToLast"); break; - case 2: pcPad->Type.setValue("UpToFirst"); break; - case 3: pcPad->Type.setValue("UpToFace"); break; - default: pcPad->Type.setValue("TwoLengths"); + switch (static_cast(index)) { + case Modes::Dimension: + pcPad->Type.setValue("Length"); + // Avoid error message + if (ui->lengthEdit->value() < Base::Quantity(Precision::Confusion(), Base::Unit::Length)) + ui->lengthEdit->setValue(5.0); + break; + case Modes::ToLast: + pcPad->Type.setValue("UpToLast"); + break; + case Modes::ToFirst: + pcPad->Type.setValue("UpToFirst"); + break; + case Modes::ToFace: + pcPad->Type.setValue("UpToFace"); + break; + case Modes::TwoDimensions: + pcPad->Type.setValue("TwoLengths"); + break; } updateUI(index); recomputeFeature(); } -void TaskPadParameters::onButtonFace(const bool pressed) -{ - this->blockConnection(!pressed); - - // to distinguish that this is the direction selection - selectionFace = true; - - // only faces are allowed - TaskSketchBasedParameters::onSelectReference(pressed, false, true, false); - - // Update button if onButtonFace() is called explicitly - ui->buttonFace->setChecked(pressed); -} - -void TaskPadParameters::onFaceName(const QString& text) -{ - if (text.isEmpty()) { - // if user cleared the text field then also clear the properties - ui->lineFaceName->setProperty("FeatureName", QVariant()); - ui->lineFaceName->setProperty("FaceName", QVariant()); - } - else { - // expect that the label of an object is used - QStringList parts = text.split(QChar::fromLatin1(':')); - QString label = parts[0]; - QVariant name = objectNameByLabel(label, ui->lineFaceName->property("FeatureName")); - if (name.isValid()) { - parts[0] = name.toString(); - QString uptoface = parts.join(QString::fromLatin1(":")); - ui->lineFaceName->setProperty("FeatureName", name); - ui->lineFaceName->setProperty("FaceName", setUpToFace(uptoface)); - } - else { - ui->lineFaceName->setProperty("FeatureName", QVariant()); - ui->lineFaceName->setProperty("FaceName", QVariant()); - } - } -} - -double TaskPadParameters::getLength(void) const -{ - return ui->lengthEdit->value().getValue(); -} - -double TaskPadParameters::getLength2(void) const -{ - return ui->lengthEdit2->value().getValue(); -} - -bool TaskPadParameters::getAlongSketchNormal(void) const -{ - return ui->checkBoxAlongDirection->isChecked(); -} - -bool TaskPadParameters::getCustom(void) const -{ - // index 2 is hardcoded to custom vector - return ui->directionCB->currentIndex() == 2 ? true : false; -} - -std::string TaskPadParameters::getReferenceAxis(void) const -{ - std::vector sub; - App::DocumentObject* obj; - getReferenceAxis(obj, sub); - return buildLinkSingleSubPythonStr(obj, sub); -} - -double TaskPadParameters::getXDirection(void) const -{ - return ui->XDirectionEdit->value(); -} - -double TaskPadParameters::getYDirection(void) const -{ - return ui->YDirectionEdit->value(); -} - -double TaskPadParameters::getZDirection(void) const -{ - return ui->ZDirectionEdit->value(); -} - -double TaskPadParameters::getOffset(void) const -{ - return ui->offsetEdit->value().getValue(); -} - -bool TaskPadParameters::getReversed(void) const -{ - return ui->checkBoxReversed->isChecked(); -} - -bool TaskPadParameters::getMidplane(void) const -{ - return ui->checkBoxMidplane->isChecked(); -} - -int TaskPadParameters::getMode(void) const -{ - return ui->changeMode->currentIndex(); -} - -QString TaskPadParameters::getFaceName(void) const -{ - // 'Up to face' mode - if (getMode() == 3) { - QVariant featureName = ui->lineFaceName->property("FeatureName"); - if (featureName.isValid()) { - QString faceName = ui->lineFaceName->property("FaceName").toString(); - return getFaceReference(featureName.toString(), faceName); - } - } - return QString::fromLatin1("None"); -} - -TaskPadParameters::~TaskPadParameters() -{ -} - -void TaskPadParameters::changeEvent(QEvent *e) -{ - TaskBox::changeEvent(e); - if (e->type() == QEvent::LanguageChange) { - ui->lengthEdit->blockSignals(true); - ui->lengthEdit2->blockSignals(true); - ui->XDirectionEdit->blockSignals(true); - ui->YDirectionEdit->blockSignals(true); - ui->ZDirectionEdit->blockSignals(true); - ui->directionCB->blockSignals(true); - int index = ui->directionCB->currentIndex(); - ui->directionCB->clear(); - ui->directionCB->addItem(tr("Sketch normal")); - ui->directionCB->addItem(tr("Select reference...")); - ui->directionCB->addItem(tr("Custom direction")); - ui->directionCB->setCurrentIndex(index); - ui->offsetEdit->blockSignals(true); - ui->lineFaceName->blockSignals(true); - ui->changeMode->blockSignals(true); - index = ui->changeMode->currentIndex(); - ui->retranslateUi(proxy); - ui->changeMode->clear(); - ui->changeMode->addItem(tr("Dimension")); - ui->changeMode->addItem(tr("To last")); - ui->changeMode->addItem(tr("To first")); - ui->changeMode->addItem(tr("Up to face")); - ui->changeMode->addItem(tr("Two dimensions")); - ui->changeMode->setCurrentIndex(index); - - ui->lineFaceName->setPlaceholderText(tr("No face selected")); - QVariant featureName = ui->lineFaceName->property("FeatureName"); - if (featureName.isValid()) { - QStringList parts = ui->lineFaceName->text().split(QChar::fromLatin1(':')); - QByteArray upToFace = ui->lineFaceName->property("FaceName").toByteArray(); - int faceId = -1; - bool ok = false; - if (upToFace.indexOf("Face") == 0) { - faceId = upToFace.remove(0,4).toInt(&ok); - } - - if (ok) { - ui->lineFaceName->setText(QString::fromLatin1("%1:%2%3") - .arg(parts[0]) - .arg(tr("Face")) - .arg(faceId)); - } - else { - ui->lineFaceName->setText(parts[0]); - } - } - - ui->lengthEdit->blockSignals(false); - ui->lengthEdit2->blockSignals(false); - ui->XDirectionEdit->blockSignals(false); - ui->YDirectionEdit->blockSignals(false); - ui->ZDirectionEdit->blockSignals(false); - ui->directionCB->blockSignals(false); - ui->offsetEdit->blockSignals(false); - ui->lineFaceName->blockSignals(false); - ui->changeMode->blockSignals(false); - } -} - -void TaskPadParameters::getReferenceAxis(App::DocumentObject*& obj, std::vector& sub) const -{ - if (axesInList.empty()) - throw Base::RuntimeError("Not initialized!"); - - int num = ui->directionCB->currentIndex(); - const App::PropertyLinkSub& lnk = *(axesInList[num]); - if (lnk.getValue() == 0) { - // Note: Is is possible that a face of an object is directly padded without defining a profile shape - obj = nullptr; - sub.clear(); - //throw Base::RuntimeError("Still in reference selection mode; reference wasn't selected yet"); - } - else { - PartDesign::ProfileBased* pcDirection = static_cast(vp->getObject()); - if (!pcDirection->getDocument()->isIn(lnk.getValue())) - throw Base::RuntimeError("Object was deleted"); - - obj = lnk.getValue(); - sub = lnk.getSubValues(); - } -} - -void TaskPadParameters::saveHistory(void) -{ - // save the user values to history - ui->lengthEdit->pushToHistory(); - ui->lengthEdit2->pushToHistory(); - ui->offsetEdit->pushToHistory(); -} - void TaskPadParameters::apply() { auto obj = vp->getObject(); @@ -842,7 +198,10 @@ void TaskPadParameters::apply() FCMD_OBJ_CMD(obj, "ReferenceAxis = " << getReferenceAxis()); FCMD_OBJ_CMD(obj, "AlongSketchNormal = " << (getAlongSketchNormal() ? 1 : 0)); FCMD_OBJ_CMD(obj,"Type = " << getMode()); - QString facename = getFaceName(); + QString facename = QString::fromLatin1("None"); + if (static_cast(getMode()) == Modes::ToFace) { + facename = getFaceName(); + } FCMD_OBJ_CMD(obj,"UpToFace = " << facename.toLatin1().data()); FCMD_OBJ_CMD(obj,"Reversed = " << (getReversed()?1:0)); FCMD_OBJ_CMD(obj,"Midplane = " << (getMidplane()?1:0)); diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.h b/src/Mod/PartDesign/Gui/TaskPadParameters.h index 174fe43fad..b5acca2f3e 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.h @@ -28,11 +28,9 @@ #include #include -#include "TaskSketchBasedParameters.h" +#include "TaskExtrudeParameters.h" #include "ViewProviderPad.h" -class Ui_TaskPadParameters; - namespace App { class Property; } @@ -44,65 +42,28 @@ class ViewProvider; namespace PartDesignGui { -class TaskPadParameters : public TaskSketchBasedParameters +class TaskPadParameters : public TaskExtrudeParameters { Q_OBJECT + enum class Modes { + Dimension, + ToLast, + ToFirst, + ToFace, + TwoDimensions + }; + public: TaskPadParameters(ViewProviderPad *PadView, QWidget *parent = 0, bool newObj=false); ~TaskPadParameters(); - virtual void saveHistory() override; virtual void apply() override; - void fillDirectionCombo(); - void addAxisToCombo(App::DocumentObject* linkObj, std::string linkSubname, QString itemText, - bool hasSketch = true); - -private Q_SLOTS: - void onLengthChanged(double); - void onLength2Changed(double); - void onDirectionCBChanged(int); - void onAlongSketchNormalChanged(bool); - void onDirectionToggled(bool); - void onXDirectionEditChanged(double); - void onYDirectionEditChanged(double); - void onZDirectionEditChanged(double); - void onOffsetChanged(double); - void onMidplaneChanged(bool); - void onReversedChanged(bool); - void onButtonFace(const bool pressed = true); - void onFaceName(const QString& text); - void onModeChanged(int); - -protected: - void changeEvent(QEvent *e) override; - App::PropertyLinkSub* propReferenceAxis; - void getReferenceAxis(App::DocumentObject*& obj, std::vector& sub) const; - private: - double getLength(void) const; - double getLength2(void) const; - bool getAlongSketchNormal(void) const; - bool getCustom(void) const; - std::string getReferenceAxis(void) const; - double getXDirection(void) const; - double getYDirection(void) const; - double getZDirection(void) const; - double getOffset(void) const; - bool getReversed(void) const; - bool getMidplane(void) const; - int getMode(void) const; - QString getFaceName(void) const; - void onSelectionChanged(const Gui::SelectionChanges& msg) override; - void updateUI(int index); - void updateDirectionEdits(void); - -private: - QWidget* proxy; - std::unique_ptr ui; - bool selectionFace; - std::vector> axesInList; + void onModeChanged(int index) override; + void translateModeList(int index) override; + void updateUI(int index) override; }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskPadParameters.ui b/src/Mod/PartDesign/Gui/TaskPadParameters.ui index 026cd8cd77..50967168dc 100644 --- a/src/Mod/PartDesign/Gui/TaskPadParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskPadParameters.ui @@ -58,9 +58,6 @@ - - Offset from face at which pad will end - false @@ -256,9 +253,6 @@ measured along the specified direction - - Reverses pad direction - Reversed diff --git a/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp b/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp index a2d9cd765d..ba80f2bd9e 100644 --- a/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp @@ -60,6 +60,8 @@ #include "Utils.h" #include "TaskFeaturePick.h" +Q_DECLARE_METATYPE(App::PropertyLinkSubList::SubSet); + using namespace PartDesignGui; using namespace Gui; @@ -540,21 +542,26 @@ bool TaskPipeParameters::accept() copies.push_back(pcPipe->AuxillerySpine.getValue()); } - std::vector objs; + std::vector subSets; int index = 0; - for (App::DocumentObject* obj : pcPipe->Sections.getValues()) { - if (!pcActiveBody->hasObject(obj) && !pcActiveBody->getOrigin()->hasObject(obj)) { - objs.push_back(PartDesignGui::TaskFeaturePick::makeCopy(obj, "", dlg.radioIndependent->isChecked())); - copies.push_back(objs.back()); + for (auto &subSet : pcPipe->Sections.getSubListValues()) { + if (!pcActiveBody->hasObject(subSet.first) && + !pcActiveBody->getOrigin()->hasObject(subSet.first)) { + subSets.push_back( + std::make_pair( + PartDesignGui::TaskFeaturePick::makeCopy( + subSet.first, "", dlg.radioIndependent->isChecked()), + subSet.second)); + copies.push_back(subSets.back().first); } else { - objs.push_back(obj); + subSets.push_back(subSet); } index++; } - pcPipe->Sections.setValues(objs); + pcPipe->Sections.setSubListValues(subSets); } } @@ -931,15 +938,18 @@ TaskPipeScaling::TaskPipeScaling(ViewProviderPipe* PipeView, bool /*newObj*/, QW ui->listWidgetReferences->setContextMenuPolicy(Qt::ActionsContextMenu); connect(remove, SIGNAL(triggered()), this, SLOT(onDeleteSection())); + connect(ui->listWidgetReferences->model(), + SIGNAL(rowsMoved(QModelIndex, int, int, QModelIndex, int)), this, SLOT(indexesMoved())); + this->groupLayout()->addWidget(proxy); PartDesign::Pipe* pipe = static_cast(PipeView->getObject()); - for (auto obj : pipe->Sections.getValues()) { - Gui::Application::Instance->showViewProvider(obj); - QString label = make2DLabel(obj, pipe->Sections.getSubValues(obj)); + for (auto &subSet : pipe->Sections.getSubListValues()) { + Gui::Application::Instance->showViewProvider(subSet.first); + QString label = make2DLabel(subSet.first, subSet.second); QListWidgetItem* item = new QListWidgetItem(); item->setText(label); - item->setData(Qt::UserRole, QByteArray(obj->getNameInDocument())); + item->setData(Qt::UserRole, QVariant::fromValue(subSet)); ui->listWidgetReferences->addItem(item); } @@ -957,6 +967,27 @@ TaskPipeScaling::~TaskPipeScaling() } } +void TaskPipeScaling::indexesMoved() +{ + QAbstractItemModel* model = qobject_cast(sender()); + if (!model) + return; + + PartDesign::Pipe* pipe = static_cast(vp->getObject()); + auto originals = pipe->Sections.getSubListValues(); + + QByteArray name; + int rows = model->rowCount(); + for (int i = 0; i < rows; i++) { + QModelIndex index = model->index(i, 0); + originals[i] = index.data(Qt::UserRole).value(); + } + + pipe->Sections.setSubListValues(originals); + recomputeFeature(); + updateUI(ui->stackedWidget->currentIndex()); +} + void TaskPipeScaling::clearButtons(const selectionModes notThis) { // TODO: Clear buttons in the other pipe taskboxes as well @@ -1022,7 +1053,8 @@ void TaskPipeScaling::onSelectionChanged(const SelectionChanges& msg) if (selectionMode == refAdd) { QListWidgetItem* item = new QListWidgetItem(); item->setText(label); - item->setData(Qt::UserRole, QByteArray(msg.pObjectName)); + item->setData(Qt::UserRole, + QVariant::fromValue(std::make_pair(object, std::vector(1, msg.pSubName)))); ui->listWidgetReferences->addItem(item); } else if (selectionMode == refRemove) { @@ -1098,7 +1130,7 @@ void TaskPipeScaling::onDeleteSection() int row = ui->listWidgetReferences->currentRow(); QListWidgetItem* item = ui->listWidgetReferences->takeItem(row); if (item) { - QByteArray data = item->data(Qt::UserRole).toByteArray(); + QByteArray data(item->data(Qt::UserRole).value().first->getNameInDocument()); delete item; // search inside the list of sections diff --git a/src/Mod/PartDesign/Gui/TaskPipeParameters.h b/src/Mod/PartDesign/Gui/TaskPipeParameters.h index 8c534f135e..f080561792 100644 --- a/src/Mod/PartDesign/Gui/TaskPipeParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPipeParameters.h @@ -145,6 +145,7 @@ private Q_SLOTS: void onButtonRefRemove(bool checked); void updateUI(int idx); void onDeleteSection(); + void indexesMoved(); protected: enum selectionModes { none, refAdd, refRemove }; diff --git a/src/Mod/PartDesign/Gui/TaskPipeScaling.ui b/src/Mod/PartDesign/Gui/TaskPipeScaling.ui index d06cbe8bc9..c3d535007b 100644 --- a/src/Mod/PartDesign/Gui/TaskPipeScaling.ui +++ b/src/Mod/PartDesign/Gui/TaskPipeScaling.ui @@ -6,8 +6,8 @@ 0 0 - 353 - 407 + 262 + 270 @@ -97,7 +97,14 @@ - + + + List can be reordered by dragging + + + QAbstractItemView::InternalMove + + diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp b/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp index 016d4c9aa2..c478ff5617 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.cpp @@ -30,23 +30,12 @@ # include #endif -#include "ui_TaskPocketParameters.h" +#include "ui_TaskPadParameters.h" #include "TaskPocketParameters.h" -#include -#include -#include -#include -#include -#include #include -#include -#include #include -#include #include #include -#include "TaskSketchBasedParameters.h" -#include "ReferenceSelection.h" using namespace PartDesignGui; using namespace Gui; @@ -54,175 +43,58 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskPocketParameters */ TaskPocketParameters::TaskPocketParameters(ViewProviderPocket *PocketView,QWidget *parent, bool newObj) - : TaskSketchBasedParameters(PocketView, parent, "PartDesign_Pocket", tr("Pocket parameters")) - , ui(new Ui_TaskPocketParameters) + : TaskExtrudeParameters(PocketView, parent, "PartDesign_Pocket", tr("Pocket parameters")) , oldLength(0) { - // we need a separate container widget to add all controls to - proxy = new QWidget(this); - ui->setupUi(proxy); -#if QT_VERSION >= 0x040700 - ui->lineFaceName->setPlaceholderText(tr("No face selected")); -#endif - - this->groupLayout()->addWidget(proxy); + ui->offsetEdit->setToolTip(tr("Offset from face at which pocket will end")); + ui->checkBoxReversed->setToolTip(tr("Reverses pocket direction")); // set the history path ui->lengthEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketLength")); ui->lengthEdit2->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketLength2")); ui->offsetEdit->setParamGrpPath(QByteArray("User parameter:BaseApp/History/PocketOffset")); - // Get the feature data - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - Base::Quantity l = pcPocket->Length.getQuantityValue(); - Base::Quantity l2 = pcPocket->Length2.getQuantityValue(); - Base::Quantity off = pcPocket->Offset.getQuantityValue(); - bool alongNormal = pcPocket->AlongSketchNormal.getValue(); - bool useCustom = pcPocket->UseCustomVector.getValue(); - double xs = pcPocket->Direction.getValue().x; - double ys = pcPocket->Direction.getValue().y; - double zs = pcPocket->Direction.getValue().z; - bool midplane = pcPocket->Midplane.getValue(); - bool reversed = pcPocket->Reversed.getValue(); - int index = pcPocket->Type.getValue(); // must extract value here, clear() kills it! - App::DocumentObject* obj = pcPocket->UpToFace.getValue(); - std::vector subStrings = pcPocket->UpToFace.getSubValues(); - std::string upToFace; - int faceId = -1; - if ((obj != NULL) && !subStrings.empty()) { - upToFace = subStrings.front(); - if (upToFace.substr(0,4) == "Face") - faceId = std::atoi(&upToFace[4]); - } - - // set decimals for the direction edits - // do this here before the edits are filed to avoid rounding mistakes - int UserDecimals = Base::UnitsApi::getDecimals(); - ui->XDirectionEdit->setDecimals(UserDecimals); - ui->YDirectionEdit->setDecimals(UserDecimals); - ui->ZDirectionEdit->setDecimals(UserDecimals); - - // Fill data into dialog elements - // the direction combobox is later filled in updateUI() - ui->lengthEdit->setValue(l); - ui->lengthEdit2->setValue(l2); - ui->offsetEdit->setValue(off); - ui->checkBoxAlongDirection->setChecked(alongNormal); - ui->checkBoxDirection->setChecked(useCustom); - onDirectionToggled(useCustom); - // disable to change the direction if not custom - if (!useCustom) { - ui->XDirectionEdit->setEnabled(false); - ui->YDirectionEdit->setEnabled(false); - ui->ZDirectionEdit->setEnabled(false); - } - ui->XDirectionEdit->setValue(xs); - ui->YDirectionEdit->setValue(ys); - ui->ZDirectionEdit->setValue(zs); - ui->checkBoxMidplane->setChecked(midplane); - ui->checkBoxReversed->setChecked(reversed); - - // Set object labels - if (obj && PartDesign::Feature::isDatum(obj)) { - ui->lineFaceName->setText(QString::fromUtf8(obj->Label.getValue())); - ui->lineFaceName->setProperty("FeatureName", QByteArray(obj->getNameInDocument())); - } - else if (obj && faceId >= 0) { - ui->lineFaceName->setText(QString::fromLatin1("%1:%2%3") - .arg(QString::fromUtf8(obj->Label.getValue())) - .arg(tr("Face")) - .arg(faceId)); - ui->lineFaceName->setProperty("FeatureName", QByteArray(obj->getNameInDocument())); - } - else { - ui->lineFaceName->clear(); - ui->lineFaceName->setProperty("FeatureName", QVariant()); - } - - ui->lineFaceName->setProperty("FaceName", QByteArray(upToFace.c_str())); - - ui->changeMode->clear(); - ui->changeMode->insertItem(0, tr("Dimension")); - ui->changeMode->insertItem(1, tr("Through all")); - ui->changeMode->insertItem(2, tr("To first")); - ui->changeMode->insertItem(3, tr("Up to face")); - ui->changeMode->insertItem(4, tr("Two dimensions")); - ui->changeMode->setCurrentIndex(index); - - // Bind input fields to properties - ui->lengthEdit->bind(pcPocket->Length); - ui->lengthEdit2->bind(pcPocket->Length2); - ui->offsetEdit->bind(pcPocket->Offset); - ui->XDirectionEdit->bind(App::ObjectIdentifier::parse(pcPocket, std::string("Direction.x"))); - ui->YDirectionEdit->bind(App::ObjectIdentifier::parse(pcPocket, std::string("Direction.y"))); - ui->ZDirectionEdit->bind(App::ObjectIdentifier::parse(pcPocket, std::string("Direction.z"))); - - QMetaObject::connectSlotsByName(this); - - connect(ui->lengthEdit, SIGNAL(valueChanged(double)), - this, SLOT(onLengthChanged(double))); - connect(ui->lengthEdit2, SIGNAL(valueChanged(double)), - this, SLOT(onLength2Changed(double))); - connect(ui->offsetEdit, SIGNAL(valueChanged(double)), - this, SLOT(onOffsetChanged(double))); - connect(ui->directionCB, SIGNAL(activated(int)), - this, SLOT(onDirectionCBChanged(int))); - connect(ui->checkBoxAlongDirection, SIGNAL(toggled(bool)), - this, SLOT(onAlongSketchNormalChanged(bool))); - connect(ui->checkBoxDirection, SIGNAL(toggled(bool)), - this, SLOT(onDirectionToggled(bool))); - connect(ui->XDirectionEdit, SIGNAL(valueChanged(double)), - this, SLOT(onXDirectionEditChanged(double))); - connect(ui->YDirectionEdit, SIGNAL(valueChanged(double)), - this, SLOT(onYDirectionEditChanged(double))); - connect(ui->ZDirectionEdit, SIGNAL(valueChanged(double)), - this, SLOT(onZDirectionEditChanged(double))); - connect(ui->checkBoxMidplane, SIGNAL(toggled(bool)), - this, SLOT(onMidplaneChanged(bool))); - connect(ui->checkBoxReversed, SIGNAL(toggled(bool)), - this, SLOT(onReversedChanged(bool))); - connect(ui->changeMode, SIGNAL(currentIndexChanged(int)), - this, SLOT(onModeChanged(int))); - connect(ui->buttonFace, SIGNAL(clicked()), - this, SLOT(onButtonFace())); - connect(ui->lineFaceName, SIGNAL(textEdited(QString)), - this, SLOT(onFaceName(QString))); - connect(ui->checkBoxUpdateView, SIGNAL(toggled(bool)), - this, SLOT(onUpdateView(bool))); - - this->propReferenceAxis = &(pcPocket->ReferenceAxis); - - // Due to signals attached after changes took took into effect we should update the UI now. - updateUI(index); + setupDialog(); // if it is a newly created object use the last value of the history - // TODO: newObj doesn't supplied normally by any caller (2015-07-24, Fat-Zer) - if (newObj){ - ui->lengthEdit->setToLastUsedValue(); - ui->lengthEdit->selectNumber(); - ui->lengthEdit2->setToLastUsedValue(); - ui->lengthEdit2->selectNumber(); - ui->offsetEdit->setToLastUsedValue(); - ui->offsetEdit->selectNumber(); + if (newObj) { + readValuesFromHistory(); } } +TaskPocketParameters::~TaskPocketParameters() +{ +} + +void TaskPocketParameters::translateModeList(int index) +{ + ui->changeMode->clear(); + ui->changeMode->addItem(tr("Dimension")); + ui->changeMode->addItem(tr("Through all")); + ui->changeMode->addItem(tr("To first")); + ui->changeMode->addItem(tr("Up to face")); + ui->changeMode->addItem(tr("Two dimensions")); + ui->changeMode->setCurrentIndex(index); +} + void TaskPocketParameters::updateUI(int index) { // update direction combobox fillDirectionCombo(); // disable/hide everything unless we are sure we don't need it + // exception: the direction parameters are in any case visible bool isLengthEditVisible = false; - bool isLengthEdit2Visible = false; + bool isLengthEdit2Visible = false; bool isOffsetEditVisible = false; bool isOffsetEditEnabled = true; bool isMidplateEnabled = false; bool isReversedEnabled = false; bool isFaceEditEnabled = false; - // dimension - if (index == 0) { + Modes mode = static_cast(index); + + if (mode == Modes::Dimension) { isLengthEditVisible = true; ui->lengthEdit->selectNumber(); // Make sure that the spin box has the focus to get key events @@ -233,23 +105,20 @@ void TaskPocketParameters::updateUI(int index) // Reverse only makes sense if Midplane is not true isReversedEnabled = !ui->checkBoxMidplane->isChecked(); } - // through all - else if (index == 1) { + else if (mode == Modes::ThroughAll) { isOffsetEditVisible = true; isOffsetEditEnabled = false; // offset may have some meaning for through all but it doesn't work isMidplateEnabled = true; isReversedEnabled = !ui->checkBoxMidplane->isChecked(); } - // up to first - else if (index == 2) { + else if (mode == Modes::ToFirst) { isOffsetEditVisible = true; isReversedEnabled = true; // Will change the direction it seeks for its first face? // It may work not quite as expected but useful if sketch oriented upside-down. // (may happen in bodies) // FIXME: Fix probably lies somewhere in IF block on line 125 of FeaturePocket.cpp } - // up to face - else if (index == 3) { + else if (mode == Modes::ToFace) { isOffsetEditVisible = true; isReversedEnabled = true; isFaceEditEnabled = true; @@ -258,12 +127,11 @@ void TaskPocketParameters::updateUI(int index) if (ui->lineFaceName->property("FeatureName").isNull()) onButtonFace(true); } - // two dimensions - else { + else if (mode == Modes::TwoDimensions) { isLengthEditVisible = true; isLengthEdit2Visible = true; isReversedEnabled = true; - } + } ui->lengthEdit->setVisible( isLengthEditVisible ); ui->lengthEdit->setEnabled( isLengthEditVisible ); @@ -289,311 +157,12 @@ void TaskPocketParameters::updateUI(int index) } } -void TaskPocketParameters::onSelectionChanged(const Gui::SelectionChanges& msg) -{ - if (msg.Type == Gui::SelectionChanges::AddSelection) { - // if we have an edge selection for the pocket direction - if (!selectionFace) { - std::vector edge; - App::DocumentObject* selObj; - if (getReferencedSelection(vp->getObject(), msg, selObj, edge) && selObj) { - exitSelectionMode(); - propReferenceAxis->setValue(selObj, edge); - recomputeFeature(); - // update direction combobox - fillDirectionCombo(); - } - } - else { // if we have a selection of a face - QString refText = onAddSelection(msg); - if (refText.length() > 0) { - ui->lineFaceName->blockSignals(true); - ui->lineFaceName->setText(refText); - ui->lineFaceName->setProperty("FeatureName", QByteArray(msg.pObjectName)); - ui->lineFaceName->setProperty("FaceName", QByteArray(msg.pSubName)); - ui->lineFaceName->blockSignals(false); - // Turn off reference selection mode - onButtonFace(false); - } - else { - ui->lineFaceName->blockSignals(true); - ui->lineFaceName->clear(); - ui->lineFaceName->setProperty("FeatureName", QVariant()); - ui->lineFaceName->setProperty("FaceName", QVariant()); - ui->lineFaceName->blockSignals(false); - } - } - } else if (msg.Type == Gui::SelectionChanges::ClrSelection) { - ui->lineFaceName->blockSignals(true); - ui->lineFaceName->clear(); - ui->lineFaceName->setProperty("FeatureName", QVariant()); - ui->lineFaceName->setProperty("FaceName", QVariant()); - ui->lineFaceName->blockSignals(false); - } -} - -void TaskPocketParameters::onLengthChanged(double len) -{ - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - pcPocket->Length.setValue(len); - recomputeFeature(); -} - -void TaskPocketParameters::onLength2Changed(double len) -{ - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - pcPocket->Length2.setValue(len); - recomputeFeature(); -} - -void TaskPocketParameters::onOffsetChanged(double len) -{ - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - pcPocket->Offset.setValue(len); - recomputeFeature(); -} - -void TaskPocketParameters::fillDirectionCombo() -{ - bool oldVal_blockUpdate = blockUpdate; - blockUpdate = true; - - if (axesInList.empty()) { - bool hasFace = false; - ui->directionCB->clear(); - // we can have sketches or faces - // for sketches just get the sketch normal - PartDesign::ProfileBased* pcFeat = static_cast(vp->getObject()); - Part::Part2DObject* pcSketch = dynamic_cast(pcFeat->Profile.getValue()); - // for faces we test if it is verified and if we can get its normal - if (!pcSketch) { - try { - Part::Feature* pcFeature = pcFeat->getVerifiedObject(); - Base::Vector3d SketchVector = pcFeat->getProfileNormal(); - Q_UNUSED(pcFeature) - Q_UNUSED(SketchVector) - hasFace = true; - } - catch (const Base::Exception& e) { - new App::DocumentObjectExecReturn(e.what()); - } - } - if (pcSketch) - addAxisToCombo(pcSketch, "N_Axis", tr("Sketch normal")); - else if (hasFace) - addAxisToCombo(pcFeat->Profile.getValue(), std::string(), tr("Face normal"), false); - // add the other entries - addAxisToCombo(0, std::string(), tr("Select reference...")); - // we start with the sketch normal as proposal for the custom direction - if (pcSketch) - addAxisToCombo(pcSketch, "N_Axis", tr("Custom direction")); - else if (hasFace) - addAxisToCombo(pcFeat->Profile.getValue(), std::string(), tr("Custom direction"), false); - } - - // add current link, if not in list - // first, figure out the item number for current axis - int indexOfCurrent = -1; - App::DocumentObject* ax = propReferenceAxis->getValue(); - const std::vector& subList = propReferenceAxis->getSubValues(); - for (size_t i = 0; i < axesInList.size(); i++) { - if (ax == axesInList[i]->getValue() && subList == axesInList[i]->getSubValues()) { - indexOfCurrent = i; - break; - } - } - // if the axis is not yet listed in the combobox - if (indexOfCurrent == -1 && ax) { - assert(subList.size() <= 1); - std::string sub; - if (!subList.empty()) - sub = subList[0]; - addAxisToCombo(ax, sub, getRefStr(ax, subList)); - indexOfCurrent = axesInList.size() - 1; - // the axis is not the normal, thus enable along direction - ui->checkBoxAlongDirection->setEnabled(true); - // we don't have custom direction thus disable its settings - ui->XDirectionEdit->setEnabled(false); - ui->YDirectionEdit->setEnabled(false); - ui->ZDirectionEdit->setEnabled(false); - } - - // highlight either current index or set custom direction - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - bool hasCustom = pcPocket->UseCustomVector.getValue(); - if (indexOfCurrent != -1 && !hasCustom) - ui->directionCB->setCurrentIndex(indexOfCurrent); - if (hasCustom) - ui->directionCB->setCurrentIndex(2); - - blockUpdate = oldVal_blockUpdate; -} - -void TaskPocketParameters::addAxisToCombo(App::DocumentObject* linkObj, - std::string linkSubname, QString itemText, bool hasSketch) -{ - this->ui->directionCB->addItem(itemText); - this->axesInList.emplace_back(new App::PropertyLinkSub); - App::PropertyLinkSub& lnk = *(axesInList.back()); - lnk.setValue(linkObj, std::vector(1, linkSubname)); - // if we have a face, we leave the link empty since we cannot - // store the face normal as sublink - if (hasSketch) - lnk.setValue(linkObj, std::vector(1, linkSubname)); -} - -void TaskPocketParameters::onDirectionCBChanged(int num) -{ - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - - if (axesInList.empty() || !pcPocket) - return; - - // we use this scheme for 'num' - // 0: normal to sketch or face - // 1: selection mode - // 2: custom - // 3-x: edges selected in the 3D model - - // check the axis - // when the link is empty we are either in selection mode - // or we are normal to a face - App::PropertyLinkSub& lnk = *(axesInList[num]); - if (num == 1) { - // enter reference selection mode - this->blockConnection(false); - // to distinguish that this is the direction selection - selectionFace = false; - TaskSketchBasedParameters::onSelectReference(true, true, false, true, true); - return; - } - else if (lnk.getValue() != 0) { - if (!pcPocket->getDocument()->isIn(lnk.getValue())) { - Base::Console().Error("Object was deleted\n"); - return; - } - propReferenceAxis->Paste(lnk); - } - - // in case the user is in selection mode, but changed his mind before selecting anything - exitSelectionMode(); - - try { - recomputeFeature(); - } - catch (const Base::Exception& e) { - e.ReportException(); - } - - // disable AlongSketchNormal when the direction is already normal - if (num == 0) - ui->checkBoxAlongDirection->setEnabled(false); - else - ui->checkBoxAlongDirection->setEnabled(true); - // if custom direction is used, show it - if (num == 2) { - ui->checkBoxDirection->setChecked(true); - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - pcPocket->UseCustomVector.setValue(true); - } - else { - ui->checkBoxDirection->setChecked(false); - pcPocket->UseCustomVector.setValue(false); - } - // if we dont use custom direction, only allow to show its direction - if (num != 2) { - ui->XDirectionEdit->setEnabled(false); - ui->YDirectionEdit->setEnabled(false); - ui->ZDirectionEdit->setEnabled(false); - } - else { - ui->XDirectionEdit->setEnabled(true); - ui->YDirectionEdit->setEnabled(true); - ui->ZDirectionEdit->setEnabled(true); - } - // recompute and update the direction - recomputeFeature(); - updateDirectionEdits(); -} - -void TaskPocketParameters::onAlongSketchNormalChanged(bool on) -{ - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - pcPocket->AlongSketchNormal.setValue(on); - recomputeFeature(); -} - -void TaskPocketParameters::onDirectionToggled(bool on) -{ - if (on) - ui->groupBoxDirection->show(); - else - ui->groupBoxDirection->hide(); -} - -void TaskPocketParameters::onXDirectionEditChanged(double len) -{ - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - pcPocket->Direction.setValue(len, pcPocket->Direction.getValue().y, pcPocket->Direction.getValue().z); - recomputeFeature(); - // checking for case of a null vector is done in FeaturePocket.cpp - // if there was a null vector, the normal vector of the sketch is used. - // therefore the vector component edits must be updated - updateDirectionEdits(); -} - -void TaskPocketParameters::onYDirectionEditChanged(double len) -{ - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - pcPocket->Direction.setValue(pcPocket->Direction.getValue().x, len, pcPocket->Direction.getValue().z); - recomputeFeature(); - updateDirectionEdits(); -} - -void TaskPocketParameters::onZDirectionEditChanged(double len) -{ - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - pcPocket->Direction.setValue(pcPocket->Direction.getValue().x, pcPocket->Direction.getValue().y, len); - recomputeFeature(); - updateDirectionEdits(); -} - -void TaskPocketParameters::updateDirectionEdits(void) -{ - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - // we don't want to execute the onChanged edits, but just update their contents - ui->XDirectionEdit->blockSignals(true); - ui->YDirectionEdit->blockSignals(true); - ui->ZDirectionEdit->blockSignals(true); - ui->XDirectionEdit->setValue(pcPocket->Direction.getValue().x); - ui->YDirectionEdit->setValue(pcPocket->Direction.getValue().y); - ui->ZDirectionEdit->setValue(pcPocket->Direction.getValue().z); - ui->XDirectionEdit->blockSignals(false); - ui->YDirectionEdit->blockSignals(false); - ui->ZDirectionEdit->blockSignals(false); -} - -void TaskPocketParameters::onMidplaneChanged(bool on) -{ - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - pcPocket->Midplane.setValue(on); - ui->checkBoxReversed->setEnabled(!on); - recomputeFeature(); -} - -void TaskPocketParameters::onReversedChanged(bool on) -{ - PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - pcPocket->Reversed.setValue(on); - recomputeFeature(); -} - void TaskPocketParameters::onModeChanged(int index) { PartDesign::Pocket* pcPocket = static_cast(vp->getObject()); - switch (index) { - case 0: + switch (static_cast(index)) { + case Modes::Dimension: // Why? See below for "UpToFace" if (oldLength < Precision::Confusion()) oldLength = 5.0; @@ -601,15 +170,15 @@ void TaskPocketParameters::onModeChanged(int index) ui->lengthEdit->setValue(oldLength); pcPocket->Type.setValue("Length"); break; - case 1: + case Modes::ThroughAll: oldLength = pcPocket->Length.getValue(); pcPocket->Type.setValue("ThroughAll"); break; - case 2: + case Modes::ToFirst: oldLength = pcPocket->Length.getValue(); pcPocket->Type.setValue("UpToFirst"); break; - case 3: + case Modes::ToFace: // Because of the code at the beginning of Pocket::execute() which is used to detect // broken legacy parts, we must set the length to zero here! oldLength = pcPocket->Length.getValue(); @@ -617,223 +186,16 @@ void TaskPocketParameters::onModeChanged(int index) pcPocket->Length.setValue(0.0); ui->lengthEdit->setValue(0.0); break; - default: + case Modes::TwoDimensions: oldLength = pcPocket->Length.getValue(); pcPocket->Type.setValue("TwoLengths"); + break; } updateUI(index); recomputeFeature(); } -void TaskPocketParameters::onButtonFace(const bool pressed) -{ - this->blockConnection(!pressed); - - TaskSketchBasedParameters::onSelectReference(pressed, false, true, false); - - // Update button if onButtonFace() is called explicitly - ui->buttonFace->setChecked(pressed); -} - -void TaskPocketParameters::onFaceName(const QString& text) -{ - if (text.isEmpty()) { - // if user cleared the text field then also clear the properties - ui->lineFaceName->setProperty("FeatureName", QVariant()); - ui->lineFaceName->setProperty("FaceName", QVariant()); - } - else { - // expect that the label of an object is used - QStringList parts = text.split(QChar::fromLatin1(':')); - QString label = parts[0]; - QVariant name = objectNameByLabel(label, ui->lineFaceName->property("FeatureName")); - if (name.isValid()) { - parts[0] = name.toString(); - QString uptoface = parts.join(QString::fromLatin1(":")); - ui->lineFaceName->setProperty("FeatureName", name); - ui->lineFaceName->setProperty("FaceName", setUpToFace(uptoface)); - } - else { - ui->lineFaceName->setProperty("FeatureName", QVariant()); - ui->lineFaceName->setProperty("FaceName", QVariant()); - } - } -} - -double TaskPocketParameters::getLength(void) const -{ - return ui->lengthEdit->value().getValue(); -} - -double TaskPocketParameters::getLength2(void) const -{ - return ui->lengthEdit2->value().getValue(); -} - -double TaskPocketParameters::getOffset(void) const -{ - return ui->offsetEdit->value().getValue(); -} - -bool TaskPocketParameters::getAlongSketchNormal(void) const -{ - return ui->checkBoxAlongDirection->isChecked(); -} - -bool TaskPocketParameters::getCustom(void) const -{ - // index 2 is hardcoded to custom vector - return ui->directionCB->currentIndex() == 2 ? true : false; -} - -std::string TaskPocketParameters::getReferenceAxis(void) const -{ - std::vector sub; - App::DocumentObject* obj; - getReferenceAxis(obj, sub); - return buildLinkSingleSubPythonStr(obj, sub); -} - -double TaskPocketParameters::getXDirection(void) const -{ - return ui->XDirectionEdit->value(); -} - -double TaskPocketParameters::getYDirection(void) const -{ - return ui->YDirectionEdit->value(); -} - -double TaskPocketParameters::getZDirection(void) const -{ - return ui->ZDirectionEdit->value(); -} - -bool TaskPocketParameters::getReversed(void) const -{ - return ui->checkBoxReversed->isChecked(); -} - -bool TaskPocketParameters::getMidplane(void) const -{ - return ui->checkBoxMidplane->isChecked(); -} - -int TaskPocketParameters::getMode(void) const -{ - return ui->changeMode->currentIndex(); -} - -QString TaskPocketParameters::getFaceName(void) const -{ - // 'Up to face' mode - if (getMode() == 3) { - QVariant featureName = ui->lineFaceName->property("FeatureName"); - if (featureName.isValid()) { - QString faceName = ui->lineFaceName->property("FaceName").toString(); - return getFaceReference(featureName.toString(), faceName); - } - } - return QString::fromLatin1("None"); -} - -TaskPocketParameters::~TaskPocketParameters() -{ -} - -void TaskPocketParameters::changeEvent(QEvent *e) -{ - TaskBox::changeEvent(e); - if (e->type() == QEvent::LanguageChange) { - ui->lengthEdit->blockSignals(true); - ui->lengthEdit2->blockSignals(true); - ui->offsetEdit->blockSignals(true); - ui->XDirectionEdit->blockSignals(true); - ui->YDirectionEdit->blockSignals(true); - ui->ZDirectionEdit->blockSignals(true); - ui->directionCB->blockSignals(true); - int index = ui->directionCB->currentIndex(); - ui->directionCB->clear(); - ui->directionCB->addItem(tr("Sketch normal")); - ui->directionCB->addItem(tr("Select reference...")); - ui->directionCB->addItem(tr("Custom direction")); - ui->directionCB->setCurrentIndex(index); - ui->lineFaceName->blockSignals(true); - ui->changeMode->blockSignals(true); - index = ui->changeMode->currentIndex(); - ui->retranslateUi(proxy); - ui->changeMode->clear(); - ui->changeMode->addItem(tr("Dimension")); - ui->changeMode->addItem(tr("Through all")); - ui->changeMode->addItem(tr("To first")); - ui->changeMode->addItem(tr("Up to face")); - ui->changeMode->addItem(tr("Two dimensions")); - ui->changeMode->setCurrentIndex(index); - -#if QT_VERSION >= 0x040700 - ui->lineFaceName->setPlaceholderText(tr("No face selected")); -#endif - QVariant featureName = ui->lineFaceName->property("FeatureName"); - if (featureName.isValid()) { - QStringList parts = ui->lineFaceName->text().split(QChar::fromLatin1(':')); - QByteArray upToFace = ui->lineFaceName->property("FaceName").toByteArray(); - int faceId = -1; - bool ok = false; - if (upToFace.indexOf("Face") == 0) { - faceId = upToFace.remove(0,4).toInt(&ok); - } - - if (ok) { - ui->lineFaceName->setText(QString::fromLatin1("%1:%2%3") - .arg(parts[0]) - .arg(tr("Face")) - .arg(faceId)); - } - else { - ui->lineFaceName->setText(parts[0]); - } - } - - ui->lengthEdit->blockSignals(false); - ui->lengthEdit2->blockSignals(false); - ui->offsetEdit->blockSignals(false); - ui->lineFaceName->blockSignals(false); - ui->changeMode->blockSignals(false); - } -} - -void TaskPocketParameters::getReferenceAxis(App::DocumentObject*& obj, std::vector& sub) const -{ - if (axesInList.empty()) - throw Base::RuntimeError("Not initialized!"); - - int num = ui->directionCB->currentIndex(); - const App::PropertyLinkSub& lnk = *(axesInList[num]); - if (lnk.getValue() == 0) { - // Note: Is is possible that a face of an object is directly pocketed without defining a profile shape - obj = nullptr; - sub.clear(); - //throw Base::RuntimeError("Still in reference selection mode; reference wasn't selected yet"); - } - else { - PartDesign::ProfileBased* pcDirection = static_cast(vp->getObject()); - if (!pcDirection->getDocument()->isIn(lnk.getValue())) - throw Base::RuntimeError("Object was deleted"); - - obj = lnk.getValue(); - sub = lnk.getSubValues(); - } -} - -void TaskPocketParameters::saveHistory(void) -{ - // save the user values to history - ui->lengthEdit->pushToHistory(); - ui->lengthEdit2->pushToHistory(); - ui->offsetEdit->pushToHistory(); -} - void TaskPocketParameters::apply() { auto obj = vp->getObject(); @@ -842,7 +204,10 @@ void TaskPocketParameters::apply() ui->lengthEdit2->apply(); FCMD_OBJ_CMD(obj,"Type = " << getMode()); - QString facename = getFaceName(); + QString facename = QString::fromLatin1("None"); + if (static_cast(getMode()) == Modes::ToFace) { + facename = getFaceName(); + } FCMD_OBJ_CMD(obj,"UpToFace = " << facename.toLatin1().data()); FCMD_OBJ_CMD(obj,"Reversed = " << (getReversed()?1:0)); FCMD_OBJ_CMD(obj,"Midplane = " << (getMidplane()?1:0)); diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.h b/src/Mod/PartDesign/Gui/TaskPocketParameters.h index db6e8abdef..30c1414495 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.h +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.h @@ -28,11 +28,9 @@ #include #include -#include "TaskSketchBasedParameters.h" +#include "TaskExtrudeParameters.h" #include "ViewProviderPocket.h" -class Ui_TaskPocketParameters; - namespace App { class Property; } @@ -44,67 +42,31 @@ class ViewProvider; namespace PartDesignGui { -class TaskPocketParameters : public TaskSketchBasedParameters +class TaskPocketParameters : public TaskExtrudeParameters { Q_OBJECT + enum class Modes { + Dimension, + ThroughAll, + ToFirst, + ToFace, + TwoDimensions + }; + public: TaskPocketParameters(ViewProviderPocket *PocketView, QWidget *parent = 0, bool newObj=false); ~TaskPocketParameters(); - virtual void saveHistory() override; virtual void apply() override; - void fillDirectionCombo(); - void addAxisToCombo(App::DocumentObject* linkObj, std::string linkSubname, QString itemText, - bool hasSketch = true); - -private Q_SLOTS: - void onLengthChanged(double); - void onLength2Changed(double); - void onOffsetChanged(double); - void onDirectionCBChanged(int); - void onAlongSketchNormalChanged(bool); - void onDirectionToggled(bool); - void onXDirectionEditChanged(double); - void onYDirectionEditChanged(double); - void onZDirectionEditChanged(double); - void onMidplaneChanged(bool); - void onReversedChanged(bool); - void onButtonFace(const bool pressed = true); - void onFaceName(const QString& text); - void onModeChanged(int); - -protected: - void changeEvent(QEvent *e) override; - App::PropertyLinkSub* propReferenceAxis; - void getReferenceAxis(App::DocumentObject*& obj, std::vector& sub) const; +private: + void onModeChanged(int index) override; + void translateModeList(int index) override; + void updateUI(int index) override; private: - double getLength(void) const; - double getLength2(void) const; - double getOffset(void) const; - bool getAlongSketchNormal(void) const; - bool getCustom(void) const; - std::string getReferenceAxis(void) const; - double getXDirection(void) const; - double getYDirection(void) const; - double getZDirection(void) const; - int getMode(void) const; - bool getMidplane(void) const; - bool getReversed(void) const; - QString getFaceName(void) const; - - void onSelectionChanged(const Gui::SelectionChanges& msg) override; - void updateUI(int index); - void updateDirectionEdits(void); - -private: - QWidget* proxy; - std::unique_ptr ui; double oldLength; - bool selectionFace; - std::vector> axesInList; }; /// simulation dialog for the TaskView diff --git a/src/Mod/PartDesign/Gui/TaskPocketParameters.ui b/src/Mod/PartDesign/Gui/TaskPocketParameters.ui index 4b1171b122..e67e7f6b77 100644 --- a/src/Mod/PartDesign/Gui/TaskPocketParameters.ui +++ b/src/Mod/PartDesign/Gui/TaskPocketParameters.ui @@ -58,9 +58,6 @@ - - Offset from face at which pocket will end - false diff --git a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.cpp b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.cpp index 2fa8f63e01..197dbeb9c8 100644 --- a/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPolarPatternParameters.cpp @@ -324,7 +324,7 @@ void TaskPolarPatternParameters::onAxisChanged(int /*num*/) showBase(); selectionMode = reference; Gui::Selection().clearSelection(); - addReferenceSelectionGate(true, false, false, false, true); + addReferenceSelectionGate(AllowSelection::EDGE | AllowSelection::CIRCLE); } else { exitSelectionMode(); pcPolarPattern->Axis.Paste(axesLinks.getCurrentLink()); diff --git a/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp b/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp index 64f0ac1b58..9a31600a18 100644 --- a/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPrimitiveParameters.cpp @@ -398,7 +398,7 @@ void TaskBoxPrimitives::onCylinderRadiusChanged(double v) { void TaskBoxPrimitives::onCylinderXSkewChanged(double v) { PartDesign::Cylinder* cyl = static_cast(vp->getObject()); // we must assure that if the user incremented from e.g. 85 degree with the - // spin buttons he does not end at 90.0 but 89.9999 which is shown rounded to 90 degree + // spin buttons, they do not end at 90.0 but at 89.9999 which is shown rounded to 90 degree if ((v < 90.0) && (v > -90.0)) { cyl->FirstAngle.setValue(v); } @@ -415,7 +415,7 @@ void TaskBoxPrimitives::onCylinderXSkewChanged(double v) { void TaskBoxPrimitives::onCylinderYSkewChanged(double v) { PartDesign::Cylinder* cyl = static_cast(vp->getObject()); // we must assure that if the user incremented from e.g. 85 degree with the - // spin buttons he does not end at 90.0 but 89.9999 which is shown rounded to 90 degree + // spin buttons, they do not end at 90.0 but at 89.9999 which is shown rounded to 90 degree if ((v < 90.0) && (v > -90.0)) { cyl->SecondAngle.setValue(v); } @@ -572,7 +572,7 @@ void TaskBoxPrimitives::onPrismHeightChanged(double v) { void TaskBoxPrimitives::onPrismXSkewChanged(double v) { PartDesign::Prism* sph = static_cast(vp->getObject()); // we must assure that if the user incremented from e.g. 85 degree with the - // spin buttons he does not end at 90.0 but 89.9999 which is shown rounded to 90 degree + // spin buttons, they do not end at 90.0 but at 89.9999 which is shown rounded to 90 degree if ((v < 90.0) && (v > -90.0)) { sph->FirstAngle.setValue(v); } @@ -589,7 +589,7 @@ void TaskBoxPrimitives::onPrismXSkewChanged(double v) { void TaskBoxPrimitives::onPrismYSkewChanged(double v) { PartDesign::Prism* sph = static_cast(vp->getObject()); // we must assure that if the user incremented from e.g. 85 degree with the - // spin buttons he does not end at 90.0 but 89.9999 which is shown rounded to 90 degree + // spin buttons, they do not end at 90.0 but at 89.9999 which is shown rounded to 90 degree if ((v < 90.0) && (v > -90.0)) { sph->SecondAngle.setValue(v); } diff --git a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp index 892ad00ffa..a8db50ea67 100644 --- a/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskRevolutionParameters.cpp @@ -262,7 +262,9 @@ void TaskRevolutionParameters::onAxisChanged(int num) App::PropertyLinkSub &lnk = *(axesInList[num]); if (lnk.getValue() == 0) { // enter reference selection mode - TaskSketchBasedParameters::onSelectReference(true, true, false, true, true); + TaskSketchBasedParameters::onSelectReference(true, AllowSelection::EDGE | + AllowSelection::PLANAR | + AllowSelection::CIRCLE); } else { if (!pcRevolution->getDocument()->isIn(lnk.getValue())){ Base::Console().Error("Object was deleted\n"); diff --git a/src/Mod/PartDesign/Gui/TaskShapeBinder.cpp b/src/Mod/PartDesign/Gui/TaskShapeBinder.cpp index ff869fdba5..bd6bf4cba0 100644 --- a/src/Mod/PartDesign/Gui/TaskShapeBinder.cpp +++ b/src/Mod/PartDesign/Gui/TaskShapeBinder.cpp @@ -91,8 +91,8 @@ TaskShapeBinder::TaskShapeBinder(ViewProviderShapeBinder *view, bool /*newObj*/, for (auto sub : subs) ui->listWidgetReferences->addItem(QString::fromStdString(sub)); - //make sure th euser sees al important things: the base feature to select edges and the - //spine/auxiliary spine he already selected + //make sure the user sees all important things: the base feature to select edges and the + //spine/auxiliary spine they already selected if(obj) { auto* svp = doc->getViewProvider(obj); if(svp) { @@ -148,8 +148,8 @@ TaskShapeBinder::~TaskShapeBinder() PartDesign::Pipe* pipe = static_cast(vp->getObject()); Gui::Document* doc = Gui::Application::Instance->activeDocument(); - //make sure th euser sees al important things: the base feature to select edges and the - //spine/auxiliary spine he already selected + //make sure the user sees all important things: the base feature to select edges and the + //spine/auxiliary spine they already selected if(pipe->BaseFeature.getValue()) doc->getViewProvider(pipe->BaseFeature.getValue())->hide(); if(pipe->Spine.getValue()) { diff --git a/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.cpp b/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.cpp index d44f7d7f66..e5a7ddf572 100644 --- a/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.cpp @@ -112,7 +112,7 @@ void TaskSketchBasedParameters::finishReferenceSelection(App::DocumentObject* pr } } -void TaskSketchBasedParameters::onSelectReference(const bool pressed, const bool edge, const bool face, const bool planar, const bool circle) { +void TaskSketchBasedParameters::onSelectReference(const bool pressed, AllowSelectionFlags allow) { // Note: Even if there is no solid, App::Plane and Part::Datum can still be selected PartDesign::ProfileBased* pcSketchBased = dynamic_cast(vp->getObject()); @@ -123,9 +123,9 @@ void TaskSketchBasedParameters::onSelectReference(const bool pressed, const bool if (pressed) { startReferenceSelection(pcSketchBased, prevSolid); Gui::Selection().clearSelection(); - Gui::Selection().addSelectionGate - (new ReferenceSelection(prevSolid, edge, face, planar, false, false, circle)); - } else { + Gui::Selection().addSelectionGate(new ReferenceSelection(prevSolid, allow)); + } + else { Gui::Selection().rmvSelectionGate(); finishReferenceSelection(pcSketchBased, prevSolid); } @@ -135,7 +135,7 @@ void TaskSketchBasedParameters::onSelectReference(const bool pressed, const bool void TaskSketchBasedParameters::exitSelectionMode() { - onSelectReference(false, false, false, false); + onSelectReference(false, AllowSelection::NONE); } QVariant TaskSketchBasedParameters::setUpToFace(const QString& text) diff --git a/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.h b/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.h index 7ef9c2cd82..5fd872f256 100644 --- a/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.h +++ b/src/Mod/PartDesign/Gui/TaskSketchBasedParameters.h @@ -29,6 +29,7 @@ #include "ViewProvider.h" #include "TaskFeatureParameters.h" +#include "EnumFlags.h" namespace App { class Property; @@ -53,7 +54,7 @@ protected: const QString onAddSelection(const Gui::SelectionChanges& msg); virtual void startReferenceSelection(App::DocumentObject* profile, App::DocumentObject* base); virtual void finishReferenceSelection(App::DocumentObject* profile, App::DocumentObject* base); - void onSelectReference(const bool pressed, const bool edge, const bool face, const bool planar, const bool circle = false); + void onSelectReference(const bool pressed, AllowSelectionFlags); void exitSelectionMode(); QVariant setUpToFace(const QString& text); /// Try to find the name of a feature with the given label. diff --git a/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp b/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp index db09de8d6a..87deeaa282 100644 --- a/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskTransformedParameters.cpp @@ -410,10 +410,9 @@ void TaskTransformedParameters::exitSelectionMode() } } -void TaskTransformedParameters::addReferenceSelectionGate(bool edge, bool face, bool planar, bool whole, bool circle) +void TaskTransformedParameters::addReferenceSelectionGate(AllowSelectionFlags allow) { - std::unique_ptr gateRefPtr( - new ReferenceSelection(getBaseObject(), edge, face, planar, false, whole, circle)); + std::unique_ptr gateRefPtr(new ReferenceSelection(getBaseObject(), allow)); std::unique_ptr gateDepPtr(new NoDependentsSelection(getTopTransformedObject())); Gui::Selection().addSelectionGate(new CombineSelectionFilterGates(gateRefPtr, gateDepPtr)); } diff --git a/src/Mod/PartDesign/Gui/TaskTransformedParameters.h b/src/Mod/PartDesign/Gui/TaskTransformedParameters.h index e8c98d75e4..dd22971828 100644 --- a/src/Mod/PartDesign/Gui/TaskTransformedParameters.h +++ b/src/Mod/PartDesign/Gui/TaskTransformedParameters.h @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -188,7 +189,7 @@ protected: void hideBase(); void showBase(); - void addReferenceSelectionGate(bool edge, bool face, bool planar=true, bool whole=false, bool circle=false); + void addReferenceSelectionGate(AllowSelectionFlags); bool isViewUpdated() const; int getUpdateViewTimeout() const; diff --git a/src/Mod/PartDesign/Gui/ViewProvider.cpp b/src/Mod/PartDesign/Gui/ViewProvider.cpp index 156424678f..8717eb0660 100644 --- a/src/Mod/PartDesign/Gui/ViewProvider.cpp +++ b/src/Mod/PartDesign/Gui/ViewProvider.cpp @@ -66,10 +66,9 @@ ViewProvider::~ViewProvider() bool ViewProvider::doubleClicked(void) { try { - PartDesign::Body* body = PartDesign::Body::findBodyOf(getObject()); QString text = QObject::tr("Edit %1").arg(QString::fromUtf8(getObject()->Label.getValue())); Gui::Command::openCommand(text.toUtf8()); - PartDesignGui::setEdit(pcObject,body); + FCMD_SET_EDIT(pcObject); } catch (const Base::Exception&) { Gui::Command::abortCommand(); diff --git a/src/Mod/PartDesign/Gui/ViewProvider.h b/src/Mod/PartDesign/Gui/ViewProvider.h index 6ff7ffccb4..9757469234 100644 --- a/src/Mod/PartDesign/Gui/ViewProvider.h +++ b/src/Mod/PartDesign/Gui/ViewProvider.h @@ -29,6 +29,7 @@ #include #include +#include namespace PartDesignGui { diff --git a/src/Mod/PartDesign/Gui/ViewProviderLoft.h b/src/Mod/PartDesign/Gui/ViewProviderLoft.h index a043d87261..01f24e76c5 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderLoft.h +++ b/src/Mod/PartDesign/Gui/ViewProviderLoft.h @@ -44,9 +44,7 @@ public: virtual bool onDelete(const std::vector &); void highlightReferences(const bool on, bool auxiliary); - - virtual bool allowTreeOrderSwap(const App::DocumentObject *, const App::DocumentObject *) const { return false; } - + protected: virtual bool setEdit(int ModNum); virtual void unsetEdit(int ModNum); diff --git a/src/Mod/PartDesign/Gui/ViewProviderMirrored.h b/src/Mod/PartDesign/Gui/ViewProviderMirrored.h index 1de7253cdd..59a1a6d347 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderMirrored.h +++ b/src/Mod/PartDesign/Gui/ViewProviderMirrored.h @@ -32,10 +32,10 @@ class PartDesignGuiExport ViewProviderMirrored : public ViewProviderTransformed { PROPERTY_HEADER(PartDesignGui::ViewProviderMirrored); public: - ViewProviderMirrored() - { featureName = std::string("Mirrored"); - sPixmap = "PartDesign_Mirrored.svg"; - } + ViewProviderMirrored() { + featureName = std::string("Mirrored"); + sPixmap = "PartDesign_Mirrored.svg"; + } protected: /// Returns a newly create dialog for the part to be placed in the task view diff --git a/src/Mod/PartDesign/Gui/ViewProviderMultiTransform.h b/src/Mod/PartDesign/Gui/ViewProviderMultiTransform.h index 43c73449e0..66c7040272 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderMultiTransform.h +++ b/src/Mod/PartDesign/Gui/ViewProviderMultiTransform.h @@ -32,9 +32,10 @@ class PartDesignGuiExport ViewProviderMultiTransform : public ViewProviderTransf { PROPERTY_HEADER(PartDesignGui::ViewProviderMultiTransform); public: - ViewProviderMultiTransform() - { featureName = std::string("MultiTransform"); - sPixmap = "PartDesign_MultiTransform.svg"; } + ViewProviderMultiTransform() { + featureName = std::string("MultiTransform"); + sPixmap = "PartDesign_MultiTransform.svg"; + } std::vector claimChildren(void) const; diff --git a/src/Mod/PartDesign/Gui/ViewProviderPipe.h b/src/Mod/PartDesign/Gui/ViewProviderPipe.h index 3215cdfd29..e3efe3a695 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderPipe.h +++ b/src/Mod/PartDesign/Gui/ViewProviderPipe.h @@ -52,9 +52,7 @@ public: virtual bool onDelete(const std::vector &); void highlightReferences(Reference mode, bool on); - - virtual bool allowTreeOrderSwap(const App::DocumentObject *, const App::DocumentObject *) const { return false; } - + protected: virtual QIcon getIcon(void) const; virtual bool setEdit(int ModNum); diff --git a/src/Mod/PartDesign/Gui/ViewProviderPolarPattern.h b/src/Mod/PartDesign/Gui/ViewProviderPolarPattern.h index 480ccef95b..a4441cc8bf 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderPolarPattern.h +++ b/src/Mod/PartDesign/Gui/ViewProviderPolarPattern.h @@ -32,9 +32,10 @@ class PartDesignGuiExport ViewProviderPolarPattern : public ViewProviderTransfor { PROPERTY_HEADER(PartDesignGui::ViewProviderPolarPattern); public: - ViewProviderPolarPattern() - { featureName = std::string("PolarPattern"); - sPixmap = "PartDesign_PolarPattern.svg"; } + ViewProviderPolarPattern() { + featureName = std::string("PolarPattern"); + sPixmap = "PartDesign_PolarPattern.svg"; + } protected: /// Returns a newly create dialog for the part to be placed in the task view diff --git a/src/Mod/PartDesign/Gui/ViewProviderScaled.h b/src/Mod/PartDesign/Gui/ViewProviderScaled.h index 6c928e6421..fc9ccb85f1 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderScaled.h +++ b/src/Mod/PartDesign/Gui/ViewProviderScaled.h @@ -32,9 +32,10 @@ class PartDesignGuiExport ViewProviderScaled : public ViewProviderTransformed { PROPERTY_HEADER(PartDesignGui::ViewProviderScaled); public: - ViewProviderScaled() - { featureName = std::string("Scaled"); - sPixmap = "PartDesign_Scaled.svg"; } + ViewProviderScaled() { + featureName = std::string("Scaled"); + sPixmap = "PartDesign_Scaled.svg"; + } protected: /// Returns a newly create dialog for the part to be placed in the task view diff --git a/src/Mod/PartDesign/Gui/ViewProviderShapeBinder.cpp b/src/Mod/PartDesign/Gui/ViewProviderShapeBinder.cpp index 47bddf8086..31e7113fad 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderShapeBinder.cpp +++ b/src/Mod/PartDesign/Gui/ViewProviderShapeBinder.cpp @@ -159,13 +159,13 @@ void ViewProviderShapeBinder::highlightReferences(const bool on, bool /*auxiliar for (std::string e : subs) { // Note: stoi may throw, but it strictly shouldn't happen - if(e.substr(4) == "Edge") { + if(e.compare(0, 4, "Edge") == 0) { int idx = std::stoi(e.substr(4)) - 1; assert ( idx>=0 ); if ( idx < (ssize_t) lcolors.size() ) lcolors[idx] = App::Color(1.0,0.0,1.0); // magenta } - else if(e.substr(4) == "Face") { + else if(e.compare(0, 4, "Face") == 0) { int idx = std::stoi(e.substr(4)) - 1; assert ( idx>=0 ); if ( idx < (ssize_t) fcolors.size() ) diff --git a/src/Mod/PartDesign/Gui/Workbench.cpp b/src/Mod/PartDesign/Gui/Workbench.cpp index 4ae7939dda..76f8cc1eda 100644 --- a/src/Mod/PartDesign/Gui/Workbench.cpp +++ b/src/Mod/PartDesign/Gui/Workbench.cpp @@ -452,22 +452,19 @@ void Workbench::activated() Gui::Control().showTaskView(); // Let us be notified when a document is activated, so that we can update the ActivePartObject - Gui::Application::Instance->signalActiveDocument.connect(boost::bind(&Workbench::slotActiveDocument, this, bp::_1)); - App::GetApplication().signalNewDocument.connect(boost::bind(&Workbench::slotNewDocument, this, bp::_1)); - App::GetApplication().signalFinishRestoreDocument.connect(boost::bind(&Workbench::slotFinishRestoreDocument, this, bp::_1)); - App::GetApplication().signalDeleteDocument.connect(boost::bind(&Workbench::slotDeleteDocument, this, bp::_1)); - // Watch out for objects being added to the active document, so that we can add them to the body - //App::GetApplication().signalNewObject.connect(boost::bind(&Workbench::slotNewObject, this, bp::_1)); + activeDoc = Gui::Application::Instance->signalActiveDocument.connect(boost::bind(&Workbench::slotActiveDocument, this, bp::_1)); + createDoc = App::GetApplication().signalNewDocument.connect(boost::bind(&Workbench::slotNewDocument, this, bp::_1)); + finishDoc = App::GetApplication().signalFinishRestoreDocument.connect(boost::bind(&Workbench::slotFinishRestoreDocument, this, bp::_1)); + deleteDoc = App::GetApplication().signalDeleteDocument.connect(boost::bind(&Workbench::slotDeleteDocument, this, bp::_1)); } void Workbench::deactivated() { // Let us be notified when a document is activated, so that we can update the ActivePartObject - Gui::Application::Instance->signalActiveDocument.disconnect(boost::bind(&Workbench::slotActiveDocument, this, bp::_1)); - App::GetApplication().signalNewDocument.disconnect(boost::bind(&Workbench::slotNewDocument, this, bp::_1)); - App::GetApplication().signalFinishRestoreDocument.disconnect(boost::bind(&Workbench::slotFinishRestoreDocument, this, bp::_1)); - App::GetApplication().signalDeleteDocument.disconnect(boost::bind(&Workbench::slotDeleteDocument, this, bp::_1)); - //App::GetApplication().signalNewObject.disconnect(boost::bind(&Workbench::slotNewObject, this, bp::_1)); + activeDoc.disconnect(); + createDoc.disconnect(); + finishDoc.disconnect(); + deleteDoc.disconnect(); removeTaskWatcher(); // reset the active Body diff --git a/src/Mod/PartDesign/Gui/Workbench.h b/src/Mod/PartDesign/Gui/Workbench.h index 382fab4e95..647bd6c54a 100644 --- a/src/Mod/PartDesign/Gui/Workbench.h +++ b/src/Mod/PartDesign/Gui/Workbench.h @@ -24,7 +24,9 @@ #ifndef PARTDESIGN_WORKBENCH_H #define PARTDESIGN_WORKBENCH_H +#include #include +#include namespace Gui { @@ -36,12 +38,6 @@ class ViewProviderDocumentObject; namespace PartDesignGui { -// pointer to the active assembly object -//extern PartDesign::Body *ActivePartObject; -//extern Gui::Document *ActiveGuiDoc; -//extern App::Document *ActiveAppDoc; -//extern Gui::ViewProviderDocumentObject *ActiveVp; - /** * @author Werner Mayer */ @@ -80,6 +76,11 @@ private: void _switchToDocument(const App::Document* doc); +private: + boost::signals2::connection activeDoc; + boost::signals2::connection createDoc; + boost::signals2::connection finishDoc; + boost::signals2::connection deleteDoc; }; } // namespace PartDesignGui diff --git a/src/Mod/Path/Generators/rotation_generator.py b/src/Mod/Path/Generators/rotation_generator.py new file mode 100644 index 0000000000..47749b49a5 --- /dev/null +++ b/src/Mod/Path/Generators/rotation_generator.py @@ -0,0 +1,205 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2021 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + + +# Technical Debt. This generator currently assumes 3+2 axis rotation of CA. +# The main generator function should be extended to include other flavors of 3+2 + + +import PathScripts.PathLog as PathLog +import math +import Path +import FreeCAD +from enum import Enum + +__title__ = "Rotation Path Generator" +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "Generates the rotation toolpath" + + +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + + +class refAxis(Enum): + x = FreeCAD.Vector(1, 0, 0) + y = FreeCAD.Vector(0, 1, 0) + z = FreeCAD.Vector(0, 0, 1) + + +def relAngle(vec, ref): + """ + Takes a vector and a reference axis (refAxis) vector. Calculates the + relative angle. The result is returned in degrees (plus or minus) + """ + + PathLog.debug("vec: {} ref: {}".format(vec, ref)) + norm = vec * 1 # copy vec so we don't alter original + + if ref == refAxis.x: + plane = refAxis.y.value + elif ref == refAxis.y: + plane = refAxis.z.value + else: + plane = refAxis.x.value + + norm.projectToPlane(FreeCAD.Vector(0, 0, 0), plane) + + ref = ref.value + rot = FreeCAD.Rotation(norm, ref) + ang = math.degrees(rot.Angle) + angle = ang * plane.dot(rot.Axis) + PathLog.debug("relative ang: {}".format(angle)) + + return angle + + +def __getCRotation(normalVector, cMin=-360, cMax=360): + """ + Calculate the valid C axis rotations component to align the normalVector + with either the +y or -y axis. + multiple poses may be possible. Returns a list of all valid poses + """ + PathLog.debug("normalVector: {} cMin: {} cMax: {}".format(normalVector, cMin, cMax)) + + angle = relAngle(normalVector, refAxis.y) + + # Given an angle, there are four possibilities; rotating +- to each of the + # two axes +y and -y + candidates = [angle] + if angle == 0: + candidates.append(180) + elif angle == 180: + candidates.append(0) + elif angle >= 0: + candidates.append(angle - 180) + candidates.append(180 + angle) + candidates.append(angle - 360) + else: + candidates.append(angle + 180) + candidates.append(-180 + angle) + candidates.append(angle + 360) + + # final results are candidates that don't violate rotation limits + results = [c for c in candidates if c >= cMin and c <= cMax] + + return results + + +def __getARotation(normalVector, aMin=-360, aMax=360): + """ + Calculate the A axis rotation component. + Final rotation is always assumed to be around +X. The sign of the returned + value indicates direction of rotation. + + Returns None if rotation violates min/max constraints + """ + + angle = relAngle(normalVector, refAxis.z) + + # only return a result if it doesn't violate rotation constraints + if angle > aMin and angle <= aMax: + return angle + else: + return None + + +def generate(normalVector, aMin=-360, aMax=360, cMin=-360, cMax=360, compound=False): + """ + Generates Gcode rotation to align a vector (alignVector) with the positive Z axis. + + It first rotates around the Z axis (C rotation) + to align the vector the positive Y axis. Then around the X axis + (A rotation). + + The min and max arguments dictate the range of motion allowed rotation in the respective + axis. + Default assumes continous rotation. + + returns a list of path commands for the shortest valid solution + + if compound is False, axis moves will be broken out to individual commands + + The normalVector input from a typical face (f) can be obtained like this: + + u, v = f.ParameterRange[:2] + n = f.normalAt(u,v) + plm = obj.getGlobalPlacement() + rot = plm.Rotation + normalVector = rot.multVec(n + """ + + PathLog.track( + "\n=============\n normalVector: {}\n aMin: {}\n aMax: {}\n cMin: {}\n cMax: {}".format( + normalVector, aMin, aMax, cMin, cMax + ) + ) + + # Calculate C rotation + cResults = __getCRotation(normalVector, cMin, cMax) + PathLog.debug("C Rotation results {}".format(cResults)) + + solutions = [] + for result in cResults: + + # calculate a new vector based on the result + rot = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), result) + newvec = rot.multVec(normalVector) + + # Get the candidate A rotation for the new vector + aResult = __getARotation(newvec, aMin, aMax) + + PathLog.debug( + "\n=====\nFor C Rotation: {}\n Calculated A {}\n".format(result, aResult) + ) + + if aResult is not None: + solutions.append({"A": aResult, "C": result}) + + if len(solutions) == 0: # No valid solution found + raise ValueError("No valid rotation solution found") + + # find pose with the shortest transit length + best = solutions[0] + curlen = math.fabs(best["A"]) + math.fabs(best["C"]) + for solution in solutions[1:]: + testlen = math.fabs(solution["A"]) + math.fabs(solution["C"]) + if testlen < curlen: + best = solution + curlen = testlen + + PathLog.debug("best result: {}".format(best)) + + # format and return rotation commands + commands = [] + if compound: + commands.append(Path.Command("G0", best)) + else: + for key, val in best.items(): + print(key, val) + commands.append(Path.Command("G0", {key: val})) + + return commands diff --git a/src/Mod/Path/PathScripts/PathProfile.py b/src/Mod/Path/PathScripts/PathProfile.py index d68066ca0b..2b786e1079 100644 --- a/src/Mod/Path/PathScripts/PathProfile.py +++ b/src/Mod/Path/PathScripts/PathProfile.py @@ -48,7 +48,11 @@ __doc__ = ( ) __contributors__ = "Schildkroet" -PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # Qt translation handling @@ -73,7 +77,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): def initAreaOpProperties(self, obj, warn=False): """initAreaOpProperties(obj) ... create operation specific properties""" - self.addNewProps = list() + self.addNewProps = [] for (prtyp, nm, grp, tt) in self.areaOpProperties(): if not hasattr(obj, nm): @@ -342,10 +346,9 @@ class ObjectProfile(PathAreaOp.ObjectOp): def areaOpShapes(self, obj): """areaOpShapes(obj) ... returns envelope for all base shapes or wires""" - PathLog.track() shapes = [] - remainingObjBaseFeatures = list() + remainingObjBaseFeatures = [] self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False self.inaccessibleMsg = translate( "PathProfile", @@ -383,10 +386,12 @@ class ObjectProfile(PathAreaOp.ObjectOp): obj.Base and len(obj.Base) > 0 ): # The user has selected subobjects from the base. Process each. shapes.extend(self._processEdges(obj, remainingObjBaseFeatures)) + PathLog.track("returned {} shapes".format(len(shapes))) + PathLog.track(remainingObjBaseFeatures) if obj.Base and len(obj.Base) > 0 and not remainingObjBaseFeatures: # Edges were already processed, or whole model targeted. - PathLog.debug("remainingObjBaseFeatures is False") + PathLog.track("remainingObjBaseFeatures is False") elif ( remainingObjBaseFeatures and len(remainingObjBaseFeatures) > 0 ): # Process remaining features after edges processed above. @@ -403,12 +408,15 @@ class ObjectProfile(PathAreaOp.ObjectOp): if numpy.isclose( abs(shape.normalAt(0, 0).z), 1 ): # horizontal face - for wire in shape.Wires[1:]: + for wire in shape.Wires: + if wire.hashCode() == shape.OuterWire.hashCode(): + continue holes.append((base.Shape, wire)) # Add face depth to list faceDepths.append(shape.BoundBox.ZMin) else: + PathLog.track() ignoreSub = base.Name + "." + sub msg = translate( "PathProfile", @@ -475,6 +483,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): else: # Try to build targets from the job models # No base geometry selected, so treating operation like a exterior contour operation + PathLog.track() self.opUpdateDepths(obj) if 1 == len(self.model) and hasattr(self.model[0], "Proxy"): @@ -498,7 +507,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Method to handle each model as a whole, when no faces are selected def _processEachModel(self, obj): - shapeTups = list() + shapeTups = [] for base in self.model: if hasattr(base, "Shape"): env = PathUtils.getEnvelope( @@ -510,22 +519,22 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Edges pre-processing def _processEdges(self, obj, remainingObjBaseFeatures): - shapes = list() - basewires = list() + PathLog.track("remainingObjBaseFeatures: {}".format(remainingObjBaseFeatures)) + shapes = [] + basewires = [] ezMin = None self.cutOut = self.tool.Diameter - for p in range(0, len(obj.Base)): - (base, subsList) = obj.Base[p] - keepFaces = list() - edgelist = list() + for base, subsList in obj.Base: + keepFaces = [] + edgelist = [] for sub in subsList: shape = getattr(base.Shape, sub) # extract and process edges if isinstance(shape, Part.Edge): edgelist.append(getattr(base.Shape, sub)) # save faces for regular processing - if isinstance(shape, Part.Face): + elif isinstance(shape, Part.Face): keepFaces.append(sub) if len(edgelist) > 0: basewires.append((base, DraftGeomUtils.findWires(edgelist))) @@ -535,6 +544,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): if len(keepFaces) > 0: # save faces for returning and processing remainingObjBaseFeatures.append((base, keepFaces)) + PathLog.track(basewires) for base, wires in basewires: for wire in wires: if wire.isClosed(): @@ -569,7 +579,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): zDiff = math.fabs(wire.BoundBox.ZMin - obj.FinalDepth.Value) if flattened and zDiff >= self.JOB.GeometryTolerance.Value: cutWireObjs = False - openEdges = list() + openEdges = [] passOffsets = [self.ofstRadius] (origWire, flatWire) = flattened @@ -708,8 +718,8 @@ class ObjectProfile(PathAreaOp.ObjectOp): self._addDebugObject("CutArea", cutArea) # Get top and bottom faces of cut area (CA), and combine faces when necessary - topFc = list() - botFc = list() + topFc = [] + botFc = [] bbZMax = cutArea.BoundBox.ZMax bbZMin = cutArea.BoundBox.ZMin for f in range(0, len(cutArea.Faces)): @@ -872,9 +882,9 @@ class ObjectProfile(PathAreaOp.ObjectOp): def _extractPathWire(self, obj, base, flatWire, cutShp): PathLog.debug("_extractPathWire()") - subLoops = list() - rtnWIRES = list() - osWrIdxs = list() + subLoops = [] + rtnWIRES = [] + osWrIdxs = [] subDistFactor = ( 1.0 # Raise to include sub wires at greater distance from original ) @@ -897,10 +907,10 @@ class ObjectProfile(PathAreaOp.ObjectOp): pass else: PathLog.error("No area to offset shape returned.") - return list() + return [] except Exception as ee: PathLog.error("No area to offset shape returned.\n{}".format(ee)) - return list() + return [] self._addDebugObject("OffsetShape", ofstShp) @@ -909,7 +919,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): osWrIdxs.append(w) # Identify two vertexes for dividing offset loop - NEAR0 = self._findNearestVertex(ofstShp, cent0) + NEAR0 = self._findNearestVertex(ofstShp, cent0) # min0i = 0 min0 = NEAR0[0][4] for n in range(0, len(NEAR0)): @@ -921,7 +931,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): near0Shp = Part.makeLine(cent0, pnt0) self._addDebugObject("Near0", near0Shp) - NEAR1 = self._findNearestVertex(ofstShp, cent1) + NEAR1 = self._findNearestVertex(ofstShp, cent1) # min1i = 0 min1 = NEAR1[0][4] for n in range(0, len(NEAR1)): @@ -941,7 +951,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): ) # Debugging - ''' + """ if self.isDebug: PathLog.debug('min0i is {}.'.format(min0i)) PathLog.debug('min1i is {}.'.format(min1i)) @@ -949,14 +959,14 @@ class ObjectProfile(PathAreaOp.ObjectOp): PathLog.debug('NEAR1[{}] is {}.'.format(w1, NEAR1[w1])) PathLog.debug('NEAR0 is {}.'.format(NEAR0)) PathLog.debug('NEAR1 is {}.'.format(NEAR1)) - ''' + """ mainWire = ofstShp.Wires[w0] # Check for additional closed loops in offset wire by checking distance to iTAG or eTAG elements if numOSWires > 1: # check all wires for proximity(children) to intersection tags - tagsComList = list() + tagsComList = [] for T in self.cutSideTags.Faces: tcom = T.CenterOfMass tv = FreeCAD.Vector(tcom.x, tcom.y, 0.0) @@ -987,8 +997,8 @@ class ObjectProfile(PathAreaOp.ObjectOp): except Exception as ee: PathLog.error("Failed to identify offset edge.\n{}".format(ee)) return False - edgs0 = list() - edgs1 = list() + edgs0 = [] + edgs1 = [] for e in edgeIdxs0: edgs0.append(mainWire.Edges[e]) for e in edgeIdxs1: @@ -1030,7 +1040,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): def sortDist(tup): return tup[4] - PNTS = list() + PNTS = [] for w in range(0, len(shape.Wires)): WR = shape.Wires[w] V = WR.Vertexes[0] @@ -1061,7 +1071,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): V2 = FreeCAD.Vector(VV2.X, VV2.Y, VV2.Z) lenE = len(wire.Edges) - FLGS = list() + FLGS = [] for e in range(0, lenE): FLGS.append(0) @@ -1098,11 +1108,11 @@ class ObjectProfile(PathAreaOp.ObjectOp): # PathLog.debug('_separateWireAtVertexes() FLGS: {}'.format(FLGS)) - PRE = list() - POST = list() - IDXS = list() - IDX1 = list() - IDX2 = list() + PRE = [] + POST = [] + IDXS = [] + IDX1 = [] + IDX2 = [] for e in range(0, lenE): f = FLGS[e] PRE.append(f) @@ -1174,7 +1184,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): # Eif # Debugging - ''' + """ if self.isDebug: PathLog.debug('grps[0]: {}'.format(grps[0])) PathLog.debug('grps[1]: {}'.format(grps[1])) @@ -1182,7 +1192,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): PathLog.debug('wireIdxs[1]: {}'.format(wireIdxs[1])) PathLog.debug('PRE: {}'.format(PRE)) PathLog.debug('IDXS: {}'.format(IDXS)) - ''' + """ return (wireIdxs[0], wireIdxs[1]) def _makeCrossSection(self, shape, sliceZ, zHghtTrgt=False): @@ -1191,7 +1201,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): Makes face shape from cross-section object. Returns face shape at zHghtTrgt.""" PathLog.debug("_makeCrossSection()") # Create cross-section of shape and translate - wires = list() + wires = [] slcs = shape.slice(FreeCAD.Vector(0, 0, 1), sliceZ) if len(slcs) > 0: for i in slcs: @@ -1220,8 +1230,8 @@ class ObjectProfile(PathAreaOp.ObjectOp): def _makeIntersectionTags(self, useWire, numOrigEdges, fdv): PathLog.debug("_makeIntersectionTags()") # Create circular probe tags around perimiter of wire - extTags = list() - intTags = list() + extTags = [] + intTags = [] tagRad = self.radius / 2 tagCnt = 0 begInt = False diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index 5152ae044a..8e5c1e0b42 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -788,8 +788,6 @@ class depth_params(object): def __init__(self, clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths=None, equalstep=False): '''self, clearance_height, safe_height, start_depth, step_down, z_finish_depth, final_depth, [user_depths=None], equalstep=False''' - if z_finish_step > step_down: - raise ValueError('z_finish_step must be less than step_down') self.__clearance_height = clearance_height self.__safe_height = safe_height @@ -801,6 +799,9 @@ class depth_params(object): self.data = self.__get_depths(equalstep=equalstep) self.index = 0 + if self.__z_finish_step > self.__step_down: + raise ValueError('z_finish_step must be less than step_down') + def __iter__(self): self.index = 0 return self diff --git a/src/Mod/Path/PathTests/TestPathDepthParams.py b/src/Mod/Path/PathTests/TestPathDepthParams.py index 3dfa419e7f..fed0779fc8 100644 --- a/src/Mod/Path/PathTests/TestPathDepthParams.py +++ b/src/Mod/Path/PathTests/TestPathDepthParams.py @@ -26,151 +26,188 @@ import unittest class depthTestCases(unittest.TestCase): def test00(self): - '''Stepping down to zero ''' - clearance_height= 15 - safe_height = 12 + """Stepping down to zero""" + args = { + "clearance_height": 15, + "safe_height": 12, + "start_depth": 10, + "step_down": 2, + "z_finish_step": 1, + "final_depth": 0, + "user_depths": None, + } - start_depth = 10 - step_down = 2 - z_finish_step = 1 - final_depth = 0 - user_depths = None + expected = [8, 6, 4, 2, 1, 0] - expected =[8,6,4,2,1,0] - - d = PathUtils.depth_params(clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths) + d = PathUtils.depth_params(**args) r = [i for i in d] - self.assertListEqual (r, expected) + self.assertListEqual(r, expected) def test10(self): - '''Stepping from zero to a negative depth ''' + """Stepping from zero to a negative depth""" - clearance_height= 10 - safe_height = 5 + args = { + "clearance_height": 10, + "safe_height": 5, + "start_depth": 0, + "step_down": 2, + "z_finish_step": 0, + "final_depth": -10, + "user_depths": None, + } - start_depth = 0 - step_down = 2 - z_finish_step = 0 - final_depth = -10 - user_depths = None + expected = [-2, -4, -6, -8, -10] - expected =[-2, -4, -6, -8, -10] - - d = PathUtils.depth_params(clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths) + d = PathUtils.depth_params(**args) r = [i for i in d] - self.assertListEqual (r, expected) + self.assertListEqual(r, expected) def test20(self): - '''Start and end are equal or start lower than finish ''' - clearance_height= 15 - safe_height = 12 + """Start and end are equal or start lower than finish""" - start_depth = 10 - step_down = 2 - z_finish_step = 0 - final_depth = 10 - user_depths = None + args = { + "clearance_height": 15, + "safe_height": 12, + "start_depth": 10, + "step_down": 2, + "z_finish_step": 0, + "final_depth": 10, + "user_depths": None, + } + expected = [10] - expected =[10] - - d = PathUtils.depth_params(clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths) + d = PathUtils.depth_params(**args) r = [i for i in d] - self.assertListEqual (r, expected) + self.assertListEqual(r, expected) - start_depth = 10 - final_depth = 15 + args["start_depth"] = 10 + args["final_depth"] = 15 - expected =[] + expected = [] - d = PathUtils.depth_params(clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths) + d = PathUtils.depth_params(**args) r = [i for i in d] - self.assertListEqual (r, expected) + self.assertListEqual(r, expected) def test30(self): - '''User Parameters passed in''' - clearance_height= 10 - safe_height = 5 + """User Parameters passed in""" + args = { + "clearance_height": 10, + "safe_height": 5, + "start_depth": 0, + "step_down": 2, + "z_finish_step": 0, + "final_depth": -10, + "user_depths": [2, 4, 8, 10, 11, 12], + } - start_depth = 0 - step_down = 2 - z_finish_step = 0 - final_depth = -10 - user_depths = [2, 4, 8, 10, 11, 12] + expected = [2, 4, 8, 10, 11, 12] - expected =[2, 4, 8, 10, 11, 12] - - d = PathUtils.depth_params(clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths) + d = PathUtils.depth_params(**args) r = [i for i in d] - self.assertListEqual (r, expected) + self.assertListEqual(r, expected) def test40(self): - '''z_finish_step passed in.''' - clearance_height= 10 - safe_height = 5 + """z_finish_step passed in.""" + args = { + "clearance_height": 10, + "safe_height": 5, + "start_depth": 0, + "step_down": 2, + "z_finish_step": 1, + "final_depth": -10, + "user_depths": None, + } - start_depth = 0 - step_down = 2 - z_finish_step = 1 - final_depth = -10 - user_depths = None + expected = [-2, -4, -6, -8, -9, -10] - expected =[-2, -4, -6, -8, -9, -10] - - d = PathUtils.depth_params(clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths) + d = PathUtils.depth_params(**args) r = [i for i in d] - self.assertListEqual (r, expected) - + self.assertListEqual(r, expected) def test50(self): - '''stepping down with equalstep=True''' - clearance_height= 10 - safe_height = 5 + """stepping down with equalstep=True""" + args = { + "clearance_height": 10, + "safe_height": 5, + "start_depth": 10, + "step_down": 3, + "z_finish_step": 0, + "final_depth": 0, + "user_depths": None, + "equalstep": True, + } - start_depth = 10 - step_down = 3 - z_finish_step = 0 - final_depth = 0 - user_depths = None + expected = [7.5, 5.0, 2.5, 0] - expected =[7.5, 5.0, 2.5, 0] - - d = PathUtils.depth_params(clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths, equalstep=True) + d = PathUtils.depth_params(**args) r = [i for i in d] - self.assertListEqual (r, expected) - + self.assertListEqual(r, expected) def test60(self): - '''stepping down with equalstep=True and a finish depth''' - clearance_height= 10 - safe_height = 5 + """stepping down with equalstep=True and a finish depth""" + args = { + "clearance_height": 10, + "safe_height": 5, + "start_depth": 10, + "step_down": 3, + "z_finish_step": 1, + "final_depth": 0, + "user_depths": None, + } - start_depth = 10 - step_down = 3 - z_finish_step = 1 - final_depth = 0 - user_depths = None + expected = [7.0, 4.0, 1.0, 0] - expected =[7.0, 4.0, 1.0, 0] - - d = PathUtils.depth_params(clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths, equalstep=True) + d = PathUtils.depth_params(**args) r = [i for i in d] - self.assertListEqual (r, expected) + self.assertListEqual(r, expected) def test70(self): - '''stepping down with stepdown greater than total depth''' - clearance_height= 10 - safe_height = 5 + """stepping down with stepdown greater than total depth""" + args = { + "clearance_height": 10, + "safe_height": 5, + "start_depth": 10, + "step_down": 20, + "z_finish_step": 1, + "final_depth": 0, + "user_depths": None, + } - start_depth = 10 - step_down = 20 - z_finish_step = 1 - final_depth = 0 - user_depths = None + expected = [1.0, 0] - expected =[1.0, 0] - - d = PathUtils.depth_params(clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths) + d = PathUtils.depth_params(**args) r = [i for i in d] - self.assertListEqual (r, expected) + self.assertListEqual(r, expected) + def test80(self): + """Test handling of negative step-down, negative finish step, and relative size of step/finish""" + # negative steps should be converted to positive values + args = { + "clearance_height": 3, + "safe_height": 3, + "start_depth": 2, + "step_down": -1, + "z_finish_step": -1, + "final_depth": 0, + "user_depths": None, + } + + expected = [1.0, 0] + + d = PathUtils.depth_params(**args) + r = [i for i in d] + self.assertListEqual(r, expected) + + # a step_down less than the finish step is an error + args = { + "clearance_height": 3, + "safe_height": 3, + "start_depth": 2, + "step_down": 0.1, + "z_finish_step": 1, + "final_depth": 0, + "user_depths": None, + } + self.assertRaises(ValueError, PathUtils.depth_params, **args) diff --git a/src/Mod/Path/PathTests/TestPathRotationGenerator.py b/src/Mod/Path/PathTests/TestPathRotationGenerator.py new file mode 100644 index 0000000000..c9a3065d58 --- /dev/null +++ b/src/Mod/Path/PathTests/TestPathRotationGenerator.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2021 sliptonic * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import Path +import FreeCAD +import Generators.rotation_generator as generator +import PathScripts.PathLog as PathLog +import PathTests.PathTestUtils as PathTestUtils +import numpy as np + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +PathLog.trackModule(PathLog.thisModule()) + + +class TestPathRotationGenerator(PathTestUtils.PathTestBase): + def test00(self): + """Test relAngle function""" + v = FreeCAD.Vector(0.5, 0.5, 0.5) + self.assertTrue(np.isclose(generator.relAngle(v, generator.refAxis.x), 45)) + self.assertTrue(np.isclose(generator.relAngle(v, generator.refAxis.y), 45)) + self.assertTrue(np.isclose(generator.relAngle(v, generator.refAxis.z), 45)) + + v = FreeCAD.Vector(-0.5, 0.5, 0.5) + self.assertTrue(np.isclose(generator.relAngle(v, generator.refAxis.x), 135)) + self.assertTrue(np.isclose(generator.relAngle(v, generator.refAxis.y), -45)) + self.assertTrue(np.isclose(generator.relAngle(v, generator.refAxis.z), 45)) + + v = FreeCAD.Vector(-0.5, -0.5, -0.5) + self.assertTrue(np.isclose(generator.relAngle(v, generator.refAxis.x), -135)) + self.assertTrue(np.isclose(generator.relAngle(v, generator.refAxis.y), -135)) + self.assertTrue(np.isclose(generator.relAngle(v, generator.refAxis.z), -135)) + + def test10(self): + """Test Basic Rotation Generator Return""" + v1 = FreeCAD.Vector(0.0, 0.0, 1.0) + args = { + "normalVector": v1, + "aMin": -360, + "aMax": 360, + "cMin": -360, + "cMax": 360, + "compound": True, + } + + result = generator.generate(**args) + + self.assertTrue(type(result) is list) + self.assertTrue(len(result) == 1) + self.assertTrue(type(result[0]) is Path.Command) + + command = result[0] + self.assertTrue(np.isclose(command.Parameters["A"], 0)) + self.assertTrue(np.isclose(command.Parameters["C"], 0)) + + args["compound"] = False + result = generator.generate(**args) + self.assertTrue(len(result) == 2) + + PathLog.debug(result) + + def test20(self): + """Test non-zero rotation""" + v1 = FreeCAD.Vector(0.5, 0.5, 0.5) + args = { + "normalVector": v1, + "aMin": -360, + "aMax": 360, + "cMin": -360, + "cMax": 360, + "compound": True, + } + + result = generator.generate(**args) + + command = result[0] + PathLog.debug(command.Parameters) + self.assertTrue(np.isclose(command.Parameters["A"], 54.736)) + self.assertTrue(np.isclose(command.Parameters["C"], 45)) + + PathLog.track(result) + + def test30(self): + """Test A limits""" + v1 = FreeCAD.Vector(0.5, 0.5, 0.5) + + args = {"normalVector": v1, "cMin": -360, "cMax": 360, "compound": True} + + # Constrain a axis rotation negative + args["aMin"] = -90 + args["aMax"] = 0 + + result = generator.generate(**args) + PathLog.debug(result) + + command = result[0] + self.assertTrue(np.isclose(command.Parameters["A"], -54.736)) + self.assertTrue(np.isclose(command.Parameters["C"], -135)) + + # Constrain a axis rotation positive + args["aMin"] = 0 + args["aMax"] = 90 + + result = generator.generate(**args) + PathLog.debug(result) + + command = result[0] + self.assertTrue(np.isclose(command.Parameters["A"], 54.736)) + self.assertTrue(np.isclose(command.Parameters["C"], 45)) + + def test40(self): + """Test C limits""" + v1 = FreeCAD.Vector(0.5, 0.5, 0.5) + + args = {"normalVector": v1, "aMin": -360, "aMax": 360, "compound": True} + + # Constrain a axis rotation negative + args["cMin"] = -180 + args["cMax"] = 0 + + result = generator.generate(**args) + PathLog.debug(result) + + command = result[0] + self.assertTrue(np.isclose(command.Parameters["A"], -54.736)) + self.assertTrue(np.isclose(command.Parameters["C"], -135)) + + # Constrain a axis rotation positive + args["cMin"] = 0 + args["cMax"] = 180 + + result = generator.generate(**args) + PathLog.debug(result) + + command = result[0] + self.assertTrue(np.isclose(command.Parameters["A"], 54.736)) + self.assertTrue(np.isclose(command.Parameters["C"], 45)) + + def test50(self): + """Test handling of no valid solution""" + v1 = FreeCAD.Vector(0.5, 0.5, 0.5) + args = { + "normalVector": v1, + "aMin": 0, + "aMax": 10, + "cMin": 0, + "cMax": 10, + "compound": True, + } + + self.assertRaises(ValueError, generator.generate, **args) diff --git a/src/Mod/Path/PathTests/TestPathStock.py b/src/Mod/Path/PathTests/TestPathStock.py index 8f9d22e412..af2471d611 100644 --- a/src/Mod/Path/PathTests/TestPathStock.py +++ b/src/Mod/Path/PathTests/TestPathStock.py @@ -45,7 +45,6 @@ class TestPathStock(PathTestBase): model = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", "Model") model.addObject(self.base) self.job.Model = model - self.job.addProperty('App::PropertyLink', 'Proxy') self.job.Proxy = FakeJobProxy() def tearDown(self): diff --git a/src/Mod/Sketcher/App/Sketch.cpp b/src/Mod/Sketcher/App/Sketch.cpp index e1880af526..22f3858ba8 100644 --- a/src/Mod/Sketcher/App/Sketch.cpp +++ b/src/Mod/Sketcher/App/Sketch.cpp @@ -265,7 +265,7 @@ int Sketch::setUpSketch(const std::vector &GeoList, while(unsatisfied_groups) { // We tried hard not to arrive to an unsatisfied group, so we try harder // This loop has the advantage that the user will notice increased effort to solve, - // so may understand he is abusing the block constraint, while guaranteing that wrong + // so they may understand that they are abusing the block constraint, while guaranteeing that wrong // behaviour of the block constraint is not undetected. // Another QR iteration diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index e737bf4925..91bef48921 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -3435,14 +3435,14 @@ bool SketchObject::isCarbonCopyAllowed(App::Document *pDoc, App::DocumentObject double doty = sy * ly; // the planes of the sketches must be parallel - if(!allowUnaligned && dot != 1.0 && dot != -1.0) { + if(!allowUnaligned && fabs(fabs(dot)-1) > Precision::Confusion()) { if (rsn) *rsn = rlNonParallel; return false; } // the axis must be aligned - if(!allowUnaligned && ((dotx != 1.0 && dotx != -1.0) || (doty != 1.0 && doty != -1.0))) { + if(!allowUnaligned && ((fabs(fabs(dotx)-1) > Precision::Confusion()) || (fabs(fabs(doty)-1) > Precision::Confusion()))) { if (rsn) *rsn = rlAxesMisaligned; return false; @@ -3454,14 +3454,14 @@ bool SketchObject::isCarbonCopyAllowed(App::Document *pDoc, App::DocumentObject double alignment = ddir * lnormal; - if(!allowUnaligned && (alignment != 1.0 && alignment != -1.0) && (psObj->Placement.getValue().getPosition() != this->Placement.getValue().getPosition()) ){ + if(!allowUnaligned && (fabs(fabs(alignment)-1) > Precision::Confusion()) && (psObj->Placement.getValue().getPosition() != this->Placement.getValue().getPosition()) ){ if (rsn) *rsn = rlOriginsMisaligned; return false; } - xinv = allowUnaligned?false:(dotx != 1.0); - yinv = allowUnaligned?false:(doty != 1.0); + xinv = allowUnaligned?false:(fabs(dotx-1) > Precision::Confusion()); + yinv = allowUnaligned?false:(fabs(doty-1) > Precision::Confusion()); return true; } diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index f6a4f0b57b..ccd25e94a6 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -7209,13 +7209,13 @@ void CmdSketcherConstrainInternalAlignment::activated(int iMsg) const Part::Geometry *geo = Obj->getGeometry(GeoId); - if (geo->getTypeId() == Part::GeomPoint::getClassTypeId()) + if (geo && geo->getTypeId() == Part::GeomPoint::getClassTypeId()) pointids.push_back(GeoId); - else if (geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) + else if (geo && geo->getTypeId() == Part::GeomLineSegment::getClassTypeId()) lineids.push_back(GeoId); - else if (geo->getTypeId() == Part::GeomEllipse::getClassTypeId()) + else if (geo && geo->getTypeId() == Part::GeomEllipse::getClassTypeId()) ellipseids.push_back(GeoId); - else if (geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) + else if (geo && geo->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) arcsofellipseids.push_back(GeoId); else { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), diff --git a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp index ba1496e225..ac64ff6663 100644 --- a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp +++ b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp @@ -6997,6 +6997,8 @@ class DrawSketchHandlerSlot : public DrawSketchHandler public: DrawSketchHandlerSlot() : Mode(STATUS_SEEK_First) + , SnapMode(SNAP_MODE_Free) + , SnapDir(SNAP_DIR_Horz) , dx(0), dy(0), r(0) , EditCurve(35) { @@ -7009,6 +7011,18 @@ public: STATUS_End }; + enum SNAP_MODE + { + SNAP_MODE_Free, + SNAP_MODE_Straight + }; + + enum SNAP_DIR + { + SNAP_DIR_Horz, + SNAP_DIR_Vert + }; + virtual void activated(ViewProviderSketch*) { setCrosshairCursor("Sketcher_Pointer_Slot"); @@ -7028,16 +7042,25 @@ public: dx = onSketchPos.x - StartPos.x; dy = onSketchPos.y - StartPos.y; + if(QApplication::keyboardModifiers() == Qt::ControlModifier) + SnapMode = SNAP_MODE_Straight; + else + SnapMode = SNAP_MODE_Free; + double a = 0; double rev = 0; if (fabs(dx) > fabs(dy)) { r = fabs(dx) / 4; rev = Base::sgn(dx); + SnapDir = SNAP_DIR_Horz; + if (SnapMode == SNAP_MODE_Straight) dy = 0; } else { r = fabs(dy) / 4; a = 8; rev = Base::sgn(dy); + SnapDir = SNAP_DIR_Vert; + if (SnapMode == SNAP_MODE_Straight) dx = 0; } // draw the arcs with each 16 segments @@ -7067,7 +7090,7 @@ public: setPositionText(onSketchPos, text); sketchgui->drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f, 0.f))) { + if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(dx, dy), AutoConstraint::VERTEX_NO_TANGENCY)) { renderSuggestConstraintsCursor(sugConstr2); return; } @@ -7121,6 +7144,31 @@ public: try { Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add slot")); + + AutoConstraint lastCons = {Sketcher::None, Sketcher::Constraint::GeoUndef, Sketcher::none}; + if (!sugConstr2.empty()) lastCons = sugConstr2.back(); + + ostringstream snapCon = ostringstream(""); + if (SnapMode == SNAP_MODE_Straight) { + snapCon << "conList.append(Sketcher.Constraint('"; + if (SnapDir == SNAP_DIR_Horz) { + snapCon << "Horizontal"; + } + else { + snapCon << "Vertical"; + } + snapCon << "'," << firstCurve + 2 << "))\n"; + + // If horizontal/vertical already applied because of snap, do not duplicate with Autocontraint + if (lastCons.Type == Sketcher::Horizontal || lastCons.Type == Sketcher::Vertical) + sugConstr2.pop_back(); + } + else { + // If horizontal/vertical Autoconstraint suggested, applied it on first line (rather than last arc) + if (lastCons.Type == Sketcher::Horizontal || lastCons.Type == Sketcher::Vertical) + sugConstr2.back().GeoId = firstCurve + 2; + } + Gui::Command::doCommand(Gui::Command::Doc, "geoList = []\n" "geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n" @@ -7134,6 +7182,7 @@ public: "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n" "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n" "conList.append(Sketcher.Constraint('Equal', %i, %i))\n" + "%s" "%s.addConstraint(conList)\n" "del geoList, conList\n", StartPos.x, StartPos.y, // center of the arc1 @@ -7151,6 +7200,7 @@ public: firstCurve + 1, firstCurve + 3, // tangent3 firstCurve + 3, firstCurve, // tangent4 firstCurve, firstCurve + 1, // equal constraint + snapCon.str().c_str(), // horizontal/vertical constraint if snapping Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch Gui::Command::commitCommand(); @@ -7193,11 +7243,14 @@ public: else { sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider } + SnapMode = SNAP_MODE_Straight; } return true; } protected: BoxMode Mode; + SNAP_MODE SnapMode; + SNAP_DIR SnapDir; Base::Vector2d StartPos; double dx, dy, r; std::vector EditCurve; diff --git a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp index f9e6aadfa1..939dcb2b04 100644 --- a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp +++ b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp @@ -1538,7 +1538,7 @@ void SketcherCopy::activate(SketcherCopy::Op op) } } - // Ask the user if he wants to clone or to simple copy + // Ask the user if they want to clone or to simple copy /* int ret = QMessageBox::question(Gui::getMainWindow(), QObject::tr("Dimensional/Geometric constraints"), QObject::tr("Do you want to clone the object, i.e. substitute dimensional constraints by geometric constraints?"), diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp index dc571a673d..2f4ea5e379 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp +++ b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp @@ -354,11 +354,11 @@ int DrawSketchHandler::seekAutoConstraint(std::vector &suggested constr.Type = Sketcher::None; constr.GeoId = GeoId; constr.PosId = PosId; - if (type == AutoConstraint::VERTEX && PosId != Sketcher::none) + if ((type == AutoConstraint::VERTEX || type == AutoConstraint::VERTEX_NO_TANGENCY) && PosId != Sketcher::none) constr.Type = Sketcher::Coincident; else if (type == AutoConstraint::CURVE && PosId != Sketcher::none) constr.Type = Sketcher::PointOnObject; - else if (type == AutoConstraint::VERTEX && PosId == Sketcher::none && hitobject->getTypeId() != Part::GeomBSplineCurve::getClassTypeId()) + else if ((type == AutoConstraint::VERTEX || type == AutoConstraint::VERTEX_NO_TANGENCY) && PosId == Sketcher::none && hitobject->getTypeId() != Part::GeomBSplineCurve::getClassTypeId()) constr.Type = Sketcher::PointOnObject; else if (type == AutoConstraint::CURVE && PosId == Sketcher::none) constr.Type = Sketcher::Tangent; @@ -405,6 +405,9 @@ int DrawSketchHandler::seekAutoConstraint(std::vector &suggested if (constr.Type != Sketcher::None) suggestedConstraints.push_back(constr); + // Do not seek for tangent if we are actually building a primitive + if (type == AutoConstraint::VERTEX_NO_TANGENCY) return suggestedConstraints.size(); + // Find if there are tangent constraints (currently arcs and circles) int tangId = Constraint::GeoUndef; @@ -578,6 +581,8 @@ void DrawSketchHandler::createAutoConstraints(const std::vector // Iterate through constraints std::vector::const_iterator it = autoConstrs.begin(); for (; it != autoConstrs.end(); ++it) { + int geoId2 = it->GeoId; + switch (it->Type) { case Sketcher::Coincident: { @@ -588,7 +593,6 @@ void DrawSketchHandler::createAutoConstraints(const std::vector , geoId1, posId1, it->GeoId, it->PosId); } break; case Sketcher::PointOnObject: { - int geoId2 = it->GeoId; Sketcher::PointPos posId2 = it->PosId; if (posId1 == Sketcher::none) { // Auto constraining an edge so swap parameters @@ -599,11 +603,16 @@ void DrawSketchHandler::createAutoConstraints(const std::vector Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('PointOnObject',%i,%i,%i)) " , geoId1, posId1, geoId2); } break; + // In special case of Horizontal/Vertical constraint, geoId2 is normally unused and should be 'Constraint::GeoUndef' + // However it can be used as a way to require the function to apply these constraints on another geometry + // In this case the caller as to set geoId2, then it will be used as target instead of geoId2 case Sketcher::Horizontal: { - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Horizontal',%i)) ", geoId1); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Horizontal',%i)) ", + geoId2 != Constraint::GeoUndef ? geoId2 : geoId1); } break; case Sketcher::Vertical: { - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Vertical',%i)) ", geoId1); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Vertical',%i)) ", + geoId2 != Constraint::GeoUndef ? geoId2 : geoId1); } break; case Sketcher::Tangent: { Sketcher::SketchObject* Obj = static_cast(sketchgui->getObject()); @@ -611,8 +620,6 @@ void DrawSketchHandler::createAutoConstraints(const std::vector const Part::Geometry *geom1 = Obj->getGeometry(geoId1); const Part::Geometry *geom2 = Obj->getGeometry(it->GeoId); - int geoId2 = it->GeoId; - // ellipse tangency support using construction elements (lines) if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomEllipse::getClassTypeId() || diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandler.h b/src/Mod/Sketcher/Gui/DrawSketchHandler.h index b7cc951ccc..572a1cbe4c 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandler.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandler.h @@ -45,7 +45,8 @@ struct AutoConstraint enum TargetType { VERTEX, - CURVE + CURVE, + VERTEX_NO_TANGENCY }; Sketcher::ConstraintType Type; int GeoId; diff --git a/src/Mod/Sketcher/Gui/SketchOrientationDialog.cpp b/src/Mod/Sketcher/Gui/SketchOrientationDialog.cpp index 329ffe0276..f34091bfe7 100644 --- a/src/Mod/Sketcher/Gui/SketchOrientationDialog.cpp +++ b/src/Mod/Sketcher/Gui/SketchOrientationDialog.cpp @@ -60,7 +60,7 @@ void SketchOrientationDialog::accept() bool reverse = ui->Reverse_checkBox->isChecked(); if (ui->XY_radioButton->isChecked()) { if (reverse) { - Pos = Base::Placement(Base::Vector3d(0,0,offset),Base::Rotation(-1.0,0.0,0.0,0.0)); + Pos = Base::Placement(Base::Vector3d(0,0,offset),Base::Rotation(1.0,0.0,0.0,0.0)); DirType = 1; } else { @@ -70,21 +70,21 @@ void SketchOrientationDialog::accept() } else if (ui->XZ_radioButton->isChecked()) { if (reverse) { - Pos = Base::Placement(Base::Vector3d(0,offset,0),Base::Rotation(Base::Vector3d(0,sqrt(2.0)/2.0,sqrt(2.0)/2.0),M_PI)); + Pos = Base::Placement(Base::Vector3d(0,offset,0),Base::Rotation(0.0,1.0,1.0,0.0)); DirType = 3; } else { - Pos = Base::Placement(Base::Vector3d(0,offset,0),Base::Rotation(Base::Vector3d(-1,0,0),1.5*M_PI)); + Pos = Base::Placement(Base::Vector3d(0,offset,0),Base::Rotation(1.0,0.0,0.0,1.0)); DirType = 2; } } else if (ui->YZ_radioButton->isChecked()) { if (reverse) { - Pos = Base::Placement(Base::Vector3d(offset,0,0),Base::Rotation(-0.5,0.5,0.5,-0.5)); + Pos = Base::Placement(Base::Vector3d(offset,0,0),Base::Rotation(-1.0,1.0,1.0,-1.0)); DirType = 5; } else { - Pos = Base::Placement(Base::Vector3d(offset,0,0),Base::Rotation(0.5,0.5,0.5,0.5)); + Pos = Base::Placement(Base::Vector3d(offset,0,0),Base::Rotation(1.0,1.0,1.0,1.0)); DirType = 4; } } diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index e22254d2af..0dfef57c23 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -3239,10 +3239,10 @@ QString ViewProviderSketch::getPresentationString(const Constraint *constraint) } if (constraint->Type == Sketcher::Diameter){ - valueStr.insert(0, QChar(8960)); // Diameter sign + valueStr.prepend(QChar(216)); // Diameter sign } else if (constraint->Type == Sketcher::Radius){ - valueStr.insert(0, QChar(82)); // Capital letter R + valueStr.prepend(QChar(82)); // Capital letter R } /** @@ -3931,7 +3931,7 @@ void ViewProviderSketch::initItemsSizes() // // Rationale: // -> Other WBs use the default value as is - // -> If a user has a HDPI, he will eventually change the value for the other WBs + // -> If a user has a HDPI, they will eventually change the value for the other WBs // -> If we correct the value here in addition, we would get two times a resize edit->MarkerSize = markersize; } diff --git a/src/Mod/Spreadsheet/App/PropertySheet.cpp b/src/Mod/Spreadsheet/App/PropertySheet.cpp index 571453c1a1..7f73663540 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.cpp +++ b/src/Mod/Spreadsheet/App/PropertySheet.cpp @@ -477,39 +477,44 @@ Cell * PropertySheet::nonNullCellAt(CellAddress address) void PropertySheet::setContent(CellAddress address, const char *value) { Cell * cell = nonNullCellAt(address); - assert(cell != 0); - cell->setContent(value); } void PropertySheet::setAlignment(CellAddress address, int _alignment) { - nonNullCellAt(address)->setAlignment(_alignment); + Cell * cell = nonNullCellAt(address); + assert(cell != 0); + if (cell->address != address) return; //Reject alignment change for merged cell except top-left one + cell->setAlignment(_alignment); } void PropertySheet::setStyle(CellAddress address, const std::set &_style) { - assert(nonNullCellAt(address) != 0); - nonNullCellAt(address)->setStyle(_style); + Cell * cell = nonNullCellAt(address); + assert(cell != 0); + cell->setStyle(_style); } void PropertySheet::setForeground(CellAddress address, const App::Color &color) { - assert(nonNullCellAt(address) != 0); - nonNullCellAt(address)->setForeground(color); + Cell * cell = nonNullCellAt(address); + assert(cell != 0); + cell->setForeground(color); } void PropertySheet::setBackground(CellAddress address, const App::Color &color) { - assert(nonNullCellAt(address) != 0); - nonNullCellAt(address)->setBackground(color); + Cell * cell = nonNullCellAt(address); + assert(cell != 0); + cell->setBackground(color); } void PropertySheet::setDisplayUnit(CellAddress address, const std::string &unit) { - assert(nonNullCellAt(address) != 0); - nonNullCellAt(address)->setDisplayUnit(unit); + Cell * cell = nonNullCellAt(address); + assert(cell != 0); + cell->setDisplayUnit(unit); } @@ -520,12 +525,11 @@ void PropertySheet::setAlias(CellAddress address, const std::string &alias) const Cell * aliasedCell = getValueFromAlias(alias); Cell * cell = nonNullCellAt(address); + assert(cell != 0); if (aliasedCell != 0 && cell != aliasedCell) throw Base::ValueError("Alias already defined."); - assert(cell != 0); - /* Mark cells depending on this cell dirty; they need to be resolved when an alias changes or disappears */ std::string fullName = owner->getFullName() + "." + address.toString(); @@ -561,14 +565,16 @@ void PropertySheet::setAlias(CellAddress address, const std::string &alias) void PropertySheet::setComputedUnit(CellAddress address, const Base::Unit &unit) { - assert(nonNullCellAt(address) != 0); - nonNullCellAt(address)->setComputedUnit(unit); + Cell * cell = nonNullCellAt(address); + assert(cell != 0); + cell->setComputedUnit(unit); } void PropertySheet::setSpans(CellAddress address, int rows, int columns) { - assert(nonNullCellAt(address) != 0); - nonNullCellAt(address)->setSpans(rows, columns); + Cell * cell = nonNullCellAt(address); + assert(cell != 0); + cell->setSpans(rows, columns); } void PropertySheet::clearAlias(CellAddress address) diff --git a/src/Mod/Spreadsheet/Gui/CMakeLists.txt b/src/Mod/Spreadsheet/Gui/CMakeLists.txt index 61ff766fa2..cbffc4e9b3 100644 --- a/src/Mod/Spreadsheet/Gui/CMakeLists.txt +++ b/src/Mod/Spreadsheet/Gui/CMakeLists.txt @@ -8,12 +8,10 @@ include_directories( ${XercesC_INCLUDE_DIRS} ) -generate_from_xml(SpreadsheetViewPy) generate_from_xml(ViewProviderSpreadsheetPy) # The XML files set(SpreadsheetGui_XML_SRCS - SpreadsheetViewPy.xml ViewProviderSpreadsheetPy.xml ) @@ -77,7 +75,6 @@ SET(SpreadsheetGui_SRCS Resources/Spreadsheet.qrc SpreadsheetView.cpp SpreadsheetView.h - SpreadsheetViewPyImp.cpp SpreadsheetDelegate.h SpreadsheetDelegate.cpp SheetTableView.cpp diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp index e65365c3eb..d5f079138e 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp @@ -30,7 +30,9 @@ # include # include # include +# include #endif +# include #include #include @@ -887,16 +889,18 @@ void SheetTableView::edit ( const QModelIndex & index ) QTableView::edit(index); } -void SheetTableView::contextMenuEvent(QContextMenuEvent *) { +void SheetTableView::contextMenuEvent(QContextMenuEvent *) +{ const QMimeData* mimeData = QApplication::clipboard()->mimeData(); - if(!selectionModel()->hasSelection()) { + if (!selectionModel()->hasSelection()) { actionCut->setEnabled(false); actionCopy->setEnabled(false); actionDel->setEnabled(false); actionPaste->setEnabled(false); actionSplit->setEnabled(false); actionMerge->setEnabled(false); - }else{ + } + else { actionPaste->setEnabled(mimeData && (mimeData->hasText() || mimeData->hasText())); actionCut->setEnabled(true); actionCopy->setEnabled(true); @@ -908,4 +912,129 @@ void SheetTableView::contextMenuEvent(QContextMenuEvent *) { contextMenu->exec(QCursor::pos()); } +QString SheetTableView::toHtml() const +{ + std::set cells = sheet->getCells()->getUsedCells(); + int rowCount = 1; + int colCount = 1; + for (const auto& it : cells) { + rowCount = std::max(rowCount, it.row()); + colCount = std::max(colCount, it.col()); + } + + std::unique_ptr doc(new QTextDocument); + doc->setDocumentMargin(10); + QTextCursor cursor(doc.get()); + + cursor.movePosition(QTextCursor::Start); + + QTextTableFormat tableFormat; + tableFormat.setCellSpacing(0.0); + tableFormat.setCellPadding(2.0); + QVector constraints; + for (int col = 0; col < colCount + 1; col++) { + constraints.append(QTextLength(QTextLength::FixedLength, sheet->getColumnWidth(col))); + } + constraints.prepend(QTextLength(QTextLength::FixedLength, 30.0)); + tableFormat.setColumnWidthConstraints(constraints); + + QTextCharFormat boldFormat; + QFont boldFont = boldFormat.font(); + boldFont.setBold(true); + boldFormat.setFont(boldFont); + + QColor bgColor; + bgColor.setNamedColor(QLatin1String("#f0f0f0")); + QTextCharFormat bgFormat; + bgFormat.setBackground(QBrush(bgColor)); + + QTextTable *table = cursor.insertTable(rowCount + 2, colCount + 2, tableFormat); + + // The header cells of the rows + for (int row = 0; row < rowCount + 1; row++) { + QTextTableCell headerCell = table->cellAt(row+1, 0); + headerCell.setFormat(bgFormat); + QTextCursor headerCellCursor = headerCell.firstCursorPosition(); + QString data = model()->headerData(row, Qt::Vertical).toString(); + headerCellCursor.insertText(data, boldFormat); + } + + // The header cells of the columns + for (int col = 0; col < colCount + 1; col++) { + QTextTableCell headerCell = table->cellAt(0, col+1); + headerCell.setFormat(bgFormat); + QTextCursor headerCellCursor = headerCell.firstCursorPosition(); + QTextBlockFormat blockFormat = headerCellCursor.blockFormat(); + blockFormat.setAlignment(Qt::AlignHCenter); + headerCellCursor.setBlockFormat(blockFormat); + QString data = model()->headerData(col, Qt::Horizontal).toString(); + headerCellCursor.insertText(data, boldFormat); + } + + // The cells + for (const auto& it : cells) { + if (sheet->isMergedCell(it)) { + int rows, cols; + sheet->getSpans(it, rows, cols); + table->mergeCells(it.row() + 1, it.col() + 1, rows, cols); + } + QModelIndex index = model()->index(it.row(), it.col()); + + QTextCharFormat cellFormat; + QTextTableCell cell = table->cellAt(it.row() + 1, it.col() + 1); + + // font + QVariant font = model()->data(index, Qt::FontRole); + if (font.isValid()) { + cellFormat.setFont(font.value()); + } + + // foreground + QVariant fgColor = model()->data(index, Qt::ForegroundRole); + if (fgColor.isValid()) { + cellFormat.setForeground(QBrush(fgColor.value())); + } + + // background + QVariant cbgClor = model()->data(index, Qt::BackgroundRole); + if (cbgClor.isValid()) { + QTextCharFormat bgFormat; + bgFormat.setBackground(QBrush(cbgClor.value())); + cell.setFormat(bgFormat); + } + + QTextCursor cellCursor = cell.firstCursorPosition(); + + // alignment + QVariant align = model()->data(index, Qt::TextAlignmentRole); + if (align.isValid()) { + Qt::Alignment alignment = static_cast(align.toInt()); + QTextBlockFormat blockFormat = cellCursor.blockFormat(); + blockFormat.setAlignment(alignment); + cellCursor.setBlockFormat(blockFormat); + + // This doesn't seem to have any effect on single cells but works if several + // cells are merged + QTextCharFormat::VerticalAlignment valign = QTextCharFormat::AlignMiddle; + QTextCharFormat format = cell.format(); + if (alignment & Qt::AlignTop) { + valign = QTextCharFormat::AlignTop; + } + else if (alignment & Qt::AlignBottom) { + valign = QTextCharFormat::AlignBottom; + } + format.setVerticalAlignment(valign); + cell.setFormat(format); + } + + // text + QString data = model()->data(index).toString().simplified(); + cellCursor.insertText(data, cellFormat); + } + + cursor.movePosition(QTextCursor::End); + cursor.insertBlock(); + return doc->toHtml(); +} + #include "moc_SheetTableView.cpp" diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.h b/src/Mod/Spreadsheet/Gui/SheetTableView.h index 840a4961e2..b8e6c62522 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.h +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.h @@ -58,6 +58,7 @@ public: void edit(const QModelIndex &index); void setSheet(Spreadsheet::Sheet *_sheet); std::vector selectedRanges() const; + QString toHtml() const; public Q_SLOTS: void mergeCells(); diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp b/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp index 3895df45ca..36b444b4c5 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp @@ -26,10 +26,14 @@ # include # include # include +# include +# include +# include # include # include # include # include +# include # include # include # include @@ -46,6 +50,7 @@ #include #include #include +#include #include #include #include @@ -53,7 +58,6 @@ #include "qtcolorpicker.h" #include "SpreadsheetView.h" -#include "SpreadsheetViewPy.h" #include "SpreadsheetDelegate.h" #include "ui_Sheet.h" @@ -192,22 +196,78 @@ bool SheetView::onHasMsg(const char *pMsg) const App::Document* doc = getAppDocument(); return doc && doc->getAvailableUndos() > 0; } - else if (strcmp("Redo",pMsg) == 0) { + if (strcmp("Redo",pMsg) == 0) { App::Document* doc = getAppDocument(); return doc && doc->getAvailableRedos() > 0; } - else if (strcmp("Save",pMsg) == 0) + if (strcmp("Save",pMsg) == 0) return true; - else if (strcmp("SaveAs",pMsg) == 0) + if (strcmp("SaveAs",pMsg) == 0) return true; - else if (strcmp("Cut",pMsg) == 0) + if (strcmp("Cut",pMsg) == 0) return true; - else if (strcmp("Copy",pMsg) == 0) + if (strcmp("Copy",pMsg) == 0) return true; - else if (strcmp("Paste",pMsg) == 0) + if (strcmp("Paste",pMsg) == 0) return true; - else - return false; + if (strcmp(pMsg, "Print") == 0) + return true; + if (strcmp(pMsg, "PrintPreview") == 0) + return true; + if (strcmp(pMsg, "PrintPdf") == 0) + return true; + + return false; +} + +/** + * Shows the printer dialog. + */ +void SheetView::print() +{ + QPrinter printer(QPrinter::ScreenResolution); + printer.setOrientation(QPrinter::Landscape); + printer.setFullPage(true); + QPrintDialog dlg(&printer, this); + if (dlg.exec() == QDialog::Accepted) { + print(&printer); + } +} + +void SheetView::printPreview() +{ + QPrinter printer(QPrinter::ScreenResolution); + printer.setOrientation(QPrinter::Landscape); + QPrintPreviewDialog dlg(&printer, this); + connect(&dlg, SIGNAL(paintRequested (QPrinter *)), + this, SLOT(print(QPrinter *))); + dlg.exec(); +} + +void SheetView::print(QPrinter* printer) +{ +#if 0 + ui->cells->render(printer); +#endif + std::unique_ptr document = std::make_unique(); + document->setHtml(ui->cells->toHtml()); + document->print(printer); +} + +/** + * Prints the document into a Pdf file. + */ +void SheetView::printPdf() +{ + QString filename = FileDialog::getSaveFileName(this, tr("Export PDF"), QString(), + QString::fromLatin1("%1 (*.pdf)").arg(tr("PDF file"))); + if (!filename.isEmpty()) { + QPrinter printer(QPrinter::ScreenResolution); + printer.setOrientation(QPrinter::Landscape); + printer.setOutputFormat(QPrinter::PdfFormat); + printer.setOutputFileName(filename); + print(&printer); + } } void SheetView::setCurrentCell(QString str) @@ -469,8 +529,15 @@ void SheetViewPy::init_type() behaviors().supportRepr(); behaviors().supportGetattr(); behaviors().supportSetattr(); - + + add_varargs_method("selectedRanges", &SheetViewPy::selectedRanges, "selectedRanges(): Get a list of all selected ranges"); + add_varargs_method("selectedCells", &SheetViewPy::selectedCells, "selectedCells(): Get a list of all selected cells"); + add_varargs_method("select", &SheetViewPy::select, "select(cell,flags): Select (or deselect) the given cell, applying QItemSelectionModel.SelectionFlags\nselect(topLeft,bottomRight,flags): Select (or deselect) the given range, applying QItemSelectionModel.SelectionFlags"); + add_varargs_method("currentIndex", &SheetViewPy::currentIndex, "currentIndex(): Get the current index"); + add_varargs_method("setCurrentIndex", &SheetViewPy::setCurrentIndex, "setCurrentIndex(cell): Set the current index to the named cell (e.g. 'A1')"); + add_varargs_method("getSheet", &SheetViewPy::getSheet, "getSheet()"); + add_varargs_method("cast_to_base", &SheetViewPy::cast_to_base, "cast_to_base() cast to MDIView class"); behaviors().readyType(); } @@ -498,8 +565,11 @@ Py::Object SheetViewPy::repr() // appear for SheetViewPy, too. Py::Object SheetViewPy::getattr(const char * attr) { - if (!getSheetViewPtr()) - throw Py::RuntimeError("Cannot print representation of deleted object"); + if (!getSheetViewPtr()) { + std::ostringstream s_out; + s_out << "Cannot access attribute '" << attr << "' of deleted object"; + throw Py::RuntimeError(s_out.str()); + } std::string name( attr ); if (name == "__dict__" || name == "__class__") { Py::Dict dict_self(BaseType::getattr("__dict__")); @@ -531,4 +601,87 @@ Py::Object SheetViewPy::getSheet(const Py::Tuple& args) return Py::asObject(new Spreadsheet::SheetPy(getSheetViewPtr()->getSheet())); } +Py::Object SheetViewPy::cast_to_base(const Py::Tuple&) +{ + return Gui::MDIViewPy::create(base.getMDIViewPtr()); +} + +Py::Object SheetViewPy::selectedRanges(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + SheetView* sheetView = getSheetViewPtr(); + std::vector ranges = sheetView->selectedRanges(); + Py::List list; + for (const auto& range : ranges) + { + list.append(Py::String(range.rangeString())); + } + + return list; +} + +Py::Object SheetViewPy::selectedCells(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + SheetView* sheetView = getSheetViewPtr(); + QModelIndexList cells = sheetView->selectedIndexes(); + Py::List list; + for (const auto& cell : cells) { + list.append(Py::String(App::CellAddress(cell.row(), cell.column()).toString())); + } + + return list; +} + +Py::Object SheetViewPy::select(const Py::Tuple& _args) +{ + SheetView* sheetView = getSheetViewPtr(); + + Py::Sequence args(_args.ptr()); + + const char* cell; + const char* topLeft; + const char* bottomRight; + int flags = 0; + if (args.size() == 2 && PyArg_ParseTuple(_args.ptr(), "si", &cell, &flags)) { + sheetView->select(App::CellAddress(cell), static_cast(flags)); + } + else if (args.size() == 3 && PyArg_ParseTuple(_args.ptr(), "ssi", &topLeft, &bottomRight, &flags)) { + sheetView->select(App::CellAddress(topLeft), App::CellAddress(bottomRight), static_cast(flags)); + } + else { + if (args.size() == 2) + throw Base::TypeError("Expects the arguments to be a cell name (e.g. 'A1') and QItemSelectionModel.SelectionFlags"); + else if (args.size() == 3) + throw Base::TypeError("Expects the arguments to be a cell name (e.g. 'A1'), a second cell name (e.g. 'B5'), and QItemSelectionModel.SelectionFlags"); + else + throw Base::TypeError("Wrong arguments to select: specify either a cell, or two cells (for a range), and QItemSelectionModel.SelectionFlags"); + } + return Py::None(); +} + +Py::Object SheetViewPy::currentIndex(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + SheetView* sheetView = getSheetViewPtr(); + auto index = sheetView->currentIndex(); + Py::String str(App::CellAddress(index.row(), index.column()).toString()); + return str; +} + +Py::Object SheetViewPy::setCurrentIndex(const Py::Tuple& args) +{ + SheetView* sheetView = getSheetViewPtr(); + + const char* cell; + if (PyArg_ParseTuple(args.ptr(), "s", &cell)) { + sheetView->setCurrentIndex(App::CellAddress(cell)); + } + return Py::None(); +} + + #include "moc_SpreadsheetView.cpp" diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetView.h b/src/Mod/Spreadsheet/Gui/SpreadsheetView.h index 03691eace0..46d6665a6c 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetView.h +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetView.h @@ -66,6 +66,14 @@ public: bool onMsg(const char* pMsg,const char** ppReturn); bool onHasMsg(const char* pMsg) const; + /** @name Printing */ + //@{ + void print(); + void printPdf(); + void printPreview(); + void print(QPrinter*); + //@} + void updateCell(const App::Property * prop); Spreadsheet::Sheet * getSheet() { return sheet; } @@ -130,6 +138,13 @@ public: Py::Object repr(); Py::Object getattr(const char *); Py::Object getSheet(const Py::Tuple&); + Py::Object cast_to_base(const Py::Tuple&); + + Py::Object selectedRanges(const Py::Tuple&); + Py::Object selectedCells(const Py::Tuple&); + Py::Object select(const Py::Tuple&); + Py::Object currentIndex(const Py::Tuple&); + Py::Object setCurrentIndex(const Py::Tuple&); SheetView* getSheetViewPtr(); diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetViewPy.xml b/src/Mod/Spreadsheet/Gui/SpreadsheetViewPy.xml deleted file mode 100644 index 618ddfa7b4..0000000000 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetViewPy.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - SpreadsheetView object - - - - returns the sheet being displayed - - - - - diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetViewPyImp.cpp b/src/Mod/Spreadsheet/Gui/SpreadsheetViewPyImp.cpp deleted file mode 100644 index 08b47d8e5d..0000000000 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetViewPyImp.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "PreCompiled.h" - -#include "SpreadsheetViewPy.h" -#include "SpreadsheetViewPy.cpp" -#include "ui_Sheet.h" - -#include - -using namespace SpreadsheetGui; - -PyObject* SpreadsheetViewPy::getSheet(PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) - return nullptr; - return new Spreadsheet::SheetPy(getSheetViewPtr()->getSheet()); -} - -// returns a string which represents the object e.g. when printed in python -std::string SpreadsheetViewPy::representation(void) const -{ - return std::string(""); -} - -PyObject *SpreadsheetViewPy::getCustomAttributes(const char* /*attr*/) const -{ - return 0; -} - -int SpreadsheetViewPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) -{ - return 0; -} diff --git a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPy.xml b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPy.xml index d14f891c03..2434e12d36 100644 --- a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPy.xml +++ b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPy.xml @@ -15,32 +15,6 @@ ViewProviderSheet class - - - returns a list with the selected ranges of cells - - - - - returns a list with the selected cells - - - - - select(index, flags): Select the specified cell using the given QItemSelectionModel.SelectionFlag set -select(topLeft, bottomRight, flags): Select the specified range using the given QItemSelectionModel.SelectionFlag set - - - - - Get the current active cell - - - - - Set the current active cell - - Get access to the sheet view diff --git a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPyImp.cpp b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPyImp.cpp index bea7958ee6..12ba9d5684 100644 --- a/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPyImp.cpp +++ b/src/Mod/Spreadsheet/Gui/ViewProviderSpreadsheetPyImp.cpp @@ -14,83 +14,6 @@ std::string ViewProviderSpreadsheetPy::representation(void) const return std::string(""); } - -PyObject* ViewProviderSpreadsheetPy::selectedRanges(PyObject* /*obj*/) -{ - ViewProviderSheet* vp = this->getViewProviderSheetPtr(); - SheetView *sheetView = vp->getView(); - std::vector ranges = sheetView->selectedRanges(); - Py::List list; - for (const auto &range : ranges) - { - list.append(Py::String(range.rangeString())); - } - - return Py::new_reference_to(list); -} - -PyObject* ViewProviderSpreadsheetPy::selectedCells(PyObject* /*obj*/) -{ - ViewProviderSheet* vp = this->getViewProviderSheetPtr(); - SheetView *sheetView = vp->getView(); - QModelIndexList cells = sheetView->selectedIndexes(); - Py::List list; - for (const auto &cell : cells) { - list.append(Py::String(App::CellAddress(cell.row(), cell.column()).toString())); - } - - return Py::new_reference_to(list); -} - -PyObject* ViewProviderSpreadsheetPy::select(PyObject* _args) -{ - ViewProviderSheet* vp = this->getViewProviderSheetPtr(); - SheetView* sheetView = vp->getView(); - - Py::Sequence args(_args); - - const char* cell; - const char* topLeft; - const char* bottomRight; - int flags = 0; - if (args.size() == 2 && PyArg_ParseTuple(_args, "si", &cell, &flags)) { - sheetView->select(App::CellAddress(cell), static_cast(flags)); - } - else if (args.size() == 3 && PyArg_ParseTuple(_args, "ssi", &topLeft, &bottomRight, &flags)) { - sheetView->select(App::CellAddress(topLeft), App::CellAddress(bottomRight), static_cast(flags)); - } - else { - if (args.size() == 2) - throw Base::TypeError("Expects the arguments to be a cell name (e.g. 'A1') and QItemSelectionModel.SelectionFlags"); - else if (args.size() == 3) - throw Base::TypeError("Expects the arguments to be a cell name (e.g. 'A1'), a second cell name (e.g. 'B5'), and QItemSelectionModel.SelectionFlags"); - else - throw Base::TypeError("Wrong arguments to select: specify either a cell, or two cells (for a range), and QItemSelectionModel.SelectionFlags"); - } - Py_RETURN_NONE; -} - -PyObject* ViewProviderSpreadsheetPy::currentIndex(PyObject* /*_args*/) -{ - ViewProviderSheet* vp = this->getViewProviderSheetPtr(); - SheetView* sheetView = vp->getView(); - auto index = sheetView->currentIndex(); - Py::String str(App::CellAddress(index.row(), index.column()).toString()); - return Py::new_reference_to(str); -} - -PyObject* ViewProviderSpreadsheetPy::setCurrentIndex(PyObject* args) -{ - ViewProviderSheet* vp = this->getViewProviderSheetPtr(); - SheetView* sheetView = vp->getView(); - - const char* cell; - if (PyArg_ParseTuple(args, "s", &cell)) { - sheetView->setCurrentIndex(App::CellAddress(cell)); - } - Py_RETURN_NONE; -} - PyObject* ViewProviderSpreadsheetPy::getView(PyObject* args) { if (!PyArg_ParseTuple(args, "")) diff --git a/src/Mod/TechDraw/Gui/AppTechDrawGui.cpp b/src/Mod/TechDraw/Gui/AppTechDrawGui.cpp index 5f2978d8bb..4e84646f4e 100644 --- a/src/Mod/TechDraw/Gui/AppTechDrawGui.cpp +++ b/src/Mod/TechDraw/Gui/AppTechDrawGui.cpp @@ -125,6 +125,7 @@ PyMOD_INIT_FUNC(TechDrawGui) TechDrawGui::Workbench::init(); TechDrawGui::MDIViewPage::init(); + TechDrawGui::MDIViewPagePy::init_type(); TechDrawGui::ViewProviderPage::init(); TechDrawGui::ViewProviderDrawingView::init(); diff --git a/src/Mod/TechDraw/Gui/CMakeLists.txt b/src/Mod/TechDraw/Gui/CMakeLists.txt index a8cfc77149..1cffb31d8f 100644 --- a/src/Mod/TechDraw/Gui/CMakeLists.txt +++ b/src/Mod/TechDraw/Gui/CMakeLists.txt @@ -36,11 +36,8 @@ else(BUILD_QT5) ) endif(BUILD_QT5) -generate_from_xml(MDIViewPagePy) - # The XML files set(TechDrawGui_XML_SRCS - MDIViewPagePy.xml ) link_directories(${OCC_LIBRARY_DIR}) @@ -206,7 +203,6 @@ SET(TechDrawGui_SRCS SET(TechDrawGuiView_SRCS MDIViewPage.cpp MDIViewPage.h - MDIViewPagePyImp.cpp QGVPage.cpp QGVPage.h QGCustomText.cpp diff --git a/src/Mod/TechDraw/Gui/MDIViewPage.cpp b/src/Mod/TechDraw/Gui/MDIViewPage.cpp index d5404a5cb8..8b305bf0bd 100644 --- a/src/Mod/TechDraw/Gui/MDIViewPage.cpp +++ b/src/Mod/TechDraw/Gui/MDIViewPage.cpp @@ -47,7 +47,6 @@ #include #include "MDIViewPage.h" -#include "MDIViewPagePy.h" #include #include @@ -71,6 +70,7 @@ #include #include +#include #include #include #include @@ -1375,4 +1375,85 @@ MDIViewPage *MDIViewPage::getFromScene(const QGraphicsScene *scene) return nullptr; } +// ---------------------------------------------------------------------------- + +void MDIViewPagePy::init_type() +{ + behaviors().name("MDIViewPagePy"); + behaviors().doc("Python binding class for the MDI view page class"); + // you must have overwritten the virtual functions + behaviors().supportRepr(); + behaviors().supportGetattr(); + behaviors().supportSetattr(); + + add_varargs_method("getPage", &MDIViewPagePy::getPage, "getPage() returns the page being displayed"); + add_varargs_method("cast_to_base", &MDIViewPagePy::cast_to_base, "cast_to_base() cast to MDIView class"); + behaviors().readyType(); +} + +MDIViewPagePy::MDIViewPagePy(MDIViewPage *mdi) + : base(mdi) +{ +} + +MDIViewPagePy::~MDIViewPagePy() +{ +} + +Py::Object MDIViewPagePy::repr() +{ + std::ostringstream s_out; + if (!getMDIViewPagePtr()) + throw Py::RuntimeError("Cannot print representation of deleted object"); + s_out << "MDI view page"; + return Py::String(s_out.str()); +} + +// Since with PyCXX it's not possible to make a sub-class of MDIViewPy +// a trick is to use MDIViewPy as class member and override getattr() to +// join the attributes of both classes. This way all methods of MDIViewPy +// appear for SheetViewPy, too. +Py::Object MDIViewPagePy::getattr(const char * attr) +{ + if (!getMDIViewPagePtr()) { + std::ostringstream s_out; + s_out << "Cannot access attribute '" << attr << "' of deleted object"; + throw Py::RuntimeError(s_out.str()); + } + std::string name( attr ); + if (name == "__dict__" || name == "__class__") { + Py::Dict dict_self(BaseType::getattr("__dict__")); + Py::Dict dict_base(base.getattr("__dict__")); + for (auto it : dict_base) { + dict_self.setItem(it.first, it.second); + } + return dict_self; + } + + try { + return BaseType::getattr(attr); + } + catch (Py::AttributeError& e) { + e.clear(); + return base.getattr(attr); + } +} + +MDIViewPage* MDIViewPagePy::getMDIViewPagePtr() +{ + return qobject_cast(base.getMDIViewPtr()); +} + +Py::Object MDIViewPagePy::getPage(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + return Py::asObject(new TechDraw::DrawPagePy(getMDIViewPagePtr()->getPage())); +} + +Py::Object MDIViewPagePy::cast_to_base(const Py::Tuple&) +{ + return Gui::MDIViewPy::create(base.getMDIViewPtr()); +} + #include diff --git a/src/Mod/TechDraw/Gui/MDIViewPage.h b/src/Mod/TechDraw/Gui/MDIViewPage.h index ba5c85b4cd..e2f2655ce8 100644 --- a/src/Mod/TechDraw/Gui/MDIViewPage.h +++ b/src/Mod/TechDraw/Gui/MDIViewPage.h @@ -27,6 +27,7 @@ #include "ViewProviderPage.h" #include +#include #include #include @@ -96,7 +97,7 @@ public: PyObject* getPyObject(); TechDraw::DrawPage * getPage() { return m_vpPage->getDrawPage(); } - QGVPage* getQGVPage(void) {return m_view;}; + QGVPage* getQGVPage(void) {return m_view;} QGraphicsScene* m_scene; @@ -173,6 +174,26 @@ private: QList deleteItems; }; +class MDIViewPagePy : public Py::PythonExtension +{ +public: + using BaseType = Py::PythonExtension; + static void init_type(); + + MDIViewPagePy(MDIViewPage *mdi); + ~MDIViewPagePy(); + + Py::Object repr(); + Py::Object getattr(const char *); + Py::Object getPage(const Py::Tuple&); + Py::Object cast_to_base(const Py::Tuple&); + + MDIViewPage* getMDIViewPagePtr(); + +protected: + Gui::MDIViewPy base; +}; + } // namespace MDIViewPageGui diff --git a/src/Mod/TechDraw/Gui/MDIViewPagePy.xml b/src/Mod/TechDraw/Gui/MDIViewPagePy.xml deleted file mode 100644 index 25292969f8..0000000000 --- a/src/Mod/TechDraw/Gui/MDIViewPagePy.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - MDIViewPage object - - - - returns the page being displayed - - - - - diff --git a/src/Mod/TechDraw/Gui/MDIViewPagePyImp.cpp b/src/Mod/TechDraw/Gui/MDIViewPagePyImp.cpp deleted file mode 100644 index e59dc8c326..0000000000 --- a/src/Mod/TechDraw/Gui/MDIViewPagePyImp.cpp +++ /dev/null @@ -1,43 +0,0 @@ - -#include "PreCompiled.h" - -#include "Mod/TechDraw/Gui/MDIViewPage.h" - -// inclusion of the generated files (generated out of MDIViewPagePy.xml) -#include "MDIViewPagePy.h" -#include "MDIViewPagePy.cpp" - -#include - -using namespace TechDrawGui; - -// returns a string which represents the object e.g. when printed in python -std::string MDIViewPagePy::representation() const -{ - return std::string(""); -} - - - -PyObject* MDIViewPagePy::getPage(PyObject * args) -{ - if (!PyArg_ParseTuple(args, "")) - return nullptr; - return new TechDraw::DrawPagePy(getMDIViewPagePtr()->getPage()); -} - - - - - -PyObject *MDIViewPagePy::getCustomAttributes(const char* /*attr*/) const -{ - return nullptr; -} - -int MDIViewPagePy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) -{ - return 0; -} - - diff --git a/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp b/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp index 5a252d0b54..d8e3c6571c 100644 --- a/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp @@ -24,6 +24,7 @@ #include "PreCompiled.h" #ifndef _PreComp_ #include +#include #include #include #include @@ -34,6 +35,7 @@ #include #include #include +#include #include #endif diff --git a/src/Mod/TechDraw/Gui/mrichtextedit.cpp b/src/Mod/TechDraw/Gui/mrichtextedit.cpp index 7cc23e368c..ad136a9358 100644 --- a/src/Mod/TechDraw/Gui/mrichtextedit.cpp +++ b/src/Mod/TechDraw/Gui/mrichtextedit.cpp @@ -146,15 +146,15 @@ MRichTextEdit::MRichTextEdit(QWidget *parent, QString textIn) : QWidget(parent) // link - f_link->setShortcut(Qt::CTRL + Qt::Key_L); + f_link->setShortcut(QKeySequence(QString::fromUtf8("CTRL+L"))); connect(f_link, SIGNAL(clicked(bool)), this, SLOT(textLink(bool))); // bold, italic & underline - f_bold->setShortcut(Qt::CTRL + Qt::Key_B); - f_italic->setShortcut(Qt::CTRL + Qt::Key_I); - f_underline->setShortcut(Qt::CTRL + Qt::Key_U); + f_bold->setShortcut(QKeySequence(QString::fromUtf8("CTRL+B"))); + f_italic->setShortcut(QKeySequence(QString::fromUtf8("CTRL+I"))); + f_underline->setShortcut(QKeySequence(QString::fromUtf8("CTRL+U"))); connect(f_bold, SIGNAL(clicked()), this, SLOT(textBold())); connect(f_italic, SIGNAL(clicked()), this, SLOT(textItalic())); @@ -184,16 +184,16 @@ MRichTextEdit::MRichTextEdit(QWidget *parent, QString textIn) : QWidget(parent) // lists - f_list_bullet->setShortcut(Qt::CTRL + Qt::Key_Minus); - f_list_ordered->setShortcut(Qt::CTRL + Qt::Key_Equal); + f_list_bullet->setShortcut(QKeySequence(QString::fromUtf8("CTRL+-"))); + f_list_ordered->setShortcut(QKeySequence(QString::fromUtf8("CTRL+="))); connect(f_list_bullet, SIGNAL(clicked(bool)), this, SLOT(listBullet(bool))); connect(f_list_ordered, SIGNAL(clicked(bool)), this, SLOT(listOrdered(bool))); // indentation - f_indent_dec->setShortcut(Qt::CTRL + Qt::Key_Comma); - f_indent_inc->setShortcut(Qt::CTRL + Qt::Key_Period); + f_indent_dec->setShortcut(QKeySequence(QString::fromUtf8("CTRL+,"))); + f_indent_inc->setShortcut(QKeySequence(QString::fromUtf8("CTRL+."))); connect(f_indent_inc, SIGNAL(clicked()), this, SLOT(increaseIndentation())); connect(f_indent_dec, SIGNAL(clicked()), this, SLOT(decreaseIndentation())); diff --git a/src/Mod/Test/CMakeLists.txt b/src/Mod/Test/CMakeLists.txt index 741a12e451..81d1422ed8 100644 --- a/src/Mod/Test/CMakeLists.txt +++ b/src/Mod/Test/CMakeLists.txt @@ -13,7 +13,6 @@ SET(Test_SRCS unittestgui.py testmakeWireString.py TestPythonSyntax.py - TreeView.py ) SOURCE_GROUP("" FILES ${Test_SRCS}) diff --git a/src/Mod/Test/Document.py b/src/Mod/Test/Document.py index fd5eb15af4..663b1a662e 100644 --- a/src/Mod/Test/Document.py +++ b/src/Mod/Test/Document.py @@ -1325,7 +1325,7 @@ class DocumentPropertyCases(unittest.TestCase): # testing the up and downstream stuff props=self.Obj.supportedProperties() for i in props: - self.Obj.addProperty(i,i) + self.Obj.addProperty(i,i.replace(':','_')) tempPath = tempfile.gettempdir() tempFile = tempPath + os.sep + "PropertyTests.FCStd" self.Doc.saveAs(tempFile) diff --git a/src/Mod/Test/InitGui.py b/src/Mod/Test/InitGui.py index 2bd25e6850..cbb18f8588 100644 --- a/src/Mod/Test/InitGui.py +++ b/src/Mod/Test/InitGui.py @@ -77,5 +77,4 @@ Gui.addWorkbench(TestWorkbench()) FreeCAD.__unit_test__ += [ "Workbench", "Menu", "Menu.MenuDeleteCases", - "Menu.MenuCreateCases", - "TreeView"] + "Menu.MenuCreateCases" ] diff --git a/src/Mod/Test/TreeView.py b/src/Mod/Test/TreeView.py deleted file mode 100644 index 019309ae1a..0000000000 --- a/src/Mod/Test/TreeView.py +++ /dev/null @@ -1,244 +0,0 @@ - -# TreeView test module - -import os -import time -import tempfile -import unittest -import FreeCAD - -from PySide import QtCore, QtGui -import FreeCADGui - - -class TreeViewTestCase(unittest.TestCase): - def setUp(self): - pass - - def tearDown(self): - pass - - def getTreeWidget(self): - mainWnd = FreeCADGui.getMainWindow() - treeDock = mainWnd.findChild(QtGui.QDockWidget, "Tree View") - if treeDock is None: - treeDock = mainWnd.findChild(QtGui.QDockWidget, "Combo View") - if not treeDock is None: - tabWidget = treeDock.findChild(QtGui.QTabWidget, "combiTab") - if not tabWidget is None: - tabWidget.setCurrentIndex(0) - self.assertTrue(not treeDock is None, "No Tree View docks available") - - treeView = treeDock.findChild(QtGui.QTreeWidget) - self.assertTrue(not treeView is None, "No Tree View widget found") - - return treeView - - def waitForTreeViewSync(self): - start = time.time() - while time.time() < start + 0.5: - FreeCADGui.updateGui() - - def selectDocItem(self, docItem): - - treeView = self.getTreeWidget() - - if docItem.TypeId == "App::Document": - appNode = treeView.topLevelItem(0) - self.assertTrue(not appNode is None, "No Application top level node") - - docNode = next((appNode.child(i) for i in range(appNode.childCount()) - if appNode.child(i).text(0) == docItem.Label), None) - self.assertTrue(not docNode is None, "No test Document node") - treeView.setCurrentItem(docNode) - else: - FreeCADGui.Selection.clearSelection() - FreeCADGui.Selection.addSelection(docItem) - self.waitForTreeViewSync() - - def trySwapOuterTreeViewItems(self, docItem, transposable): - - self.selectDocItem(docItem) - treeView = self.getTreeWidget() - - selected = treeView.selectedItems() - self.assertTrue(len(selected) == 1, - "Unexpected count of selected items: " + str(len(selected))) - selected = selected[0] - - originalState = [ selected.child(i).text(0) for i in range(selected.childCount()) ] - self.assertTrue(len(originalState) >= 1, - "No children found in item " + selected.text(0)) - - targetState = originalState.copy() - if transposable: - targetState[0], targetState[-1] = targetState[-1], targetState[0] - - treeView.setCurrentItem(selected.child(0)) - self.waitForTreeViewSync() - - # One move down attempt more to test boundary behaviour - for i in range(len(originalState)): - FreeCADGui.runCommand("Std_GroupMoveDown") - self.waitForTreeViewSync() - - treeView.setCurrentItem(selected.child(len(originalState) - 2 if len(originalState) > 1 else 0)) - self.waitForTreeViewSync() - - # One move up attempt more to test boundary behaviour - for i in range(len(originalState) - 1): - FreeCADGui.runCommand("Std_GroupMoveUp") - self.waitForTreeViewSync() - - finalState = [ selected.child(i).text(0) for i in range(selected.childCount()) ] - self.assertTrue(targetState == finalState, - "Unexpected final state: %s\nExpected: %s" % (finalState, targetState)) - - def getChildrenOf(self, docItem): - - self.selectDocItem(docItem) - treeView = self.getTreeWidget() - - selected = treeView.selectedItems() - self.assertTrue(len(selected) == 1, - "Unexpected count of selected items: " + str(len(selected))) - selected = selected[0] - - return [ selected.child(i).text(0) for i in range(selected.childCount()) ] - - - def testMoveTransposableItems(self): - # Makes sense only if Gui is shown - if not FreeCAD.GuiUp: - return - - FreeCAD.TestEnvironment = True - - doc = FreeCAD.newDocument("TreeViewTest1") - FreeCAD.setActiveDocument(doc.Name) - - box = doc.addObject("Part::Box", "Box") - cyl = doc.addObject("Part::Cylinder", "Cylinder") - sph = doc.addObject("Part::Sphere", "Sphere") - con = doc.addObject("Part::Cone", "Cone") - doc.recompute() - - self.trySwapOuterTreeViewItems(doc, True) - - grp = doc.addObject("App::DocumentObjectGroup", "Group") - grp.addObjects([ box, cyl, sph, con ]) - doc.recompute() - - self.trySwapOuterTreeViewItems(grp, True) - - del FreeCAD.TestEnvironment - - - def testMoveUnmovableItems(self): - # Makes sense only if Gui is shown - if not FreeCAD.GuiUp: - return - - FreeCAD.TestEnvironment = True - - doc = FreeCAD.newDocument("TreeViewTest2") - FreeCAD.setActiveDocument(doc.Name) - - sph = doc.addObject("Part::Sphere", "Sphere") - con = doc.addObject("Part::Cone", "Cone") - doc.recompute() - - cut = doc.addObject("Part::Cut", "Cut") - cut.Base = sph - cut.Tool = con - doc.recompute() - - self.trySwapOuterTreeViewItems(cut, False) - - bdy = doc.addObject("PartDesign::Body", "Body") - box = doc.addObject("PartDesign::AdditiveBox", "Box") - bdy.addObject(box) - doc.recompute() - - FreeCADGui.Selection.clearSelection() - FreeCADGui.Selection.addSelection(bdy) - self.waitForTreeViewSync() - - treeView = self.getTreeWidget() - treeView.selectedItems()[0].setExpanded(True) - self.waitForTreeViewSync() - - FreeCADGui.Selection.clearSelection() - FreeCADGui.Selection.addSelection(doc.Name, bdy.Name, box.Name + ".Face6") - self.waitForTreeViewSync() - - cha = bdy.newObject("PartDesign::Chamfer", "Chamfer") - cha.Base = (box, ["Face6"]) - doc.recompute() - - cyl = doc.addObject("PartDesign::SubtractiveCylinder", "Cylinder") - bdy.addObject(cyl) - doc.recompute() - - self.trySwapOuterTreeViewItems(bdy, False) - - del FreeCAD.TestEnvironment - - - def testItemOrderSaveAndRestore(self): - # Makes sense only if Gui is shown - if not FreeCAD.GuiUp: - return - - FreeCAD.TestEnvironment = True - - doc = FreeCAD.newDocument("TreeViewTest3") - FreeCAD.setActiveDocument(doc.Name) - - grp = doc.addObject("App::DocumentObjectGroup", "Group") - box = doc.addObject("Part::Box", "Box") - cyl = doc.addObject("Part::Cylinder", "Cylinder") - sph = doc.addObject("Part::Sphere", "Sphere") - con = doc.addObject("Part::Cone", "Cone") - doc.recompute() - - origOrder = self.getChildrenOf(doc) - self.assertTrue(origOrder == ["Group", "Box", "Cylinder", "Sphere", "Cone"]) - - origOrderFile = tempfile.gettempdir() + os.sep + "TreeViewTest3_1.fcstd" - doc.saveAs(origOrderFile) - - FreeCADGui.Selection.clearSelection() - FreeCADGui.Selection.addSelection(con) - FreeCADGui.Selection.addSelection(cyl) - self.waitForTreeViewSync() - - FreeCADGui.runCommand("Std_GroupMoveUp") - self.waitForTreeViewSync() - - FreeCADGui.Selection.clearSelection() - FreeCADGui.Selection.addSelection(grp) - FreeCADGui.Selection.addSelection(box) - self.waitForTreeViewSync() - - FreeCADGui.runCommand("Std_GroupMoveDown") - self.waitForTreeViewSync() - - newOrder = self.getChildrenOf(doc) - self.assertTrue(newOrder == ["Cylinder", "Cone", "Sphere", "Group", "Box"]) - - newOrderFile = tempfile.gettempdir() + os.sep + "TreeViewTest3_2.fcstd" - doc.saveAs(newOrderFile) - - FreeCAD.closeDocument(doc.Name) - self.waitForTreeViewSync() - - doc = FreeCAD.open(origOrderFile) - order = self.getChildrenOf(doc) - self.assertTrue(order == origOrder) - - doc = FreeCAD.open(newOrderFile) - order = self.getChildrenOf(doc) - self.assertTrue(order == newOrder) - - del FreeCAD.TestEnvironment diff --git a/src/Mod/Web/Gui/AppWebGui.cpp b/src/Mod/Web/Gui/AppWebGui.cpp index a82508b93d..f48928d97a 100644 --- a/src/Mod/Web/Gui/AppWebGui.cpp +++ b/src/Mod/Web/Gui/AppWebGui.cpp @@ -170,6 +170,7 @@ PyMOD_INIT_FUNC(WebGui) // instantiating the commands CreateWebCommands(); + WebGui::BrowserView::init(); WebGui::Workbench::init(); // add resources and reloads the translators diff --git a/src/Mod/Web/Gui/BrowserView.cpp b/src/Mod/Web/Gui/BrowserView.cpp index 3914e06ffd..2c60b5f760 100644 --- a/src/Mod/Web/Gui/BrowserView.cpp +++ b/src/Mod/Web/Gui/BrowserView.cpp @@ -77,6 +77,7 @@ using QWebEnginePage = QWebPage; #include "CookieJar.h" #include #include +#include #include #include #include @@ -170,17 +171,25 @@ void UrlWidget::display() class BrowserViewPy : public Py::PythonExtension { public: - static void init_type(void); // announce properties and methods + using BaseType = Py::PythonExtension; + static void init_type(); // announce properties and methods BrowserViewPy(BrowserView* view); ~BrowserViewPy(); Py::Object repr(); + Py::Object getattr(const char *); + Py::Object cast_to_base(const Py::Tuple&); Py::Object setHtml(const Py::Tuple&); + Py::Object load(const Py::Tuple&); + Py::Object stop(const Py::Tuple&); + Py::Object url(const Py::Tuple&); + + BrowserView* getBrowserViewPtr(); private: - QPointer myWebView; + Gui::MDIViewPy base; }; void BrowserViewPy::init_type() @@ -194,9 +203,13 @@ void BrowserViewPy::init_type() behaviors().readyType(); add_varargs_method("setHtml",&BrowserViewPy::setHtml,"setHtml(str)"); + add_varargs_method("load",&BrowserViewPy::load,"load(url)"); + add_varargs_method("stop",&BrowserViewPy::stop,"stop()"); + add_varargs_method("url",&BrowserViewPy::url,"url()"); + add_varargs_method("cast_to_base", &BrowserViewPy::cast_to_base, "cast_to_base() cast to MDIView class"); } -BrowserViewPy::BrowserViewPy(BrowserView* view) : myWebView(view) +BrowserViewPy::BrowserViewPy(BrowserView* view) : base(view) { } @@ -204,6 +217,16 @@ BrowserViewPy::~BrowserViewPy() { } +BrowserView* BrowserViewPy::getBrowserViewPtr() +{ + return qobject_cast(base.getMDIViewPtr()); +} + +Py::Object BrowserViewPy::cast_to_base(const Py::Tuple&) +{ + return Gui::MDIViewPy::create(base.getMDIViewPtr()); +} + Py::Object BrowserViewPy::repr() { std::stringstream s; @@ -211,6 +234,36 @@ Py::Object BrowserViewPy::repr() return Py::String(s.str()); } +// Since with PyCXX it's not possible to make a sub-class of MDIViewPy +// a trick is to use MDIViewPy as class member and override getattr() to +// join the attributes of both classes. This way all methods of MDIViewPy +// appear for SheetViewPy, too. +Py::Object BrowserViewPy::getattr(const char * attr) +{ + if (!getBrowserViewPtr()) { + std::ostringstream s_out; + s_out << "Cannot access attribute '" << attr << "' of deleted object"; + throw Py::RuntimeError(s_out.str()); + } + std::string name( attr ); + if (name == "__dict__" || name == "__class__") { + Py::Dict dict_self(BaseType::getattr("__dict__")); + Py::Dict dict_base(base.getattr("__dict__")); + for (auto it : dict_base) { + dict_self.setItem(it.first, it.second); + } + return dict_self; + } + + try { + return BaseType::getattr(attr); + } + catch (Py::AttributeError& e) { + e.clear(); + return base.getattr(attr); + } +} + Py::Object BrowserViewPy::setHtml(const Py::Tuple& args) { char* HtmlCode; @@ -221,10 +274,38 @@ Py::Object BrowserViewPy::setHtml(const Py::Tuple& args) std::string EncodedHtml = std::string(HtmlCode); PyMem_Free(HtmlCode); - if (myWebView) - myWebView->setHtml(QString::fromUtf8(EncodedHtml.c_str()), QUrl(QString::fromUtf8(BaseUrl))); + getBrowserViewPtr()->setHtml(QString::fromUtf8(EncodedHtml.c_str()), QUrl(QString::fromUtf8(BaseUrl))); return Py::None(); } + +Py::Object BrowserViewPy::load(const Py::Tuple& args) +{ + char* BaseUrl; + if (!PyArg_ParseTuple(args.ptr(), "s", &BaseUrl)) + throw Py::Exception(); + + getBrowserViewPtr()->load(BaseUrl); + return Py::None(); +} + +Py::Object BrowserViewPy::stop(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + + getBrowserViewPtr()->stop(); + return Py::None(); +} + +Py::Object BrowserViewPy::url(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + + QUrl url = getBrowserViewPtr()->url(); + return Py::String(url.toString().toStdString()); +} + } /** @@ -359,6 +440,8 @@ void WebView::triggerContextMenuAction(int id) /* TRANSLATOR Gui::BrowserView */ +TYPESYSTEM_SOURCE_ABSTRACT(WebGui::BrowserView, Gui::MDIView) + /** * Constructs a BrowserView which is a child of 'parent', with the * name 'name'. diff --git a/src/Mod/Web/Gui/BrowserView.h b/src/Mod/Web/Gui/BrowserView.h index 6123460479..9ce249eddf 100644 --- a/src/Mod/Web/Gui/BrowserView.h +++ b/src/Mod/Web/Gui/BrowserView.h @@ -33,7 +33,7 @@ #include namespace WebGui { class WebEngineUrlRequestInterceptor; -}; +} #elif defined(QTWEBKIT) #include #endif @@ -79,6 +79,8 @@ class WebGuiExport BrowserView : public Gui::MDIView, { Q_OBJECT + TYPESYSTEM_HEADER(); + public: BrowserView(QWidget* parent); ~BrowserView(); diff --git a/src/Tools/fcbt/CreatePyModule.py b/src/Tools/fcbt/CreatePyModule.py index ab5e31ee27..25f8e45a36 100644 --- a/src/Tools/fcbt/CreatePyModule.py +++ b/src/Tools/fcbt/CreatePyModule.py @@ -115,6 +115,7 @@ def validateApp(AppName): sys.exit() sys.stdout.write("Please enter a name for your application:") +sys.stdout.flush() AppName = sys.stdin.readline()[:-1] validateApp(AppName) createApp(AppName) diff --git a/src/WindowsInstaller/include/init.nsh b/src/WindowsInstaller/include/init.nsh index af385577cd..f11153ef46 100644 --- a/src/WindowsInstaller/include/init.nsh +++ b/src/WindowsInstaller/include/init.nsh @@ -89,8 +89,8 @@ Function .onInit ${endif} ${if} $0 != "" # check if the uninstaller was acidentally deleted - # if so don't bother the user if he really wants to install a new FreeCAD over an existing one - # because he won't have a chance to deny this + # if so, don't bother the user if they really want to install a new FreeCAD over an existing one + # because they won't have a chance to deny this StrCpy $4 $0 -16 # remove '\bin\FreeCAD.exe' # (for FileCheck the variables $0 and $1 cannot be used) !insertmacro FileCheck $5 "Uninstall-${APP_NAME}.exe" "$4" # macro from Utils.nsh @@ -98,7 +98,7 @@ Function .onInit Goto ForceInstallation ${endif} # installing over an existing installation of the same FreeCAD release is not necessary - # if the users does this he most probably has a problem with FreeCAD that can better be solved + # if the users does this, they most probably have a problem with FreeCAD that can better be solved # by reinstalling FreeCAD # for beta and other test releases over-installing can even cause errors MessageBox MB_YESNO|MB_DEFBUTTON2|MB_ICONEXCLAMATION "$(AlreadyInstalled)" /SD IDNO IDYES ForceInstallation