diff --git a/cMake/FreeCAD_Helpers/CreatePackagingTargets.cmake b/cMake/FreeCAD_Helpers/CreatePackagingTargets.cmake index 60cd19c758..de8b52c6d3 100644 --- a/cMake/FreeCAD_Helpers/CreatePackagingTargets.cmake +++ b/cMake/FreeCAD_Helpers/CreatePackagingTargets.cmake @@ -5,25 +5,37 @@ macro(CreatePackagingTargets) #add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source) add_custom_target(dist-git COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/src/Tools/makedist.py - --srcdir=${CMAKE_SOURCE_DIR} --bindir=${CMAKE_BINARY_DIR} + --bindir=${CMAKE_BINARY_DIR} + --major=${PACKAGE_VERSION_MAJOR} + --minor=${PACKAGE_VERSION_MINOR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) add_custom_target(distdfsg-git COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/src/Tools/makedist.py - --srcdir=${CMAKE_SOURCE_DIR} --bindir=${CMAKE_BINARY_DIR} --dfsg + --bindir=${CMAKE_BINARY_DIR} + --major=${PACKAGE_VERSION_MAJOR} + --minor=${PACKAGE_VERSION_MINOR} + --dfsg WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) - if(CMAKE_COMPILER_IS_GNUCXX OR MINGW) + if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX OR MINGW) add_custom_target(distcheck-git COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/src/Tools/makedist.py - --srcdir=${CMAKE_SOURCE_DIR} --bindir=${CMAKE_BINARY_DIR} --check + --bindir=${CMAKE_BINARY_DIR} + --major=${PACKAGE_VERSION_MAJOR} + --minor=${PACKAGE_VERSION_MINOR} + --check WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) add_custom_target(distcheckdfsg-git COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/src/Tools/makedist.py - --srcdir=${CMAKE_SOURCE_DIR} --bindir=${CMAKE_BINARY_DIR} --dfsg --check + --bindir=${CMAKE_BINARY_DIR} + --major=${PACKAGE_VERSION_MAJOR} + --minor=${PACKAGE_VERSION_MINOR} + --dfsg + --check WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) - endif(CMAKE_COMPILER_IS_GNUCXX OR MINGW) + endif() endmacro(CreatePackagingTargets) diff --git a/cMake/FreeCAD_Helpers/SetupQt.cmake b/cMake/FreeCAD_Helpers/SetupQt.cmake index 1e5519fe23..230d4198a0 100644 --- a/cMake/FreeCAD_Helpers/SetupQt.cmake +++ b/cMake/FreeCAD_Helpers/SetupQt.cmake @@ -23,9 +23,9 @@ if(BUILD_GUI) elseif(${FREECAD_USE_QTWEBMODULE} MATCHES "Qt WebEngine") find_package(Qt5WebEngineWidgets REQUIRED) else() # Automatic - find_package(Qt5WebKitWidgets QUIET) - if(NOT Qt5WebKitWidgets_FOUND) - find_package(Qt5WebEngineWidgets REQUIRED) + find_package(Qt5WebEngineWidgets QUIET) + if(NOT Qt5WebEngineWidgets_FOUND) + find_package(Qt5WebKitWidgets REQUIRED) endif() endif() endif() diff --git a/data/examples/PartDesignExample.FCStd b/data/examples/PartDesignExample.FCStd index 8e5278bc54..58a0116085 100644 Binary files a/data/examples/PartDesignExample.FCStd and b/data/examples/PartDesignExample.FCStd differ diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 2cdda7d8f5..c42d186de0 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -55,6 +55,8 @@ #include #endif +#include + #include "Application.h" #include "Document.h" @@ -99,6 +101,7 @@ #include "Document.h" #include "DocumentObjectGroup.h" #include "DocumentObjectFileIncluded.h" +#include "DocumentObserver.h" #include "InventorObject.h" #include "VRMLObject.h" #include "Annotation.h" @@ -487,6 +490,7 @@ bool Application::closeDocument(const char* name) setActiveDocument((Document*)0); std::unique_ptr delDoc (pos->second); DocMap.erase( pos ); + DocFileMap.erase(FileInfo(delDoc->FileName.getValue()).filePath()); _objCount = -1; @@ -566,8 +570,10 @@ int Application::addPendingDocument(const char *FileName, const char *objName, b return -1; assert(FileName && FileName[0]); assert(objName && objName[0]); - auto ret = _pendingDocMap.emplace(FileName,std::set()); - ret.first->second.emplace(objName); + if(!_docReloadAttempts[FileName].emplace(objName).second) + return -1; + auto ret = _pendingDocMap.emplace(FileName,std::vector()); + ret.first->second.push_back(objName); if(ret.second) { _pendingDocs.push_back(ret.first->first.c_str()); return 1; @@ -623,6 +629,41 @@ Document* Application::openDocument(const char * FileName, bool createView) { return 0; } +Document *Application::getDocumentByPath(const char *path, PathMatchMode checkCanonical) const { + if(!path || !path[0]) + return nullptr; + if(DocFileMap.empty()) { + for(const auto &v : DocMap) { + const auto &file = v.second->FileName.getStrValue(); + if(file.size()) + DocFileMap[FileInfo(file.c_str()).filePath()] = v.second; + } + } + auto it = DocFileMap.find(FileInfo(path).filePath()); + if(it != DocFileMap.end()) + return it->second; + + if (checkCanonical == PathMatchMode::MatchAbsolute) + return nullptr; + + std::string filepath = FileInfo(path).filePath(); + QString canonicalPath = QFileInfo(QString::fromUtf8(path)).canonicalFilePath(); + for (const auto &v : DocMap) { + QFileInfo fi(QString::fromUtf8(v.second->FileName.getValue())); + if (canonicalPath == fi.canonicalFilePath()) { + if (checkCanonical == PathMatchMode::MatchCanonical) + return v.second; + bool samePath = (canonicalPath == QString::fromUtf8(filepath.c_str())); + FC_WARN("Identical physical path '" << canonicalPath.toUtf8().constData() << "'\n" + << (samePath?"":" for file '") << (samePath?"":filepath.c_str()) << (samePath?"":"'\n") + << " with existing document '" << v.second->Label.getValue() + << "' in path: '" << v.second->FileName.getValue() << "'"); + break; + } + } + return nullptr; +} + std::vector Application::openDocuments(const std::vector &filenames, const std::vector *paths, const std::vector *labels, @@ -640,6 +681,7 @@ std::vector Application::openDocuments(const std::vector _pendingDocs.clear(); _pendingDocsReopen.clear(); _pendingDocMap.clear(); + _docReloadAttempts.clear(); signalStartOpenDocument(); @@ -649,118 +691,162 @@ std::vector Application::openDocuments(const std::vector for (auto &name : filenames) _pendingDocs.push_back(name.c_str()); - std::map newDocs; + std::map timings; FC_TIME_INIT(t); - for (std::size_t count=0;; ++count) { - const char *name = _pendingDocs.front(); - _pendingDocs.pop_front(); - bool isMainDoc = count < filenames.size(); + std::vector openedDocs; - try { - _objCount = -1; - std::set objNames; - if (_allowPartial) { - auto it = _pendingDocMap.find(name); - if (it != _pendingDocMap.end()) - objNames.swap(it->second); - } + int pass = 0; + do { + std::set newDocs; + for (std::size_t count=0;; ++count) { + std::string name = std::move(_pendingDocs.front()); + _pendingDocs.pop_front(); + bool isMainDoc = (pass == 0 && count < filenames.size()); - FC_TIME_INIT(t1); - DocTiming timing; + try { + _objCount = -1; + std::vector objNames; + if (_allowPartial) { + auto it = _pendingDocMap.find(name); + if (it != _pendingDocMap.end()) { + if(isMainDoc) + it->second.clear(); + else + objNames.swap(it->second); + _pendingDocMap.erase(it); + } + } - const char *path = name; - const char *label = 0; - if (isMainDoc) { - if (paths && paths->size()>count) - path = (*paths)[count].c_str(); + FC_TIME_INIT(t1); + DocTiming timing; - if (labels && labels->size()>count) - label = (*labels)[count].c_str(); - } + const char *path = name.c_str(); + const char *label = 0; + if (isMainDoc) { + if (paths && paths->size()>count) + path = (*paths)[count].c_str(); - auto doc = openDocumentPrivate(path, name, label, isMainDoc, createView, objNames); - FC_DURATION_PLUS(timing.d1,t1); - if (doc) - newDocs.emplace(doc,timing); + if (labels && labels->size()>count) + label = (*labels)[count].c_str(); + } + + auto doc = openDocumentPrivate(path, name.c_str(), label, isMainDoc, createView, std::move(objNames)); + FC_DURATION_PLUS(timing.d1,t1); + if (doc) { + timings[doc].d1 += timing.d1; + newDocs.emplace(doc); + } - if (isMainDoc) - res[count] = doc; - _objCount = -1; - } - catch (const Base::Exception &e) { - if (!errs && isMainDoc) - throw; - if (errs && isMainDoc) - (*errs)[count] = e.what(); - else - Console().Error("Exception opening file: %s [%s]\n", name, e.what()); - } - catch (const std::exception &e) { - if (!errs && isMainDoc) - throw; - if (errs && isMainDoc) - (*errs)[count] = e.what(); - else - Console().Error("Exception opening file: %s [%s]\n", name, e.what()); - } - catch (...) { - if (errs) { if (isMainDoc) - (*errs)[count] = "unknown error"; + res[count] = doc; + _objCount = -1; } - else { - _pendingDocs.clear(); + catch (const Base::Exception &e) { + e.ReportException(); + if (!errs && isMainDoc) + throw; + if (errs && isMainDoc) + (*errs)[count] = e.what(); + else + Console().Error("Exception opening file: %s [%s]\n", name.c_str(), e.what()); + } + catch (const std::exception &e) { + if (!errs && isMainDoc) + throw; + if (errs && isMainDoc) + (*errs)[count] = e.what(); + else + Console().Error("Exception opening file: %s [%s]\n", name.c_str(), e.what()); + } + catch (...) { + if (errs) { + if (isMainDoc) + (*errs)[count] = "unknown error"; + } + else { + _pendingDocs.clear(); + _pendingDocsReopen.clear(); + _pendingDocMap.clear(); + throw; + } + } + + if (_pendingDocs.empty()) { + if(_pendingDocsReopen.empty()) + break; + _pendingDocs = std::move(_pendingDocsReopen); _pendingDocsReopen.clear(); - _pendingDocMap.clear(); - throw; + for(const auto &file : _pendingDocs) { + auto doc = getDocumentByPath(file.c_str()); + if(doc) + closeDocument(doc->getName()); + } } } - if (_pendingDocs.empty()) { - if (_pendingDocsReopen.empty()) - break; - _allowPartial = false; - _pendingDocs.swap(_pendingDocsReopen); + ++pass; + _pendingDocMap.clear(); + + std::vector docs; + docs.reserve(newDocs.size()); + for(const auto &d : newDocs) { + auto doc = d.getDocument(); + if(!doc) + continue; + // Notify PropertyXLink to attach newly opened documents and restore + // relevant external links + PropertyXLink::restoreDocument(*doc); + docs.push_back(doc); } - } - _pendingDocs.clear(); - _pendingDocsReopen.clear(); - _pendingDocMap.clear(); + Base::SequencerLauncher seq("Postprocessing...", docs.size()); - Base::SequencerLauncher seq("Postprocessing...", newDocs.size()); - - std::vector docs; - docs.reserve(newDocs.size()); - for (auto &v : newDocs) { - // Notify PropertyXLink to attach newly opened documents and restore - // relevant external links - PropertyXLink::restoreDocument(*v.first); - docs.push_back(v.first); - } - - // After external links has been restored, we can now sort the document - // according to their dependency order. - docs = Document::getDependentDocuments(docs, true); - for (auto it=docs.begin(); it!=docs.end();) { - Document *doc = *it; - // It is possible that the newly opened document depends on an existing - // document, which will be included with the above call to - // Document::getDependentDocuments(). Make sure to exclude that. - auto dit = newDocs.find(doc); - if (dit == newDocs.end()) { - it = docs.erase(it); - continue; + // After external links has been restored, we can now sort the document + // according to their dependency order. + try { + docs = Document::getDependentDocuments(docs, true); + } catch (Base::Exception &e) { + e.ReportException(); } - ++it; - FC_TIME_INIT(t1); - // Finalize document restoring with the correct order - doc->afterRestore(true); - FC_DURATION_PLUS(dit->second.d2,t1); - seq.next(); - } + for(auto it=docs.begin(); it!=docs.end();) { + auto doc = *it; + + // It is possible that the newly opened document depends on an existing + // document, which will be included with the above call to + // Document::getDependentDocuments(). Make sure to exclude that. + if(!newDocs.count(doc)) { + it = docs.erase(it); + continue; + } + + auto &timing = timings[doc]; + FC_TIME_INIT(t1); + // Finalize document restoring with the correct order + if(doc->afterRestore(true)) { + openedDocs.push_back(doc); + it = docs.erase(it); + } else { + ++it; + // Here means this is a partial loaded document, and we need to + // reload it fully because of touched objects. The reason of + // reloading a partial document with touched object is because + // partial document is supposed to be readonly, while a + // 'touched' object requires recomputation. And an object may + // become touched during restoring if externally linked + // document time stamp mismatches with the stamp saved. + _pendingDocs.push_back(doc->FileName.getValue()); + _pendingDocMap.erase(doc->FileName.getValue()); + } + FC_DURATION_PLUS(timing.d2,t1); + seq.next(); + } + // Close the document for reloading + for(const auto doc : docs) + closeDocument(doc->getName()); + + }while(!_pendingDocs.empty()); // Set the active document using the first successfully restored main // document (i.e. documents explicitly asked for by caller). @@ -771,14 +857,14 @@ std::vector Application::openDocuments(const std::vector } } - for (auto doc : docs) { - auto &timing = newDocs[doc]; - FC_DURATION_LOG(timing.d1, doc->getName() << " restore"); - FC_DURATION_LOG(timing.d2, doc->getName() << " postprocess"); + for (auto &doc : openedDocs) { + auto &timing = timings[doc]; + FC_DURATION_LOG(timing.d1, doc.getDocumentName() << " restore"); + FC_DURATION_LOG(timing.d2, doc.getDocumentName() << " postprocess"); } FC_TIME_LOG(t,"total"); - _isRestoring = false; + signalFinishOpenDocument(); return res; } @@ -786,7 +872,7 @@ std::vector Application::openDocuments(const std::vector Document* Application::openDocumentPrivate(const char * FileName, const char *propFileName, const char *label, bool isMainDoc, bool createView, - const std::set &objNames) + std::vector &&objNames) { FileInfo File(FileName); @@ -797,55 +883,51 @@ Document* Application::openDocumentPrivate(const char * FileName, } // Before creating a new document we check whether the document is already open - std::string filepath = File.filePath(); - QString canonicalPath = QFileInfo(QString::fromUtf8(FileName)).canonicalFilePath(); - for (std::map::iterator it = DocMap.begin(); it != DocMap.end(); ++it) { - // get unique path separators - std::string fi = FileInfo(it->second->FileName.getValue()).filePath(); - if (filepath != fi) { - if (canonicalPath == QFileInfo(QString::fromUtf8(fi.c_str())).canonicalFilePath()) { - bool samePath = (canonicalPath == QString::fromUtf8(FileName)); - FC_WARN("Identical physical path '" << canonicalPath.toUtf8().constData() << "'\n" - << (samePath?"":" for file '") << (samePath?"":FileName) << (samePath?"":"'\n") - << " with existing document '" << it->second->Label.getValue() - << "' in path: '" << it->second->FileName.getValue() << "'"); - } - continue; - } - if(it->second->testStatus(App::Document::PartialDoc) - || it->second->testStatus(App::Document::PartialRestore)) { + auto doc = getDocumentByPath(File.filePath().c_str(), PathMatchMode::MatchCanonicalWarning); + if(doc) { + if(doc->testStatus(App::Document::PartialDoc) + || doc->testStatus(App::Document::PartialRestore)) { // Here means a document is already partially loaded, but the document // is requested again, either partial or not. We must check if the // document contains the required object if(isMainDoc) { // Main document must be open fully, so close and reopen - closeDocument(it->first.c_str()); - break; - } - - if(_allowPartial) { + closeDocument(doc->getName()); + doc = nullptr; + } else if(_allowPartial) { bool reopen = false; - for(auto &name : objNames) { - auto obj = it->second->getObject(name.c_str()); + for(const auto &name : objNames) { + auto obj = doc->getObject(name.c_str()); if(!obj || obj->testStatus(App::PartialObject)) { reopen = true; + // NOTE: We are about to reload this document with + // extra objects. However, it is possible to repeat + // this process several times, if it is linked by + // multiple documents and each with a different set of + // objects. To partially solve this problem, we do not + // close and reopen the document immediately here, but + // add it to _pendingDocsReopen to delay reloading. + for(auto obj : doc->getObjects()) + objNames.push_back(obj->getNameInDocument()); + _pendingDocMap[doc->FileName.getValue()] = std::move(objNames); break; } } if(!reopen) return 0; } - auto &names = _pendingDocMap[FileName]; - names.clear(); - _pendingDocsReopen.push_back(FileName); - return 0; + + if(doc) { + _pendingDocsReopen.emplace_back(FileName); + return 0; + } } if(!isMainDoc) return 0; - - return it->second; + else if(doc) + return doc; } std::string name; @@ -867,6 +949,8 @@ Document* Application::openDocumentPrivate(const char * FileName, try { // read the document newDoc->restore(File.filePath().c_str(),true,objNames); + if(DocFileMap.size()) + DocFileMap[FileInfo(newDoc->FileName.getValue()).filePath()] = newDoc; return newDoc; } // if the project file itself is corrupt then @@ -956,14 +1040,14 @@ Application::TransactionSignaller::~TransactionSignaller() { } } -const char* Application::getHomePath(void) const +std::string Application::getHomePath() { - return _mConfig["AppHomePath"].c_str(); + return mConfig["AppHomePath"]; } -const char* Application::getExecutableName(void) const +std::string Application::getExecutableName() { - return _mConfig["ExeName"].c_str(); + return mConfig["ExeName"]; } std::string Application::getTempPath() @@ -1463,6 +1547,7 @@ void Application::slotStartSaveDocument(const App::Document& doc, const std::str void Application::slotFinishSaveDocument(const App::Document& doc, const std::string& filename) { + DocFileMap.clear(); this->signalFinishSaveDocument(doc, filename); } @@ -1810,6 +1895,7 @@ void Application::initTypes(void) App ::PropertyPlacement ::init(); App ::PropertyPlacementList ::init(); App ::PropertyPlacementLink ::init(); + App ::PropertyRotation ::init(); App ::PropertyGeometry ::init(); App ::PropertyComplexGeoData ::init(); App ::PropertyColor ::init(); @@ -1923,6 +2009,332 @@ void Application::initTypes(void) new ExceptionProducer; } +namespace { +pair customSyntax(const string& s) +{ +#if defined(FC_OS_MACOSX) + if (s.find("-psn_") == 0) + return make_pair(string("psn"), s.substr(5)); +#endif + if (s.find("-display") == 0) + return make_pair(string("display"), string("null")); + else if (s.find("-style") == 0) + return make_pair(string("style"), string("null")); + else if (s.find("-graphicssystem") == 0) + return make_pair(string("graphicssystem"), string("null")); + else if (s.find("-widgetcount") == 0) + return make_pair(string("widgetcount"), string("")); + else if (s.find("-geometry") == 0) + return make_pair(string("geometry"), string("null")); + else if (s.find("-font") == 0) + return make_pair(string("font"), string("null")); + else if (s.find("-fn") == 0) + return make_pair(string("fn"), string("null")); + else if (s.find("-background") == 0) + return make_pair(string("background"), string("null")); + else if (s.find("-bg") == 0) + return make_pair(string("bg"), string("null")); + else if (s.find("-foreground") == 0) + return make_pair(string("foreground"), string("null")); + else if (s.find("-fg") == 0) + return make_pair(string("fg"), string("null")); + else if (s.find("-button") == 0) + return make_pair(string("button"), string("null")); + else if (s.find("-btn") == 0) + return make_pair(string("btn"), string("null")); + else if (s.find("-name") == 0) + return make_pair(string("name"), string("null")); + else if (s.find("-title") == 0) + return make_pair(string("title"), string("null")); + else if (s.find("-visual") == 0) + return make_pair(string("visual"), string("null")); +// else if (s.find("-ncols") == 0) +// return make_pair(string("ncols"), boost::program_options::value(1)); +// else if (s.find("-cmap") == 0) +// return make_pair(string("cmap"), string("null")); + else if ('@' == s[0]) + return std::make_pair(string("response-file"), s.substr(1)); + else + return make_pair(string(), string()); +} + +void parseProgramOptions(int ac, char ** av, const string& exe, variables_map& vm) +{ + // Declare a group of options that will be + // allowed only on the command line + options_description generic("Generic options"); + generic.add_options() + ("version,v", "Prints version string") + ("help,h", "Prints help message") + ("console,c", "Starts in console mode") + ("response-file", value(),"Can be specified with '@name', too") + ("dump-config", "Dumps configuration") + ("get-config", value(), "Prints the value of the requested configuration key") + ; + + // Declare a group of options that will be + // allowed both on the command line and in + // the config file + std::stringstream descr; + descr << "Writes " << exe << ".log to the user directory."; + boost::program_options::options_description config("Configuration"); + config.add_options() + //("write-log,l", value(), "write a log file") + ("write-log,l", descr.str().c_str()) + ("log-file", value(), "Unlike --write-log this allows logging to an arbitrary file") + ("user-cfg,u", value(),"User config file to load/save user settings") + ("system-cfg,s", value(),"System config file to load/save system settings") + ("run-test,t", value() ,"Test case - or 0 for all") + ("module-path,M", value< vector >()->composing(),"Additional module paths") + ("python-path,P", value< vector >()->composing(),"Additional python paths") + ("single-instance", "Allow to run a single instance of the application") + ; + + + // Hidden options, will be allowed both on the command line and + // in the config file, but will not be shown to the user. + boost::program_options::options_description hidden("Hidden options"); + hidden.add_options() + ("input-file", boost::program_options::value< vector >(), "input file") + ("output", boost::program_options::value(),"output file") + ("hidden", "don't show the main window") + // this are to ignore for the window system (QApplication) + ("style", boost::program_options::value< string >(), "set the application GUI style") + ("stylesheet", boost::program_options::value< string >(), "set the application stylesheet") + ("session", boost::program_options::value< string >(), "restore the application from an earlier session") + ("reverse", "set the application's layout direction from right to left") + ("widgetcount", "print debug messages about widgets") + ("graphicssystem", boost::program_options::value< string >(), "backend to be used for on-screen widgets and pixmaps") + ("display", boost::program_options::value< string >(), "set the X-Server") + ("geometry ", boost::program_options::value< string >(), "set the X-Window geometry") + ("font", boost::program_options::value< string >(), "set the X-Window font") + ("fn", boost::program_options::value< string >(), "set the X-Window font") + ("background", boost::program_options::value< string >(), "set the X-Window background color") + ("bg", boost::program_options::value< string >(), "set the X-Window background color") + ("foreground", boost::program_options::value< string >(), "set the X-Window foreground color") + ("fg", boost::program_options::value< string >(), "set the X-Window foreground color") + ("button", boost::program_options::value< string >(), "set the X-Window button color") + ("btn", boost::program_options::value< string >(), "set the X-Window button color") + ("name", boost::program_options::value< string >(), "set the X-Window name") + ("title", boost::program_options::value< string >(), "set the X-Window title") + ("visual", boost::program_options::value< string >(), "set the X-Window to color scheme") + ("ncols", boost::program_options::value< int >(), "set the X-Window to color scheme") + ("cmap", "set the X-Window to color scheme") +#if defined(FC_OS_MACOSX) + ("psn", boost::program_options::value< string >(), "process serial number") +#endif + ; + + // Ignored options, will be safely ignored. Mostly used by underlying libs. + //boost::program_options::options_description x11("X11 options"); + //x11.add_options() + // ("display", boost::program_options::value< string >(), "set the X-Server") + // ; + //0000723: improper handling of qt specific command line arguments + std::vector args; + bool merge=false; + for (int i=1; i().c_str()); + if (!ifs) { + Base::Console().Error("Could no open the response file\n"); + std::stringstream str; + str << "Could no open the response file: '" + << vm["response-file"].as() << "'" << endl; + throw Base::UnknownProgramOption(str.str()); + } + // Read the whole file into a string + stringstream ss; + ss << ifs.rdbuf(); + // Split the file content + char_separator sep(" \n\r"); + tokenizer > tok(ss.str(), sep); + vector args; + copy(tok.begin(), tok.end(), back_inserter(args)); + // Parse the file and store the options + store( boost::program_options::command_line_parser(args). + options(cmdline_options).positional(p).extra_parser(customSyntax).run(), vm); + } +} + +void processProgramOptions(const variables_map& vm, std::map& mConfig) +{ + if (vm.count("version")) { + std::stringstream str; + str << mConfig["ExeName"] << " " << mConfig["ExeVersion"] + << " Revision: " << mConfig["BuildRevision"] << std::endl; + throw Base::ProgramInformation(str.str()); + } + + if (vm.count("console")) { + mConfig["Console"] = "1"; + mConfig["RunMode"] = "Cmd"; + } + + if (vm.count("module-path")) { + vector Mods = vm["module-path"].as< vector >(); + string temp; + for (vector::const_iterator It= Mods.begin();It != Mods.end();++It) + temp += *It + ";"; + temp.erase(temp.end()-1); + mConfig["AdditionalModulePaths"] = temp; + } + + if (vm.count("python-path")) { + vector Paths = vm["python-path"].as< vector >(); + for (vector::const_iterator It= Paths.begin();It != Paths.end();++It) + Base::Interpreter().addPythonPath(It->c_str()); + } + + if (vm.count("input-file")) { + vector files(vm["input-file"].as< vector >()); + int OpenFileCount=0; + for (vector::const_iterator It = files.begin();It != files.end();++It) { + + //cout << "Input files are: " + // << vm["input-file"].as< vector >() << "\n"; + + std::ostringstream temp; + temp << "OpenFile" << OpenFileCount; + mConfig[temp.str()] = *It; + OpenFileCount++; + } + std::ostringstream buffer; + buffer << OpenFileCount; + mConfig["OpenFileCount"] = buffer.str(); + } + + if (vm.count("output")) { + string file = vm["output"].as(); + mConfig["SaveFile"] = file; + } + + if (vm.count("hidden")) { + mConfig["StartHidden"] = "1"; + } + + if (vm.count("write-log")) { + mConfig["LoggingFile"] = "1"; + //mConfig["LoggingFileName"] = vm["write-log"].as(); + mConfig["LoggingFileName"] = mConfig["UserAppData"] + mConfig["ExeName"] + ".log"; + } + + if (vm.count("log-file")) { + mConfig["LoggingFile"] = "1"; + mConfig["LoggingFileName"] = vm["log-file"].as(); + } + + if (vm.count("user-cfg")) { + mConfig["UserParameter"] = vm["user-cfg"].as(); + } + + if (vm.count("system-cfg")) { + mConfig["SystemParameter"] = vm["system-cfg"].as(); + } + + if (vm.count("run-test")) { + string testCase = vm["run-test"].as(); + if ( "0" == testCase) { + testCase = "TestApp.All"; + } + mConfig["TestCase"] = testCase; + mConfig["RunMode"] = "Internal"; + mConfig["ScriptFileName"] = "FreeCADTest"; + //sScriptName = FreeCADTest; + } + + if (vm.count("single-instance")) { + mConfig["SingleInstance"] = "1"; + } + + if (vm.count("dump-config")) { + std::stringstream str; + for (std::map::iterator it=mConfig.begin(); it != mConfig.end(); ++it) { + str << it->first << "=" << it->second << std::endl; + } + throw Base::ProgramInformation(str.str()); + } + + if (vm.count("get-config")) { + std::string configKey = vm["get-config"].as(); + std::stringstream str; + std::map::iterator pos; + pos = mConfig.find(configKey); + if (pos != mConfig.end()) { + str << pos->second; + } + str << std::endl; + throw Base::ProgramInformation(str.str()); + } +} +} + void Application::initConfig(int argc, char ** argv) { // find the home path.... @@ -1961,6 +2373,9 @@ void Application::initConfig(int argc, char ** argv) } } + variables_map vm; + parseProgramOptions(argc, argv, mConfig["ExeName"], vm); + // extract home paths ExtractUserPath(); @@ -1979,8 +2394,8 @@ void Application::initConfig(int argc, char ** argv) else Base::Console().Warning("Encoding of Python paths failed\n"); - // Parse the options that have impact on the init process - ParseOptions(argc,argv); + // Handle the options that have impact on the init process + processProgramOptions(vm, mConfig); // Init console =========================================================== Base::PyGILStateLocker lock; @@ -2384,55 +2799,6 @@ namespace boost { namespace program_options { } } #endif -pair customSyntax(const string& s) -{ -#if defined(FC_OS_MACOSX) - if (s.find("-psn_") == 0) - return make_pair(string("psn"), s.substr(5)); -#endif - if (s.find("-display") == 0) - return make_pair(string("display"), string("null")); - else if (s.find("-style") == 0) - return make_pair(string("style"), string("null")); - else if (s.find("-graphicssystem") == 0) - return make_pair(string("graphicssystem"), string("null")); - else if (s.find("-widgetcount") == 0) - return make_pair(string("widgetcount"), string("")); - else if (s.find("-geometry") == 0) - return make_pair(string("geometry"), string("null")); - else if (s.find("-font") == 0) - return make_pair(string("font"), string("null")); - else if (s.find("-fn") == 0) - return make_pair(string("fn"), string("null")); - else if (s.find("-background") == 0) - return make_pair(string("background"), string("null")); - else if (s.find("-bg") == 0) - return make_pair(string("bg"), string("null")); - else if (s.find("-foreground") == 0) - return make_pair(string("foreground"), string("null")); - else if (s.find("-fg") == 0) - return make_pair(string("fg"), string("null")); - else if (s.find("-button") == 0) - return make_pair(string("button"), string("null")); - else if (s.find("-btn") == 0) - return make_pair(string("btn"), string("null")); - else if (s.find("-name") == 0) - return make_pair(string("name"), string("null")); - else if (s.find("-title") == 0) - return make_pair(string("title"), string("null")); - else if (s.find("-visual") == 0) - return make_pair(string("visual"), string("null")); -// else if (s.find("-ncols") == 0) -// return make_pair(string("ncols"), boost::program_options::value(1)); -// else if (s.find("-cmap") == 0) -// return make_pair(string("cmap"), string("null")); - else if ('@' == s[0]) - return std::make_pair(string("response-file"), s.substr(1)); - else - return make_pair(string(), string()); - -} - // A helper function to simplify the main part. template ostream& operator<<(ostream& os, const vector& v) @@ -2441,358 +2807,96 @@ ostream& operator<<(ostream& os, const vector& v) return os; } -void Application::ParseOptions(int ac, char ** av) +namespace { + +boost::filesystem::path stringToPath(std::string str) { - // Declare a group of options that will be - // allowed only on the command line - options_description generic("Generic options"); - generic.add_options() - ("version,v", "Prints version string") - ("help,h", "Prints help message") - ("console,c", "Starts in console mode") - ("response-file", value(),"Can be specified with '@name', too") - ("dump-config", "Dumps configuration") - ("get-config", value(), "Prints the value of the requested configuration key") - ; - - // Declare a group of options that will be - // allowed both on the command line and in - // the config file - std::string descr("Writes a log file to:\n"); - descr += mConfig["UserAppData"]; - descr += mConfig["ExeName"]; - descr += ".log"; - boost::program_options::options_description config("Configuration"); - config.add_options() - //("write-log,l", value(), "write a log file") - ("write-log,l", descr.c_str()) - ("log-file", value(), "Unlike --write-log this allows logging to an arbitrary file") - ("user-cfg,u", value(),"User config file to load/save user settings") - ("system-cfg,s", value(),"System config file to load/save system settings") - ("run-test,t", value() ,"Test case - or 0 for all") - ("module-path,M", value< vector >()->composing(),"Additional module paths") - ("python-path,P", value< vector >()->composing(),"Additional python paths") - ("single-instance", "Allow to run a single instance of the application") - ; - - - // Hidden options, will be allowed both on the command line and - // in the config file, but will not be shown to the user. - boost::program_options::options_description hidden("Hidden options"); - hidden.add_options() - ("input-file", boost::program_options::value< vector >(), "input file") - ("output", boost::program_options::value(),"output file") - ("hidden", "don't show the main window") - // this are to ignore for the window system (QApplication) - ("style", boost::program_options::value< string >(), "set the application GUI style") - ("stylesheet", boost::program_options::value< string >(), "set the application stylesheet") - ("session", boost::program_options::value< string >(), "restore the application from an earlier session") - ("reverse", "set the application's layout direction from right to left") - ("widgetcount", "print debug messages about widgets") - ("graphicssystem", boost::program_options::value< string >(), "backend to be used for on-screen widgets and pixmaps") - ("display", boost::program_options::value< string >(), "set the X-Server") - ("geometry ", boost::program_options::value< string >(), "set the X-Window geometry") - ("font", boost::program_options::value< string >(), "set the X-Window font") - ("fn", boost::program_options::value< string >(), "set the X-Window font") - ("background", boost::program_options::value< string >(), "set the X-Window background color") - ("bg", boost::program_options::value< string >(), "set the X-Window background color") - ("foreground", boost::program_options::value< string >(), "set the X-Window foreground color") - ("fg", boost::program_options::value< string >(), "set the X-Window foreground color") - ("button", boost::program_options::value< string >(), "set the X-Window button color") - ("btn", boost::program_options::value< string >(), "set the X-Window button color") - ("name", boost::program_options::value< string >(), "set the X-Window name") - ("title", boost::program_options::value< string >(), "set the X-Window title") - ("visual", boost::program_options::value< string >(), "set the X-Window to color scheme") - ("ncols", boost::program_options::value< int >(), "set the X-Window to color scheme") - ("cmap", "set the X-Window to color scheme") -#if defined(FC_OS_MACOSX) - ("psn", boost::program_options::value< string >(), "process serial number") +#if defined(FC_OS_WIN32) + std::wstring_convert> converter; + boost::filesystem::path path(converter.from_bytes(str)); +#else + boost::filesystem::path path(str); #endif - ; - - // Ignored options, will be safely ignored. Mostly used by underlying libs. - //boost::program_options::options_description x11("X11 options"); - //x11.add_options() - // ("display", boost::program_options::value< string >(), "set the X-Server") - // ; - //0000723: improper handling of qt specific command line arguments - std::vector args; - bool merge=false; - for (int i=1; i().c_str()); - if (!ifs) { - Base::Console().Error("Could no open the response file\n"); - std::stringstream str; - str << "Could no open the response file: '" - << vm["response-file"].as() << "'" << endl; - throw Base::UnknownProgramOption(str.str()); - } - // Read the whole file into a string - stringstream ss; - ss << ifs.rdbuf(); - // Split the file content - char_separator sep(" \n\r"); - tokenizer > tok(ss.str(), sep); - vector args; - copy(tok.begin(), tok.end(), back_inserter(args)); - // Parse the file and store the options - store( boost::program_options::command_line_parser(args). - options(cmdline_options).positional(p).extra_parser(customSyntax).run(), vm); - } - - if (vm.count("version")) { - std::stringstream str; - str << mConfig["ExeName"] << " " << mConfig["ExeVersion"] - << " Revision: " << mConfig["BuildRevision"] << std::endl; - throw Base::ProgramInformation(str.str()); - } - - if (vm.count("console")) { - mConfig["Console"] = "1"; - mConfig["RunMode"] = "Cmd"; - } - - if (vm.count("module-path")) { - vector Mods = vm["module-path"].as< vector >(); - string temp; - for (vector::const_iterator It= Mods.begin();It != Mods.end();++It) - temp += *It + ";"; - temp.erase(temp.end()-1); - mConfig["AdditionalModulePaths"] = temp; - } - - if (vm.count("python-path")) { - vector Paths = vm["python-path"].as< vector >(); - for (vector::const_iterator It= Paths.begin();It != Paths.end();++It) - Base::Interpreter().addPythonPath(It->c_str()); - } - - if (vm.count("input-file")) { - vector files(vm["input-file"].as< vector >()); - int OpenFileCount=0; - for (vector::const_iterator It = files.begin();It != files.end();++It) { - - //cout << "Input files are: " - // << vm["input-file"].as< vector >() << "\n"; - - std::ostringstream temp; - temp << "OpenFile" << OpenFileCount; - mConfig[temp.str()] = *It; - OpenFileCount++; - } - std::ostringstream buffer; - buffer << OpenFileCount; - mConfig["OpenFileCount"] = buffer.str(); - } - - if (vm.count("output")) { - string file = vm["output"].as(); - mConfig["SaveFile"] = file; - } - - if (vm.count("hidden")) { - mConfig["StartHidden"] = "1"; - } - - if (vm.count("write-log")) { - mConfig["LoggingFile"] = "1"; - //mConfig["LoggingFileName"] = vm["write-log"].as(); - mConfig["LoggingFileName"] = mConfig["UserAppData"] + mConfig["ExeName"] + ".log"; - } - - if (vm.count("log-file")) { - mConfig["LoggingFile"] = "1"; - mConfig["LoggingFileName"] = vm["log-file"].as(); - } - - if (vm.count("user-cfg")) { - mConfig["UserParameter"] = vm["user-cfg"].as(); - } - - if (vm.count("system-cfg")) { - mConfig["SystemParameter"] = vm["system-cfg"].as(); - } - - if (vm.count("run-test")) { - string testCase = vm["run-test"].as(); - if ( "0" == testCase) { - testCase = "TestApp.All"; - } - mConfig["TestCase"] = testCase; - mConfig["RunMode"] = "Internal"; - mConfig["ScriptFileName"] = "FreeCADTest"; - //sScriptName = FreeCADTest; - } - - if (vm.count("single-instance")) { - mConfig["SingleInstance"] = "1"; - } - - if (vm.count("dump-config")) { - std::stringstream str; - for (std::map::iterator it=mConfig.begin(); it != mConfig.end(); ++it) { - str << it->first << "=" << it->second << std::endl; - } - throw Base::ProgramInformation(str.str()); - } - - if (vm.count("get-config")) { - std::string configKey = vm["get-config"].as(); - std::stringstream str; - std::map::iterator pos; - pos = mConfig.find(configKey); - if (pos != mConfig.end()) { - str << pos->second; - } - str << std::endl; - throw Base::ProgramInformation(str.str()); - } + return path; } -void Application::ExtractUserPath() +std::string pathToString(const boost::filesystem::path& p) { - // std paths - mConfig["BinPath"] = mConfig["AppHomePath"] + "bin" + PATHSEP; - mConfig["DocPath"] = mConfig["AppHomePath"] + "doc" + PATHSEP; +#if defined(FC_OS_WIN32) + std::wstring_convert> converter; + return converter.to_bytes(p.wstring()); +#else + return p.string(); +#endif +} - // Set application tmp. directory - mConfig["AppTempPath"] = Base::FileInfo::getTempPath(); - - // this is to support a portable version of FreeCAD - QProcessEnvironment env(QProcessEnvironment::systemEnvironment()); - QString userHome = env.value(QString::fromLatin1("FREECAD_USER_HOME")); - QString userData = env.value(QString::fromLatin1("FREECAD_USER_DATA")); - QString userTemp = env.value(QString::fromLatin1("FREECAD_USER_TEMP")); - - // verify env. variables - if (!userHome.isEmpty()) { - QDir dir(userHome); - if (dir.exists()) - userHome = QDir::toNativeSeparators(dir.canonicalPath()); - else - userHome.clear(); - } - - if (!userData.isEmpty()) { - QDir dir(userData); - if (dir.exists()) - userData = QDir::toNativeSeparators(dir.canonicalPath()); - else - userData.clear(); - } - else if (!userHome.isEmpty()) { - // if FREECAD_USER_HOME is set but not FREECAD_USER_DATA - userData = userHome; - } - - // override temp directory if set by env. variable - if (!userTemp.isEmpty()) { - QDir dir(userTemp); - if (dir.exists()) { - userTemp = dir.canonicalPath(); - userTemp += QDir::separator(); - userTemp = QDir::toNativeSeparators(userTemp); - mConfig["AppTempPath"] = userTemp.toUtf8().data(); - } - } - else if (!userHome.isEmpty()) { - // if FREECAD_USER_HOME is set but not FREECAD_USER_TEMP - QDir dir(userHome); - dir.mkdir(QString::fromLatin1("temp")); - QFileInfo fi(dir, QString::fromLatin1("temp")); - QString tmp(fi.absoluteFilePath()); - tmp += QDir::separator(); - tmp = QDir::toNativeSeparators(tmp); - mConfig["AppTempPath"] = tmp.toUtf8().data(); - } - -#if defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_BSD) +QString getDefaultHome() +{ + QString path; +#if defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_BSD) || defined(FC_OS_MACOSX) // Default paths for the user specific stuff struct passwd *pwd = getpwuid(getuid()); - if (pwd == NULL) + if (!pwd) + throw Base::RuntimeError("Getting HOME path from system failed!"); + path = QString::fromUtf8(pwd->pw_dir); + +#elif defined(FC_OS_WIN32) + WCHAR szPath[MAX_PATH]; + std::wstring_convert> converter; + // Get the default path where we can save our documents. It seems that + // 'CSIDL_MYDOCUMENTS' doesn't work on all machines, so we use 'CSIDL_PERSONAL' + // which does the same. + if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PERSONAL, NULL, 0, szPath))) { + path = QString::fromStdString(converter.to_bytes(szPath)); + } + else { throw Base::RuntimeError("Getting HOME path from system failed!"); - mConfig["UserHomePath"] = pwd->pw_dir; - if (!userHome.isEmpty()) { - mConfig["UserHomePath"] = userHome.toUtf8().data(); } - boost::filesystem::path appData(pwd->pw_dir); - if (!userData.isEmpty()) - appData = userData.toUtf8().data(); +#else +# error "Implement getDefaultHome() for your platform." +#endif - if (!boost::filesystem::exists(appData)) { - // This should never ever happen - throw Base::FileSystemError("Application data directory " + appData.string() + " does not exist!"); + return path; +} + +QString getDefaultUserData(const QString& home) +{ +#if defined(FC_OS_WIN32) + WCHAR szPath[MAX_PATH]; + std::wstring_convert> converter; + if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, szPath))) { + return QString::fromStdString(converter.to_bytes(szPath)); } +#endif + return home; +} + +void getUserData(boost::filesystem::path& appData) +{ +#if defined(FC_OS_MACOSX) + appData = appData / "Library" / "Preferences"; +#else + Q_UNUSED(appData) +#endif +} + +void getConfigOrChache(std::map& mConfig, boost::filesystem::path& appData) +{ + // If 'AppDataSkipVendor' is defined, the value of 'ExeVendor' must not be part of + // the path. + if (mConfig.find("AppDataSkipVendor") == mConfig.end()) { + appData /= mConfig["ExeVendor"]; + } + appData /= mConfig["ExeName"]; +} + +void getApplicationData(std::map& mConfig, boost::filesystem::path& appData) +{ + // Actually the name of the directory where the parameters are stored should be the name of + // the application due to branding reasons. +#if defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_BSD) // If 'AppDataSkipVendor' is defined, the value of 'ExeVendor' must not be part of // the path. if (mConfig.find("AppDataSkipVendor") == mConfig.end()) { @@ -2802,52 +2906,46 @@ void Application::ExtractUserPath() appData /= "." + mConfig["ExeName"]; } - // Actually the name of the directory where the parameters are stored should be the name of - // the application due to branding reasons. - - // In order to write to our data path, we must create some directories, first. - if (!boost::filesystem::exists(appData) && !Py_IsInitialized()) { - try { - boost::filesystem::create_directories(appData); - } catch (const boost::filesystem::filesystem_error& e) { - throw Base::FileSystemError("Could not create app data directories. Failed with: " + e.code().message()); - } +#elif defined(FC_OS_MACOSX) || defined(FC_OS_WIN32) + getConfigOrChache(mConfig, appData); +#endif +} + +QString findUserHomePath(const QString& userHome) +{ + if (userHome.isEmpty()) { + return getDefaultHome(); } - - mConfig["UserAppData"] = appData.string() + PATHSEP; - -#elif defined(FC_OS_MACOSX) - // Default paths for the user specific stuff on the platform - struct passwd *pwd = getpwuid(getuid()); - if (pwd == NULL) - throw Base::RuntimeError("Getting HOME path from system failed!"); - mConfig["UserHomePath"] = pwd->pw_dir; - if (!userHome.isEmpty()) { - mConfig["UserHomePath"] = userHome.toUtf8().data(); + else { + return userHome; } +} - boost::filesystem::path appData(pwd->pw_dir); - if (!userData.isEmpty()) - appData = userData.toUtf8().data(); +boost::filesystem::path findUserDataPath(std::map& mConfig, const QString& userHome, const QString& userData) +{ + QString dataPath = userData; + if (dataPath.isEmpty()) { + dataPath = getDefaultUserData(userHome); + } + boost::filesystem::path appData(stringToPath(dataPath.toStdString())); - appData = appData / "Library" / "Preferences"; + // If a custom user data path is given then don't modify it + if (userData.isEmpty()) + getUserData(appData); if (!boost::filesystem::exists(appData)) { // This should never ever happen throw Base::FileSystemError("Application data directory " + appData.string() + " does not exist!"); } - // If 'AppDataSkipVendor' is defined, the value of 'ExeVendor' must not be part of - // the path. - if (mConfig.find("AppDataSkipVendor") == mConfig.end()) { - appData /= mConfig["ExeVendor"]; - } - appData /= mConfig["ExeName"]; + // In the second step we want the directory where user settings of the application can be + // kept. There we create a directory with name of the vendor and a sub-directory with name + // of the application. + // + // If a custom user data path is given then don't modify it + if (userData.isEmpty()) + getApplicationData(mConfig, appData); - - // Actually the name of the directory where the parameters are stored should be the name of - // the application due to branding reasons. - // In order to write to our data path, we must create some directories, first. if (!boost::filesystem::exists(appData) && !Py_IsInitialized()) { try { @@ -2857,72 +2955,163 @@ void Application::ExtractUserPath() } } - mConfig["UserAppData"] = appData.string() + PATHSEP; + return appData; +} -#elif defined(FC_OS_WIN32) - WCHAR szPath[MAX_PATH]; - std::wstring_convert> converter; - // Get the default path where we can save our documents. It seems that - // 'CSIDL_MYDOCUMENTS' doesn't work on all machines, so we use 'CSIDL_PERSONAL' - // which does the same. - if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PERSONAL, NULL, 0, szPath))) { - mConfig["UserHomePath"] = converter.to_bytes(szPath); - } - else { - mConfig["UserHomePath"] = mConfig["AppHomePath"]; +boost::filesystem::path findCachePath(std::map& mConfig, const QString& cacheHome, const QString& userTemp) +{ + QString dataPath = userTemp; + if (dataPath.isEmpty()) { + dataPath = cacheHome; } - if (!userHome.isEmpty()) { - mConfig["UserHomePath"] = userHome.toUtf8().data(); + boost::filesystem::path appData(stringToPath(dataPath.toStdString())); + +#if !defined(FC_OS_WIN32) + // If a custom user temp path is given then don't modify it + if (userTemp.isEmpty()) { + getConfigOrChache(mConfig, appData); + appData /= "Cache"; } - - // In the second step we want the directory where user settings of the application can be - // kept. There we create a directory with name of the vendor and a sub-directory with name - // of the application. - if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, szPath))) { - boost::filesystem::path appData(szPath); - if (!userData.isEmpty()) - appData = userData.toStdWString(); - - if (!boost::filesystem::exists(appData)) { - // This should never ever happen - throw Base::FileSystemError("Application data directory " + appData.string() + " does not exist!"); - } - - // If 'AppDataSkipVendor' is defined, the value of 'ExeVendor' must not be part of - // the path. - if (mConfig.find("AppDataSkipVendor") == mConfig.end()) { - appData /= mConfig["ExeVendor"]; - } - appData /= mConfig["ExeName"]; - - // Actually the name of the directory where the parameters are stored should be the name of - // the application due to branding reasons. - - // In order to write to our data path, we must create some directories, first. - if (!boost::filesystem::exists(appData) && !Py_IsInitialized()) { - try { - boost::filesystem::create_directories(appData); - } catch (const boost::filesystem::filesystem_error& e) { - throw Base::FileSystemError("Could not create app data directories. Failed with: " + e.code().message()); - } - } - - mConfig["UserAppData"] = converter.to_bytes(appData.wstring()) + PATHSEP; - - // Create the default macro directory - boost::filesystem::path macroDir = converter.from_bytes(getUserMacroDir()); - if (!boost::filesystem::exists(macroDir) && !Py_IsInitialized()) { - try { - boost::filesystem::create_directories(macroDir); - } catch (const boost::filesystem::filesystem_error& e) { - throw Base::FileSystemError("Could not create macro directory. Failed with: " + e.code().message()); - } - } - } -#else -# error "Implement ExtractUserPath() for your platform." #endif + + // In order to write to our data path, we must create some directories, first. + if (!boost::filesystem::exists(appData)) { + try { + boost::filesystem::create_directories(appData); + } catch (const boost::filesystem::filesystem_error& e) { + throw Base::FileSystemError("Could not create cache directories. Failed with: " + e.code().message()); + } + } + + return appData; +} + +std::tuple getCustomPaths() +{ + QProcessEnvironment env(QProcessEnvironment::systemEnvironment()); + QString userHome = env.value(QString::fromLatin1("FREECAD_USER_HOME")); + QString userData = env.value(QString::fromLatin1("FREECAD_USER_DATA")); + QString userTemp = env.value(QString::fromLatin1("FREECAD_USER_TEMP")); + + auto toNativePath = [](QString& path) { + if (!path.isEmpty()) { + QDir dir(path); + if (dir.exists()) + path = QDir::toNativeSeparators(dir.canonicalPath()); + else + path.clear(); + } + }; + + // verify env. variables + toNativePath(userHome); + toNativePath(userData); + toNativePath(userTemp); + + // if FREECAD_USER_HOME is set but not FREECAD_USER_DATA + if (!userHome.isEmpty() && userData.isEmpty()) { + userData = userHome; + } + + // if FREECAD_USER_HOME is set but not FREECAD_USER_TEMP + if (!userHome.isEmpty() && userTemp.isEmpty()) { + QDir dir(userHome); + dir.mkdir(QString::fromLatin1("temp")); + QFileInfo fi(dir, QString::fromLatin1("temp")); + userTemp = fi.absoluteFilePath(); + } + + return std::tuple(userHome, userData, userTemp); +} + +std::tuple getXDGPaths() +{ + auto checkXdgPath = [](QString& path) { + if (!path.isEmpty()) { + QDir dir(path); + if (!dir.exists() || dir.isRelative()) + path.clear(); + } + }; + + QProcessEnvironment env(QProcessEnvironment::systemEnvironment()); + QString configHome = env.value(QString::fromLatin1("XDG_CONFIG_HOME")); + QString cacheHome = env.value(QString::fromLatin1("XDG_CACHE_HOME")); + + checkXdgPath(configHome); + checkXdgPath(cacheHome); + + // If env. are not set or invalid + QDir home(getDefaultHome()); + if (configHome.isEmpty()) { +#if defined(FC_OS_MACOSX) + QFileInfo fi(home, QString::fromLatin1("Library/Preferences")); +#else + QFileInfo fi(home, QString::fromLatin1(".config")); +#endif + configHome = fi.absoluteFilePath(); + } + + if (cacheHome.isEmpty()) { +#if defined(FC_OS_MACOSX) + QFileInfo fi(home, QString::fromLatin1("Library/Caches")); +#elif defined(FC_OS_WIN32) + QFileInfo fi(QString::fromStdString(Base::FileInfo::getTempPath())); +#else + QFileInfo fi(home, QString::fromLatin1(".cache")); +#endif + cacheHome = fi.absoluteFilePath(); + } + + return std::make_tuple(configHome, cacheHome); +} +} + +void Application::ExtractUserPath() +{ + // std paths + mConfig["BinPath"] = mConfig["AppHomePath"] + "bin" + PATHSEP; + mConfig["DocPath"] = mConfig["AppHomePath"] + "doc" + PATHSEP; + + // this is to support a portable version of FreeCAD + auto paths = getCustomPaths(); + QString userHome = std::get<0>(paths); + QString userData = std::get<1>(paths); + QString userTemp = std::get<2>(paths); + + auto xdgPaths = getXDGPaths(); + //QString configHome = std::get<0>(xdgPaths); + QString cacheHome = std::get<1>(xdgPaths); + + // User home path + // + userHome = findUserHomePath(userHome); + mConfig["UserHomePath"] = userHome.toUtf8().data(); + + + // User data path + // + boost::filesystem::path appData = findUserDataPath(mConfig, userHome, userData); + mConfig["UserAppData"] = pathToString(appData) + PATHSEP; + + + // Set application tmp. directory + // + boost::filesystem::path cache = findCachePath(mConfig, cacheHome, userTemp); + mConfig["AppTempPath"] = pathToString(cache) + PATHSEP; + + + // Create the default macro directory + // + boost::filesystem::path macroDir = stringToPath(getUserMacroDir()); + if (!boost::filesystem::exists(macroDir) && !Py_IsInitialized()) { + try { + boost::filesystem::create_directories(macroDir); + } catch (const boost::filesystem::filesystem_error& e) { + throw Base::FileSystemError("Could not create macro directory. Failed with: " + e.code().message()); + } + } } #if defined (FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_BSD) diff --git a/src/App/Application.h b/src/App/Application.h index 506c3a364e..54872ecec7 100644 --- a/src/App/Application.h +++ b/src/App/Application.h @@ -120,6 +120,33 @@ public: App::Document* getActiveDocument(void) const; /// Retrieve a named document App::Document* getDocument(const char *Name) const; + + /// Path matching mode for getDocumentByPath() + enum class PathMatchMode { + /// Match by resolving to absolute file path + MatchAbsolute = 0, + /** Match by absolute path first. If not found then match by resolving + * to canonical file path where any intermediate '.' '..' and symlinks + * are resolved. + */ + MatchCanonical = 1, + /** Same as MatchCanonical, but if a document is found by canonical + * path match, which means the document can be resolved using two + * different absolute path, a warning is printed and the found document + * is not returned. This is to allow the caller to intentionally load + * the same physical file as separate documents. + */ + MatchCanonicalWarning = 2, + }; + /** Retrieve a document based on file path + * + * @param path: file path + * @param checkCanonical: file path matching mode, @sa PathMatchMode. + * @return Return the document found by matching with the given path + */ + App::Document* getDocumentByPath(const char *path, + PathMatchMode checkCanonical = PathMatchMode::MatchAbsolute) const; + /// gets the (internal) name of the document const char * getDocumentName(const App::Document* ) const; /// get a list of all documents in the application @@ -190,6 +217,8 @@ public: boost::signals2::signal signalStartRestoreDocument; /// signal on restoring Document boost::signals2::signal signalFinishRestoreDocument; + /// signal on pending reloading of a partial Document + boost::signals2::signal signalPendingReloadDocument; /// signal on starting to save Document boost::signals2::signal signalStartSaveDocument; /// signal on saved Document @@ -366,8 +395,8 @@ public: /** @name Application directories */ //@{ - const char* getHomePath(void) const; - const char* getExecutableName(void) const; + static std::string getHomePath(); + static std::string getExecutableName(); /*! Returns the temporary directory. By default, this is set to the system's temporary directory but can be customized by the user. @@ -441,7 +470,7 @@ protected: /// open single document only App::Document* openDocumentPrivate(const char * FileName, const char *propFileName, - const char *label, bool isMainDoc, bool createView, const std::set &objNames); + const char *label, bool isMainDoc, bool createView, std::vector &&objNames); /// Helper class for App::Document to signal on close/abort transaction class AppExport TransactionSignaller { @@ -526,8 +555,6 @@ private: static void logStatus(void); // the one and only pointer to the application object static Application *_pcSingleton; - /// argument helper function - static void ParseOptions(int argc, char ** argv); /// checks if the environment is alright //static void CheckEnv(void); /// Search for the FreeCAD home path based on argv[0] @@ -559,13 +586,19 @@ private: std::vector _mImportTypes; std::vector _mExportTypes; std::map DocMap; + mutable std::map DocFileMap; std::map mpcPramManager; std::map &_mConfig; App::Document* _pActiveDoc; - std::deque _pendingDocs; - std::deque _pendingDocsReopen; - std::map > _pendingDocMap; + std::deque _pendingDocs; + std::deque _pendingDocsReopen; + std::map > _pendingDocMap; + + // To prevent infinite recursion of reloading a partial document due a truly + // missing object + std::map > _docReloadAttempts; + bool _isRestoring; bool _allowPartial; bool _isClosingAll; diff --git a/src/App/ApplicationPy.cpp b/src/App/ApplicationPy.cpp index 0eaa6ac788..de213ff32e 100644 --- a/src/App/ApplicationPy.cpp +++ b/src/App/ApplicationPy.cpp @@ -691,7 +691,7 @@ PyObject* Application::sGetHomePath(PyObject * /*self*/, PyObject *args) if (!PyArg_ParseTuple(args, "")) // convert args: Python->C return NULL; // NULL triggers exception - Py::String homedir(GetApplication().getHomePath(),"utf-8"); + Py::String homedir(Application::getHomePath(),"utf-8"); return Py::new_reference_to(homedir); } diff --git a/src/App/Document.cpp b/src/App/Document.cpp index 1f108b8df3..0c6fceb43f 100644 --- a/src/App/Document.cpp +++ b/src/App/Document.cpp @@ -1685,7 +1685,7 @@ std::string Document::getTransientDirectoryName(const std::string& uuid, const s std::stringstream s; QCryptographicHash hash(QCryptographicHash::Sha1); hash.addData(filename.c_str(), filename.size()); - s << App::Application::getTempPath() << GetApplication().getExecutableName() + s << App::Application::getTempPath() << App::Application::getExecutableName() << "_Doc_" << uuid << "_" << hash.result().toHex().left(6).constData() << "_" << QCoreApplication::applicationPid(); @@ -1866,7 +1866,6 @@ void Document::exportObjects(const std::vector& obj, std:: #define FC_ELEMENT_OBJECT_DEPS "ObjectDeps" #define FC_ATTR_DEP_COUNT "Count" #define FC_ATTR_DEP_OBJ_NAME "Name" -#define FC_ATTR_DEP_COUNT "Count" #define FC_ATTR_DEP_ALLOW_PARTIAL "AllowPartial" #define FC_ELEMENT_OBJECT_DEP "Dep" @@ -2693,7 +2692,7 @@ bool Document::isAnyRestoring() { // Open the document void Document::restore (const char *filename, - bool delaySignal, const std::set &objNames) + bool delaySignal, const std::vector &objNames) { clearUndos(); d->activeObject = 0; @@ -2752,8 +2751,7 @@ void Document::restore (const char *filename, d->partialLoadObjects.emplace(name,true); try { Document::Restore(reader); - } - catch (const Base::Exception& e) { + } catch (const Base::Exception& e) { Base::Console().Error("Invalid Document.xml: %s\n", e.what()); setStatus(Document::RestoreError, true); } @@ -2777,15 +2775,16 @@ void Document::restore (const char *filename, afterRestore(true); } -void Document::afterRestore(bool checkPartial) { +bool Document::afterRestore(bool checkPartial) { Base::FlagToggler<> flag(_IsRestoring,false); if(!afterRestore(d->objectArray,checkPartial)) { FC_WARN("Reload partial document " << getName()); - restore(); - return; + GetApplication().signalPendingReloadDocument(*this); + return false; } GetApplication().signalFinishRestoreDocument(*this); setStatus(Document::Restoring, false); + return true; } bool Document::afterRestore(const std::vector &objArray, bool checkPartial) @@ -2861,9 +2860,12 @@ bool Document::afterRestore(const std::vector &objArray, bool std::string errMsg; if(link && (res=link->checkRestore(&errMsg))) { d->touchedObjs.insert(obj); - if(res==1) + if(res==1 || checkPartial) { FC_WARN(obj->getFullName() << '.' << prop->getName() << ": " << errMsg); - else { + setStatus(Document::LinkStampChanged, true); + if(checkPartial) + return false; + } else { FC_ERR(obj->getFullName() << '.' << prop->getName() << ": " << errMsg); d->addRecomputeLog(errMsg,obj); setStatus(Document::PartialRestore, true); diff --git a/src/App/Document.h b/src/App/Document.h index a4b2285cdc..6d0e7c5ec2 100644 --- a/src/App/Document.h +++ b/src/App/Document.h @@ -74,7 +74,8 @@ public: PartialDoc = 7, AllowPartialRecompute = 8, // allow recomputing editing object if SkipRecompute is set TempDoc = 9, // Mark as temporary document without prompt for save - RestoreError = 10 + RestoreError = 10, + LinkStampChanged = 11, // Indicates during restore time if any linked document's time stamp has changed }; /** @name Properties */ @@ -195,8 +196,8 @@ public: bool saveCopy(const char* file) const; /// Restore the document from the file in Property Path void restore (const char *filename=0, - bool delaySignal=false, const std::set &objNames={}); - void afterRestore(bool checkPartial=false); + bool delaySignal=false, const std::vector &objNames={}); + bool afterRestore(bool checkPartial=false); bool afterRestore(const std::vector &, bool checkPartial=false); enum ExportStatus { NotExporting, diff --git a/src/App/DocumentObserver.h b/src/App/DocumentObserver.h index c92cb3155b..b93373ae2b 100644 --- a/src/App/DocumentObserver.h +++ b/src/App/DocumentObserver.h @@ -62,6 +62,14 @@ public: /*! Assignment operator */ void operator=(const std::string&); + bool operator==(const DocumentT &other) const { + return document == other.document; + } + + bool operator<(const DocumentT &other) const { + return document < other.document; + } + /*! Get a pointer to the document or 0 if it doesn't exist any more. */ Document* getDocument() const; /*! Get the name of the document. */ @@ -387,6 +395,11 @@ public: bool operator!= (const WeakPtrT& p) const { return ptr != p.ptr; } + /*! Get a pointer to the object or 0 if it doesn't exist any more. */ + T* get() const noexcept + { + return ptr.get(); + } private: // disable diff --git a/src/App/PropertyGeo.cpp b/src/App/PropertyGeo.cpp index 02909b32d5..062e78a7b9 100644 --- a/src/App/PropertyGeo.cpp +++ b/src/App/PropertyGeo.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include "Document.h" #include "DocumentObject.h" @@ -938,6 +939,183 @@ void PropertyPlacementLink::Paste(const Property &from) hasSetValue(); } +//************************************************************************** +//************************************************************************** +// PropertyRotation +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +TYPESYSTEM_SOURCE(App::PropertyRotation , App::Property) + +PropertyRotation::PropertyRotation() +{ + +} + + +PropertyRotation::~PropertyRotation() +{ + +} + +void PropertyRotation::setValue(const Base::Rotation &rot) +{ + aboutToSetValue(); + _rot = rot; + hasSetValue(); +} + +bool PropertyRotation::setValueIfChanged(const Base::Rotation &rot, double atol) +{ + if (_rot.isSame(rot, atol)) { + return false; + } + + setValue(rot); + return true; +} + + +const Base::Rotation & PropertyRotation::getValue() const +{ + return _rot; +} + +void PropertyRotation::getPaths(std::vector &paths) const +{ + paths.push_back(ObjectIdentifier(*this) + << ObjectIdentifier::SimpleComponent(ObjectIdentifier::String("Angle"))); + paths.push_back(ObjectIdentifier(*this) + << ObjectIdentifier::SimpleComponent(ObjectIdentifier::String("Axis")) + << ObjectIdentifier::SimpleComponent(ObjectIdentifier::String("x"))); + paths.push_back(ObjectIdentifier(*this) + << ObjectIdentifier::SimpleComponent(ObjectIdentifier::String("Axis")) + << ObjectIdentifier::SimpleComponent(ObjectIdentifier::String("y"))); + paths.push_back(ObjectIdentifier(*this) + << ObjectIdentifier::SimpleComponent(ObjectIdentifier::String("Axis")) + << ObjectIdentifier::SimpleComponent(ObjectIdentifier::String("z"))); +} + +void PropertyRotation::setPathValue(const ObjectIdentifier &path, const boost::any &value) +{ + if (path.getSubPathStr() == ".Angle") { + double avalue; + + if (value.type() == typeid(Base::Quantity)) + avalue = boost::any_cast(value).getValue(); + else if (value.type() == typeid(double)) + avalue = boost::any_cast(value); + else if (value.type() == typeid(int)) + avalue = boost::any_cast(value); + else if (value.type() == typeid(unsigned int)) + avalue = boost::any_cast(value); + else if (value.type() == typeid(short)) + avalue = boost::any_cast(value); + else if (value.type() == typeid(unsigned short)) + avalue = boost::any_cast(value); + else if (value.type() == typeid(long)) + avalue = boost::any_cast(value); + else if (value.type() == typeid(unsigned long)) + avalue = boost::any_cast(value); + else + throw std::bad_cast(); + + Property::setPathValue(path, Base::toRadians(avalue)); + } + else { + Property::setPathValue(path, value); + } +} + +const boost::any PropertyRotation::getPathValue(const ObjectIdentifier &path) const +{ + std::string p = path.getSubPathStr(); + + if (p == ".Angle") { + // Convert angle to degrees + return Base::Quantity(Base::toDegrees(boost::any_cast(Property::getPathValue(path))), Unit::Angle); + } + else { + return Property::getPathValue(path); + } +} + +bool PropertyRotation::getPyPathValue(const ObjectIdentifier &path, Py::Object &res) const +{ + std::string p = path.getSubPathStr(); + if (p == ".Angle") { + Base::Vector3d axis; double angle; + _rot.getValue(axis,angle); + res = Py::asObject(new QuantityPy(new Quantity(Base::toDegrees(angle),Unit::Angle))); + return true; + } + + return false; +} + +PyObject *PropertyRotation::getPyObject() +{ + return new Base::RotationPy(new Base::Rotation(_rot)); +} + +void PropertyRotation::setPyObject(PyObject *value) +{ + if (PyObject_TypeCheck(value, &(Base::MatrixPy::Type))) { + Base::MatrixPy *object = static_cast(value); + Base::Matrix4D mat = object->value(); + Base::Rotation p; + p.setValue(mat); + setValue(p); + } + else if (PyObject_TypeCheck(value, &(Base::RotationPy::Type))) { + setValue(*static_cast(value)->getRotationPtr()); + } + else { + std::string error = std::string("type must be 'Matrix' or 'Rotation', not "); + error += value->ob_type->tp_name; + throw Base::TypeError(error); + } +} + +void PropertyRotation::Save (Base::Writer &writer) const +{ + Vector3d axis; + double rfAngle; + _rot.getValue(axis, rfAngle); + + writer.Stream() << writer.ind() << "\n"; +} + +void PropertyRotation::Restore(Base::XMLReader &reader) +{ + reader.readElement("PropertyRotation"); + aboutToSetValue(); + + _rot = Rotation(Vector3d(reader.getAttributeAsFloat("Ox"), + reader.getAttributeAsFloat("Oy"), + reader.getAttributeAsFloat("Oz")), + reader.getAttributeAsFloat("A")); + hasSetValue(); +} + +Property *PropertyRotation::Copy() const +{ + PropertyRotation *p = new PropertyRotation(); + p->_rot = _rot; + return p; +} + +void PropertyRotation::Paste(const Property &from) +{ + aboutToSetValue(); + _rot = dynamic_cast(from)._rot; + hasSetValue(); +} + // ------------------------------------------------------------ TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyGeometry , App::Property) diff --git a/src/App/PropertyGeo.h b/src/App/PropertyGeo.h index 99103b7b29..f22be71507 100644 --- a/src/App/PropertyGeo.h +++ b/src/App/PropertyGeo.h @@ -35,6 +35,7 @@ #include "Property.h" #include "PropertyLinks.h" #include "ComplexGeoData.h" +#include namespace Base { class Writer; @@ -281,8 +282,9 @@ private: Base::Matrix4D _cMat; }; -/** Vector properties - * This is the father of all properties handling Integers. +/// Property representing a placement +/*! + * Encapsulates a Base::Placement in a Property */ class AppExport PropertyPlacement: public Property { @@ -408,6 +410,73 @@ protected: }; +/// Property representing a rotation +/*! + * Encapsulates a Base::Rotation in a Property + */ +class AppExport PropertyRotation : public Property +{ + TYPESYSTEM_HEADER_WITH_OVERRIDE(); + +public: + /** + * A constructor. + * A more elaborate description of the constructor. + */ + PropertyRotation(); + + /** + * A destructor. + * A more elaborate description of the destructor. + */ + virtual ~PropertyRotation(); + + /** Sets the property + */ + void setValue(const Base::Rotation &rot); + + /** Sets property only if changed + * @param pos: input placement + * @param tol: position tolerance + * @param atol: angular tolerance + */ + bool setValueIfChanged(const Base::Rotation &rot, double atol=1e-12); + + /** This method returns a string representation of the property + */ + const Base::Rotation &getValue() const; + + /// Get valid paths for this property; used by auto completer + void getPaths(std::vector &paths) const override; + + void setPathValue(const ObjectIdentifier &path, const boost::any &value) override; + + virtual const boost::any getPathValue(const ObjectIdentifier &path) const override; + + virtual bool getPyPathValue(const ObjectIdentifier &path, Py::Object &res) const override; + + const char* getEditorName() const override { + return "Gui::PropertyEditor::PropertyRotationItem"; + } + + virtual PyObject *getPyObject() override; + virtual void setPyObject(PyObject *) override; + + virtual void Save (Base::Writer &writer) const override; + virtual void Restore(Base::XMLReader &reader) override; + + virtual Property *Copy() const override; + virtual void Paste(const Property &from) override; + + virtual unsigned int getMemSize () const override { + return sizeof(Base::Placement); + } + +private: + Base::Rotation _rot; +}; + + /** The base class for all basic geometry properties. * @author Werner Mayer */ diff --git a/src/App/PropertyLinks.cpp b/src/App/PropertyLinks.cpp index e78abd3c22..f92879aa4e 100644 --- a/src/App/PropertyLinks.cpp +++ b/src/App/PropertyLinks.cpp @@ -2458,6 +2458,7 @@ class App::DocInfo : public: typedef boost::signals2::scoped_connection Connection; Connection connFinishRestoreDocument; + Connection connPendingReloadDocument; Connection connDeleteDocument; Connection connSaveDocument; Connection connDeletedObject; @@ -2589,6 +2590,7 @@ public: FC_LOG("deinit " << (pcDoc?pcDoc->getName():filePath())); assert(links.empty()); connFinishRestoreDocument.disconnect(); + connPendingReloadDocument.disconnect(); connDeleteDocument.disconnect(); connSaveDocument.disconnect(); connDeletedObject.disconnect(); @@ -2606,6 +2608,8 @@ public: App::Application &app = App::GetApplication(); connFinishRestoreDocument = app.signalFinishRestoreDocument.connect( boost::bind(&DocInfo::slotFinishRestoreDocument,this,bp::_1)); + connPendingReloadDocument = app.signalPendingReloadDocument.connect( + boost::bind(&DocInfo::slotFinishRestoreDocument,this,bp::_1)); connDeleteDocument = app.signalDeleteDocument.connect( boost::bind(&DocInfo::slotDeleteDocument,this,bp::_1)); connSaveDocument = app.signalSaveDocument.connect( @@ -2617,6 +2621,8 @@ public: else{ for(App::Document *doc : App::GetApplication().getDocuments()) { if(getFullPath(doc->getFileName()) == fullpath) { + if(doc->testStatus(App::Document::PartialDoc) && !doc->getObject(objName)) + break; attach(doc); return; } @@ -2642,22 +2648,36 @@ public: continue; } auto obj = doc->getObject(link->objectName.c_str()); - if(!obj) + if(obj) + link->restoreLink(obj); + else if (doc->testStatus(App::Document::PartialDoc)) { + App::GetApplication().addPendingDocument( + doc->FileName.getValue(), + link->objectName.c_str(), + false); + FC_WARN("reloading partial document '" << doc->FileName.getValue() + << "' due to object " << link->objectName); + } else FC_WARN("object '" << link->objectName << "' not found in document '" << doc->getName() << "'"); - else - link->restoreLink(obj); } for(auto &v : parentLinks) { v.first->setFlag(PropertyLinkBase::LinkRestoring); v.first->aboutToSetValue(); for(auto link : v.second) { auto obj = doc->getObject(link->objectName.c_str()); - if(!obj) + if(obj) + link->restoreLink(obj); + else if (doc->testStatus(App::Document::PartialDoc)) { + App::GetApplication().addPendingDocument( + doc->FileName.getValue(), + link->objectName.c_str(), + false); + FC_WARN("reloading partial document '" << doc->FileName.getValue() + << "' due to object " << link->objectName); + } else FC_WARN("object '" << link->objectName << "' not found in document '" << doc->getName() << "'"); - else - link->restoreLink(obj); } v.first->hasSetValue(); v.first->setFlag(PropertyLinkBase::LinkRestoring,false); @@ -2723,16 +2743,17 @@ public: } } - // time stamp changed, touch the linking document. Unfortunately, there - // is no way to setModfied() for an App::Document. We don't want to touch - // all PropertyXLink for a document, because the linked object is - // potentially unchanged. So we just touch at most one. + // time stamp changed, touch the linking document. std::set docs; for(auto link : links) { auto linkdoc = static_cast(link->getContainer())->getDocument(); auto ret = docs.insert(linkdoc); - if(ret.second && !linkdoc->isTouched()) - link->touch(); + if(ret.second) { + // This will signal the Gui::Document to call setModified(); + FC_LOG("touch document " << linkdoc->getName() + << " on time stamp change of " << link->getFullName()); + linkdoc->Comment.touch(); + } } } @@ -3473,7 +3494,12 @@ PropertyXLink::getDocumentOutList(App::Document *doc) { std::map > ret; for(auto &v : _DocInfoMap) { for(auto link : v.second->links) { - if(!v.second->pcDoc) continue; + if(!v.second->pcDoc + || link->getScope() == LinkScope::Hidden + || link->testStatus(Property::PropTransient) + || link->testStatus(Property::Transient) + || link->testStatus(Property::PropNoPersist)) + continue; auto obj = dynamic_cast(link->getContainer()); if(!obj || !obj->getNameInDocument() || !obj->getDocument()) continue; @@ -3493,6 +3519,11 @@ PropertyXLink::getDocumentInList(App::Document *doc) { continue; auto &docs = ret[v.second->pcDoc]; for(auto link : v.second->links) { + if(link->getScope() == LinkScope::Hidden + || link->testStatus(Property::PropTransient) + || link->testStatus(Property::Transient) + || link->testStatus(Property::PropNoPersist)) + continue; auto obj = dynamic_cast(link->getContainer()); if(obj && obj->getNameInDocument() && obj->getDocument()) docs.insert(obj->getDocument()); @@ -4460,12 +4491,12 @@ void PropertyXLinkContainer::breakLink(App::DocumentObject *obj, bool clear) { } int PropertyXLinkContainer::checkRestore(std::string *msg) const { - if(_LinkRestored) - return 1; - for(auto &v : _XLinks) { - int res = v.second->checkRestore(msg); - if(res) - return res; + if(_LinkRestored) { + for(auto &v : _XLinks) { + int res = v.second->checkRestore(msg); + if(res) + return res; + } } return 0; } diff --git a/src/Base/Parameter.cpp b/src/Base/Parameter.cpp index 53236af1cc..b115e4e02f 100644 --- a/src/Base/Parameter.cpp +++ b/src/Base/Parameter.cpp @@ -732,11 +732,8 @@ std::string ParameterGrp::GetASCII(const char* Name, const char * pPreset) const DOMNode *pcElem2 = pcElem->getFirstChild(); if (pcElem2) return std::string(StrXUTF8(pcElem2->getNodeValue()).c_str()); - else if (pPreset==0) - return std::string(""); - else - return std::string(pPreset); + return std::string(""); } std::vector ParameterGrp::GetASCIIs(const char * sFilter) const diff --git a/src/Base/Rotation.cpp b/src/Base/Rotation.cpp index 646731561d..67a6e5d525 100644 --- a/src/Base/Rotation.cpp +++ b/src/Base/Rotation.cpp @@ -683,7 +683,7 @@ void Rotation::getYawPitchRoll(double& y, double& p, double& r) const // south pole y = 0.0; p = -D_PI/2.0; - r = -2.0 * atan2(quat[0],quat[3]); + r = 2.0 * atan2(quat[0],quat[3]); } else { y = atan2(2.0*(q01+q23),(q00+q33)-(q11+q22)); diff --git a/src/Base/RotationPy.xml b/src/Base/RotationPy.xml index bfb60e2a70..35e804d310 100644 --- a/src/Base/RotationPy.xml +++ b/src/Base/RotationPy.xml @@ -83,15 +83,36 @@ - + + + + setYawPitchRoll(angle1, angle2, angle3) + Set the Euler angles of this rotation + as yaw-pitch-roll in XY'Z'' convention. + + NOTE: The angles are in degree + + + + - toEuler() -> list + getYawPitchRoll() -> list Get the Euler angles of this rotation as yaw-pitch-roll in XY'Z'' convention - + NOTE: The angles are in degree + + + + + setEulerAngles(seq, angle1, angle2, angle3) + Set the Euler angles in a given sequence for this rotation. + 'seq' is the Euler sequence name. You get all possible values with toEulerAngles() + + + diff --git a/src/Base/RotationPyImp.cpp b/src/Base/RotationPyImp.cpp index a66997f3b9..ea467a678b 100644 --- a/src/Base/RotationPyImp.cpp +++ b/src/Base/RotationPyImp.cpp @@ -35,7 +35,7 @@ using namespace Base; // returns a string which represents the object e.g. when printed in python -std::string RotationPy::representation(void) const +std::string RotationPy::representation() const { RotationPy::PointerType ptr = reinterpret_cast(_pcTwinPointer); Py::Float q0(ptr->getValue()[0]); @@ -44,10 +44,10 @@ std::string RotationPy::representation(void) const Py::Float q3(ptr->getValue()[3]); std::stringstream str; str << "Rotation ("; - str << (std::string)q0.repr() << ", " - << (std::string)q1.repr() << ", " - << (std::string)q2.repr() << ", " - << (std::string)q3.repr(); + str << static_cast(q0.repr()) << ", " + << static_cast(q1.repr()) << ", " + << static_cast(q2.repr()) << ", " + << static_cast(q3.repr()); str << ")"; return str.str(); @@ -60,7 +60,7 @@ PyObject *RotationPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // P } // constructor method -int RotationPy::PyInit(PyObject* args, PyObject* /*kwd*/) +int RotationPy::PyInit(PyObject* args, PyObject* kwds) { PyObject* o; if (PyArg_ParseTuple(args, "")) { @@ -76,10 +76,18 @@ int RotationPy::PyInit(PyObject* args, PyObject* /*kwd*/) PyErr_Clear(); double angle; - if (PyArg_ParseTuple(args, "O!d", &(Base::VectorPy::Type), &o, &angle)) { - // NOTE: The last parameter defines the rotation angle in degree. - getRotationPtr()->setValue(static_cast(o)->value(), Base::toRadians(angle)); - return 0; + static char *kw_deg[] = {"Axis", "Degree", nullptr}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "O!d", kw_deg, &(Base::VectorPy::Type), &o, &angle)) { + // NOTE: The last parameter defines the rotation angle in degree. + getRotationPtr()->setValue(static_cast(o)->value(), Base::toRadians(angle)); + return 0; + } + + PyErr_Clear(); + static char *kw_rad[] = {"Axis", "Radian", nullptr}; + if (PyArg_ParseTupleAndKeywords(args, kwds, "O!d", kw_rad, &(Base::VectorPy::Type), &o, &angle)) { + getRotationPtr()->setValue(static_cast(o)->value(), angle); + return 0; } PyErr_Clear(); @@ -162,7 +170,7 @@ int RotationPy::PyInit(PyObject* args, PyObject* /*kwd*/) PyErr_Clear(); PyObject *v3; - char *priority = nullptr; + const char *priority = nullptr; if (PyArg_ParseTuple(args, "O!O!O!|s", &(Base::VectorPy::Type), &v1, &(Base::VectorPy::Type), &v2, &(Base::VectorPy::Type), &v3, @@ -208,11 +216,11 @@ PyObject* RotationPy::richCompare(PyObject *v, PyObject *w, int op) Base::Rotation r1 = *static_cast(v)->getRotationPtr(); Base::Rotation r2 = *static_cast(w)->getRotationPtr(); - PyObject *res=0; + PyObject *res=nullptr; if (op != Py_EQ && op != Py_NE) { PyErr_SetString(PyExc_TypeError, "no ordering relation is defined for Rotation"); - return 0; + return nullptr; } else if (op == Py_EQ) { res = (r1 == r2) ? Py_True : Py_False; @@ -235,7 +243,7 @@ PyObject* RotationPy::richCompare(PyObject *v, PyObject *w, int op) PyObject* RotationPy::invert(PyObject * args) { if (!PyArg_ParseTuple(args, "")) - return 0; + return nullptr; this->getRotationPtr()->invert(); Py_Return; } @@ -243,7 +251,7 @@ PyObject* RotationPy::invert(PyObject * args) PyObject* RotationPy::inverted(PyObject * args) { if (!PyArg_ParseTuple(args, "")) - return 0; + return nullptr; Rotation mult = this->getRotationPtr()->inverse(); return new RotationPy(new Rotation(mult)); } @@ -252,7 +260,7 @@ PyObject* RotationPy::multiply(PyObject * args) { PyObject *rot; if (!PyArg_ParseTuple(args, "O!", &(RotationPy::Type), &rot)) - return NULL; + return nullptr; Rotation mult = (*getRotationPtr()) * (*static_cast(rot)->getRotationPtr()); return new RotationPy(new Rotation(mult)); } @@ -261,7 +269,7 @@ PyObject* RotationPy::multVec(PyObject * args) { PyObject *obj; if (!PyArg_ParseTuple(args, "O!", &(VectorPy::Type), &obj)) - return NULL; + return nullptr; Base::Vector3d vec(static_cast(obj)->value()); getRotationPtr()->multVec(vec, vec); return new VectorPy(new Vector3d(vec)); @@ -279,10 +287,19 @@ PyObject* RotationPy::slerp(PyObject * args) return new RotationPy(new Rotation(sl)); } -PyObject* RotationPy::toEuler(PyObject * args) +PyObject* RotationPy::setYawPitchRoll(PyObject * args) +{ + double A,B,C; + if (!PyArg_ParseTuple(args, "ddd", &A, &B, &C)) + return nullptr; + this->getRotationPtr()->setYawPitchRoll(A,B,C); + Py_Return; +} + +PyObject* RotationPy::getYawPitchRoll(PyObject * args) { if (!PyArg_ParseTuple(args, "")) - return NULL; + return nullptr; double A,B,C; this->getRotationPtr()->getYawPitchRoll(A,B,C); @@ -293,11 +310,29 @@ PyObject* RotationPy::toEuler(PyObject * args) return Py::new_reference_to(tuple); } +PyObject* RotationPy::setEulerAngles(PyObject * args) +{ + const char *seq; + double A,B,C; + if (!PyArg_ParseTuple(args, "sddd", &seq, &A, &B, &C)) + return nullptr; + + try { + getRotationPtr()->setEulerAngles( + Rotation::eulerSequenceFromName(seq), A, B, C); + Py_Return; + } + catch (const Base::Exception& e) { + e.setPyException(); + return nullptr; + } +} + PyObject* RotationPy::toEulerAngles(PyObject * args) { const char *seq = nullptr; if (!PyArg_ParseTuple(args, "|s", &seq)) - return NULL; + return nullptr; if (!seq) { Py::List res; for (int i=1; i(rot)->getRotationPtr(); bool same = tol > 0.0 ? rot1.isSame(rot2, tol) : rot1.isSame(rot2); @@ -343,7 +378,7 @@ PyObject* RotationPy::isSame(PyObject *args) PyObject* RotationPy::isIdentity(PyObject *args) { if (!PyArg_ParseTuple(args, "")) - return NULL; + return nullptr; bool null = getRotationPtr()->isIdentity(); return Py_BuildValue("O", (null ? Py_True : Py_False)); } @@ -351,12 +386,12 @@ PyObject* RotationPy::isIdentity(PyObject *args) PyObject* RotationPy::isNull(PyObject *args) { if (!PyArg_ParseTuple(args, "")) - return NULL; + return nullptr; bool null = getRotationPtr()->isNull(); return Py_BuildValue("O", (null ? Py_True : Py_False)); } -Py::Tuple RotationPy::getQ(void) const +Py::Tuple RotationPy::getQ() const { double q0, q1, q2, q3; this->getRotationPtr()->getValue(q0,q1,q2,q3); @@ -371,14 +406,14 @@ Py::Tuple RotationPy::getQ(void) const void RotationPy::setQ(Py::Tuple arg) { - double q0 = (double)Py::Float(arg.getItem(0)); - double q1 = (double)Py::Float(arg.getItem(1)); - double q2 = (double)Py::Float(arg.getItem(2)); - double q3 = (double)Py::Float(arg.getItem(3)); + double q0 = static_cast(Py::Float(arg.getItem(0))); + double q1 = static_cast(Py::Float(arg.getItem(1))); + double q2 = static_cast(Py::Float(arg.getItem(2))); + double q3 = static_cast(Py::Float(arg.getItem(3))); this->getRotationPtr()->setValue(q0,q1,q2,q3); } -Py::Object RotationPy::getRawAxis(void) const +Py::Object RotationPy::getRawAxis() const { Base::Vector3d axis; double angle; this->getRotationPtr()->getRawValue(axis, angle); @@ -400,7 +435,7 @@ void RotationPy::setAxis(Py::Object arg) this->getRotationPtr()->setValue(axis, angle); } -Py::Float RotationPy::getAngle(void) const +Py::Float RotationPy::getAngle() const { Base::Vector3d axis; double angle; this->getRotationPtr()->getValue(axis, angle); @@ -437,7 +472,11 @@ PyObject *RotationPy::getCustomAttributes(const char* attr) const this->getRotationPtr()->getYawPitchRoll(A,B,C); return PyFloat_FromDouble(C); } - return 0; + else if (strcmp(attr, "toEuler") == 0) { + Py::Object self(const_cast(this), false); + return Py::new_reference_to(self.getAttr("getYawPitchRoll")); + } + return nullptr; } int RotationPy::setCustomAttributes(const char* attr, PyObject* obj) @@ -521,19 +560,17 @@ PyObject* RotationPy::number_multiply_handler(PyObject *self, PyObject *other) } PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_power_handler (PyObject* self, PyObject* other, PyObject* arg) { if (!PyObject_TypeCheck(self, &(RotationPy::Type)) || - - !PyLong_Check(other) - || arg != Py_None - ) + !PyLong_Check(other) || + arg != Py_None) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } Rotation a = static_cast(self)->value(); @@ -552,49 +589,49 @@ PyObject * RotationPy::number_power_handler (PyObject* self, PyObject* other, Py PyObject* RotationPy::number_add_handler(PyObject * /*self*/, PyObject * /*other*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject* RotationPy::number_subtract_handler(PyObject * /*self*/, PyObject * /*other*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_divide_handler (PyObject* /*self*/, PyObject* /*other*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_remainder_handler (PyObject* /*self*/, PyObject* /*other*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_divmod_handler (PyObject* /*self*/, PyObject* /*other*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_negative_handler (PyObject* /*self*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_positive_handler (PyObject* /*self*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_absolute_handler (PyObject* /*self*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } int RotationPy::number_nonzero_handler (PyObject* /*self*/) @@ -605,48 +642,48 @@ int RotationPy::number_nonzero_handler (PyObject* /*self*/) PyObject * RotationPy::number_invert_handler (PyObject* /*self*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_lshift_handler (PyObject* /*self*/, PyObject* /*other*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_rshift_handler (PyObject* /*self*/, PyObject* /*other*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_and_handler (PyObject* /*self*/, PyObject* /*other*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_xor_handler (PyObject* /*self*/, PyObject* /*other*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_or_handler (PyObject* /*self*/, PyObject* /*other*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_int_handler (PyObject * /*self*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } PyObject * RotationPy::number_float_handler (PyObject * /*self*/) { PyErr_SetString(PyExc_NotImplementedError, "Not implemented"); - return 0; + return nullptr; } diff --git a/src/Base/Translate.cpp b/src/Base/Translate.cpp index 50eeddfed5..2c6db05c5f 100644 --- a/src/Base/Translate.cpp +++ b/src/Base/Translate.cpp @@ -24,6 +24,7 @@ #include "Translate.h" #include +#include using namespace Base; @@ -62,6 +63,12 @@ Translate::Translate() &Translate::trNoop, "QT_TR_NOOP_UTF8(sourcetext)\n" "Same as QT_TR_NOOP"); + add_varargs_method("installTranslator", + &Translate::installTranslator, + "Install a translator for testing purposes"); + add_varargs_method("removeTranslators", + &Translate::removeTranslators, + "Remove test translators"); initialize("This module is the Translate module"); // register with Python } @@ -109,3 +116,37 @@ Py::Object Translate::trNoop(const Py::Tuple& args) throw Py::Exception(); return Py::Object(arg1); } + +Py::Object Translate::installTranslator(const Py::Tuple& args) +{ + char* Name; + if (!PyArg_ParseTuple(args.ptr(), "et","utf-8",&Name)) + throw Py::Exception(); + QString filename = QString::fromUtf8(Name); + PyMem_Free(Name); + + bool ok = false; + QFileInfo fi(filename); + std::shared_ptr translator(std::make_shared(nullptr)); + translator->setObjectName(fi.fileName()); + if (translator->load(filename)) { + qApp->installTranslator(translator.get()); + translators.push_back(translator); + ok = true; + } + + return Py::Boolean(ok); +} + +Py::Object Translate::removeTranslators(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + bool ok = true; + for (const auto& it : translators) { + ok &= QCoreApplication::removeTranslator(it.get()); + } + + translators.clear(); + return Py::Boolean(ok); +} diff --git a/src/Base/Translate.h b/src/Base/Translate.h index 1b75a2fe26..bb73095db8 100644 --- a/src/Base/Translate.h +++ b/src/Base/Translate.h @@ -26,10 +26,14 @@ #include #include +#include +#include +#include #ifndef FC_GLOBAL_H #include #endif + namespace Base { class BaseExport Translate : public Py::ExtensionModule @@ -43,6 +47,11 @@ private: Py::Object translateNoop(const Py::Tuple& args); Py::Object translateNoop3(const Py::Tuple& args); Py::Object trNoop(const Py::Tuple& args); + Py::Object installTranslator(const Py::Tuple& args); + Py::Object removeTranslators(const Py::Tuple& args); + +private: + std::list> translators; }; } // namespace Base diff --git a/src/Gui/ActiveObjectList.cpp b/src/Gui/ActiveObjectList.cpp index aff4d1b06b..2d7bf2184d 100644 --- a/src/Gui/ActiveObjectList.cpp +++ b/src/Gui/ActiveObjectList.cpp @@ -44,93 +44,107 @@ App::DocumentObject *ActiveObjectList::getObject(const ObjectInfo &info, bool re App::DocumentObject **parent, std::string *subname) const { - if(parent) *parent = info.obj; - if(subname) *subname = info.subname; + if (parent) + *parent = info.obj; + if (subname) + *subname = info.subname; auto obj = info.obj; - if(!obj || !obj->getNameInDocument()) - return 0; - if(info.subname.size()) { + if (!obj || !obj->getNameInDocument()) + return nullptr; + if (!info.subname.empty()) { obj = obj->getSubObject(info.subname.c_str()); - if(!obj) - return 0; + if (!obj) + return nullptr; } - return resolve?obj->getLinkedObject(true):obj; + + return resolve ? obj->getLinkedObject(true) : obj; } void ActiveObjectList::setHighlight(const ObjectInfo &info, HighlightMode mode, bool enable) { - auto obj = getObject(info,false); - if(!obj) return; + auto obj = getObject(info, false); + if (!obj) + return; auto vp = dynamic_cast(Application::Instance->getViewProvider(obj)); - if(!vp) return; + if (!vp) + return; if (TreeParams::Instance()->TreeActiveAutoExpand()) { vp->getDocument()->signalExpandObject(*vp, enable ? TreeItemMode::ExpandPath : TreeItemMode::CollapseItem, info.obj, info.subname.c_str()); } - vp->getDocument()->signalHighlightObject(*vp, mode,enable,info.obj,info.subname.c_str()); + vp->getDocument()->signalHighlightObject(*vp, mode, enable, info.obj, info.subname.c_str()); } Gui::ActiveObjectList::ObjectInfo Gui::ActiveObjectList::getObjectInfo(App::DocumentObject *obj, const char *subname) const { ObjectInfo info; - info.obj = 0; - if(!obj || !obj->getNameInDocument()) + info.obj = nullptr; + if (!obj || !obj->getNameInDocument()) return info; - if(subname) { + + if (subname) { info.obj = obj; info.subname = subname; - }else{ + } + else { // If the input object is not from this document, it must be brought in // by some link type object of this document. We only accept the object // if we can find such object in the current selection. auto sels = Gui::Selection().getSelection(_Doc->getDocument()->getName(),false); - for(auto &sel : sels) { - if(sel.pObject == obj || sel.pObject->getLinkedObject(true)==obj) { + for (auto &sel : sels) { + if (sel.pObject == obj || sel.pObject->getLinkedObject(true)==obj) { info.obj = sel.pObject; break; } - for(auto dot=strchr(sel.SubName,'.');dot;dot=strchr(dot+1,'.')) { + + for (auto dot=strchr(sel.SubName,'.');dot;dot=strchr(dot+1,'.')) { std::string subname(sel.SubName,dot-sel.SubName+1); auto sobj = sel.pObject->getSubObject(subname.c_str()); - if(!sobj) break; - if(sobj == obj || sobj->getLinkedObject(true) == obj) { + if (!sobj) + break; + if (sobj == obj || sobj->getLinkedObject(true) == obj) { info.obj = sel.pObject; info.subname = subname; break; } } - if(info.obj) break; + + if (info.obj) + break; } - if(!info.obj && obj->getDocument()==_Doc->getDocument()) + + if (!info.obj && obj->getDocument()==_Doc->getDocument()) info.obj = obj; } return info; } bool Gui::ActiveObjectList::hasObject(App::DocumentObject *obj, - const char *name, const char *subname) const + const char *name, const char *subname) const { auto it = _ObjectMap.find(name); - if(it==_ObjectMap.end()) + if (it == _ObjectMap.end()) return false; - auto info = getObjectInfo(obj,subname); - return info.obj==it->second.obj && info.subname==it->second.subname; + auto info = getObjectInfo(obj, subname); + return info.obj == it->second.obj && info.subname == it->second.subname; } void Gui::ActiveObjectList::setObject(App::DocumentObject* obj, const char* name, - const char *subname, const Gui::HighlightMode& mode) + const char *subname, const Gui::HighlightMode& mode) { auto it = _ObjectMap.find(name); - if(it!=_ObjectMap.end()) { - setHighlight(it->second,mode,false); + if (it!=_ObjectMap.end()) { + setHighlight(it->second, mode, false); _ObjectMap.erase(it); } - if(!obj) return; + + if (!obj) + return; auto info = getObjectInfo(obj,subname); - if(!info.obj) { + if (!info.obj) { FC_ERR("Cannot set active object " << obj->getFullName() << '.' << (subname?subname:"") << " in document '" << _Doc->getDocument()->getName() @@ -139,7 +153,7 @@ void Gui::ActiveObjectList::setObject(App::DocumentObject* obj, const char* name } _ObjectMap[name] = info; - setHighlight(info,mode,true); + setHighlight(info, mode, true); } bool Gui::ActiveObjectList::hasObject(const char*name)const @@ -149,13 +163,11 @@ bool Gui::ActiveObjectList::hasObject(const char*name)const void ActiveObjectList::objectDeleted(const ViewProviderDocumentObject &vp) { - //maybe boost::bimap or boost::multi_index - for (auto it = _ObjectMap.begin(); it != _ObjectMap.end(); ++it) - { - if (it->second.obj == vp.getObject()) - { - _ObjectMap.erase(it); - return; + //maybe boost::bimap or boost::multi_index + for (auto it = _ObjectMap.begin(); it != _ObjectMap.end(); ++it) { + if (it->second.obj == vp.getObject()) { + _ObjectMap.erase(it); + return; + } } - } } diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 99500fa9c4..0dc5ed879a 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -170,6 +170,7 @@ struct ApplicationP ~ApplicationP() { delete macroMngr; + delete prefPackManager; } /// list of all handled documents @@ -1954,13 +1955,13 @@ void Application::runApplication(void) mainApp.setApplicationName(QString::fromUtf8(it->second.c_str())); } else { - mainApp.setApplicationName(QString::fromUtf8(App::GetApplication().getExecutableName())); + mainApp.setApplicationName(QString::fromStdString(App::Application::getExecutableName())); } #ifndef Q_OS_MACX mainApp.setWindowIcon(Gui::BitmapFactory().pixmap(App::Application::Config()["AppIcon"].c_str())); #endif QString plugin; - plugin = QString::fromUtf8(App::GetApplication().getHomePath()); + plugin = QString::fromStdString(App::Application::getHomePath()); plugin += QLatin1String("/plugins"); QCoreApplication::addLibraryPath(plugin); @@ -2126,7 +2127,7 @@ void Application::runApplication(void) // init the Inventor subsystem initOpenInventor(); - QString home = QString::fromUtf8(App::GetApplication().getHomePath()); + QString home = QString::fromStdString(App::Application::getHomePath()); it = cfg.find("WindowTitle"); if (it != cfg.end()) { @@ -2266,7 +2267,7 @@ void Application::runApplication(void) try { std::stringstream s; - s << App::Application::getTempPath() << App::GetApplication().getExecutableName() + s << App::Application::getTempPath() << App::Application::getExecutableName() << "_" << QCoreApplication::applicationPid() << ".lock"; // open a lock file with the PID Base::FileInfo fi(s.str()); @@ -2416,80 +2417,8 @@ void Application::setStyleSheet(const QString& qssFile, bool tiledBackground) void Application::checkForPreviousCrashes() { - QDir tmp = QString::fromUtf8(App::Application::getTempPath().c_str()); - tmp.setNameFilters(QStringList() << QString::fromLatin1("*.lock")); - tmp.setFilter(QDir::Files); - - QList restoreDocFiles; - QString exeName = QString::fromLatin1(App::GetApplication().getExecutableName()); - QList locks = tmp.entryInfoList(); - for (QList::iterator it = locks.begin(); it != locks.end(); ++it) { - QString bn = it->baseName(); - // ignore the lock file for this instance - QString pid = QString::number(QCoreApplication::applicationPid()); - if (bn.startsWith(exeName) && bn.indexOf(pid) < 0) { - QString fn = it->absoluteFilePath(); - boost::interprocess::file_lock flock((const char*)fn.toLocal8Bit()); - if (flock.try_lock()) { - // OK, this file is a leftover from a previous crash - QString crashed_pid = bn.mid(exeName.length()+1); - // search for transient directories with this PID - QString filter; - QTextStream str(&filter); - str << exeName << "_Doc_*_" << crashed_pid; - tmp.setNameFilters(QStringList() << filter); - tmp.setFilter(QDir::Dirs); - QList dirs = tmp.entryInfoList(); - if (dirs.isEmpty()) { - // delete the lock file immediately if no transient directories are related - tmp.remove(fn); - } - else { - int countDeletedDocs = 0; - QString recovery_files = QString::fromLatin1("fc_recovery_files"); - for (QList::iterator it = dirs.begin(); it != dirs.end(); ++it) { - QDir doc_dir(it->absoluteFilePath()); - doc_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries); - uint entries = doc_dir.entryList().count(); - if (entries == 0) { - // in this case we can delete the transient directory because - // we cannot do anything - if (tmp.rmdir(it->filePath())) - countDeletedDocs++; - } - // search for the existence of a recovery file - else if (doc_dir.exists(QLatin1String("fc_recovery_file.xml"))) { - // store the transient directory in case it's not empty - restoreDocFiles << *it; - } - // search for the 'fc_recovery_files' sub-directory and check that it's the only entry - else if (entries == 1 && doc_dir.exists(recovery_files)) { - // if the sub-directory is empty delete the transient directory - QDir rec_dir(doc_dir.absoluteFilePath(recovery_files)); - rec_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries); - if (rec_dir.entryList().isEmpty()) { - doc_dir.rmdir(recovery_files); - if (tmp.rmdir(it->filePath())) - countDeletedDocs++; - } - } - } - - // all directories corresponding to the lock file have been deleted - // so delete the lock file, too - if (countDeletedDocs == dirs.size()) { - tmp.remove(fn); - } - } - } - } - } - - if (!restoreDocFiles.isEmpty()) { - Gui::Dialog::DocumentRecovery dlg(restoreDocFiles, Gui::getMainWindow()); - if (dlg.foundDocuments()) - dlg.exec(); - } + Gui::Dialog::DocumentRecoveryFinder finder; + finder.checkForPreviousCrashes(); } App::Document *Application::reopen(App::Document *doc) { diff --git a/src/Gui/ApplicationPy.cpp b/src/Gui/ApplicationPy.cpp index cf23554b0d..82a883cb1f 100644 --- a/src/Gui/ApplicationPy.cpp +++ b/src/Gui/ApplicationPy.cpp @@ -1029,7 +1029,7 @@ PyObject* Application::sAddResPath(PyObject * /*self*/, PyObject *args) PyMem_Free(filePath); if (QDir::isRelativePath(path)) { // Home path ends with '/' - QString home = QString::fromUtf8(App::GetApplication().getHomePath()); + QString home = QString::fromStdString(App::Application::getHomePath()); path = home + path; } @@ -1048,7 +1048,7 @@ PyObject* Application::sAddLangPath(PyObject * /*self*/, PyObject *args) PyMem_Free(filePath); if (QDir::isRelativePath(path)) { // Home path ends with '/' - QString home = QString::fromUtf8(App::GetApplication().getHomePath()); + QString home = QString::fromStdString(App::Application::getHomePath()); path = home + path; } @@ -1066,7 +1066,7 @@ PyObject* Application::sAddIconPath(PyObject * /*self*/, PyObject *args) PyMem_Free(filePath); if (QDir::isRelativePath(path)) { // Home path ends with '/' - QString home = QString::fromUtf8(App::GetApplication().getHomePath()); + QString home = QString::fromStdString(App::Application::getHomePath()); path = home + path; } diff --git a/src/Gui/Assistant.cpp b/src/Gui/Assistant.cpp index f903c7a28f..e905fe8dfd 100644 --- a/src/Gui/Assistant.cpp +++ b/src/Gui/Assistant.cpp @@ -85,8 +85,8 @@ bool Assistant::startAssistant() if (proc->state() != QProcess::Running) { #ifdef Q_OS_WIN QString app; - app = QDir::toNativeSeparators(QString::fromUtf8 - (App::GetApplication().getHomePath()) + QLatin1String("bin/")); + app = QDir::toNativeSeparators(QString::fromStdString + (App::Application::getHomePath()) + QLatin1String("bin/")); #elif defined(Q_OS_MAC) QString app = QCoreApplication::applicationDirPath() + QDir::separator(); #else @@ -95,8 +95,8 @@ bool Assistant::startAssistant() app += QLatin1String("assistant"); // get the name of the executable and the doc path - QString exe = QString::fromUtf8(App::GetApplication().getExecutableName()); - QString doc = QString::fromUtf8(App::Application::getHelpDir().c_str()); + QString exe = QString::fromStdString(App::Application::getExecutableName()); + QString doc = QString::fromStdString(App::Application::getHelpDir()); QString qhc = doc + exe.toLower() + QLatin1String(".qhc"); diff --git a/src/Gui/BitmapFactory.cpp b/src/Gui/BitmapFactory.cpp index bfa5317cf1..2e9ffd7156 100644 --- a/src/Gui/BitmapFactory.cpp +++ b/src/Gui/BitmapFactory.cpp @@ -97,15 +97,15 @@ BitmapFactoryInst& BitmapFactoryInst::instance(void) std::map::const_iterator it; it = App::GetApplication().Config().find("ProgramIcons"); if (it != App::GetApplication().Config().end()) { - QString home = QString::fromUtf8(App::GetApplication().getHomePath()); + QString home = QString::fromStdString(App::Application::getHomePath()); QString path = QString::fromUtf8(it->second.c_str()); if (QDir(path).isRelative()) { path = QFileInfo(QDir(home), path).absoluteFilePath(); } _pcSingleton->addPath(path); } - _pcSingleton->addPath(QString::fromLatin1("%1/icons").arg(QString::fromUtf8(App::GetApplication().getHomePath()))); - _pcSingleton->addPath(QString::fromLatin1("%1/icons").arg(QString::fromUtf8(App::GetApplication().Config()["UserAppData"].c_str()))); + _pcSingleton->addPath(QString::fromLatin1("%1/icons").arg(QString::fromStdString(App::Application::getHomePath()))); + _pcSingleton->addPath(QString::fromLatin1("%1/icons").arg(QString::fromStdString(App::Application::getUserAppDataDir()))); _pcSingleton->addPath(QLatin1String(":/icons/")); _pcSingleton->addPath(QLatin1String(":/Icons/")); } diff --git a/src/Gui/BlenderNavigationStyle.cpp b/src/Gui/BlenderNavigationStyle.cpp index b5b0cfbc42..563b124e86 100644 --- a/src/Gui/BlenderNavigationStyle.cpp +++ b/src/Gui/BlenderNavigationStyle.cpp @@ -88,12 +88,10 @@ SbBool BlenderNavigationStyle::processSoEvent(const SoEvent * const ev) const SoType type(ev->getTypeId()); const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion(); - const SbVec2s size(vp.getViewportSizePixels()); - const SbVec2f prevnormalized = this->lastmouseposition; const SbVec2s pos(ev->getPosition()); - const SbVec2f posn((float) pos[0] / (float) std::max((int)(size[0] - 1), 1), - (float) pos[1] / (float) std::max((int)(size[1] - 1), 1)); + const SbVec2f posn = normalizePixelPos(pos); + const SbVec2f prevnormalized = this->lastmouseposition; this->lastmouseposition = posn; // Set to true if any event processing happened. Note that it is not @@ -107,15 +105,7 @@ SbBool BlenderNavigationStyle::processSoEvent(const SoEvent * const ev) // Mismatches in state of the modifier keys happens if the user // presses or releases them outside the viewer window. - if (this->ctrldown != ev->wasCtrlDown()) { - this->ctrldown = ev->wasCtrlDown(); - } - if (this->shiftdown != ev->wasShiftDown()) { - this->shiftdown = ev->wasShiftDown(); - } - if (this->altdown != ev->wasAltDown()) { - this->altdown = ev->wasAltDown(); - } + syncModifierKeys(ev); // give the nodes in the foreground root the chance to handle events (e.g color bar) if (!viewer->isEditing()) { @@ -126,41 +116,8 @@ SbBool BlenderNavigationStyle::processSoEvent(const SoEvent * const ev) // Keyboard handling if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { - const SoKeyboardEvent * const event = (const SoKeyboardEvent *) ev; - const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; - switch (event->getKey()) { - case SoKeyboardEvent::LEFT_CONTROL: - case SoKeyboardEvent::RIGHT_CONTROL: - this->ctrldown = press; - break; - case SoKeyboardEvent::LEFT_SHIFT: - case SoKeyboardEvent::RIGHT_SHIFT: - this->shiftdown = press; - break; - case SoKeyboardEvent::LEFT_ALT: - case SoKeyboardEvent::RIGHT_ALT: - this->altdown = press; - break; - case SoKeyboardEvent::H: - processed = true; - viewer->saveHomePosition(); - break; - case SoKeyboardEvent::R: - processed = true; - viewer->resetToHomePosition(); - break; - case SoKeyboardEvent::S: - case SoKeyboardEvent::HOME: - case SoKeyboardEvent::LEFT_ARROW: - case SoKeyboardEvent::UP_ARROW: - case SoKeyboardEvent::RIGHT_ARROW: - case SoKeyboardEvent::DOWN_ARROW: - if (!this->isViewing()) - this->setViewing(true); - break; - default: - break; - } + const SoKeyboardEvent * const event = static_cast(ev); + processed = processKeyboardEvent(event); } // Mouse Button / Spaceball Button handling @@ -179,10 +136,6 @@ SbBool BlenderNavigationStyle::processSoEvent(const SoEvent * const ev) this->seekToPoint(pos); // implicitly calls interactiveCountInc() processed = true; } - //else if (press && (this->currentmode == NavigationStyle::IDLE)) { - // this->setViewing(true); - // processed = true; - //} else if (press && (this->currentmode == NavigationStyle::PANNING || this->currentmode == NavigationStyle::ZOOMING)) { newmode = NavigationStyle::DRAGGING; @@ -193,30 +146,8 @@ SbBool BlenderNavigationStyle::processSoEvent(const SoEvent * const ev) else if (viewer->isEditing() && (this->currentmode == NavigationStyle::SPINNING)) { processed = true; } - // issue #0002433: avoid to swallow the UP event if down the - // scene graph somewhere a dialog gets opened - else if (press) { - SbTime tmp = (ev->getTime() - mouseDownConsumedEvent.getTime()); - float dci = (float)QApplication::doubleClickInterval()/1000.0f; - // a double-click? - if (tmp.getValue() < dci) { - mouseDownConsumedEvent = *event; - mouseDownConsumedEvent.setTime(ev->getTime()); - processed = true; - } - else { - mouseDownConsumedEvent.setTime(ev->getTime()); - // 'ANY' is used to mark that we don't know yet if it will - // be a double-click event. - mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); - } - } - else if (!press) { - if (mouseDownConsumedEvent.getButton() == SoMouseButtonEvent::BUTTON1) { - // now handle the postponed event - inherited::processSoEvent(&mouseDownConsumedEvent); - mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); - } + else { + processed = processClickEvent(event); } break; case SoMouseButtonEvent::BUTTON2: @@ -328,11 +259,6 @@ SbBool BlenderNavigationStyle::processSoEvent(const SoEvent * const ev) this->lockButton1 = false; processed = true; } - - //if (curmode == NavigationStyle::DRAGGING) { - // if (doSpin()) - // newmode = NavigationStyle::SPINNING; - //} break; case BUTTON1DOWN: case CTRLDOWN|BUTTON1DOWN: @@ -354,9 +280,6 @@ SbBool BlenderNavigationStyle::processSoEvent(const SoEvent * const ev) } newmode = NavigationStyle::DRAGGING; break; - //case BUTTON1DOWN|BUTTON2DOWN|BUTTON3DOWN: - // newmode = NavigationStyle::ZOOMING; - // break; case CTRLDOWN|SHIFTDOWN|BUTTON2DOWN: case CTRLDOWN|BUTTON3DOWN: newmode = NavigationStyle::ZOOMING; @@ -378,10 +301,8 @@ SbBool BlenderNavigationStyle::processSoEvent(const SoEvent * const ev) // If not handled in this class, pass on upwards in the inheritance // hierarchy. - if (/*(curmode == NavigationStyle::SELECTION || viewer->isEditing()) && */!processed) + if (!processed) processed = inherited::processSoEvent(ev); - else - return true; return processed; } diff --git a/src/Gui/CADNavigationStyle.cpp b/src/Gui/CADNavigationStyle.cpp index cd8067abb3..c9e351fb2b 100644 --- a/src/Gui/CADNavigationStyle.cpp +++ b/src/Gui/CADNavigationStyle.cpp @@ -92,12 +92,10 @@ SbBool CADNavigationStyle::processSoEvent(const SoEvent * const ev) const SoType type(ev->getTypeId()); const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion(); - const SbVec2s size(vp.getViewportSizePixels()); - const SbVec2f prevnormalized = this->lastmouseposition; const SbVec2s pos(ev->getPosition()); - const SbVec2f posn((float) pos[0] / (float) std::max((int)(size[0] - 1), 1), - (float) pos[1] / (float) std::max((int)(size[1] - 1), 1)); + const SbVec2f posn = normalizePixelPos(pos); + const SbVec2f prevnormalized = this->lastmouseposition; this->lastmouseposition = posn; // Set to true if any event processing happened. Note that it is not @@ -111,15 +109,7 @@ SbBool CADNavigationStyle::processSoEvent(const SoEvent * const ev) // Mismatches in state of the modifier keys happens if the user // presses or releases them outside the viewer window. - if (this->ctrldown != ev->wasCtrlDown()) { - this->ctrldown = ev->wasCtrlDown(); - } - if (this->shiftdown != ev->wasShiftDown()) { - this->shiftdown = ev->wasShiftDown(); - } - if (this->altdown != ev->wasAltDown()) { - this->altdown = ev->wasAltDown(); - } + syncModifierKeys(ev); // give the nodes in the foreground root the chance to handle events (e.g color bar) if (!viewer->isEditing()) { @@ -130,37 +120,8 @@ SbBool CADNavigationStyle::processSoEvent(const SoEvent * const ev) // Keyboard handling if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { - const SoKeyboardEvent * const event = (const SoKeyboardEvent *) ev; - const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; - switch (event->getKey()) { - case SoKeyboardEvent::LEFT_CONTROL: - case SoKeyboardEvent::RIGHT_CONTROL: - this->ctrldown = press; - break; - case SoKeyboardEvent::LEFT_SHIFT: - case SoKeyboardEvent::RIGHT_SHIFT: - this->shiftdown = press; - break; - case SoKeyboardEvent::LEFT_ALT: - case SoKeyboardEvent::RIGHT_ALT: - this->altdown = press; - break; - case SoKeyboardEvent::H: - processed = true; - viewer->saveHomePosition(); - break; - case SoKeyboardEvent::S: - case SoKeyboardEvent::HOME: - case SoKeyboardEvent::LEFT_ARROW: - case SoKeyboardEvent::UP_ARROW: - case SoKeyboardEvent::RIGHT_ARROW: - case SoKeyboardEvent::DOWN_ARROW: - if (!this->isViewing()) - this->setViewing(true); - break; - default: - break; - } + const SoKeyboardEvent * const event = static_cast(ev); + processed = processKeyboardEvent(event); } // Mouse Button / Spaceball Button handling @@ -174,39 +135,11 @@ SbBool CADNavigationStyle::processSoEvent(const SoEvent * const ev) case SoMouseButtonEvent::BUTTON1: this->lockrecenter = true; this->button1down = press; -#if 0 // disable to avoid interferences where this key combination is used, too - if (press && ev->wasShiftDown() && - (this->currentmode != NavigationStyle::SELECTION)) { - this->centerTime = ev->getTime(); - float ratio = vp.getViewportAspectRatio(); - SbViewVolume vv = viewer->getCamera()->getViewVolume(ratio); - this->panningplane = vv.getPlane(viewer->getCamera()->focalDistance.getValue()); - this->lockrecenter = false; - } - else if (!press && ev->wasShiftDown() && - (this->currentmode != NavigationStyle::SELECTION)) { - SbTime tmp = (ev->getTime() - this->centerTime); - float dci = (float)QApplication::doubleClickInterval()/1000.0f; - // is it just a left click? - if (tmp.getValue() < dci && !this->lockrecenter) { - if (!this->moveToPoint(pos)) { - panToCenter(panningplane, posn); - this->interactiveCountDec(); - } - processed = true; - } - } - else -#endif if (press && (this->currentmode == NavigationStyle::SEEK_WAIT_MODE)) { newmode = NavigationStyle::SEEK_MODE; this->seekToPoint(pos); // implicitly calls interactiveCountInc() processed = true; } - //else if (press && (this->currentmode == NavigationStyle::IDLE)) { - // this->setViewing(true); - // processed = true; - //} else if (press && (this->currentmode == NavigationStyle::PANNING || this->currentmode == NavigationStyle::ZOOMING)) { newmode = NavigationStyle::DRAGGING; @@ -225,30 +158,8 @@ SbBool CADNavigationStyle::processSoEvent(const SoEvent * const ev) else if (viewer->isEditing() && (this->currentmode == NavigationStyle::SPINNING)) { processed = true; } - // issue #0002433: avoid to swallow the UP event if down the - // scene graph somewhere a dialog gets opened - else if (press) { - SbTime tmp = (ev->getTime() - mouseDownConsumedEvent.getTime()); - float dci = (float)QApplication::doubleClickInterval()/1000.0f; - // a double-click? - if (tmp.getValue() < dci) { - mouseDownConsumedEvent = *event; - mouseDownConsumedEvent.setTime(ev->getTime()); - processed = true; - } - else { - mouseDownConsumedEvent.setTime(ev->getTime()); - // 'ANY' is used to mark that we don't know yet if it will - // be a double-click event. - mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); - } - } - else if (!press) { - if (mouseDownConsumedEvent.getButton() == SoMouseButtonEvent::BUTTON1) { - // now handle the postponed event - inherited::processSoEvent(&mouseDownConsumedEvent); - mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); - } + else { + processed = processClickEvent(event); } break; case SoMouseButtonEvent::BUTTON2: @@ -366,11 +277,6 @@ SbBool CADNavigationStyle::processSoEvent(const SoEvent * const ev) this->lockButton1 = false; processed = true; } - - //if (curmode == NavigationStyle::DRAGGING) { - // if (doSpin()) - // newmode = NavigationStyle::SPINNING; - //} break; case BUTTON1DOWN: // make sure not to change the selection when stopping spinning @@ -403,16 +309,6 @@ SbBool CADNavigationStyle::processSoEvent(const SoEvent * const ev) case CTRLDOWN|SHIFTDOWN|BUTTON2DOWN: newmode = NavigationStyle::ZOOMING; break; - //case CTRLDOWN: - //case CTRLDOWN|BUTTON1DOWN: - //case CTRLDOWN|SHIFTDOWN: - //case CTRLDOWN|SHIFTDOWN|BUTTON1DOWN: - // newmode = NavigationStyle::SELECTION; - // break; - //case BUTTON1DOWN|BUTTON3DOWN: - //case CTRLDOWN|BUTTON3DOWN: - // newmode = NavigationStyle::ZOOMING; - // break; // There are many cases we don't handle that just falls through to // the default case, like SHIFTDOWN, CTRLDOWN, CTRLDOWN|SHIFTDOWN, @@ -424,10 +320,6 @@ SbBool CADNavigationStyle::processSoEvent(const SoEvent * const ev) default: // The default will make a spin stop and otherwise not do // anything. - //if ((curmode != NavigationStyle::SEEK_WAIT_MODE) && - // (curmode != NavigationStyle::SEEK_MODE)) { - // newmode = NavigationStyle::IDLE; - //} break; } @@ -443,10 +335,8 @@ SbBool CADNavigationStyle::processSoEvent(const SoEvent * const ev) // If not handled in this class, pass on upwards in the inheritance // hierarchy. - if (/*(curmode == NavigationStyle::SELECTION || viewer->isEditing()) && */!processed) + if (!processed) processed = inherited::processSoEvent(ev); - else - return true; return processed; } diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 3c6a607e9c..8990d7af4f 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -838,6 +838,8 @@ SET(View3D_CPP_SRCS BlenderNavigationStyle.cpp MayaGestureNavigationStyle.cpp OpenCascadeNavigationStyle.cpp + OpenSCADNavigationStyle.cpp + TinkerCADNavigationStyle.cpp TouchpadNavigationStyle.cpp GestureNavigationStyle.cpp SplitView3DInventor.cpp diff --git a/src/Gui/Command.cpp b/src/Gui/Command.cpp index cd95d3ae31..3f44c28d87 100644 --- a/src/Gui/Command.cpp +++ b/src/Gui/Command.cpp @@ -1117,7 +1117,7 @@ void MacroCommand::activated(int iMsg) d = QDir(QString::fromUtf8(cMacroPath.c_str())); } else { - QString dirstr = QString::fromUtf8(App::GetApplication().getHomePath()) + QString::fromUtf8("Macro"); + QString dirstr = QString::fromStdString(App::Application::getHomePath()) + QString::fromLatin1("Macro"); d = QDir(dirstr); } diff --git a/src/Gui/CommandDoc.cpp b/src/Gui/CommandDoc.cpp index 0f1a533b0e..b52f323731 100644 --- a/src/Gui/CommandDoc.cpp +++ b/src/Gui/CommandDoc.cpp @@ -1574,7 +1574,7 @@ void StdCmdPlacement::activated(int iMsg) bool StdCmdPlacement::isActive(void) { - return Gui::Selection().countObjectsOfType(App::GeoFeature::getClassTypeId()) == 1; + return Gui::Selection().countObjectsOfType(App::GeoFeature::getClassTypeId()) >= 1; } //=========================================================================== diff --git a/src/Gui/CommandStd.cpp b/src/Gui/CommandStd.cpp index acb60bb89a..12d62f95ef 100644 --- a/src/Gui/CommandStd.cpp +++ b/src/Gui/CommandStd.cpp @@ -502,7 +502,7 @@ StdCmdOnlineHelpWebsite::StdCmdOnlineHelpWebsite() void StdCmdOnlineHelpWebsite::activated(int iMsg) { Q_UNUSED(iMsg); - std::string defaulturl = QCoreApplication::translate(this->className(),"http://www.freecadweb.org/wiki/Online_Help_Toc").toStdString(); + std::string defaulturl = QCoreApplication::translate(this->className(),"https://wiki.freecad.org/Online_Help_Toc").toStdString(); ParameterGrp::handle hURLGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Websites"); std::string url = hURLGrp->GetASCII("OnlineHelp", defaulturl.c_str()); hURLGrp->SetASCII("OnlineHelp", url.c_str()); @@ -529,9 +529,9 @@ StdCmdFreeCADDonation::StdCmdFreeCADDonation() void StdCmdFreeCADDonation::activated(int iMsg) { - Q_UNUSED(iMsg); + Q_UNUSED(iMsg); ParameterGrp::handle hURLGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Websites"); - std::string url = hURLGrp->GetASCII("DonatePage", "https://wiki.freecadweb.org/Donate"); + std::string url = hURLGrp->GetASCII("DonatePage", "https://wiki.freecad.org/Donate"); hURLGrp->SetASCII("DonatePage", url.c_str()); OpenURLInBrowser(url.c_str()); } @@ -557,7 +557,7 @@ StdCmdFreeCADWebsite::StdCmdFreeCADWebsite() void StdCmdFreeCADWebsite::activated(int iMsg) { Q_UNUSED(iMsg); - std::string defaulturl = QCoreApplication::translate(this->className(),"http://www.freecadweb.org").toStdString(); + std::string defaulturl = QCoreApplication::translate(this->className(),"https://www.freecad.org").toStdString(); ParameterGrp::handle hURLGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Websites"); std::string url = hURLGrp->GetASCII("WebPage", defaulturl.c_str()); hURLGrp->SetASCII("WebPage", url.c_str()); @@ -585,7 +585,7 @@ StdCmdFreeCADUserHub::StdCmdFreeCADUserHub() void StdCmdFreeCADUserHub::activated(int iMsg) { Q_UNUSED(iMsg); - std::string defaulturl = QCoreApplication::translate(this->className(),"http://www.freecadweb.org/wiki/User_hub").toStdString(); + std::string defaulturl = QCoreApplication::translate(this->className(),"https://wiki.freecad.org/User_hub").toStdString(); ParameterGrp::handle hURLGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Websites"); std::string url = hURLGrp->GetASCII("Documentation", defaulturl.c_str()); hURLGrp->SetASCII("Documentation", url.c_str()); @@ -613,7 +613,7 @@ StdCmdFreeCADPowerUserHub::StdCmdFreeCADPowerUserHub() void StdCmdFreeCADPowerUserHub::activated(int iMsg) { Q_UNUSED(iMsg); - std::string defaulturl = QCoreApplication::translate(this->className(),"http://www.freecadweb.org/wiki/Power_users_hub").toStdString(); + std::string defaulturl = QCoreApplication::translate(this->className(),"https://wiki.freecad.org/Power_users_hub").toStdString(); ParameterGrp::handle hURLGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Websites"); std::string url = hURLGrp->GetASCII("PowerUsers", defaulturl.c_str()); hURLGrp->SetASCII("PowerUsers", url.c_str()); @@ -641,7 +641,7 @@ StdCmdFreeCADForum::StdCmdFreeCADForum() void StdCmdFreeCADForum::activated(int iMsg) { Q_UNUSED(iMsg); - std::string defaulturl = QCoreApplication::translate(this->className(),"http://forum.freecadweb.org").toStdString(); + std::string defaulturl = QCoreApplication::translate(this->className(),"https://forum.freecad.org").toStdString(); ParameterGrp::handle hURLGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Websites"); std::string url = hURLGrp->GetASCII("UserForum", defaulturl.c_str()); hURLGrp->SetASCII("UserForum", url.c_str()); @@ -669,7 +669,7 @@ StdCmdFreeCADFAQ::StdCmdFreeCADFAQ() void StdCmdFreeCADFAQ::activated(int iMsg) { Q_UNUSED(iMsg); - std::string defaulturl = QCoreApplication::translate(this->className(),"http://www.freecadweb.org/wiki/FAQ").toStdString(); + std::string defaulturl = QCoreApplication::translate(this->className(),"https://wiki.freecad.org/Frequently_asked_questions").toStdString(); ParameterGrp::handle hURLGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Websites"); std::string url = hURLGrp->GetASCII("FAQ", defaulturl.c_str()); hURLGrp->SetASCII("FAQ", url.c_str()); @@ -697,7 +697,7 @@ StdCmdPythonWebsite::StdCmdPythonWebsite() void StdCmdPythonWebsite::activated(int iMsg) { Q_UNUSED(iMsg); - OpenURLInBrowser("http://python.org"); + OpenURLInBrowser("https://www.python.org"); } //=========================================================================== @@ -852,7 +852,7 @@ Gui::Action * StdCmdUserEditMode::createAction(void) pcAction->setDropDownMenu(true); pcAction->setIsMode(true); applyCommandData(this->className(), pcAction); - + for (auto const &uem : Gui::Application::Instance->listUserEditModes()) { QAction* act = pcAction->addAction(QString()); auto modeName = QString::fromStdString(uem.second); @@ -860,7 +860,7 @@ Gui::Action * StdCmdUserEditMode::createAction(void) act->setIcon(BitmapFactory().iconFromTheme(qPrintable(QString::fromLatin1("Std_UserEditMode")+modeName))); act->setObjectName(QString::fromLatin1("Std_UserEditMode")+modeName); act->setWhatsThis(QString::fromLatin1(getWhatsThis())); - + if (uem.first == 0) { pcAction->setIcon(act->icon()); act->setChecked(true); diff --git a/src/Gui/CommandT.h b/src/Gui/CommandT.h index 64154d8cca..a73095e3fd 100644 --- a/src/Gui/CommandT.h +++ b/src/Gui/CommandT.h @@ -279,11 +279,11 @@ inline void cmdAppObjectShow(const App::DocumentObject* obj) { * in-place editing an object, which may be brought in through linking to an * external group. */ -inline void cmdSetEdit(const App::DocumentObject* obj) { +inline void cmdSetEdit(const App::DocumentObject* obj, int mod = 0) { if (obj && obj->getNameInDocument()) { Gui::Command::doCommand(Gui::Command::Gui, - "Gui.ActiveDocument.setEdit(App.getDocument('%s').getObject('%s'))", - obj->getDocument()->getName(), obj->getNameInDocument()); + "Gui.ActiveDocument.setEdit(App.getDocument('%s').getObject('%s'), %d)", + obj->getDocument()->getName(), obj->getNameInDocument(), mod); } } diff --git a/src/Gui/DlgActionsImp.cpp b/src/Gui/DlgActionsImp.cpp index 35aa3b9f0b..f2c85ebb99 100644 --- a/src/Gui/DlgActionsImp.cpp +++ b/src/Gui/DlgActionsImp.cpp @@ -71,7 +71,7 @@ DlgCustomActionsImp::DlgCustomActionsImp( QWidget* parent ) for (unsigned int i=0; iactionMacros->insertItem(0,d[i],QVariant(false)); - QString systemMacroDirStr = QString::fromUtf8(App::GetApplication().getHomePath()) + QString::fromUtf8("Macro"); + QString systemMacroDirStr = QString::fromStdString(App::Application::getHomePath()) + QString::fromLatin1("Macro"); d = QDir(systemMacroDirStr, QLatin1String("*.FCMacro *.py")); if (d.exists()) { for (unsigned int i=0; isetText(0, dir[i]); } - QString dirstr = QString::fromUtf8(App::GetApplication().getHomePath()) + QString::fromUtf8("Macro"); + QString dirstr = QString::fromStdString(App::Application::getHomePath()) + QString::fromLatin1("Macro"); dir = QDir(dirstr, QLatin1String("*.FCMacro *.py")); ui->systemMacroListBox->clear(); @@ -268,7 +268,7 @@ void DlgMacroExecuteImp::accept() dir =QDir(this->macroPath); } else { - QString dirstr = QString::fromUtf8(App::GetApplication().getHomePath()) + QString::fromUtf8("Macro"); + QString dirstr = QString::fromStdString(App::Application::getHomePath()) + QString::fromLatin1("Macro"); dir = QDir(dirstr); } @@ -319,7 +319,7 @@ void DlgMacroExecuteImp::on_editButton_clicked() else { //index == 1 system-wide item = ui->systemMacroListBox->currentItem(); - dir.setPath(QString::fromUtf8(App::GetApplication().getHomePath()) + QString::fromUtf8("Macro")); + dir.setPath(QString::fromStdString(App::Application::getHomePath()) + QString::fromLatin1("Macro")); } if (!item) diff --git a/src/Gui/DlgUndoRedo.cpp b/src/Gui/DlgUndoRedo.cpp index 2d183dcb60..536e647c31 100644 --- a/src/Gui/DlgUndoRedo.cpp +++ b/src/Gui/DlgUndoRedo.cpp @@ -29,9 +29,7 @@ #include "DlgUndoRedo.h" #include "Application.h" #include "MainWindow.h" -#include "Document.h" -#include "EditorView.h" -#include "TextDocumentEditorView.h" +#include "MDIView.h" using namespace Gui::Dialog; @@ -64,28 +62,11 @@ void UndoDialog::onFetchInfo() clear(); // Remove first all items MDIView* mdi = getMainWindow()->activeWindow(); - EditorView* editview = qobject_cast(mdi); - TextDocumentEditorView* textedit = qobject_cast(mdi); - if (editview) { - QStringList vecUndos = editview->undoActions(); + if (mdi) { + QStringList vecUndos = mdi->undoActions(); for (QStringList::Iterator i = vecUndos.begin(); i != vecUndos.end(); ++i) addAction(*i, this, SLOT(onSelected())); } - else if (textedit) { - QStringList vecUndos = textedit->undoActions(); - for (QStringList::Iterator i = vecUndos.begin(); i != vecUndos.end(); ++i) - addAction(*i, this, SLOT(onSelected())); - } - else if (mdi) { - Gui::Document* pcDoc = mdi->getGuiDocument(); - if (pcDoc) { - std::vector vecUndos = pcDoc->getUndoVector(); - for (std::vector::iterator i = vecUndos.begin(); i != vecUndos.end(); ++i) { - QString text = QCoreApplication::translate("Command", i->c_str()); - addAction(text, this, SLOT(onSelected())); - } - } - } } /** Closes the dialog and sends the message 'Undo' to the currently active MDI view. */ @@ -129,28 +110,11 @@ void RedoDialog::onFetchInfo() clear(); // Remove first all items MDIView* mdi = getMainWindow()->activeWindow(); - EditorView* editview = qobject_cast(mdi); - TextDocumentEditorView* textedit = qobject_cast(mdi); - if (editview) { - QStringList vecRedos = editview->redoActions(); + if (mdi) { + QStringList vecRedos = mdi->redoActions(); for (QStringList::Iterator i = vecRedos.begin(); i != vecRedos.end(); ++i) addAction(*i, this, SLOT(onSelected())); } - else if (textedit) { - QStringList vecRedos = textedit->redoActions(); - for (QStringList::Iterator i = vecRedos.begin(); i != vecRedos.end(); ++i) - addAction(*i, this, SLOT(onSelected())); - } - else if (mdi) { - Gui::Document* pcDoc = mdi->getGuiDocument(); - if (pcDoc) { - std::vector vecRedos = pcDoc->getRedoVector(); - for (std::vector::iterator i = vecRedos.begin(); i != vecRedos.end(); ++i) { - QString text = QCoreApplication::translate("Command", i->c_str()); - addAction(text, this, SLOT(onSelected())); - } - } - } } /** Closes the dialog and sends the message 'Redo' to the currently active MDI view. */ diff --git a/src/Gui/Document.cpp b/src/Gui/Document.cpp index c4fdf1a943..813e84a69f 100644 --- a/src/Gui/Document.cpp +++ b/src/Gui/Document.cpp @@ -405,13 +405,16 @@ bool Document::setEdit(Gui::ViewProvider* p, int ModNum, const char *subname) d->_editViewProviderParent = vp; d->_editSubElement.clear(); d->_editSubname.clear(); - if(subname) { + + if (subname) { const char *element = Data::ComplexGeoData::findElementName(subname); - if(element) { + if (element) { d->_editSubname = std::string(subname,element-subname); d->_editSubElement = element; - }else + } + else { d->_editSubname = subname; + } } auto sobjs = obj->getSubObjectList(subname); @@ -1096,6 +1099,31 @@ static bool checkCanonicalPath(const std::map &docs) return ret == QMessageBox::Yes; } +bool Document::askIfSavingFailed(const QString& error) +{ + int ret = QMessageBox::question( + getMainWindow(), + QObject::tr("Could not save document"), + QObject::tr("There was an issue trying to save the file. " + "This may be because some of the parent folders do not exist, " + "or you do not have sufficient permissions, " + "or for other reasons. Error details:\n\n\"%1\"\n\n" + "Would you like to save the file with a different name?") + .arg(error), + QMessageBox::Yes, QMessageBox::No); + + if (ret == QMessageBox::No) { + // TODO: Understand what exactly is supposed to be returned here + getMainWindow()->showMessage(QObject::tr("Saving aborted"), 2000); + return false; + } + else if (ret == QMessageBox::Yes) { + return saveAs(); + } + + return false; +} + /// Save the document bool Document::save(void) { @@ -1105,7 +1133,7 @@ bool Document::save(void) std::map dmap; try { docs = getDocument()->getDependentDocuments(); - for(auto it=docs.begin(); it!=docs.end();) { + for (auto it=docs.begin(); it!=docs.end();) { App::Document *doc = *it; if (doc == getDocument()) { dmap[doc] = doc->mustExecute(); @@ -1123,18 +1151,21 @@ bool Document::save(void) dmap[doc] = doc->mustExecute(); ++it; } - }catch(const Base::RuntimeError &e) { + } + catch (const Base::RuntimeError &e) { FC_ERR(e.what()); docs = {getDocument()}; dmap.clear(); dmap[getDocument()] = getDocument()->mustExecute(); } - if(docs.size()>1) { + + if (docs.size()>1) { int ret = QMessageBox::question(getMainWindow(), QObject::tr("Save dependent files"), QObject::tr("The file contains external dependencies. " "Do you want to save the dependent files, too?"), QMessageBox::Yes,QMessageBox::No); + if (ret != QMessageBox::Yes) { docs = {getDocument()}; dmap.clear(); @@ -1147,35 +1178,22 @@ bool Document::save(void) Gui::WaitCursor wc; // save all documents - for(auto doc : docs) { + for (auto doc : docs) { // Changed 'mustExecute' status may be triggered by saving external document - if(!dmap[doc] && doc->mustExecute()) { + if (!dmap[doc] && doc->mustExecute()) { App::AutoTransaction trans("Recompute"); Command::doCommand(Command::Doc,"App.getDocument(\"%s\").recompute()",doc->getName()); } + Command::doCommand(Command::Doc,"App.getDocument(\"%s\").save()",doc->getName()); auto gdoc = Application::Instance->getDocument(doc); - if(gdoc) gdoc->setModified(false); + if (gdoc) + gdoc->setModified(false); } } catch (const Base::FileException& e) { - int ret = QMessageBox::question( - getMainWindow(), - QObject::tr("Could not save document"), - QObject::tr("There was an issue trying to save the file. " - "This may be because some of the parent folders do not exist, " - "or you do not have sufficient permissions, " - "or for other reasons. Error details:\n\n\"%1\"\n\n" - "Would you like to save the file with a different name?") - .arg(QString::fromUtf8(e.what())), - QMessageBox::Yes, QMessageBox::No); - if (ret == QMessageBox::No) { - // TODO: Understand what exactly is supposed to be returned here - getMainWindow()->showMessage(QObject::tr("Saving aborted"), 2000); - return false; - } else if (ret == QMessageBox::Yes) { - return saveAs(); - } + e.ReportException(); + return askIfSavingFailed(QString::fromUtf8(e.what())); } catch (const Base::Exception& e) { QMessageBox::critical(getMainWindow(), QObject::tr("Saving document failed"), @@ -1198,6 +1216,7 @@ bool Document::saveAs(void) QString fn = FileDialog::getSaveFileName(getMainWindow(), QObject::tr("Save %1 Document").arg(exe), QString::fromUtf8(getDocument()->FileName.getValue()), QString::fromLatin1("%1 %2 (*.FCStd)").arg(exe).arg(QObject::tr("Document"))); + if (!fn.isEmpty()) { QFileInfo fi; fi.setFile(fn); @@ -1217,23 +1236,8 @@ bool Document::saveAs(void) getMainWindow()->appendRecentFile(fi.filePath()); } catch (const Base::FileException& e) { - int ret = QMessageBox::question( - getMainWindow(), - QObject::tr("Could not save document"), - QObject::tr("There was an issue trying to save the file. " - "This may be because some of the parent folders do not exist, " - "or you do not have sufficient permissions, " - "or for other reasons. Error details:\n\n\"%1\"\n\n" - "Would you like to save the file with a different name?") - .arg(QString::fromUtf8(e.what())), - QMessageBox::Yes, QMessageBox::No); - if (ret == QMessageBox::No) { - // TODO: Understand what exactly is supposed to be returned here - getMainWindow()->showMessage(QObject::tr("Saving aborted"), 2000); - return false; - } else if (ret == QMessageBox::Yes) { - return saveAs(); - } + e.ReportException(); + return askIfSavingFailed(QString::fromUtf8(e.what())); } catch (const Base::Exception& e) { QMessageBox::critical(getMainWindow(), QObject::tr("Saving document failed"), @@ -1495,7 +1499,7 @@ void Document::slotFinishRestoreDocument(const App::Document& doc) } // reset modified flag - setModified(false); + setModified(doc.testStatus(App::Document::LinkStampChanged)); } void Document::slotShowHidden(const App::Document& doc) diff --git a/src/Gui/Document.h b/src/Gui/Document.h index b24887aaf8..a7884670ed 100644 --- a/src/Gui/Document.h +++ b/src/Gui/Document.h @@ -306,6 +306,8 @@ private: /// Check other documents for the same transaction ID bool checkTransactionID(bool undo, int iSteps); + /// Ask for user interaction if saving has failed + bool askIfSavingFailed(const QString&); struct DocumentP* d; static int _iDocCount; diff --git a/src/Gui/DocumentObserver.h b/src/Gui/DocumentObserver.h index 6608fa9df7..63a20df9b4 100644 --- a/src/Gui/DocumentObserver.h +++ b/src/Gui/DocumentObserver.h @@ -274,6 +274,11 @@ public: bool operator!= (const WeakPtrT& p) const { return ptr != p.ptr; } + /*! Get a pointer to the object or 0 if it doesn't exist any more. */ + T* get() const noexcept + { + return ptr.get(); + } private: // disable diff --git a/src/Gui/DocumentRecovery.cpp b/src/Gui/DocumentRecovery.cpp index 44e7f74116..924c4e32fb 100644 --- a/src/Gui/DocumentRecovery.cpp +++ b/src/Gui/DocumentRecovery.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #include #include @@ -68,6 +69,7 @@ FC_LOG_LEVEL_INIT("Gui",true,true) using namespace Gui; using namespace Gui::Dialog; +namespace sp = std::placeholders; // taken from the script doctools.py std::string DocumentRecovery::doctools = @@ -553,41 +555,20 @@ void DocumentRecovery::on_buttonCleanup_clicked() d_ptr->ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); d_ptr->ui.buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true); - QDir tmp = QString::fromUtf8(App::Application::getTempPath().c_str()); - tmp.setNameFilters(QStringList() << QString::fromLatin1("*.lock")); - tmp.setFilter(QDir::Files); + DocumentRecoveryHandler handler; + handler.checkForPreviousCrashes(std::bind(&DocumentRecovery::cleanup, this, sp::_1, sp::_2, sp::_3)); + QMessageBox::information(this, tr("Finished"), tr("Transient directories deleted.")); +} - QString exeName = QString::fromLatin1(App::GetApplication().getExecutableName()); - QList locks = tmp.entryInfoList(); - for (QList::iterator it = locks.begin(); it != locks.end(); ++it) { - QString bn = it->baseName(); - // ignore the lock file for this instance - QString pid = QString::number(QCoreApplication::applicationPid()); - if (bn.startsWith(exeName) && bn.indexOf(pid) < 0) { - QString fn = it->absoluteFilePath(); - boost::interprocess::file_lock flock((const char*)fn.toLocal8Bit()); - if (flock.try_lock()) { - // OK, this file is a leftover from a previous crash - QString crashed_pid = bn.mid(exeName.length()+1); - // search for transient directories with this PID - QString filter; - QTextStream str(&filter); - str << exeName << "_Doc_*_" << crashed_pid; - tmp.setNameFilters(QStringList() << filter); - tmp.setFilter(QDir::Dirs); - QList dirs = tmp.entryInfoList(); - if (!dirs.isEmpty()) { - for (QList::iterator jt = dirs.begin(); jt != dirs.end(); ++jt) { - clearDirectory(*jt); - tmp.rmdir(jt->fileName()); - } - } - tmp.remove(it->fileName()); - } +void DocumentRecovery::cleanup(QDir& tmp, const QList& dirs, const QString& lockFile) +{ + if (!dirs.isEmpty()) { + for (QList::const_iterator jt = dirs.cbegin(); jt != dirs.cend(); ++jt) { + clearDirectory(*jt); + tmp.rmdir(jt->fileName()); } } - - QMessageBox::information(this, tr("Finished"), tr("Transient directories deleted.")); + tmp.remove(lockFile); } void DocumentRecovery::clearDirectory(const QFileInfo& dir) @@ -613,4 +594,102 @@ void DocumentRecovery::clearDirectory(const QFileInfo& dir) } } +// ---------------------------------------------------------------------------- + +void DocumentRecoveryFinder::checkForPreviousCrashes() +{ + DocumentRecoveryHandler handler; + handler.checkForPreviousCrashes(std::bind(&DocumentRecoveryFinder::checkDocumentDirs, this, sp::_1, sp::_2, sp::_3)); + + showRecoveryDialogIfNeeded(); +} + +void DocumentRecoveryFinder::checkDocumentDirs(QDir& tmp, const QList& dirs, const QString& fn) +{ + if (dirs.isEmpty()) { + // delete the lock file immediately if no transient directories are related + tmp.remove(fn); + } + else { + int countDeletedDocs = 0; + QString recovery_files = QString::fromLatin1("fc_recovery_files"); + for (QList::const_iterator it = dirs.cbegin(); it != dirs.cend(); ++it) { + QDir doc_dir(it->absoluteFilePath()); + doc_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries); + uint entries = doc_dir.entryList().count(); + if (entries == 0) { + // in this case we can delete the transient directory because + // we cannot do anything + if (tmp.rmdir(it->filePath())) + countDeletedDocs++; + } + // search for the existence of a recovery file + else if (doc_dir.exists(QLatin1String("fc_recovery_file.xml"))) { + // store the transient directory in case it's not empty + restoreDocFiles << *it; + } + // search for the 'fc_recovery_files' sub-directory and check that it's the only entry + else if (entries == 1 && doc_dir.exists(recovery_files)) { + // if the sub-directory is empty delete the transient directory + QDir rec_dir(doc_dir.absoluteFilePath(recovery_files)); + rec_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries); + if (rec_dir.entryList().isEmpty()) { + doc_dir.rmdir(recovery_files); + if (tmp.rmdir(it->filePath())) + countDeletedDocs++; + } + } + } + + // all directories corresponding to the lock file have been deleted + // so delete the lock file, too + if (countDeletedDocs == dirs.size()) { + tmp.remove(fn); + } + } +} + +void DocumentRecoveryFinder::showRecoveryDialogIfNeeded() +{ + if (!restoreDocFiles.isEmpty()) { + Gui::Dialog::DocumentRecovery dlg(restoreDocFiles, Gui::getMainWindow()); + if (dlg.foundDocuments()) + dlg.exec(); + } +} + +// ---------------------------------------------------------------------------- + +void DocumentRecoveryHandler::checkForPreviousCrashes(const std::function&, const QString&)> & callableFunc) const +{ + QDir tmp = QString::fromUtf8(App::Application::getTempPath().c_str()); + tmp.setNameFilters(QStringList() << QString::fromLatin1("*.lock")); + tmp.setFilter(QDir::Files); + + QString exeName = QString::fromStdString(App::Application::getExecutableName()); + QList locks = tmp.entryInfoList(); + for (QList::iterator it = locks.begin(); it != locks.end(); ++it) { + QString bn = it->baseName(); + // ignore the lock file for this instance + QString pid = QString::number(QCoreApplication::applicationPid()); + if (bn.startsWith(exeName) && bn.indexOf(pid) < 0) { + QString fn = it->absoluteFilePath(); + boost::interprocess::file_lock flock((const char*)fn.toLocal8Bit()); + if (flock.try_lock()) { + // OK, this file is a leftover from a previous crash + QString crashed_pid = bn.mid(exeName.length()+1); + // search for transient directories with this PID + QString filter; + QTextStream str(&filter); + str << exeName << "_Doc_*_" << crashed_pid; + tmp.setNameFilters(QStringList() << filter); + tmp.setFilter(QDir::Dirs); + QList dirs = tmp.entryInfoList(); + + callableFunc(tmp, dirs, it->fileName()); + } + } + } +} + #include "moc_DocumentRecovery.cpp" diff --git a/src/Gui/DocumentRecovery.h b/src/Gui/DocumentRecovery.h index 966f551173..4b47306fe5 100644 --- a/src/Gui/DocumentRecovery.h +++ b/src/Gui/DocumentRecovery.h @@ -29,6 +29,7 @@ #include #include #include +#include namespace Gui { namespace Dialog { @@ -53,6 +54,7 @@ protected: void contextMenuEvent(QContextMenuEvent*); QString createProjectFile(const QString&); void clearDirectory(const QFileInfo&); + void cleanup(QDir&, const QList&, const QString&); protected Q_SLOTS: void on_buttonCleanup_clicked(); @@ -65,6 +67,23 @@ private: Q_DECLARE_PRIVATE(DocumentRecovery) }; +class DocumentRecoveryFinder { +public: + void checkForPreviousCrashes(); + +private: + void checkDocumentDirs(QDir&, const QList&, const QString&); + void showRecoveryDialogIfNeeded(); + +private: + QList restoreDocFiles; +}; + +class DocumentRecoveryHandler { +public: + void checkForPreviousCrashes(const std::function&, const QString&)> & callableFunc) const; +}; + } //namespace Dialog } //namespace Gui diff --git a/src/Gui/DownloadItem.cpp b/src/Gui/DownloadItem.cpp index 43e59a26ba..6e87fbc5e8 100644 --- a/src/Gui/DownloadItem.cpp +++ b/src/Gui/DownloadItem.cpp @@ -273,7 +273,7 @@ void DownloadItem::init() QString DownloadItem::getDownloadDirectory() const { - QString exe = QString::fromLatin1(App::GetApplication().getExecutableName()); + QString exe = QString::fromStdString(App::Application::getExecutableName()); QString path = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QString dirPath = QDir(path).filePath(exe); Base::Reference hPath = App::GetApplication().GetUserParameter().GetGroup("BaseApp") diff --git a/src/Gui/EditorView.cpp b/src/Gui/EditorView.cpp index 75f010da77..196a077085 100644 --- a/src/Gui/EditorView.cpp +++ b/src/Gui/EditorView.cpp @@ -25,9 +25,12 @@ #ifndef _PreComp_ # include # include +# include # include # include # include +# include +# include # include # include # include @@ -35,10 +38,15 @@ # include # include # include +# include +# include # include # include +# include +# include # include # include +# include #endif #include "EditorView.h" @@ -59,6 +67,7 @@ namespace Gui { class EditorViewP { public: QPlainTextEdit* textEdit; + SearchBar* searchBar; QString fileName; EditorView::DisplayName displayName; QTimer* activityTimer; @@ -90,19 +99,28 @@ EditorView::EditorView(QPlainTextEdit* editor, QWidget* parent) d->textEdit = editor; d->textEdit->setLineWrapMode(QPlainTextEdit::NoWrap); + d->searchBar = new SearchBar(); + d->searchBar->setEditor(editor); + // update editor actions on request Gui::MainWindow* mw = Gui::getMainWindow(); connect(editor, SIGNAL(undoAvailable(bool)), mw, SLOT(updateEditorActions())); connect(editor, SIGNAL(redoAvailable(bool)), mw, SLOT(updateEditorActions())); connect(editor, SIGNAL(copyAvailable(bool)), mw, SLOT(updateEditorActions())); + connect(editor, SIGNAL(showSearchBar()), d->searchBar, SLOT(activate())); + connect(editor, SIGNAL(findNext()), d->searchBar, SLOT(findNext())); + connect(editor, SIGNAL(findPrevious()), d->searchBar, SLOT(findPrevious())); + // Create the layout containing the workspace and a tab bar QFrame* hbox = new QFrame(this); hbox->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); - QHBoxLayout* layout = new QHBoxLayout(); + QVBoxLayout* layout = new QVBoxLayout(); layout->setMargin(1); layout->addWidget(d->textEdit); + layout->addWidget(d->searchBar); d->textEdit->setParent(hbox); + d->searchBar->setParent(hbox); hbox->setLayout(layout); setCentralWidget(hbox); @@ -650,4 +668,173 @@ void PythonEditorView::hideDebugMarker() _pye->hideDebugMarker(); } +// ---------------------------------------------------------------------------- + +SearchBar::SearchBar(QWidget* parent) + : QWidget(parent) + , textEditor(nullptr) +{ + horizontalLayout = new QHBoxLayout(this); + horizontalLayout->setSpacing(3); + + closeButton = new QToolButton(this); + closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton)); + closeButton->setAutoRaise(true); + connect(closeButton, &QToolButton::clicked, this, &SearchBar::deactivate); + + horizontalLayout->addWidget(closeButton); + + searchText = new QLineEdit(this); + searchText->setClearButtonEnabled(true); + horizontalLayout->addWidget(searchText); + connect(searchText, &QLineEdit::returnPressed, this, &SearchBar::findNext); + connect(searchText, &QLineEdit::textChanged, this, &SearchBar::findCurrent); + connect(searchText, &QLineEdit::textChanged, this, &SearchBar::updateButtons); + + prevButton = new QToolButton(this); + prevButton->setIcon(style()->standardIcon(QStyle::SP_ArrowBack)); + prevButton->setAutoRaise(true); + prevButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + horizontalLayout->addWidget(prevButton); + connect(prevButton, &QToolButton::clicked, this, &SearchBar::findPrevious); + + nextButton = new QToolButton(this); + nextButton->setIcon(style()->standardIcon(QStyle::SP_ArrowForward)); + nextButton->setAutoRaise(true); + nextButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + horizontalLayout->addWidget(nextButton); + connect(nextButton, &QToolButton::clicked, this, &SearchBar::findNext); + + matchCase = new QCheckBox(this); + horizontalLayout->addWidget(matchCase); + connect(matchCase, &QCheckBox::toggled, this, &SearchBar::findCurrent); + + matchWord = new QCheckBox(this); + horizontalLayout->addWidget(matchWord); + connect(matchWord, &QCheckBox::toggled, this, &SearchBar::findCurrent); + + horizontalSpacer = new QSpacerItem(192, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + horizontalLayout->addItem(horizontalSpacer); + + retranslateUi(); + + setMinimumWidth(minimumSizeHint().width()); + updateButtons(); + hide(); +} + +void SearchBar::setEditor(QPlainTextEdit* textEdit) +{ + textEditor = textEdit; +} + +void SearchBar::keyPressEvent(QKeyEvent* event) +{ + if (event->key() == Qt::Key_Escape) { + hide(); + return; + } + + QWidget::keyPressEvent(event); +} + +void SearchBar::retranslateUi() +{ + prevButton->setText(tr("Previous")); + nextButton->setText(tr("Next")); + matchCase->setText(tr("Case sensitive")); + matchWord->setText(tr("Whole words")); +} + +void SearchBar::activate() +{ + show(); + searchText->selectAll(); + searchText->setFocus(Qt::ShortcutFocusReason); +} + +void SearchBar::deactivate() +{ + if (textEditor) + textEditor->setFocus(); + hide(); +} + +void SearchBar::findPrevious() +{ + findText(true, false, searchText->text()); +} + +void SearchBar::findNext() +{ + findText(true, true, searchText->text()); +} + +void SearchBar::findCurrent() +{ + findText(false, true, searchText->text()); +} + +void SearchBar::findText(bool skip, bool next, const QString& str) +{ + if (!textEditor) + return; + + QTextCursor cursor = textEditor->textCursor(); + QTextDocument *doc = textEditor->document(); + if (!doc || cursor.isNull()) + return; + + if (cursor.hasSelection()) + cursor.setPosition((skip && next) ? cursor.position() : cursor.anchor()); + + bool found = true; + QTextCursor newCursor = cursor; + if (!str.isEmpty()) { + QTextDocument::FindFlags options; + if (!next) + options |= QTextDocument::FindBackward; + if (matchCase->isChecked()) + options |= QTextDocument::FindCaseSensitively; + if (matchWord->isChecked()) + options |= QTextDocument::FindWholeWords; + + newCursor = doc->find(str, cursor, options); + if (newCursor.isNull()) { + QTextCursor ac(doc); + ac.movePosition(options & QTextDocument::FindBackward ? QTextCursor::End : QTextCursor::Start); + newCursor = doc->find(str, ac, options); + if (newCursor.isNull()) { + found = false; + newCursor = cursor; + } + } + } + + if (!isVisible()) + show(); + + textEditor->setTextCursor(newCursor); + + QPalette palette; + palette.setColor(QPalette::Active, QPalette::Base, found ? Qt::white : QColor(255, 80, 80)); + searchText->setPalette(palette); +} + +void SearchBar::updateButtons() +{ + bool empty = searchText->text().isEmpty(); + prevButton->setDisabled(empty); + nextButton->setDisabled(empty); +} + +void SearchBar::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) { + retranslateUi(); + } + + QWidget::changeEvent(event); +} + #include "moc_EditorView.cpp" diff --git a/src/Gui/EditorView.h b/src/Gui/EditorView.h index 54f6ccc5e7..80882adb41 100644 --- a/src/Gui/EditorView.h +++ b/src/Gui/EditorView.h @@ -30,6 +30,10 @@ QT_BEGIN_NAMESPACE class QPlainTextEdit; class QPrinter; +class QHBoxLayout; +class QToolButton; +class QCheckBox; +class QSpacerItem; QT_END_NAMESPACE namespace Gui { @@ -132,6 +136,43 @@ private: PythonEditor* _pye; }; +class SearchBar : public QWidget +{ + Q_OBJECT + +public: + SearchBar(QWidget* parent = nullptr); + + void setEditor(QPlainTextEdit *textEdit); + +protected: + void keyPressEvent(QKeyEvent*); + void changeEvent(QEvent*); + +public Q_SLOTS: + void activate(); + void deactivate(); + void findPrevious(); + void findNext(); + void findCurrent(); + +private: + void retranslateUi(); + void findText(bool skip, bool next, const QString& str); + void updateButtons(); + +private: + QPlainTextEdit* textEditor; + QHBoxLayout* horizontalLayout; + QSpacerItem* horizontalSpacer; + QToolButton* closeButton; + QLineEdit* searchText; + QToolButton* prevButton; + QToolButton* nextButton; + QCheckBox* matchCase; + QCheckBox* matchWord; +}; + } // namespace Gui #endif // GUI_EDITORVIEW_H diff --git a/src/Gui/GestureNavigationStyle.cpp b/src/Gui/GestureNavigationStyle.cpp index 98a1e3e2a7..946efb1dcf 100644 --- a/src/Gui/GestureNavigationStyle.cpp +++ b/src/Gui/GestureNavigationStyle.cpp @@ -72,6 +72,7 @@ #include "GestureNavigationStyle.h" #include +#include #include #include "View3DInventorViewer.h" #include "Application.h" @@ -918,9 +919,8 @@ SbBool GestureNavigationStyle::processSoEvent(const SoEvent* const ev) //whatever else, we don't track } } - this->ctrldown = ev->wasCtrlDown(); - this->shiftdown = ev->wasShiftDown(); - this->altdown = ev->wasAltDown(); + + syncModifierKeys(ev); smev.modifiers = (this->button1down ? NS::Event::BUTTON1DOWN : 0) | diff --git a/src/Gui/GuiApplication.cpp b/src/Gui/GuiApplication.cpp index b254658000..1a9e7e335f 100644 --- a/src/Gui/GuiApplication.cpp +++ b/src/Gui/GuiApplication.cpp @@ -177,7 +177,7 @@ public: , running(false) { timer->setSingleShot(true); - std::string exeName = App::GetApplication().getExecutableName(); + std::string exeName = App::Application::getExecutableName(); serverName = QString::fromStdString(exeName); } diff --git a/src/Gui/InventorNavigationStyle.cpp b/src/Gui/InventorNavigationStyle.cpp index 74062c51b8..b9bfe2986d 100644 --- a/src/Gui/InventorNavigationStyle.cpp +++ b/src/Gui/InventorNavigationStyle.cpp @@ -96,12 +96,10 @@ SbBool InventorNavigationStyle::processSoEvent(const SoEvent * const ev) const SoType type(ev->getTypeId()); const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion(); - const SbVec2s size(vp.getViewportSizePixels()); - const SbVec2f prevnormalized = this->lastmouseposition; const SbVec2s pos(ev->getPosition()); - const SbVec2f posn((float) pos[0] / (float) std::max((int)(size[0] - 1), 1), - (float) pos[1] / (float) std::max((int)(size[1] - 1), 1)); + const SbVec2f posn = normalizePixelPos(pos); + const SbVec2f prevnormalized = this->lastmouseposition; this->lastmouseposition = posn; // Set to true if any event processing happened. Note that it is not @@ -115,15 +113,7 @@ SbBool InventorNavigationStyle::processSoEvent(const SoEvent * const ev) // Mismatches in state of the modifier keys happens if the user // presses or releases them outside the viewer window. - if (this->ctrldown != ev->wasCtrlDown()) { - this->ctrldown = ev->wasCtrlDown(); - } - if (this->shiftdown != ev->wasShiftDown()) { - this->shiftdown = ev->wasShiftDown(); - } - if (this->altdown != ev->wasAltDown()) { - this->altdown = ev->wasAltDown(); - } + syncModifierKeys(ev); // give the nodes in the foreground root the chance to handle events (e.g color bar) if (!viewer->isEditing()) { @@ -134,37 +124,8 @@ SbBool InventorNavigationStyle::processSoEvent(const SoEvent * const ev) // Keyboard handling if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { - const SoKeyboardEvent * const event = (const SoKeyboardEvent *) ev; - const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; - switch (event->getKey()) { - case SoKeyboardEvent::LEFT_CONTROL: - case SoKeyboardEvent::RIGHT_CONTROL: - this->ctrldown = press; - break; - case SoKeyboardEvent::LEFT_SHIFT: - case SoKeyboardEvent::RIGHT_SHIFT: - this->shiftdown = press; - break; - case SoKeyboardEvent::LEFT_ALT: - case SoKeyboardEvent::RIGHT_ALT: - this->altdown = press; - break; - case SoKeyboardEvent::H: - processed = true; - viewer->saveHomePosition(); - break; - case SoKeyboardEvent::S: - case SoKeyboardEvent::HOME: - case SoKeyboardEvent::LEFT_ARROW: - case SoKeyboardEvent::UP_ARROW: - case SoKeyboardEvent::RIGHT_ARROW: - case SoKeyboardEvent::DOWN_ARROW: - if (!this->isViewing()) - this->setViewing(true); - break; - default: - break; - } + const SoKeyboardEvent * const event = static_cast(ev); + processed = processKeyboardEvent(event); } // Mouse Button / Spaceball Button handling @@ -218,30 +179,8 @@ SbBool InventorNavigationStyle::processSoEvent(const SoEvent * const ev) processed = true; this->lockrecenter = true; } - // issue #0002433: avoid to swallow the UP event if down the - // scene graph somewhere a dialog gets opened - else if (press) { - SbTime tmp = (ev->getTime() - mouseDownConsumedEvent.getTime()); - float dci = (float)QApplication::doubleClickInterval()/1000.0f; - // a double-click? - if (tmp.getValue() < dci) { - mouseDownConsumedEvent = *event; - mouseDownConsumedEvent.setTime(ev->getTime()); - processed = true; - } - else { - mouseDownConsumedEvent.setTime(ev->getTime()); - // 'ANY' is used to mark that we don't know yet if it will - // be a double-click event. - mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); - } - } - else if (!press) { - if (mouseDownConsumedEvent.getButton() == SoMouseButtonEvent::BUTTON1) { - // now handle the postponed event - inherited::processSoEvent(&mouseDownConsumedEvent); - mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); - } + else { + processed = processClickEvent(event); } break; case SoMouseButtonEvent::BUTTON2: diff --git a/src/Gui/MDIView.cpp b/src/Gui/MDIView.cpp index c95b2dbe8f..22d2a7658f 100644 --- a/src/Gui/MDIView.cpp +++ b/src/Gui/MDIView.cpp @@ -241,6 +241,34 @@ void MDIView::printPreview() std::cerr << "Printing preview not implemented for " << this->metaObject()->className() << std::endl; } +QStringList MDIView::undoActions() const +{ + QStringList actions; + Gui::Document* doc = getGuiDocument(); + if (doc) { + std::vector vecUndos = doc->getUndoVector(); + for (std::vector::iterator i = vecUndos.begin(); i != vecUndos.end(); ++i) { + actions << QCoreApplication::translate("Command", i->c_str()); + } + } + + return actions; +} + +QStringList MDIView::redoActions() const +{ + QStringList actions; + Gui::Document* doc = getGuiDocument(); + if (doc) { + std::vector vecRedos = doc->getRedoVector(); + for (std::vector::iterator i = vecRedos.begin(); i != vecRedos.end(); ++i) { + actions << QCoreApplication::translate("Command", i->c_str()); + } + } + + return actions; +} + QSize MDIView::minimumSizeHint () const { return QSize(400, 300); diff --git a/src/Gui/MDIView.h b/src/Gui/MDIView.h index 0aadbb4ead..5a0e97431a 100644 --- a/src/Gui/MDIView.h +++ b/src/Gui/MDIView.h @@ -95,6 +95,12 @@ public: virtual void printPreview(); //@} + /** @name Undo/Redo actions */ + //@{ + virtual QStringList undoActions() const; + virtual QStringList redoActions() const; + //@} + QSize minimumSizeHint () const; /// MDI view mode enum diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index 35a335e9c3..b0dfe66630 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -1490,7 +1490,7 @@ QPixmap MainWindow::aboutImage() const if (!about_path.empty() && about_image.isNull()) { QString path = QString::fromUtf8(about_path.c_str()); if (QDir(path).isRelative()) { - QString home = QString::fromUtf8(App::GetApplication().getHomePath()); + QString home = QString::fromStdString(App::Application::getHomePath()); path = QFileInfo(QDir(home), path).absoluteFilePath(); } about_image.load(path); @@ -1517,7 +1517,7 @@ QPixmap MainWindow::splashImage() const if (splash_image.isNull()) { QString path = QString::fromUtf8(splash_path.c_str()); if (QDir(path).isRelative()) { - QString home = QString::fromUtf8(App::GetApplication().getHomePath()); + QString home = QString::fromStdString(App::Application::getHomePath()); path = QFileInfo(QDir(home), path).absoluteFilePath(); } diff --git a/src/Gui/MayaGestureNavigationStyle.cpp b/src/Gui/MayaGestureNavigationStyle.cpp index dbb5815a6c..8c9bd83c6c 100644 --- a/src/Gui/MayaGestureNavigationStyle.cpp +++ b/src/Gui/MayaGestureNavigationStyle.cpp @@ -190,9 +190,7 @@ SbBool MayaGestureNavigationStyle::processSoEvent(const SoEvent * const ev) // Mismatches in state of the modifier keys happens if the user // presses or releases them outside the viewer window. - this->ctrldown = ev->wasCtrlDown(); - this->shiftdown = ev->wasShiftDown(); - this->altdown = ev->wasAltDown(); + syncModifierKeys(ev); //before this block, mouse button states in NavigationStyle::buttonXdown reflected those before current event arrived. //track mouse button states if (evIsButton) { @@ -380,11 +378,11 @@ SbBool MayaGestureNavigationStyle::processSoEvent(const SoEvent * const ev) this->mouseMoveThresholdBroken = false; pan(viewer->getSoRenderManager()->getCamera());//set up panningplane int &cnt = this->mousedownConsumedCount; - this->mousedownConsumedEvent[cnt] = *event;//hopefully, a shallow copy is enough. There are no pointers stored in events, apparently. Will lose a subclass, though. + this->mousedownConsumedEvents[cnt] = *event;//hopefully, a shallow copy is enough. There are no pointers stored in events, apparently. Will lose a subclass, though. cnt++; assert(cnt<=2); - if(cnt>static_cast(sizeof(mousedownConsumedEvent))){ - cnt=sizeof(mousedownConsumedEvent);//we are in trouble + if(cnt>static_cast(sizeof(mousedownConsumedEvents))){ + cnt=sizeof(mousedownConsumedEvents);//we are in trouble } processed = true;//just consume this event, and wait for the move threshold to be broken to start dragging/panning } @@ -398,7 +396,7 @@ SbBool MayaGestureNavigationStyle::processSoEvent(const SoEvent * const ev) if(! processed) { //re-synthesize all previously-consumed mouseDowns, if any. They might have been re-synthesized already when threshold was broken. for( int i=0; i < this->mousedownConsumedCount; i++ ){ - inherited::processSoEvent(& (this->mousedownConsumedEvent[i]));//simulate the previously-comsumed mousedown. + inherited::processSoEvent(& (this->mousedownConsumedEvents[i]));//simulate the previously-comsumed mousedown. } this->mousedownConsumedCount = 0; processed = inherited::processSoEvent(ev);//explicitly, just for clarity that we are sending a full click sequence. @@ -443,7 +441,7 @@ SbBool MayaGestureNavigationStyle::processSoEvent(const SoEvent * const ev) //no, we are not entering navigation. //re-synthesize all previously-consumed mouseDowns, if any, and propagate this mousemove. for( int i=0; i < this->mousedownConsumedCount; i++ ){ - inherited::processSoEvent(& (this->mousedownConsumedEvent[i]));//simulate the previously-comsumed mousedown. + inherited::processSoEvent(& (this->mousedownConsumedEvents[i]));//simulate the previously-comsumed mousedown. } this->mousedownConsumedCount = 0; processed = inherited::processSoEvent(ev);//explicitly, just for clarity that we are sending a full click sequence. diff --git a/src/Gui/MetaTypes.h b/src/Gui/MetaTypes.h index 75047bca9c..99b69ed431 100644 --- a/src/Gui/MetaTypes.h +++ b/src/Gui/MetaTypes.h @@ -34,6 +34,7 @@ Q_DECLARE_METATYPE(Base::Vector3d) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(Base::Matrix4D) Q_DECLARE_METATYPE(Base::Placement) +Q_DECLARE_METATYPE(Base::Rotation) Q_DECLARE_METATYPE(Base::Quantity) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(App::SubObjectT) diff --git a/src/Gui/NavigationStyle.cpp b/src/Gui/NavigationStyle.cpp index 43a4a73045..fb971292e1 100644 --- a/src/Gui/NavigationStyle.cpp +++ b/src/Gui/NavigationStyle.cpp @@ -64,7 +64,7 @@ struct NavigationStyleP { { this->animationsteps = 0; this->animationdelta = 0; - this->animsensor = 0; + this->animsensor = nullptr; this->sensitivity = 2.0f; this->resetcursorpos = false; this->rotationCenterFound = false; @@ -173,7 +173,7 @@ const Base::Type& NavigationStyleEvent::style() const TYPESYSTEM_SOURCE_ABSTRACT(Gui::NavigationStyle,Base::BaseClass) -NavigationStyle::NavigationStyle() : viewer(0), mouseSelection(0) +NavigationStyle::NavigationStyle() : viewer(nullptr), mouseSelection(nullptr) { PRIVATE(this) = new NavigationStyleP(); PRIVATE(this)->animsensor = new SoTimerSensor(NavigationStyleP::viewAnimationCB, this); @@ -261,17 +261,17 @@ void NavigationStyle::finalize() delete[] this->log.time; } -void NavigationStyle::interactiveCountInc(void) +void NavigationStyle::interactiveCountInc() { viewer->interactiveCountInc(); } -void NavigationStyle::interactiveCountDec(void) +void NavigationStyle::interactiveCountDec() { viewer->interactiveCountDec(); } -int NavigationStyle::getInteractiveCount(void) const +int NavigationStyle::getInteractiveCount() const { return viewer->getInteractiveCount(); } @@ -288,7 +288,7 @@ NavigationStyle::OrbitStyle NavigationStyle::getOrbitStyle() const return NavigationStyle::OrbitStyle(projector->getOrbitStyle()); } -SbBool NavigationStyle::isViewing(void) const +SbBool NavigationStyle::isViewing() const { return viewer->isViewing(); } @@ -298,7 +298,7 @@ void NavigationStyle::setViewing(SbBool enable) viewer->setViewing(enable); } -SbBool NavigationStyle::isSeekMode(void) const +SbBool NavigationStyle::isSeekMode() const { return viewer->isSeekMode(); } @@ -321,7 +321,7 @@ void NavigationStyle::seekToPoint(const SbVec3f& scenepos) SbBool NavigationStyle::lookAtPoint(const SbVec2s screenpos) { SoCamera* cam = viewer->getSoRenderManager()->getCamera(); - if (cam == 0) return false; + if (cam == nullptr) return false; SoRayPickAction rpaction(viewer->getSoRenderManager()->getViewportRegion()); rpaction.setPoint(screenpos); @@ -343,7 +343,7 @@ SbBool NavigationStyle::lookAtPoint(const SbVec2s screenpos) void NavigationStyle::lookAtPoint(const SbVec3f& pos) { SoCamera* cam = viewer->getSoRenderManager()->getCamera(); - if (cam == 0) return; + if (cam == nullptr) return; PRIVATE(this)->rotationCenterFound = false; // Find global coordinates of focal point. @@ -401,7 +401,7 @@ void NavigationStyle::lookAtPoint(const SbVec3f& pos) void NavigationStyle::setCameraOrientation(const SbRotation& rot, SbBool moveToCenter) { SoCamera* cam = viewer->getSoRenderManager()->getCamera(); - if (cam == 0) return; + if (cam == nullptr) return; // Find global coordinates of focal point. SbVec3f direction; @@ -611,7 +611,7 @@ void NavigationStyle::viewAll() */ void NavigationStyle::reorientCamera(SoCamera * cam, const SbRotation & rot) { - if (cam == NULL) return; + if (cam == nullptr) return; // Find global coordinates of focal point. SbVec3f direction; @@ -630,7 +630,7 @@ void NavigationStyle::reorientCamera(SoCamera * cam, const SbRotation & rot) void NavigationStyle::panCamera(SoCamera * cam, float aspectratio, const SbPlane & panplane, const SbVec2f & currpos, const SbVec2f & prevpos) { - if (cam == NULL) return; // can happen for empty scenegraph + if (cam == nullptr) return; // can happen for empty scenegraph if (currpos == prevpos) return; // useless invocation @@ -659,7 +659,7 @@ void NavigationStyle::pan(SoCamera* camera) // The plane we're projecting the mouse coordinates to get 3D // coordinates should stay the same during the whole pan // operation, so we should calculate this value here. - if (camera == NULL) { // can happen for empty scenegraph + if (camera == nullptr) { // can happen for empty scenegraph this->panningplane = SbPlane(SbVec3f(0, 0, 1), 0); } else { @@ -689,7 +689,7 @@ void NavigationStyle::panToCenter(const SbPlane & pplane, const SbVec2f & currpo */ void NavigationStyle::zoom(SoCamera * cam, float diffvalue) { - if (cam == NULL) return; // can happen for empty scenegraph + if (cam == nullptr) return; // can happen for empty scenegraph SoType t = cam->getTypeId(); SbName tname = t.getName(); @@ -870,7 +870,7 @@ void NavigationStyle::setRotationCenter(const SbVec3f& cnt) SbVec3f NavigationStyle::getFocalPoint() const { SoCamera* cam = viewer->getSoRenderManager()->getCamera(); - if (cam == 0) + if (cam == nullptr) return SbVec3f(0,0,0); // Find global coordinates of focal point. @@ -887,7 +887,7 @@ SbVec3f NavigationStyle::getFocalPoint() const void NavigationStyle::spin(const SbVec2f & pointerpos) { if (this->log.historysize < 2) return; - assert(this->spinprojector != NULL); + assert(this->spinprojector != nullptr); const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion(); SbVec2s glsize(vp.getViewportSizePixels()); @@ -965,7 +965,7 @@ void NavigationStyle::spin(const SbVec2f & pointerpos) * \param prevpos previous normalized position of mouse pointer */ void NavigationStyle::spin_simplified(SoCamera* cam, SbVec2f curpos, SbVec2f prevpos){ - assert(this->spinprojector != NULL); + assert(this->spinprojector != nullptr); // 0000333: Turntable camera rotation SbMatrix mat; @@ -1163,7 +1163,7 @@ NavigationStyle::setAnimationEnabled(const SbBool enable) */ SbBool -NavigationStyle::isAnimationEnabled(void) const +NavigationStyle::isAnimationEnabled() const { return this->spinanimatingallowed; } @@ -1172,7 +1172,7 @@ NavigationStyle::isAnimationEnabled(void) const Query if the model in the viewer is currently in spinning mode after a user drag. */ -SbBool NavigationStyle::isAnimating(void) const +SbBool NavigationStyle::isAnimating() const { return this->currentmode == NavigationStyle::SPINNING; } @@ -1195,7 +1195,7 @@ void NavigationStyle::startAnimating(const SbVec3f& axis, float velocity) this->spinRotation = rot; } -void NavigationStyle::stopAnimating(void) +void NavigationStyle::stopAnimating() { if (this->currentmode != NavigationStyle::SPINNING) { return; @@ -1321,7 +1321,7 @@ void NavigationStyle::stopSelection() if (mouseSelection) { mouseSelection->releaseMouseModel(); delete mouseSelection; - mouseSelection = 0; + mouseSelection = nullptr; } } @@ -1367,11 +1367,26 @@ void NavigationStyle::addToLog(const SbVec2s pos, const SbTime time) // This method "clears" the mouse location log, used for spin // animation calculations. -void NavigationStyle::clearLog(void) +void NavigationStyle::clearLog() { this->log.historysize = 0; } +void NavigationStyle::syncModifierKeys(const SoEvent * const ev) +{ + // Mismatches in state of the modifier keys happens if the user + // presses or releases them outside the viewer window. + if (this->ctrldown != ev->wasCtrlDown()) { + this->ctrldown = ev->wasCtrlDown(); + } + if (this->shiftdown != ev->wasShiftDown()) { + this->shiftdown = ev->wasShiftDown(); + } + if (this->altdown != ev->wasAltDown()) { + this->altdown = ev->wasAltDown(); + } +} + // The viewer is a state machine, and all changes to the current state // are made through this call. void NavigationStyle::setViewingMode(const ViewerMode newmode) @@ -1446,14 +1461,14 @@ SbBool NavigationStyle::processEvent(const SoEvent * const ev) pcPolygon = mouseSelection->getPositions(); selectedRole = mouseSelection->selectedRole(); delete mouseSelection; - mouseSelection = 0; + mouseSelection = nullptr; syncWithEvent(ev); return NavigationStyle::processSoEvent(ev); } else if (hd==AbstractMouseSelection::Cancel) { pcPolygon.clear(); delete mouseSelection; - mouseSelection = 0; + mouseSelection = nullptr; syncWithEvent(ev); return NavigationStyle::processSoEvent(ev); } @@ -1480,27 +1495,19 @@ SbBool NavigationStyle::processEvent(const SoEvent * const ev) SbBool NavigationStyle::processSoEvent(const SoEvent * const ev) { - const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion(); - const SbVec2s size(vp.getViewportSizePixels()); - const SbVec2s pos(ev->getPosition()); - const SbVec2f posn((float) pos[0] / (float) std::max((int)(size[0] - 1), 1), - (float) pos[1] / (float) std::max((int)(size[1] - 1), 1)); bool processed = false; //handle mouse wheel zoom - if(ev->isOfType(SoMouseWheelEvent::getClassTypeId())){ - doZoom( - viewer->getSoRenderManager()->getCamera(), - static_cast(ev)->getDelta(), - posn - ); - processed = true; + if (ev->isOfType(SoMouseWheelEvent::getClassTypeId())) { + const SoMouseWheelEvent * const event = static_cast(ev); + processed = processWheelEvent(event); } - if (! processed) - return viewer->processSoEventBase(ev); - else - return processed; + if (!processed) { + processed = viewer->processSoEventBase(ev); + } + + return processed; } void NavigationStyle::syncWithEvent(const SoEvent * const ev) @@ -1514,15 +1521,7 @@ void NavigationStyle::syncWithEvent(const SoEvent * const ev) // Mismatches in state of the modifier keys happens if the user // presses or releases them outside the viewer window. - if (this->ctrldown != ev->wasCtrlDown()) { - this->ctrldown = ev->wasCtrlDown(); - } - if (this->shiftdown != ev->wasShiftDown()) { - this->shiftdown = ev->wasShiftDown(); - } - if (this->altdown != ev->wasAltDown()) { - this->altdown = ev->wasAltDown(); - } + syncModifierKeys(ev); // Keyboard handling if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { @@ -1600,12 +1599,111 @@ SbBool NavigationStyle::processMotionEvent(const SoMotion3Event * const ev) return true; } +SbBool NavigationStyle::processKeyboardEvent(const SoKeyboardEvent * const event) +{ + SbBool processed = false; + const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; + switch (event->getKey()) { + case SoKeyboardEvent::LEFT_CONTROL: + case SoKeyboardEvent::RIGHT_CONTROL: + this->ctrldown = press; + break; + case SoKeyboardEvent::LEFT_SHIFT: + case SoKeyboardEvent::RIGHT_SHIFT: + this->shiftdown = press; + break; + case SoKeyboardEvent::LEFT_ALT: + case SoKeyboardEvent::RIGHT_ALT: + this->altdown = press; + break; + case SoKeyboardEvent::H: + processed = true; + viewer->saveHomePosition(); + break; + case SoKeyboardEvent::R: + processed = true; + viewer->resetToHomePosition(); + break; + case SoKeyboardEvent::S: + case SoKeyboardEvent::HOME: + case SoKeyboardEvent::LEFT_ARROW: + case SoKeyboardEvent::UP_ARROW: + case SoKeyboardEvent::RIGHT_ARROW: + case SoKeyboardEvent::DOWN_ARROW: + if (!this->isViewing()) + this->setViewing(true); + break; + case SoKeyboardEvent::PAGE_UP: + { + processed = true; + const SbVec2f posn = normalizePixelPos(event->getPosition()); + doZoom(viewer->getSoRenderManager()->getCamera(), getDelta(), posn); + break; + } + case SoKeyboardEvent::PAGE_DOWN: + { + processed = true; + const SbVec2f posn = normalizePixelPos(event->getPosition()); + doZoom(viewer->getSoRenderManager()->getCamera(), -getDelta(), posn); + break; + } + default: + break; + } + + return processed; +} + +SbBool NavigationStyle::processClickEvent(const SoMouseButtonEvent * const event) +{ + // issue #0002433: avoid to swallow the UP event if down the + // scene graph somewhere a dialog gets opened + SbBool processed = false; + const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; + if (press) { + SbTime tmp = (event->getTime() - mouseDownConsumedEvent.getTime()); + float dci = (float)QApplication::doubleClickInterval()/1000.0f; + // a double-click? + if (tmp.getValue() < dci) { + mouseDownConsumedEvent = *event; + mouseDownConsumedEvent.setTime(event->getTime()); + processed = true; + } + else { + mouseDownConsumedEvent.setTime(event->getTime()); + // 'ANY' is used to mark that we don't know yet if it will + // be a double-click event. + mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); + } + } + else if (!press) { + if (mouseDownConsumedEvent.getButton() == SoMouseButtonEvent::BUTTON1) { + // now handle the postponed event + NavigationStyle::processSoEvent(&mouseDownConsumedEvent); + mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); + } + } + + return processed; +} + +SbBool NavigationStyle::processWheelEvent(const SoMouseWheelEvent * const event) +{ + const SbVec2s pos(event->getPosition()); + const SbVec2f posn = normalizePixelPos(pos); + + //handle mouse wheel zoom + doZoom(viewer->getSoRenderManager()->getCamera(), + event->getDelta(), posn); + return true; +} + void NavigationStyle::setPopupMenuEnabled(const SbBool on) { this->menuenabled = on; } -SbBool NavigationStyle::isPopupMenuEnabled(void) const +SbBool NavigationStyle::isPopupMenuEnabled() const { return this->menuenabled; } diff --git a/src/Gui/NavigationStyle.h b/src/Gui/NavigationStyle.h index 2903ac0caa..7f493ec55a 100644 --- a/src/Gui/NavigationStyle.h +++ b/src/Gui/NavigationStyle.h @@ -37,9 +37,11 @@ #include #include #include +#include // forward declarations class SoEvent; +class SoMouseWheelEvent; class SoMotion3Event; class SoQtViewer; class SoCamera; @@ -115,11 +117,11 @@ public: void setViewer(View3DInventorViewer*); void setAnimationEnabled(const SbBool enable); - SbBool isAnimationEnabled(void) const; + SbBool isAnimationEnabled() const; void startAnimating(const SbVec3f& axis, float velocity); - void stopAnimating(void); - SbBool isAnimating(void) const; + void stopAnimating(); + SbBool isAnimating() const; void setSensitivity(float); float getSensitivity() const; @@ -151,16 +153,19 @@ public: int getViewingMode() const; virtual SbBool processEvent(const SoEvent * const ev); virtual SbBool processMotionEvent(const SoMotion3Event * const ev); + virtual SbBool processKeyboardEvent(const SoKeyboardEvent * const event); + virtual SbBool processClickEvent(const SoMouseButtonEvent * const event); + virtual SbBool processWheelEvent(const SoMouseWheelEvent * const event); void setPopupMenuEnabled(const SbBool on); - SbBool isPopupMenuEnabled(void) const; + SbBool isPopupMenuEnabled() const; void startSelection(AbstractMouseSelection*); void startSelection(SelectionMode = Lasso); void abortSelection(); void stopSelection(); SbBool isSelecting() const; - const std::vector& getPolygon(SelectionRole* role=0) const; + const std::vector& getPolygon(SelectionRole* role=nullptr) const; void setOrbitStyle(OrbitStyle style); OrbitStyle getOrbitStyle() const; @@ -169,13 +174,13 @@ protected: void initialize(); void finalize(); - void interactiveCountInc(void); - void interactiveCountDec(void); - int getInteractiveCount(void) const; + void interactiveCountInc(); + void interactiveCountDec(); + int getInteractiveCount() const; - SbBool isViewing(void) const; + SbBool isViewing() const; void setViewing(SbBool); - SbBool isSeekMode(void) const; + SbBool isSeekMode() const; void setSeekMode(SbBool enable); SbBool seekToPoint(const SbVec2s screenpos); void seekToPoint(const SbVec3f& scenepos); @@ -210,9 +215,10 @@ protected: void syncWithEvent(const SoEvent * const ev); virtual void openPopupMenu(const SbVec2s& position); - void clearLog(void); + void clearLog(); void addToLog(const SbVec2s pos, const SbTime time); + void syncModifierKeys(const SoEvent * const ev); protected: struct { // tracking mouse movement in a log @@ -224,6 +230,7 @@ protected: View3DInventorViewer* viewer; ViewerMode currentmode; + SoMouseButtonEvent mouseDownConsumedEvent; SbVec2f lastmouseposition; SbVec2s globalPos; SbVec2s localPos; @@ -293,9 +300,6 @@ public: protected: SbBool processSoEvent(const SoEvent * const ev); - -private: - SoMouseButtonEvent mouseDownConsumedEvent; }; class GuiExport CADNavigationStyle : public UserNavigationStyle { @@ -313,7 +317,6 @@ protected: private: SbBool lockButton1; - SoMouseButtonEvent mouseDownConsumedEvent; }; class GuiExport RevitNavigationStyle : public UserNavigationStyle { @@ -331,7 +334,6 @@ protected: private: SbBool lockButton1; - SoMouseButtonEvent mouseDownConsumedEvent; }; class GuiExport BlenderNavigationStyle : public UserNavigationStyle { @@ -349,7 +351,6 @@ protected: private: SbBool lockButton1; - SoMouseButtonEvent mouseDownConsumedEvent; }; class GuiExport MayaGestureNavigationStyle : public UserNavigationStyle { @@ -369,7 +370,7 @@ protected: short mouseMoveThreshold;//setting. Minimum move required to consider it a move (in pixels). bool mouseMoveThresholdBroken;//a flag that the move threshold was surpassed since last mousedown. int mousedownConsumedCount;//a flag for remembering that a mousedown of button1/button2 was consumed. - SoMouseButtonEvent mousedownConsumedEvent[5];//the event that was consumed and is to be refired. 2 should be enough, but just for a case of the maximum 5 buttons... + SoMouseButtonEvent mousedownConsumedEvents[5];//the event that was consumed and is to be refired. 2 should be enough, but just for a case of the maximum 5 buttons... bool testMoveThreshold(const SbVec2s currentPos) const; bool thisClickIsComplex;//a flag that becomes set when a complex clicking pattern is detected (i.e., two or more mouse buttons were down at the same time). @@ -388,9 +389,6 @@ public: protected: SbBool processSoEvent(const SoEvent * const ev); - -private: - SoMouseButtonEvent mouseDownConsumedEvent; }; class GuiExport OpenCascadeNavigationStyle : public UserNavigationStyle { @@ -405,9 +403,34 @@ public: protected: SbBool processSoEvent(const SoEvent * const ev); +}; -private: - SoMouseButtonEvent mouseDownConsumedEvent; +class GuiExport OpenSCADNavigationStyle : public UserNavigationStyle { + typedef UserNavigationStyle inherited; + + TYPESYSTEM_HEADER(); + +public: + OpenSCADNavigationStyle(); + ~OpenSCADNavigationStyle(); + const char* mouseButtons(ViewerMode); + +protected: + SbBool processSoEvent(const SoEvent * const ev); +}; + +class GuiExport TinkerCADNavigationStyle : public UserNavigationStyle { + typedef UserNavigationStyle inherited; + + TYPESYSTEM_HEADER(); + +public: + TinkerCADNavigationStyle(); + ~TinkerCADNavigationStyle(); + const char* mouseButtons(ViewerMode); + +protected: + SbBool processSoEvent(const SoEvent * const ev); }; } // namespace Gui diff --git a/src/Gui/NetworkRetriever.cpp b/src/Gui/NetworkRetriever.cpp index 8ba993e207..38e8528f62 100644 --- a/src/Gui/NetworkRetriever.cpp +++ b/src/Gui/NetworkRetriever.cpp @@ -418,7 +418,7 @@ Action * StdCmdDownloadOnlineHelp::createAction(void) { Action *pcAction; - QString exe = QString::fromLatin1(App::GetApplication().getExecutableName()); + QString exe = QString::fromStdString(App::Application::getExecutableName()); pcAction = new Action(this,getMainWindow()); pcAction->setText(QCoreApplication::translate( this->className(), getMenuText())); @@ -437,7 +437,7 @@ Action * StdCmdDownloadOnlineHelp::createAction(void) void StdCmdDownloadOnlineHelp::languageChange() { if (_pcAction) { - QString exe = QString::fromLatin1(App::GetApplication().getExecutableName()); + QString exe = QString::fromStdString(App::Application::getExecutableName()); _pcAction->setText(QCoreApplication::translate( this->className(), getMenuText())); _pcAction->setToolTip(QCoreApplication::translate( @@ -483,7 +483,7 @@ void StdCmdDownloadOnlineHelp::activated(int iMsg) bool canStart = false; // set output directory - QString path = QString::fromUtf8(App::GetApplication().getHomePath()); + QString path = QString::fromStdString(App::Application::getHomePath()); path += QString::fromLatin1("/doc/"); ParameterGrp::handle hURLGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/OnlineHelp"); path = QString::fromUtf8(hURLGrp->GetASCII( "DownloadLocation", path.toLatin1() ).c_str()); diff --git a/src/Gui/OpenCascadeNavigationStyle.cpp b/src/Gui/OpenCascadeNavigationStyle.cpp index b554327b66..1cbf7b322e 100644 --- a/src/Gui/OpenCascadeNavigationStyle.cpp +++ b/src/Gui/OpenCascadeNavigationStyle.cpp @@ -88,12 +88,10 @@ SbBool OpenCascadeNavigationStyle::processSoEvent(const SoEvent * const ev) const SoType type(ev->getTypeId()); const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion(); - const SbVec2s size(vp.getViewportSizePixels()); - const SbVec2f prevnormalized = this->lastmouseposition; const SbVec2s pos(ev->getPosition()); - const SbVec2f posn((float) pos[0] / (float) std::max((int)(size[0] - 1), 1), - (float) pos[1] / (float) std::max((int)(size[1] - 1), 1)); + const SbVec2f posn = normalizePixelPos(pos); + const SbVec2f prevnormalized = this->lastmouseposition; this->lastmouseposition = posn; // Set to true if any event processing happened. Note that it is not @@ -107,15 +105,7 @@ SbBool OpenCascadeNavigationStyle::processSoEvent(const SoEvent * const ev) // Mismatches in state of the modifier keys happens if the user // presses or releases them outside the viewer window. - if (this->ctrldown != ev->wasCtrlDown()) { - this->ctrldown = ev->wasCtrlDown(); - } - if (this->shiftdown != ev->wasShiftDown()) { - this->shiftdown = ev->wasShiftDown(); - } - if (this->altdown != ev->wasAltDown()) { - this->altdown = ev->wasAltDown(); - } + syncModifierKeys(ev); // give the nodes in the foreground root the chance to handle events (e.g color bar) if (!viewer->isEditing()) { @@ -126,37 +116,8 @@ SbBool OpenCascadeNavigationStyle::processSoEvent(const SoEvent * const ev) // Keyboard handling if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { - const SoKeyboardEvent * const event = (const SoKeyboardEvent *) ev; - const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; - switch (event->getKey()) { - case SoKeyboardEvent::LEFT_CONTROL: - case SoKeyboardEvent::RIGHT_CONTROL: - this->ctrldown = press; - break; - case SoKeyboardEvent::LEFT_SHIFT: - case SoKeyboardEvent::RIGHT_SHIFT: - this->shiftdown = press; - break; - case SoKeyboardEvent::LEFT_ALT: - case SoKeyboardEvent::RIGHT_ALT: - this->altdown = press; - break; - case SoKeyboardEvent::H: - processed = true; - viewer->saveHomePosition(); - break; - case SoKeyboardEvent::S: - case SoKeyboardEvent::HOME: - case SoKeyboardEvent::LEFT_ARROW: - case SoKeyboardEvent::UP_ARROW: - case SoKeyboardEvent::RIGHT_ARROW: - case SoKeyboardEvent::DOWN_ARROW: - if (!this->isViewing()) - this->setViewing(true); - break; - default: - break; - } + const SoKeyboardEvent * const event = static_cast(ev); + processed = processKeyboardEvent(event); } // Mouse Button / Spaceball Button handling @@ -185,30 +146,8 @@ SbBool OpenCascadeNavigationStyle::processSoEvent(const SoEvent * const ev) else if (viewer->isEditing() && (this->currentmode == NavigationStyle::SPINNING)) { processed = true; } - // issue #0002433: avoid to swallow the UP event if down the - // scene graph somewhere a dialog gets opened - else if (press) { - SbTime tmp = (ev->getTime() - mouseDownConsumedEvent.getTime()); - float dci = (float)QApplication::doubleClickInterval()/1000.0f; - // a double-click? - if (tmp.getValue() < dci) { - mouseDownConsumedEvent = *event; - mouseDownConsumedEvent.setTime(ev->getTime()); - processed = true; - } - else { - mouseDownConsumedEvent.setTime(ev->getTime()); - // 'ANY' is used to mark that we don't know yet if it will - // be a double-click event. - mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); - } - } - else if (!press) { - if (mouseDownConsumedEvent.getButton() == SoMouseButtonEvent::BUTTON1) { - // now handle the postponed event - inherited::processSoEvent(&mouseDownConsumedEvent); - mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); - } + else { + processed = processClickEvent(event); } break; case SoMouseButtonEvent::BUTTON2: @@ -342,10 +281,8 @@ SbBool OpenCascadeNavigationStyle::processSoEvent(const SoEvent * const ev) // If not handled in this class, pass on upwards in the inheritance // hierarchy. - if (/*(curmode == NavigationStyle::SELECTION || viewer->isEditing()) && */!processed) + if (!processed) processed = inherited::processSoEvent(ev); - else - return true; return processed; } diff --git a/src/Gui/OpenSCADNavigationStyle.cpp b/src/Gui/OpenSCADNavigationStyle.cpp new file mode 100644 index 0000000000..c1c1ee1e6b --- /dev/null +++ b/src/Gui/OpenSCADNavigationStyle.cpp @@ -0,0 +1,286 @@ +/*************************************************************************** + * 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 "InventorAll.h" +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#include +#include "NavigationStyle.h" +#include "View3DInventorViewer.h" +#include "Application.h" +#include "MenuManager.h" +#include "MouseSelection.h" + +using namespace Gui; + +// ---------------------------------------------------------------------------------- + +/* TRANSLATOR Gui::OpenSCADNavigationStyle */ + +TYPESYSTEM_SOURCE(Gui::OpenSCADNavigationStyle, Gui::UserNavigationStyle) + +OpenSCADNavigationStyle::OpenSCADNavigationStyle() +{ +} + +OpenSCADNavigationStyle::~OpenSCADNavigationStyle() +{ +} + +const char* OpenSCADNavigationStyle::mouseButtons(ViewerMode mode) +{ + switch (mode) { + case NavigationStyle::SELECTION: + return QT_TR_NOOP("Press left mouse button"); + case NavigationStyle::PANNING: + return QT_TR_NOOP("Press right mouse button and move mouse"); + case NavigationStyle::DRAGGING: + return QT_TR_NOOP("Press left mouse button and move mouse"); + case NavigationStyle::ZOOMING: + return QT_TR_NOOP("Press middle mouse button or SHIFT and right mouse button"); + default: + return "No description"; + } +} + +SbBool OpenSCADNavigationStyle::processSoEvent(const SoEvent * const ev) +{ + // Events when in "ready-to-seek" mode are ignored, except those + // which influence the seek mode itself -- these are handled further + // up the inheritance hierarchy. + if (this->isSeekMode()) { return inherited::processSoEvent(ev); } + // Switch off viewing mode + if (!this->isSeekMode() && !this->isAnimating() && this->isViewing()) + this->setViewing(false); // by default disable viewing mode to render the scene + + const SoType type(ev->getTypeId()); + + const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion(); + const SbVec2s pos(ev->getPosition()); + const SbVec2f posn = normalizePixelPos(pos); + + const SbVec2f prevnormalized = this->lastmouseposition; + this->lastmouseposition = posn; + + // Set to true if any event processing happened. Note that it is not + // necessary to restrict ourselves to only do one "action" for an + // event, we only need this flag to see if any processing happened + // at all. + SbBool processed = false; + + const ViewerMode curmode = this->currentmode; + ViewerMode newmode = curmode; + + // Mismatches in state of the modifier keys happens if the user + // presses or releases them outside the viewer window. + syncModifierKeys(ev); + + // give the nodes in the foreground root the chance to handle events (e.g color bar) + if (!viewer->isEditing()) { + processed = handleEventInForeground(ev); + if (processed) + return true; + } + + // Keyboard handling + if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { + const SoKeyboardEvent * const event = static_cast(ev); + processed = processKeyboardEvent(event); + } + + // Mouse Button / Spaceball Button handling + if (type.isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) { + const SoMouseButtonEvent * const event = (const SoMouseButtonEvent *) ev; + const int button = event->getButton(); + const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; + + switch (button) { + case SoMouseButtonEvent::BUTTON1: + this->lockrecenter = true; + this->button1down = press; + if (press && (curmode == NavigationStyle::SEEK_WAIT_MODE)) { + newmode = NavigationStyle::SEEK_MODE; + this->seekToPoint(pos); // implicitly calls interactiveCountInc() + processed = true; + } + else if (!press && (curmode == NavigationStyle::ZOOMING)) { + newmode = NavigationStyle::IDLE; + processed = true; + } + else if (!press && (curmode == NavigationStyle::DRAGGING)) { + this->setViewing(false); + processed = true; + } + else if (viewer->isEditing() && (curmode == NavigationStyle::SPINNING)) { + processed = true; + } + else { + processed = processClickEvent(event); + } + break; + case SoMouseButtonEvent::BUTTON2: + // If we are in edit mode then simply ignore the RMB events + // to pass the event to the base class. + this->lockrecenter = true; + this->button2down = press; + if (!viewer->isEditing()) { + // If we are in zoom or pan mode ignore RMB events otherwise + // the canvas doesn't get any release events + if (curmode != NavigationStyle::ZOOMING && + curmode != NavigationStyle::PANNING && + curmode != NavigationStyle::DRAGGING) { + if (this->isPopupMenuEnabled()) { + if (!press) { // release right mouse button + this->openPopupMenu(event->getPosition()); + } + } + } + } + // Alternative way of rotating & zooming + if (press && (curmode == NavigationStyle::PANNING || + curmode == NavigationStyle::ZOOMING)) { + newmode = NavigationStyle::DRAGGING; + saveCursorPosition(ev); + this->centerTime = ev->getTime(); + processed = true; + } + else if (!press && (curmode == NavigationStyle::DRAGGING)) { + newmode = NavigationStyle::IDLE; + processed = true; + } + break; + case SoMouseButtonEvent::BUTTON3: + this->button3down = press; + if (press) { + this->centerTime = ev->getTime(); + float ratio = vp.getViewportAspectRatio(); + SbViewVolume vv = viewer->getSoRenderManager()->getCamera()->getViewVolume(ratio); + this->panningplane = vv.getPlane(viewer->getSoRenderManager()->getCamera()->focalDistance.getValue()); + this->lockrecenter = false; + } + else if (curmode == NavigationStyle::PANNING) { + newmode = NavigationStyle::IDLE; + processed = true; + } + break; + default: + break; + } + } + + // Mouse Movement handling + if (type.isDerivedFrom(SoLocation2Event::getClassTypeId())) { + this->lockrecenter = true; + const SoLocation2Event * const event = (const SoLocation2Event *) ev; + if (curmode == NavigationStyle::SELECTION) { + newmode = NavigationStyle::DRAGGING; + saveCursorPosition(ev); + this->centerTime = ev->getTime(); + } + else if (curmode == NavigationStyle::ZOOMING) { + // OpenSCAD uses vertical mouse position, not horizontal + // this->zoomByCursor(posn, prevnormalized); + float value = (posn[1] - prevnormalized[1]) * 10.0f; + if (this->invertZoom) + value = -value; + zoom(viewer->getSoRenderManager()->getCamera(), value); + processed = true; + } + else if (curmode == NavigationStyle::PANNING) { + float ratio = vp.getViewportAspectRatio(); + panCamera(viewer->getSoRenderManager()->getCamera(), ratio, this->panningplane, posn, prevnormalized); + processed = true; + } + else if (curmode == NavigationStyle::DRAGGING) { + this->addToLog(event->getPosition(), event->getTime()); + this->spin(posn); + moveCursorPosition(); + processed = true; + } + } + + // Spaceball & Joystick handling + if (type.isDerivedFrom(SoMotion3Event::getClassTypeId())) { + const SoMotion3Event * const event = static_cast(ev); + if (event) + this->processMotionEvent(event); + processed = true; + } + + enum { + BUTTON1DOWN = 1 << 0, + BUTTON3DOWN = 1 << 1, + CTRLDOWN = 1 << 2, + SHIFTDOWN = 1 << 3, + BUTTON2DOWN = 1 << 4 + }; + unsigned int combo = + (this->button1down ? BUTTON1DOWN : 0) | + (this->button2down ? BUTTON2DOWN : 0) | + (this->button3down ? BUTTON3DOWN : 0) | + (this->ctrldown ? CTRLDOWN : 0) | + (this->shiftdown ? SHIFTDOWN : 0); + + switch (combo) { + case 0: + if (curmode == NavigationStyle::SPINNING) { break; } + newmode = NavigationStyle::IDLE; + break; + case BUTTON1DOWN: + if (newmode != NavigationStyle::DRAGGING) + newmode = NavigationStyle::SELECTION; + break; + case BUTTON2DOWN: + newmode = NavigationStyle::PANNING; + break; + case BUTTON3DOWN: + case SHIFTDOWN|BUTTON2DOWN: + case SHIFTDOWN|BUTTON3DOWN: + newmode = NavigationStyle::ZOOMING; + break; + default: + break; + } + + if (newmode != curmode) { + this->setViewingMode(newmode); + } + + // If not handled in this class, pass on upwards in the inheritance + // hierarchy. + if (!processed) + processed = inherited::processSoEvent(ev); + return processed; +} diff --git a/src/Gui/Quarter/ContextMenu.cpp b/src/Gui/Quarter/ContextMenu.cpp index dc28d9500b..88c3535a5e 100644 --- a/src/Gui/Quarter/ContextMenu.cpp +++ b/src/Gui/Quarter/ContextMenu.cpp @@ -58,9 +58,9 @@ ContextMenu::ContextMenu(QuarterWidget * quarterwidget) SoRenderManager * sorendermanager = quarterwidget->getSoRenderManager(); - QActionGroup * rendermodegroup = NULL; - QActionGroup * stereomodegroup = NULL; - QActionGroup * transparencytypegroup = NULL; + QActionGroup * rendermodegroup = nullptr; + QActionGroup * stereomodegroup = nullptr; + QActionGroup * transparencytypegroup = nullptr; foreach (QAction * action, quarterwidget->renderModeActions()) { if (!rendermodegroup) { @@ -138,7 +138,7 @@ ContextMenu::~ContextMenu() } QMenu * -ContextMenu::getMenu(void) const +ContextMenu::getMenu() const { return this->contextmenu; } diff --git a/src/Gui/Quarter/ContextMenu.h b/src/Gui/Quarter/ContextMenu.h index a9965001e3..13603e194d 100644 --- a/src/Gui/Quarter/ContextMenu.h +++ b/src/Gui/Quarter/ContextMenu.h @@ -48,7 +48,7 @@ public: ContextMenu(QuarterWidget * quarterwidget); ~ContextMenu(); - QMenu * getMenu(void) const; + QMenu * getMenu() const; public Q_SLOTS: void changeRenderMode(QAction * action); diff --git a/src/Gui/Quarter/DragDropHandler.cpp b/src/Gui/Quarter/DragDropHandler.cpp index 55b4d9ee43..796295cde8 100644 --- a/src/Gui/Quarter/DragDropHandler.cpp +++ b/src/Gui/Quarter/DragDropHandler.cpp @@ -50,7 +50,7 @@ #include #include -#include +#include namespace SIM { namespace Coin3D { namespace Quarter { @@ -152,7 +152,7 @@ DragDropHandlerP::dropEvent(QDropEvent * event) // attempt to import it root = SoDB::readAll(&in); - if (root == NULL) return; + if (root == nullptr) return; // set new scenegraph this->quarterwidget->setSceneGraph(root); diff --git a/src/Gui/Quarter/EventFilter.cpp b/src/Gui/Quarter/EventFilter.cpp index 695159ceea..faf056d375 100644 --- a/src/Gui/Quarter/EventFilter.cpp +++ b/src/Gui/Quarter/EventFilter.cpp @@ -177,7 +177,7 @@ EventFilter::eventFilter(QObject * obj, QEvent * qevent) Returns mouse position in global coordinates */ const QPoint & -EventFilter::globalMousePosition(void) const +EventFilter::globalMousePosition() const { return PRIVATE(this)->globalmousepos; } diff --git a/src/Gui/Quarter/ImageReader.cpp b/src/Gui/Quarter/ImageReader.cpp index 9d8336e261..75c121db98 100644 --- a/src/Gui/Quarter/ImageReader.cpp +++ b/src/Gui/Quarter/ImageReader.cpp @@ -39,12 +39,12 @@ using namespace SIM::Coin3D::Quarter; -ImageReader::ImageReader(void) +ImageReader::ImageReader() { SbImage::addReadImageCB(ImageReader::readImageCB, this); } -ImageReader::~ImageReader(void) +ImageReader::~ImageReader() { SbImage::removeReadImageCB(ImageReader::readImageCB, this); } diff --git a/src/Gui/Quarter/ImageReader.h b/src/Gui/Quarter/ImageReader.h index a0da5ad526..9d8ad2c186 100644 --- a/src/Gui/Quarter/ImageReader.h +++ b/src/Gui/Quarter/ImageReader.h @@ -43,8 +43,8 @@ namespace SIM { namespace Coin3D { namespace Quarter { class ImageReader { public: - ImageReader(void); - ~ImageReader(void); + ImageReader(); + ~ImageReader(); SbBool readImage(const SbString & filename, SbImage & image) const; diff --git a/src/Gui/Quarter/InputDevice.cpp b/src/Gui/Quarter/InputDevice.cpp index a29d74d7fa..b2cc008b6a 100644 --- a/src/Gui/Quarter/InputDevice.cpp +++ b/src/Gui/Quarter/InputDevice.cpp @@ -48,7 +48,7 @@ using namespace SIM::Coin3D::Quarter; devices. */ -InputDevice::InputDevice(void) : quarter(nullptr) +InputDevice::InputDevice() : quarter(nullptr) { this->mousepos = SbVec2s(0, 0); } diff --git a/src/Gui/Quarter/InteractionMode.cpp b/src/Gui/Quarter/InteractionMode.cpp index d9a0375bcd..f7a577261f 100644 --- a/src/Gui/Quarter/InteractionMode.cpp +++ b/src/Gui/Quarter/InteractionMode.cpp @@ -34,7 +34,7 @@ InteractionMode::setEnabled(bool yes) } bool -InteractionMode::enabled(void) const +InteractionMode::enabled() const { return this->isenabled; } @@ -62,7 +62,7 @@ InteractionMode::setOn(bool on) } bool -InteractionMode::on(void) const +InteractionMode::on() const { return this->altkeydown; } diff --git a/src/Gui/Quarter/InteractionMode.h b/src/Gui/Quarter/InteractionMode.h index 0397b3290f..d2562844b8 100644 --- a/src/Gui/Quarter/InteractionMode.h +++ b/src/Gui/Quarter/InteractionMode.h @@ -54,10 +54,10 @@ public: virtual ~InteractionMode(); void setEnabled(bool yes); - bool enabled(void) const; + bool enabled() const; void setOn(bool on); - bool on(void) const; + bool on() const; protected: virtual bool eventFilter(QObject *, QEvent * event); diff --git a/src/Gui/Quarter/Keyboard.cpp b/src/Gui/Quarter/Keyboard.cpp index b7fbf9f028..e16e4a62f3 100644 --- a/src/Gui/Quarter/Keyboard.cpp +++ b/src/Gui/Quarter/Keyboard.cpp @@ -55,7 +55,7 @@ using namespace SIM::Coin3D::Quarter; #define PRIVATE(obj) obj->pimpl -Keyboard::Keyboard(void) +Keyboard::Keyboard() { PRIVATE(this) = new KeyboardP(this); } @@ -81,7 +81,7 @@ Keyboard::translateEvent(QEvent * event) case QEvent::KeyRelease: return PRIVATE(this)->keyEvent((QKeyEvent *) event); default: - return NULL; + return nullptr; } } diff --git a/src/Gui/Quarter/KeyboardP.cpp b/src/Gui/Quarter/KeyboardP.cpp index 6854ffae7d..1cbc87907c 100644 --- a/src/Gui/Quarter/KeyboardP.cpp +++ b/src/Gui/Quarter/KeyboardP.cpp @@ -44,7 +44,7 @@ KeyboardP::KeyboardP(Keyboard * publ) PUBLIC(this) = publ; this->keyboard = new SoKeyboardEvent; - if (keyboardmap == NULL) { + if (keyboardmap == nullptr) { keyboardmap = new KeyMap; keypadmap = new KeyMap; this->initKeyMap(); @@ -57,7 +57,7 @@ KeyboardP::~KeyboardP() } bool -KeyboardP::debugKeyEvents(void) +KeyboardP::debugKeyEvents() { const char * env = coin_getenv("QUARTER_DEBUG_KEYEVENTS"); return env && (atoi(env) > 0); @@ -103,11 +103,11 @@ KeyboardP::keyEvent(QKeyEvent * qevent) return this->keyboard; } -KeyboardP::KeyMap * KeyboardP::keyboardmap = NULL; -KeyboardP::KeyMap * KeyboardP::keypadmap = NULL; +KeyboardP::KeyMap * KeyboardP::keyboardmap = nullptr; +KeyboardP::KeyMap * KeyboardP::keypadmap = nullptr; void -KeyboardP::initKeyMap(void) +KeyboardP::initKeyMap() { // keyboard keyboardmap->insert(Qt::Key_Shift, SoKeyboardEvent::LEFT_SHIFT); diff --git a/src/Gui/Quarter/KeyboardP.h b/src/Gui/Quarter/KeyboardP.h index e5dfe6a998..fb0e5d2cbd 100644 --- a/src/Gui/Quarter/KeyboardP.h +++ b/src/Gui/Quarter/KeyboardP.h @@ -49,8 +49,8 @@ public: ~KeyboardP(); const SoEvent * keyEvent(QKeyEvent * event); - void initKeyMap(void); - static bool debugKeyEvents(void); + void initKeyMap(); + static bool debugKeyEvents(); typedef QMap KeyMap; static KeyMap * keyboardmap; diff --git a/src/Gui/Quarter/Mouse.cpp b/src/Gui/Quarter/Mouse.cpp index 6667dee6bd..fa182955fc 100644 --- a/src/Gui/Quarter/Mouse.cpp +++ b/src/Gui/Quarter/Mouse.cpp @@ -95,7 +95,7 @@ using namespace SIM::Coin3D::Quarter; #define PRIVATE(obj) obj->pimpl #define PUBLIC(obj) obj->publ -Mouse::Mouse(void) +Mouse::Mouse() { PRIVATE(this) = new MouseP(this); } @@ -131,9 +131,9 @@ Mouse::translateEvent(QEvent * event) return PRIVATE(this)->mouseWheelEvent((QWheelEvent *) event); case QEvent::Resize: PRIVATE(this)->resizeEvent((QResizeEvent *) event); - return NULL; + return nullptr; default: - return NULL; + return nullptr; } } diff --git a/src/Gui/Quarter/NativeEvent.cpp b/src/Gui/Quarter/NativeEvent.cpp index def9b09c02..790f21cd68 100644 --- a/src/Gui/Quarter/NativeEvent.cpp +++ b/src/Gui/Quarter/NativeEvent.cpp @@ -55,7 +55,7 @@ NativeEvent::getEvent() const NativeEvent::NativeEvent() : QEvent(QEvent::User) { - this->rawevent = NULL; + this->rawevent = nullptr; } #endif // !HAVE_SPACENAV_LIB diff --git a/src/Gui/Quarter/QtCoinCompatibility.cpp b/src/Gui/Quarter/QtCoinCompatibility.cpp index 3ffd38cf81..c62431a462 100644 --- a/src/Gui/Quarter/QtCoinCompatibility.cpp +++ b/src/Gui/Quarter/QtCoinCompatibility.cpp @@ -22,7 +22,7 @@ QtCoinCompatibility::QImageToSbImage(const QImage & image, SbImage & sbimage) } SbVec2s size((short) w, (short) h); - sbimage.setValue(size, c, NULL); + sbimage.setValue(size, c, nullptr); unsigned char * buffer = sbimage.getValue(size, c); if (c == 1) { diff --git a/src/Gui/Quarter/Quarter.cpp b/src/Gui/Quarter/Quarter.cpp index 5d48a0f64a..976b02e478 100644 --- a/src/Gui/Quarter/Quarter.cpp +++ b/src/Gui/Quarter/Quarter.cpp @@ -152,7 +152,7 @@ using namespace SIM::Coin3D::Quarter; -static QuarterP * self = NULL; +static QuarterP * self = nullptr; /*! initialize Quarter, and implicitly Coin @@ -182,14 +182,14 @@ Quarter::init(bool initCoin) clean up resources */ void -Quarter::clean(void) +Quarter::clean() { COMPILE_ONLY_BEFORE(2,0,0,"Should not be encapsulated in double Quarter namespace"); assert(self); bool initCoin = self->initCoin; delete self; - self = NULL; + self = nullptr; if (initCoin) { // SoDB::finish() will clean up everything that has been diff --git a/src/Gui/Quarter/Quarter.h b/src/Gui/Quarter/Quarter.h index 1853b5118e..22e538e7c2 100644 --- a/src/Gui/Quarter/Quarter.h +++ b/src/Gui/Quarter/Quarter.h @@ -39,7 +39,7 @@ namespace SIM { namespace Coin3D { namespace Quarter { namespace Quarter { void QUARTER_DLL_API init(bool initCoin = true); - void QUARTER_DLL_API clean(void); + void QUARTER_DLL_API clean(); void QUARTER_DLL_API setTimerEpsilon(double sec); } diff --git a/src/Gui/Quarter/QuarterP.cpp b/src/Gui/Quarter/QuarterP.cpp index fbfd30e380..f227527065 100644 --- a/src/Gui/Quarter/QuarterP.cpp +++ b/src/Gui/Quarter/QuarterP.cpp @@ -4,13 +4,13 @@ #include "KeyboardP.h" using namespace SIM::Coin3D::Quarter; -QuarterP::StateCursorMap * QuarterP::statecursormap = NULL; +QuarterP::StateCursorMap * QuarterP::statecursormap = nullptr; -QuarterP::QuarterP(void) +QuarterP::QuarterP() { this->sensormanager = new SensorManager; this->imagereader = new ImageReader; - assert(QuarterP::statecursormap == NULL); + assert(QuarterP::statecursormap == nullptr); QuarterP::statecursormap = new StateCursorMap; } @@ -20,17 +20,17 @@ QuarterP::~QuarterP() delete this->imagereader; delete this->sensormanager; - assert(QuarterP::statecursormap != NULL); + assert(QuarterP::statecursormap != nullptr); delete QuarterP::statecursormap; // FIXME: Why not use an atexit mechanism for this? - if (KeyboardP::keyboardmap != NULL) { + if (KeyboardP::keyboardmap != nullptr) { KeyboardP::keyboardmap->clear(); KeyboardP::keypadmap->clear(); delete KeyboardP::keyboardmap; delete KeyboardP::keypadmap; - KeyboardP::keyboardmap = NULL; - KeyboardP::keypadmap = NULL; + KeyboardP::keyboardmap = nullptr; + KeyboardP::keypadmap = nullptr; } diff --git a/src/Gui/Quarter/QuarterWidget.cpp b/src/Gui/Quarter/QuarterWidget.cpp index e7034760b9..b3376d851c 100644 --- a/src/Gui/Quarter/QuarterWidget.cpp +++ b/src/Gui/Quarter/QuarterWidget.cpp @@ -52,7 +52,7 @@ #pragma warning(disable : 4267) #endif -#include +#include #include #include @@ -152,7 +152,7 @@ class CustomGLWidget : public QOpenGLWidget { public: QSurfaceFormat myFormat; - CustomGLWidget(const QSurfaceFormat& format, QWidget* parent = 0, const QOpenGLWidget* shareWidget = 0, Qt::WindowFlags f = Qt::WindowFlags()) + CustomGLWidget(const QSurfaceFormat& format, QWidget* parent = nullptr, const QOpenGLWidget* shareWidget = nullptr, Qt::WindowFlags f = Qt::WindowFlags()) : QOpenGLWidget(parent, f), myFormat(format) { Q_UNUSED(shareWidget); @@ -308,7 +308,7 @@ QuarterWidget::constructor(const QtGLFormat & format, const QtGLWidget * sharewi PRIVATE(this)->eventfilter = new EventFilter(this); PRIVATE(this)->interactionmode = new InteractionMode(this); - PRIVATE(this)->currentStateMachine = NULL; + PRIVATE(this)->currentStateMachine = nullptr; PRIVATE(this)->headlight = new SoDirectionalLight; PRIVATE(this)->headlight->ref(); @@ -364,10 +364,10 @@ QuarterWidget::~QuarterWidget() delete PRIVATE(this)->currentStateMachine; } PRIVATE(this)->headlight->unref(); - PRIVATE(this)->headlight = NULL; - this->setSceneGraph(NULL); - this->setSoRenderManager(NULL); - this->setSoEventManager(NULL); + PRIVATE(this)->headlight = nullptr; + this->setSceneGraph(nullptr); + this->setSoRenderManager(nullptr); + this->setSoEventManager(nullptr); delete PRIVATE(this)->eventfilter; delete PRIVATE(this); } @@ -418,7 +418,7 @@ QuarterWidget::setHeadlightEnabled(bool onoff) Returns true if the headlight is on, false if it is off */ bool -QuarterWidget::headlightEnabled(void) const +QuarterWidget::headlightEnabled() const { return PRIVATE(this)->headlight->on.getValue(); } @@ -427,7 +427,7 @@ QuarterWidget::headlightEnabled(void) const Returns the light used for the headlight. */ SoDirectionalLight * -QuarterWidget::getHeadlight(void) const +QuarterWidget::getHeadlight() const { return PRIVATE(this)->headlight; } @@ -452,7 +452,7 @@ QuarterWidget::setClearZBuffer(bool onoff) Returns true if the z buffer is cleared before rendering. */ bool -QuarterWidget::clearZBuffer(void) const +QuarterWidget::clearZBuffer() const { return PRIVATE(this)->clearzbuffer; } @@ -477,7 +477,7 @@ QuarterWidget::setClearWindow(bool onoff) Returns true if the rendering buffer is cleared before rendering. */ bool -QuarterWidget::clearWindow(void) const +QuarterWidget::clearWindow() const { return PRIVATE(this)->clearwindow; } @@ -503,7 +503,7 @@ QuarterWidget::setInteractionModeEnabled(bool onoff) Returns true if interaction mode is enabled, false otherwise. */ bool -QuarterWidget::interactionModeEnabled(void) const +QuarterWidget::interactionModeEnabled() const { return PRIVATE(this)->interactionmode->enabled(); } @@ -527,7 +527,7 @@ QuarterWidget::setInteractionModeOn(bool onoff) Returns true if interaction mode is on. */ bool -QuarterWidget::interactionModeOn(void) const +QuarterWidget::interactionModeOn() const { return PRIVATE(this)->interactionmode->on(); } @@ -536,7 +536,7 @@ QuarterWidget::interactionModeOn(void) const Returns the Coin cache context id for this widget. */ uint32_t -QuarterWidget::getCacheContextId(void) const +QuarterWidget::getCacheContextId() const { return PRIVATE(this)->getCacheContextId(); } @@ -562,7 +562,7 @@ QuarterWidget::setTransparencyType(TransparencyType type) \retval The current \ref TransparencyType */ QuarterWidget::TransparencyType -QuarterWidget::transparencyType(void) const +QuarterWidget::transparencyType() const { assert(PRIVATE(this)->sorendermanager); SoGLRenderAction * action = PRIVATE(this)->sorendermanager->getGLRenderAction(); @@ -590,7 +590,7 @@ QuarterWidget::setRenderMode(RenderMode mode) \retval The current \ref RenderMode */ QuarterWidget::RenderMode -QuarterWidget::renderMode(void) const +QuarterWidget::renderMode() const { assert(PRIVATE(this)->sorendermanager); return static_cast(PRIVATE(this)->sorendermanager->getRenderMode()); @@ -618,7 +618,7 @@ QuarterWidget::setStereoMode(StereoMode mode) \retval The current \ref StereoMode */ QuarterWidget::StereoMode -QuarterWidget::stereoMode(void) const +QuarterWidget::stereoMode() const { assert(PRIVATE(this)->sorendermanager); return static_cast(PRIVATE(this)->sorendermanager->getStereoMode()); @@ -636,7 +636,7 @@ the widget is located within, and updated whenever any change occurs, emitting a */ qreal -QuarterWidget::devicePixelRatio(void) const +QuarterWidget::devicePixelRatio() const { return PRIVATE(this)->device_pixel_ratio; } @@ -653,11 +653,11 @@ QuarterWidget::setSceneGraph(SoNode * node) if (PRIVATE(this)->scene) { PRIVATE(this)->scene->unref(); - PRIVATE(this)->scene = NULL; + PRIVATE(this)->scene = nullptr; } - SoCamera * camera = NULL; - SoSeparator * superscene = NULL; + SoCamera * camera = nullptr; + SoSeparator * superscene = nullptr; bool viewall = false; if (node) { @@ -690,7 +690,7 @@ QuarterWidget::setSceneGraph(SoNode * node) Returns pointer to root of scene graph */ SoNode * -QuarterWidget::getSceneGraph(void) const +QuarterWidget::getSceneGraph() const { return PRIVATE(this)->scene; } @@ -702,10 +702,10 @@ void QuarterWidget::setSoRenderManager(SoRenderManager * manager) { bool carrydata = false; - SoNode * scene = NULL; - SoCamera * camera = NULL; + SoNode * scene = nullptr; + SoCamera * camera = nullptr; SbViewportRegion vp; - if (PRIVATE(this)->sorendermanager && (manager != NULL)) { + if (PRIVATE(this)->sorendermanager && (manager != nullptr)) { scene = PRIVATE(this)->sorendermanager->getSceneGraph(); camera = PRIVATE(this)->sorendermanager->getCamera(); vp = PRIVATE(this)->sorendermanager->getViewportRegion(); @@ -735,7 +735,7 @@ QuarterWidget::setSoRenderManager(SoRenderManager * manager) Returns a pointer to the render manager. */ SoRenderManager * -QuarterWidget::getSoRenderManager(void) const +QuarterWidget::getSoRenderManager() const { return PRIVATE(this)->sorendermanager; } @@ -747,10 +747,10 @@ void QuarterWidget::setSoEventManager(SoEventManager * manager) { bool carrydata = false; - SoNode * scene = NULL; - SoCamera * camera = NULL; + SoNode * scene = nullptr; + SoCamera * camera = nullptr; SbViewportRegion vp; - if (PRIVATE(this)->soeventmanager && (manager != NULL)) { + if (PRIVATE(this)->soeventmanager && (manager != nullptr)) { scene = PRIVATE(this)->soeventmanager->getSceneGraph(); camera = PRIVATE(this)->soeventmanager->getCamera(); vp = PRIVATE(this)->soeventmanager->getViewportRegion(); @@ -780,7 +780,7 @@ QuarterWidget::setSoEventManager(SoEventManager * manager) Returns a pointer to the event manager */ SoEventManager * -QuarterWidget::getSoEventManager(void) const +QuarterWidget::getSoEventManager() const { return PRIVATE(this)->soeventmanager; } @@ -789,7 +789,7 @@ QuarterWidget::getSoEventManager(void) const Returns a pointer to the event filter */ EventFilter * -QuarterWidget::getEventFilter(void) const +QuarterWidget::getEventFilter() const { return PRIVATE(this)->eventfilter; } @@ -798,7 +798,7 @@ QuarterWidget::getEventFilter(void) const Reposition the current camera to display the entire scene */ void -QuarterWidget::viewAll(void) +QuarterWidget::viewAll() { const SbName viewallevent("sim.coin3d.coin.navigation.ViewAll"); for (int c = 0; c < PRIVATE(this)->soeventmanager->getNumSoScXMLStateMachines(); ++c) { @@ -816,7 +816,7 @@ QuarterWidget::viewAll(void) Camera typically seeks towards what the mouse is pointing at. */ void -QuarterWidget::seek(void) +QuarterWidget::seek() { const SbName seekevent("sim.coin3d.coin.navigation.Seek"); for (int c = 0; c < PRIVATE(this)->soeventmanager->getNumSoScXMLStateMachines(); ++c) { @@ -830,10 +830,10 @@ QuarterWidget::seek(void) } bool -QuarterWidget::updateDevicePixelRatio(void) { +QuarterWidget::updateDevicePixelRatio() { qreal dev_pix_ratio = 1.0; QWidget* winwidg = window(); - QWindow* win = NULL; + QWindow* win = nullptr; if(winwidg) { win = winwidg->windowHandle(); } @@ -1023,7 +1023,7 @@ bool QuarterWidget::viewportEvent(QEvent* event) render manager and render the scene by calling this method. */ void -QuarterWidget::redraw(void) +QuarterWidget::redraw() { // we're triggering the next paintGL(). Set a flag to remember this // to avoid that we process the delay queue in paintGL() @@ -1050,7 +1050,7 @@ QuarterWidget::redraw(void) Overridden from QGLWidget to render the scenegraph */ void -QuarterWidget::actualRedraw(void) +QuarterWidget::actualRedraw() { PRIVATE(this)->sorendermanager->render(PRIVATE(this)->clearwindow, PRIVATE(this)->clearzbuffer); @@ -1102,7 +1102,7 @@ QuarterWidget::setBackgroundColor(const QColor & color) rendering the scene. */ QColor -QuarterWidget::backgroundColor(void) const +QuarterWidget::backgroundColor() const { SbColor4f bg = PRIVATE(this)->sorendermanager->getBackgroundColor(); @@ -1116,7 +1116,7 @@ QuarterWidget::backgroundColor(void) const Returns the context menu used by the widget. */ QMenu * -QuarterWidget::getContextMenu(void) const +QuarterWidget::getContextMenu() const { return PRIVATE(this)->contextMenu(); } @@ -1125,7 +1125,7 @@ QuarterWidget::getContextMenu(void) const \retval Is context menu enabled? */ bool -QuarterWidget::contextMenuEnabled(void) const +QuarterWidget::contextMenuEnabled() const { return PRIVATE(this)->contextmenuenabled; } @@ -1175,8 +1175,8 @@ void QuarterWidget::removeStateMachine(SoScXMLStateMachine * statemachine) { SoEventManager * em = this->getSoEventManager(); - statemachine->setSceneGraphRoot(NULL); - statemachine->setActiveCamera(NULL); + statemachine->setSceneGraphRoot(nullptr); + statemachine->setActiveCamera(nullptr); em->removeSoScXMLStateMachine(statemachine); } @@ -1184,7 +1184,7 @@ QuarterWidget::removeStateMachine(SoScXMLStateMachine * statemachine) See \ref QWidget::minimumSizeHint */ QSize -QuarterWidget::minimumSizeHint(void) const +QuarterWidget::minimumSizeHint() const { return QSize(50, 50); } @@ -1195,7 +1195,7 @@ QuarterWidget::minimumSizeHint(void) const QuarterWidget, add these actions to the menu. */ QList -QuarterWidget::transparencyTypeActions(void) const +QuarterWidget::transparencyTypeActions() const { return PRIVATE(this)->transparencyTypeActions(); } @@ -1206,7 +1206,7 @@ QuarterWidget::transparencyTypeActions(void) const QuarterWidget, add these actions to the menu. */ QList -QuarterWidget::stereoModeActions(void) const +QuarterWidget::stereoModeActions() const { return PRIVATE(this)->stereoModeActions(); } @@ -1217,7 +1217,7 @@ QuarterWidget::stereoModeActions(void) const QuarterWidget, add these actions to the menu. */ QList -QuarterWidget::renderModeActions(void) const +QuarterWidget::renderModeActions() const { return PRIVATE(this)->renderModeActions(); } @@ -1239,7 +1239,7 @@ QuarterWidget::renderModeActions(void) const Removes any navigationModeFile set. */ void -QuarterWidget::resetNavigationModeFile(void) { +QuarterWidget::resetNavigationModeFile() { this->setNavigationModeFile(QUrl()); } @@ -1276,7 +1276,7 @@ QuarterWidget::setNavigationModeFile(const QUrl & url) if (PRIVATE(this)->currentStateMachine) { this->removeStateMachine(PRIVATE(this)->currentStateMachine); delete PRIVATE(this)->currentStateMachine; - PRIVATE(this)->currentStateMachine = NULL; + PRIVATE(this)->currentStateMachine = nullptr; PRIVATE(this)->navigationModeFile = url; } return; @@ -1287,7 +1287,7 @@ QuarterWidget::setNavigationModeFile(const QUrl & url) } QByteArray filenametmp = filename.toLocal8Bit(); - ScXMLStateMachine * stateMachine = NULL; + ScXMLStateMachine * stateMachine = nullptr; if (filenametmp.startsWith("coin:")){ stateMachine = ScXML::readFile(filenametmp.data()); @@ -1350,7 +1350,7 @@ QuarterWidget::setNavigationModeFile(const QUrl & url) \retval The current navigationModeFile */ const QUrl & -QuarterWidget::navigationModeFile(void) const +QuarterWidget::navigationModeFile() const { return PRIVATE(this)->navigationModeFile; } diff --git a/src/Gui/Quarter/QuarterWidget.h b/src/Gui/Quarter/QuarterWidget.h index d69bf2c5b6..80e781b613 100644 --- a/src/Gui/Quarter/QuarterWidget.h +++ b/src/Gui/Quarter/QuarterWidget.h @@ -81,9 +81,9 @@ class QUARTER_DLL_API QuarterWidget : public QGraphicsView { public: - explicit QuarterWidget(QWidget * parent = 0, const QtGLWidget * sharewidget = 0, Qt::WindowFlags f = Qt::WindowFlags()); - explicit QuarterWidget(QtGLContext * context, QWidget * parent = 0, const QtGLWidget * sharewidget = 0, Qt::WindowFlags f = Qt::WindowFlags()); - explicit QuarterWidget(const QtGLFormat & format, QWidget * parent = 0, const QtGLWidget * shareWidget = 0, Qt::WindowFlags f = Qt::WindowFlags()); + explicit QuarterWidget(QWidget * parent = nullptr, const QtGLWidget * sharewidget = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + explicit QuarterWidget(QtGLContext * context, QWidget * parent = nullptr, const QtGLWidget * sharewidget = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + explicit QuarterWidget(const QtGLFormat & format, QWidget * parent = nullptr, const QtGLWidget * shareWidget = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); virtual ~QuarterWidget(); enum TransparencyType { @@ -117,70 +117,70 @@ public: INTERLEAVED_COLUMNS = SoRenderManager::INTERLEAVED_COLUMNS }; - TransparencyType transparencyType(void) const; - RenderMode renderMode(void) const; - StereoMode stereoMode(void) const; + TransparencyType transparencyType() const; + RenderMode renderMode() const; + StereoMode stereoMode() const; void setBackgroundColor(const QColor & color); - QColor backgroundColor(void) const; + QColor backgroundColor() const; - qreal devicePixelRatio(void) const; + qreal devicePixelRatio() const; - void resetNavigationModeFile(void); + void resetNavigationModeFile(); void setNavigationModeFile(const QUrl & url = QUrl(QString::fromLatin1(DEFAULT_NAVIGATIONFILE))); - const QUrl & navigationModeFile(void) const; + const QUrl & navigationModeFile() const; void setContextMenuEnabled(bool yes); - bool contextMenuEnabled(void) const; - QMenu * getContextMenu(void) const; + bool contextMenuEnabled() const; + QMenu * getContextMenu() const; - bool headlightEnabled(void) const; + bool headlightEnabled() const; void setHeadlightEnabled(bool onoff); - SoDirectionalLight * getHeadlight(void) const; + SoDirectionalLight * getHeadlight() const; - bool clearZBuffer(void) const; + bool clearZBuffer() const; void setClearZBuffer(bool onoff); - bool clearWindow(void) const; + bool clearWindow() const; void setClearWindow(bool onoff); - bool interactionModeEnabled(void) const; + bool interactionModeEnabled() const; void setInteractionModeEnabled(bool onoff); - bool interactionModeOn(void) const; + bool interactionModeOn() const; void setInteractionModeOn(bool onoff); void setStateCursor(const SbName & state, const QCursor & cursor); QCursor stateCursor(const SbName & state); - uint32_t getCacheContextId(void) const; + uint32_t getCacheContextId() const; virtual void setSceneGraph(SoNode * root); - virtual SoNode * getSceneGraph(void) const; + virtual SoNode * getSceneGraph() const; void setSoEventManager(SoEventManager * manager); - SoEventManager * getSoEventManager(void) const; + SoEventManager * getSoEventManager() const; void setSoRenderManager(SoRenderManager * manager); - SoRenderManager * getSoRenderManager(void) const; + SoRenderManager * getSoRenderManager() const; - EventFilter * getEventFilter(void) const; + EventFilter * getEventFilter() const; void addStateMachine(SoScXMLStateMachine * statemachine); void removeStateMachine(SoScXMLStateMachine * statemachine); virtual bool processSoEvent(const SoEvent * event); - virtual QSize minimumSizeHint(void) const; + virtual QSize minimumSizeHint() const; - QList transparencyTypeActions(void) const; - QList stereoModeActions(void) const; - QList renderModeActions(void) const; + QList transparencyTypeActions() const; + QList stereoModeActions() const; + QList renderModeActions() const; public Q_SLOTS: - virtual void viewAll(void); - virtual void seek(void); + virtual void viewAll(); + virtual void seek(); - void redraw(void); + void redraw(); void setRenderMode(RenderMode mode); void setStereoMode(StereoMode mode); @@ -197,8 +197,8 @@ protected: virtual void paintEvent(QPaintEvent*); virtual void resizeEvent(QResizeEvent*); virtual bool viewportEvent(QEvent* event); - virtual void actualRedraw(void); - virtual bool updateDevicePixelRatio(void); + virtual void actualRedraw(); + virtual bool updateDevicePixelRatio(); private: void constructor(const QtGLFormat& format, const QtGLWidget* sharewidget); diff --git a/src/Gui/Quarter/QuarterWidgetP.cpp b/src/Gui/Quarter/QuarterWidgetP.cpp index 4cba6614b3..8c5a84c84f 100644 --- a/src/Gui/Quarter/QuarterWidgetP.cpp +++ b/src/Gui/Quarter/QuarterWidgetP.cpp @@ -57,7 +57,7 @@ #include "ContextMenu.h" #include "QuarterP.h" -#include +#include using namespace SIM::Coin3D::Quarter; @@ -67,19 +67,19 @@ public: SbList widgetlist; }; -static SbList * cachecontext_list = NULL; +static SbList * cachecontext_list = nullptr; QuarterWidgetP::QuarterWidgetP(QuarterWidget * masterptr, const QtGLWidget * sharewidget) : master(masterptr), - scene(NULL), - eventfilter(NULL), - interactionmode(NULL), - sorendermanager(NULL), - soeventmanager(NULL), + scene(nullptr), + eventfilter(nullptr), + interactionmode(nullptr), + sorendermanager(nullptr), + soeventmanager(nullptr), initialsorendermanager(false), initialsoeventmanager(false), - headlight(NULL), - cachecontext(NULL), + headlight(nullptr), + cachecontext(nullptr), contextmenuenabled(true), autoredrawenabled(true), interactionmodeenabled(false), @@ -87,7 +87,7 @@ QuarterWidgetP::QuarterWidgetP(QuarterWidget * masterptr, const QtGLWidget * sha clearwindow(true), addactions(true), device_pixel_ratio(1.0), - contextmenu(NULL) + contextmenu(nullptr) { this->cachecontext = findCacheContext(masterptr, sharewidget); @@ -121,11 +121,11 @@ QuarterWidgetP::searchForCamera(SoNode * root) return (SoCamera *) node; } } - return NULL; + return nullptr; } uint32_t -QuarterWidgetP::getCacheContextId(void) const +QuarterWidgetP::getCacheContextId() const { return this->cachecontext->id; } @@ -133,7 +133,7 @@ QuarterWidgetP::getCacheContextId(void) const QuarterWidgetP_cachecontext * QuarterWidgetP::findCacheContext(QuarterWidget * widget, const QtGLWidget * sharewidget) { - if (cachecontext_list == NULL) { + if (cachecontext_list == nullptr) { // FIXME: static memory leak cachecontext_list = new SbList ; } @@ -257,7 +257,7 @@ QuarterWidgetP::statechangecb(void * userdata, ScXMLStateMachine * statemachine, QList -QuarterWidgetP::transparencyTypeActions(void) const +QuarterWidgetP::transparencyTypeActions() const { if (this->transparencytypeactions.isEmpty()) { this->transparencytypegroup = new QActionGroup(this->master); @@ -277,7 +277,7 @@ QuarterWidgetP::transparencyTypeActions(void) const } QList -QuarterWidgetP::stereoModeActions(void) const +QuarterWidgetP::stereoModeActions() const { if (this->stereomodeactions.isEmpty()) { this->stereomodegroup = new QActionGroup(this->master); @@ -291,7 +291,7 @@ QuarterWidgetP::stereoModeActions(void) const } QList -QuarterWidgetP::renderModeActions(void) const +QuarterWidgetP::renderModeActions() const { if (this->rendermodeactions.isEmpty()) { this->rendermodegroup = new QActionGroup(this->master); @@ -308,7 +308,7 @@ QuarterWidgetP::renderModeActions(void) const #undef ADD_ACTION QMenu * -QuarterWidgetP::contextMenu(void) +QuarterWidgetP::contextMenu() { if (!this->contextmenu) { this->contextmenu = new ContextMenu(this->master); diff --git a/src/Gui/Quarter/QuarterWidgetP.h b/src/Gui/Quarter/QuarterWidgetP.h index 7b5c99dc78..d9373961cb 100644 --- a/src/Gui/Quarter/QuarterWidgetP.h +++ b/src/Gui/Quarter/QuarterWidgetP.h @@ -66,12 +66,12 @@ public: ~QuarterWidgetP(); SoCamera * searchForCamera(SoNode * root); - uint32_t getCacheContextId(void) const; - QMenu * contextMenu(void); + uint32_t getCacheContextId() const; + QMenu * contextMenu(); - QList transparencyTypeActions(void) const; - QList renderModeActions(void) const; - QList stereoModeActions(void) const; + QList transparencyTypeActions() const; + QList renderModeActions() const; + QList stereoModeActions() const; QuarterWidget * const master; SoNode * scene; diff --git a/src/Gui/Quarter/SensorManager.cpp b/src/Gui/Quarter/SensorManager.cpp index 59d2d13b55..5d2ebf115f 100644 --- a/src/Gui/Quarter/SensorManager.cpp +++ b/src/Gui/Quarter/SensorManager.cpp @@ -43,7 +43,7 @@ using namespace SIM::Coin3D::Quarter; -SensorManager::SensorManager(void) +SensorManager::SensorManager() : inherited() { this->mainthreadid = cc_thread_id(); @@ -74,7 +74,7 @@ SensorManager::SensorManager(void) SensorManager::~SensorManager() { // remove the Coin callback before shutting down - SoDB::getSensorManager()->setChangedCallback(NULL, NULL); + SoDB::getSensorManager()->setChangedCallback(nullptr, nullptr); if (this->signalthread->isRunning()) { this->signalthread->stopThread(); @@ -104,7 +104,7 @@ SensorManager::sensorQueueChangedCB(void * closure) } void -SensorManager::sensorQueueChanged(void) +SensorManager::sensorQueueChanged() { SoSensorManager * sensormanager = SoDB::getSensorManager(); assert(sensormanager); @@ -144,7 +144,7 @@ SensorManager::sensorQueueChanged(void) } void -SensorManager::idleTimeout(void) +SensorManager::idleTimeout() { SoDB::getSensorManager()->processTimerQueue(); SoDB::getSensorManager()->processDelayQueue(true); @@ -152,14 +152,14 @@ SensorManager::idleTimeout(void) } void -SensorManager::timerQueueTimeout(void) +SensorManager::timerQueueTimeout() { SoDB::getSensorManager()->processTimerQueue(); this->sensorQueueChanged(); } void -SensorManager::delayTimeout(void) +SensorManager::delayTimeout() { SoDB::getSensorManager()->processTimerQueue(); SoDB::getSensorManager()->processDelayQueue(false); diff --git a/src/Gui/Quarter/SensorManager.h b/src/Gui/Quarter/SensorManager.h index 6344cf93e6..6628ba9e3a 100644 --- a/src/Gui/Quarter/SensorManager.h +++ b/src/Gui/Quarter/SensorManager.h @@ -45,14 +45,14 @@ class SensorManager : public QObject { Q_OBJECT typedef QObject inherited; public: - SensorManager(void); + SensorManager(); ~SensorManager(); public Q_SLOTS: - void idleTimeout(void); - void delayTimeout(void); - void timerQueueTimeout(void); - void sensorQueueChanged(void); + void idleTimeout(); + void delayTimeout(); + void timerQueueTimeout(); + void sensorQueueChanged(); void setTimerEpsilon(double sec); private: diff --git a/src/Gui/Quarter/SignalThread.cpp b/src/Gui/Quarter/SignalThread.cpp index 6d18f8636d..a242286258 100644 --- a/src/Gui/Quarter/SignalThread.cpp +++ b/src/Gui/Quarter/SignalThread.cpp @@ -36,7 +36,7 @@ using namespace SIM::Coin3D::Quarter; -SignalThread::SignalThread(void) +SignalThread::SignalThread() : isstopped(false) { } @@ -46,7 +46,7 @@ SignalThread::~SignalThread() } void -SignalThread::trigger(void) +SignalThread::trigger() { // lock first to make sure the QThread is actually waiting for a signal QMutexLocker ml(&this->mutex); @@ -54,7 +54,7 @@ SignalThread::trigger(void) } void -SignalThread::stopThread(void) +SignalThread::stopThread() { QMutexLocker ml(&this->mutex); this->isstopped = true; @@ -63,7 +63,7 @@ SignalThread::stopThread(void) void -SignalThread::run(void) +SignalThread::run() { QMutexLocker ml(&this->mutex); while (!this->isstopped) { diff --git a/src/Gui/Quarter/SignalThread.h b/src/Gui/Quarter/SignalThread.h index e96117ae0c..718383991b 100644 --- a/src/Gui/Quarter/SignalThread.h +++ b/src/Gui/Quarter/SignalThread.h @@ -44,16 +44,16 @@ namespace SIM { namespace Coin3D { namespace Quarter { class SignalThread : public QThread { Q_OBJECT public: - SignalThread(void); + SignalThread(); virtual ~SignalThread(); - virtual void run(void); - void trigger(void); - void stopThread(void); + virtual void run(); + void trigger(); + void stopThread(); Q_SIGNALS: - void triggerSignal(void); + void triggerSignal(); private: QWaitCondition waitcond; diff --git a/src/Gui/Quarter/SoQTQuarterAdaptor.cpp b/src/Gui/Quarter/SoQTQuarterAdaptor.cpp index 552b055ebe..9d787b3694 100644 --- a/src/Gui/Quarter/SoQTQuarterAdaptor.cpp +++ b/src/Gui/Quarter/SoQTQuarterAdaptor.cpp @@ -168,7 +168,7 @@ void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::init() m_seekdistanceabs = false; m_seekperiod = 2.0f; m_inseekmode = false; - m_storedcamera = 0; + m_storedcamera = nullptr; m_viewingflag = false; pickRadius = 5.0; @@ -298,12 +298,12 @@ void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::convertPerspective2Ortho(const So out->height = 2.0f * focaldist * (float)tan(in->heightAngle.getValue() / 2.0); } -SoCamera* SIM::Coin3D::Quarter::SoQTQuarterAdaptor::getCamera(void) const +SoCamera* SIM::Coin3D::Quarter::SoQTQuarterAdaptor::getCamera() const { return getSoRenderManager()->getCamera(); } -const SbViewportRegion & SIM::Coin3D::Quarter::SoQTQuarterAdaptor::getViewportRegion(void) const +const SbViewportRegion & SIM::Coin3D::Quarter::SoQTQuarterAdaptor::getViewportRegion() const { return getSoRenderManager()->getViewportRegion(); } @@ -318,17 +318,17 @@ void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::setViewing(SbBool enable) if(m_viewingflag) { SoGLRenderAction* action = getSoRenderManager()->getGLRenderAction(); - if(action != NULL) + if(action != nullptr) SoLocateHighlight::turnOffCurrentHighlight(action); } } -SbBool SIM::Coin3D::Quarter::SoQTQuarterAdaptor::isViewing(void) const +SbBool SIM::Coin3D::Quarter::SoQTQuarterAdaptor::isViewing() const { return m_viewingflag; } -void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::interactiveCountInc(void) +void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::interactiveCountInc() { // Catch problems with missing interactiveCountDec() calls. assert(m_interactionnesting < 100); @@ -338,7 +338,7 @@ void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::interactiveCountInc(void) } } -void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::interactiveCountDec(void) +void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::interactiveCountDec() { if(--m_interactionnesting <= 0) { m_interactionEndCallback.invokeCallbacks(this); @@ -346,7 +346,7 @@ void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::interactiveCountDec(void) } } -int SIM::Coin3D::Quarter::SoQTQuarterAdaptor::getInteractiveCount(void) const +int SIM::Coin3D::Quarter::SoQTQuarterAdaptor::getInteractiveCount() const { return m_interactionnesting; } @@ -372,22 +372,22 @@ void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::removeFinishCallback(SIM::Coin3D: } -float SIM::Coin3D::Quarter::SoQTQuarterAdaptor::getSeekDistance(void) const +float SIM::Coin3D::Quarter::SoQTQuarterAdaptor::getSeekDistance() const { return m_seekdistance; } -float SIM::Coin3D::Quarter::SoQTQuarterAdaptor::getSeekTime(void) const +float SIM::Coin3D::Quarter::SoQTQuarterAdaptor::getSeekTime() const { return m_seekperiod; } -SbBool SIM::Coin3D::Quarter::SoQTQuarterAdaptor::isSeekMode(void) const +SbBool SIM::Coin3D::Quarter::SoQTQuarterAdaptor::isSeekMode() const { return m_inseekmode; } -SbBool SIM::Coin3D::Quarter::SoQTQuarterAdaptor::isSeekValuePercentage(void) const +SbBool SIM::Coin3D::Quarter::SoQTQuarterAdaptor::isSeekValuePercentage() const { return m_seekdistanceabs ? false : true; } @@ -541,7 +541,7 @@ void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::seeksensorCB(void* data, SoSensor if(end) thisp->setSeekMode(false); } -void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::saveHomePosition(void) +void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::saveHomePosition() { SoCamera* cam = getSoRenderManager()->getCamera(); if (!cam) { @@ -562,7 +562,7 @@ void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::saveHomePosition(void) m_storedcamera->copyFieldValues(getSoRenderManager()->getCamera()); } -void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::resetToHomePosition(void) +void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::resetToHomePosition() { SoCamera* cam = getSoRenderManager()->getCamera(); if (!cam) { @@ -724,7 +724,7 @@ void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::paintEvent(QPaintEvent* event) this->framesPerSecond = addFrametime(start); } -void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::resetFrameCounter(void) +void SIM::Coin3D::Quarter::SoQTQuarterAdaptor::resetFrameCounter() { this->framecount = 0; this->frametime = 0.0f; diff --git a/src/Gui/Quarter/SoQTQuarterAdaptor.h b/src/Gui/Quarter/SoQTQuarterAdaptor.h index 84956532d4..e9cee0b949 100644 --- a/src/Gui/Quarter/SoQTQuarterAdaptor.h +++ b/src/Gui/Quarter/SoQTQuarterAdaptor.h @@ -47,9 +47,9 @@ typedef void SoQTQuarterAdaptorCB(void* data, SoQTQuarterAdaptor* viewer); class QUARTER_DLL_API SoQTQuarterAdaptor : public QuarterWidget { public: - explicit SoQTQuarterAdaptor(QWidget* parent = 0, const QtGLWidget* sharewidget = 0, Qt::WindowFlags f = Qt::WindowFlags()); - explicit SoQTQuarterAdaptor(const QtGLFormat& format, QWidget* parent = 0, const QtGLWidget* shareWidget = 0, Qt::WindowFlags f = Qt::WindowFlags()); - explicit SoQTQuarterAdaptor(QtGLContext* context, QWidget* parent = 0, const QtGLWidget* sharewidget = 0, Qt::WindowFlags f = Qt::WindowFlags()); + explicit SoQTQuarterAdaptor(QWidget* parent = nullptr, const QtGLWidget* sharewidget = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + explicit SoQTQuarterAdaptor(const QtGLFormat& format, QWidget* parent = nullptr, const QtGLWidget* shareWidget = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + explicit SoQTQuarterAdaptor(QtGLContext* context, QWidget* parent = nullptr, const QtGLWidget* sharewidget = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); virtual ~SoQTQuarterAdaptor(); //the functions available in soqtviewer but missing in quarter @@ -59,38 +59,38 @@ public: QWidget* getGLWidget() const; virtual void setCameraType(SoType type); - SoCamera * getCamera(void) const; + SoCamera * getCamera() const; - const SbViewportRegion & getViewportRegion(void) const; + const SbViewportRegion & getViewportRegion() const; virtual void setViewing(SbBool enable); - SbBool isViewing(void) const; + SbBool isViewing() const; - void interactiveCountInc(void); - void interactiveCountDec(void); - int getInteractiveCount(void) const; + void interactiveCountInc(); + void interactiveCountDec(); + int getInteractiveCount() const; - void addStartCallback(SoQTQuarterAdaptorCB* func, void* data = NULL); - void addFinishCallback(SoQTQuarterAdaptorCB* func, void* data = NULL); - void removeStartCallback(SoQTQuarterAdaptorCB* func, void* data = NULL); - void removeFinishCallback(SoQTQuarterAdaptorCB* func, void* data = NULL); + void addStartCallback(SoQTQuarterAdaptorCB* func, void* data = nullptr); + void addFinishCallback(SoQTQuarterAdaptorCB* func, void* data = nullptr); + void removeStartCallback(SoQTQuarterAdaptorCB* func, void* data = nullptr); + void removeFinishCallback(SoQTQuarterAdaptorCB* func, void* data = nullptr); virtual void setSeekMode(SbBool enable); - SbBool isSeekMode(void) const; + SbBool isSeekMode() const; SbBool seekToPoint(const SbVec2s screenpos); void seekToPoint(const SbVec3f& scenepos); void setSeekTime(const float seconds); - float getSeekTime(void) const; + float getSeekTime() const; void setSeekDistance(const float distance); - float getSeekDistance(void) const; + float getSeekDistance() const; void setSeekValueAsPercentage(const SbBool on); - SbBool isSeekValuePercentage(void) const; + SbBool isSeekValuePercentage() const; - virtual float getPickRadius(void) const {return this->pickRadius;} + virtual float getPickRadius() const {return this->pickRadius;} virtual void setPickRadius(float pickRadius); - virtual void saveHomePosition(void); - virtual void resetToHomePosition(void); + virtual void saveHomePosition(); + virtual void resetToHomePosition(); virtual void setSceneGraph(SoNode* root) { QuarterWidget::setSceneGraph(root); @@ -100,7 +100,7 @@ public: virtual void paintEvent(QPaintEvent*); //this functions still need to be ported - virtual void afterRealizeHook(void) {} //enables spacenav and joystick in soqt, dunno if this is needed + virtual void afterRealizeHook() {} //enables spacenav and joystick in soqt, dunno if this is needed private: void init(); @@ -109,7 +109,7 @@ private: void getCameraCoordinateSystem(SoCamera * camera, SoNode * root, SbMatrix & matrix, SbMatrix & inverse); static void seeksensorCB(void * data, SoSensor * s); void moveCameraScreen(const SbVec2f & screenpos); - void resetFrameCounter(void); + void resetFrameCounter(); SbVec2f addFrametime(double ft); bool m_viewingflag; diff --git a/src/Gui/Quarter/SpaceNavigatorDevice.cpp b/src/Gui/Quarter/SpaceNavigatorDevice.cpp index 14fe49e871..5726845439 100644 --- a/src/Gui/Quarter/SpaceNavigatorDevice.cpp +++ b/src/Gui/Quarter/SpaceNavigatorDevice.cpp @@ -128,7 +128,7 @@ const SoEvent * SpaceNavigatorDevice::translateEvent(QEvent * event) { Q_UNUSED(event); - SoEvent * ret = NULL; + SoEvent * ret = nullptr; #ifdef HAVE_SPACENAV_LIB NativeEvent * ce = dynamic_cast(event); diff --git a/src/Gui/Quarter/devices/InputDevice.h b/src/Gui/Quarter/devices/InputDevice.h index bf2785d1eb..be46a1649a 100644 --- a/src/Gui/Quarter/devices/InputDevice.h +++ b/src/Gui/Quarter/devices/InputDevice.h @@ -47,7 +47,7 @@ class QuarterWidget; class QUARTER_DLL_API InputDevice { public: InputDevice(QuarterWidget * quarter); - InputDevice(void); + InputDevice(); virtual ~InputDevice() {} /*! diff --git a/src/Gui/Quarter/devices/Keyboard.h b/src/Gui/Quarter/devices/Keyboard.h index 8284798881..2299adf3d9 100644 --- a/src/Gui/Quarter/devices/Keyboard.h +++ b/src/Gui/Quarter/devices/Keyboard.h @@ -44,7 +44,7 @@ namespace SIM { namespace Coin3D { namespace Quarter { class QUARTER_DLL_API Keyboard : public InputDevice { public: Keyboard(QuarterWidget* quarter); - Keyboard(void); + Keyboard(); virtual ~Keyboard(); virtual const SoEvent * translateEvent(QEvent * event); diff --git a/src/Gui/Quarter/devices/Mouse.h b/src/Gui/Quarter/devices/Mouse.h index 49405368c2..6ac878063d 100644 --- a/src/Gui/Quarter/devices/Mouse.h +++ b/src/Gui/Quarter/devices/Mouse.h @@ -44,7 +44,7 @@ namespace SIM { namespace Coin3D { namespace Quarter { class QUARTER_DLL_API Mouse : public InputDevice { public: Mouse(QuarterWidget* quarter); - Mouse(void); + Mouse(); virtual ~Mouse(); virtual const SoEvent * translateEvent(QEvent * event); diff --git a/src/Gui/Quarter/devices/SpaceNavigatorDevice.h b/src/Gui/Quarter/devices/SpaceNavigatorDevice.h index 30224f6446..c8f6e8a5b3 100644 --- a/src/Gui/Quarter/devices/SpaceNavigatorDevice.h +++ b/src/Gui/Quarter/devices/SpaceNavigatorDevice.h @@ -43,7 +43,7 @@ namespace SIM { namespace Coin3D { namespace Quarter { class QUARTER_DLL_API SpaceNavigatorDevice : public InputDevice { public: SpaceNavigatorDevice(QuarterWidget* quarter); - SpaceNavigatorDevice(void); + SpaceNavigatorDevice(); virtual ~SpaceNavigatorDevice(); virtual const SoEvent * translateEvent(QEvent * event); diff --git a/src/Gui/Quarter/eventhandlers/EventFilter.h b/src/Gui/Quarter/eventhandlers/EventFilter.h index 68c7cbdf06..5f4f95bd1b 100644 --- a/src/Gui/Quarter/eventhandlers/EventFilter.h +++ b/src/Gui/Quarter/eventhandlers/EventFilter.h @@ -53,7 +53,7 @@ public: void registerInputDevice(InputDevice * device); void unregisterInputDevice(InputDevice * device); - const QPoint & globalMousePosition(void) const; + const QPoint & globalMousePosition() const; protected: bool eventFilter(QObject * obj, QEvent * event); diff --git a/src/Gui/RevitNavigationStyle.cpp b/src/Gui/RevitNavigationStyle.cpp index 977d0c01a2..6bc4ee8ced 100644 --- a/src/Gui/RevitNavigationStyle.cpp +++ b/src/Gui/RevitNavigationStyle.cpp @@ -88,12 +88,10 @@ SbBool RevitNavigationStyle::processSoEvent(const SoEvent * const ev) const SoType type(ev->getTypeId()); const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion(); - const SbVec2s size(vp.getViewportSizePixels()); - const SbVec2f prevnormalized = this->lastmouseposition; const SbVec2s pos(ev->getPosition()); - const SbVec2f posn((float) pos[0] / (float) std::max((int)(size[0] - 1), 1), - (float) pos[1] / (float) std::max((int)(size[1] - 1), 1)); + const SbVec2f posn = normalizePixelPos(pos); + const SbVec2f prevnormalized = this->lastmouseposition; this->lastmouseposition = posn; // Set to true if any event processing happened. Note that it is not @@ -107,15 +105,7 @@ SbBool RevitNavigationStyle::processSoEvent(const SoEvent * const ev) // Mismatches in state of the modifier keys happens if the user // presses or releases them outside the viewer window. - if (this->ctrldown != ev->wasCtrlDown()) { - this->ctrldown = ev->wasCtrlDown(); - } - if (this->shiftdown != ev->wasShiftDown()) { - this->shiftdown = ev->wasShiftDown(); - } - if (this->altdown != ev->wasAltDown()) { - this->altdown = ev->wasAltDown(); - } + syncModifierKeys(ev); // give the nodes in the foreground root the chance to handle events (e.g color bar) if (!viewer->isEditing()) { @@ -126,37 +116,8 @@ SbBool RevitNavigationStyle::processSoEvent(const SoEvent * const ev) // Keyboard handling if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { - const SoKeyboardEvent * const event = (const SoKeyboardEvent *) ev; - const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; - switch (event->getKey()) { - case SoKeyboardEvent::LEFT_CONTROL: - case SoKeyboardEvent::RIGHT_CONTROL: - this->ctrldown = press; - break; - case SoKeyboardEvent::LEFT_SHIFT: - case SoKeyboardEvent::RIGHT_SHIFT: - this->shiftdown = press; - break; - case SoKeyboardEvent::LEFT_ALT: - case SoKeyboardEvent::RIGHT_ALT: - this->altdown = press; - break; - case SoKeyboardEvent::H: - processed = true; - viewer->saveHomePosition(); - break; - case SoKeyboardEvent::S: - case SoKeyboardEvent::HOME: - case SoKeyboardEvent::LEFT_ARROW: - case SoKeyboardEvent::UP_ARROW: - case SoKeyboardEvent::RIGHT_ARROW: - case SoKeyboardEvent::DOWN_ARROW: - if (!this->isViewing()) - this->setViewing(true); - break; - default: - break; - } + const SoKeyboardEvent * const event = static_cast(ev); + processed = processKeyboardEvent(event); } // Mouse Button / Spaceball Button handling @@ -175,10 +136,6 @@ SbBool RevitNavigationStyle::processSoEvent(const SoEvent * const ev) this->seekToPoint(pos); // implicitly calls interactiveCountInc() processed = true; } - //else if (press && (this->currentmode == NavigationStyle::IDLE)) { - // this->setViewing(true); - // processed = true; - //} else if (press && (this->currentmode == NavigationStyle::PANNING || this->currentmode == NavigationStyle::ZOOMING)) { newmode = NavigationStyle::DRAGGING; @@ -189,30 +146,8 @@ SbBool RevitNavigationStyle::processSoEvent(const SoEvent * const ev) else if (viewer->isEditing() && (this->currentmode == NavigationStyle::SPINNING)) { processed = true; } - // issue #0002433: avoid to swallow the UP event if down the - // scene graph somewhere a dialog gets opened - else if (press) { - SbTime tmp = (ev->getTime() - mouseDownConsumedEvent.getTime()); - float dci = (float)QApplication::doubleClickInterval()/1000.0f; - // a double-click? - if (tmp.getValue() < dci) { - mouseDownConsumedEvent = *event; - mouseDownConsumedEvent.setTime(ev->getTime()); - processed = true; - } - else { - mouseDownConsumedEvent.setTime(ev->getTime()); - // 'ANY' is used to mark that we don't know yet if it will - // be a double-click event. - mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); - } - } - else if (!press) { - if (mouseDownConsumedEvent.getButton() == SoMouseButtonEvent::BUTTON1) { - // now handle the postponed event - inherited::processSoEvent(&mouseDownConsumedEvent); - mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); - } + else { + processed = processClickEvent(event); } break; case SoMouseButtonEvent::BUTTON2: @@ -322,11 +257,6 @@ SbBool RevitNavigationStyle::processSoEvent(const SoEvent * const ev) this->lockButton1 = false; processed = true; } - - //if (curmode == NavigationStyle::DRAGGING) { - // if (doSpin()) - // newmode = NavigationStyle::SPINNING; - //} break; case BUTTON1DOWN: case CTRLDOWN|BUTTON1DOWN: @@ -348,9 +278,6 @@ SbBool RevitNavigationStyle::processSoEvent(const SoEvent * const ev) } newmode = NavigationStyle::DRAGGING; break; - //case BUTTON1DOWN|BUTTON2DOWN|BUTTON3DOWN: - // newmode = NavigationStyle::ZOOMING; - // break; case CTRLDOWN|SHIFTDOWN|BUTTON2DOWN: case CTRLDOWN|BUTTON3DOWN: newmode = NavigationStyle::ZOOMING; @@ -372,10 +299,7 @@ SbBool RevitNavigationStyle::processSoEvent(const SoEvent * const ev) // If not handled in this class, pass on upwards in the inheritance // hierarchy. - if (/*(curmode == NavigationStyle::SELECTION || viewer->isEditing()) && */!processed) + if (!processed) processed = inherited::processSoEvent(ev); - else - return true; - return processed; } diff --git a/src/Gui/SoFCDB.cpp b/src/Gui/SoFCDB.cpp index 3d3214962e..92e9045ad3 100644 --- a/src/Gui/SoFCDB.cpp +++ b/src/Gui/SoFCDB.cpp @@ -161,6 +161,7 @@ void Gui::SoFCDB::init() PropertyDirectionItem ::init(); PropertyMatrixItem ::init(); PropertyPlacementItem ::init(); + PropertyRotationItem ::init(); PropertyEnumItem ::init(); PropertyStringListItem ::init(); PropertyFloatListItem ::init(); @@ -184,6 +185,8 @@ void Gui::SoFCDB::init() TouchpadNavigationStyle ::init(); GestureNavigationStyle ::init(); OpenCascadeNavigationStyle ::init(); + OpenSCADNavigationStyle ::init(); + TinkerCADNavigationStyle ::init(); GLGraphicsItem ::init(); GLFlagWindow ::init(); diff --git a/src/Gui/SoFCOffscreenRenderer.cpp b/src/Gui/SoFCOffscreenRenderer.cpp index a54220fac7..01c393fd46 100644 --- a/src/Gui/SoFCOffscreenRenderer.cpp +++ b/src/Gui/SoFCOffscreenRenderer.cpp @@ -166,7 +166,7 @@ void SoFCOffscreenRenderer::writeToImageFile(const char* filename, const char* c img.setText(QLatin1String("Description"), QString::fromUtf8(comment)); img.setText(QLatin1String("Creation Time"), QDateTime::currentDateTime().toString()); img.setText(QLatin1String("Software"), - QString::fromUtf8(App::GetApplication().getExecutableName())); + QString::fromStdString(App::Application::getExecutableName())); } QFile f(QString::fromUtf8(filename)); @@ -296,7 +296,7 @@ std::string SoFCOffscreenRenderer::createMIBA(const SbMatrix& mat) const com << " \n" ; com << " Unknown\n" ; com << " " << QDateTime::currentDateTime().toString().toLatin1().constData() << "\n" ; - com << " " << App::GetApplication().getExecutableName() << " " << major << "." << minor << "\n" ; + com << " " << App::Application::getExecutableName() << " " << major << "." << minor << "\n" ; com << " Unknown\n"; com << " 1.0\n"; com << " \n" ; diff --git a/src/Gui/Splashscreen.cpp b/src/Gui/Splashscreen.cpp index 4b8e5b53fd..c899f92ae9 100644 --- a/src/Gui/Splashscreen.cpp +++ b/src/Gui/Splashscreen.cpp @@ -680,7 +680,7 @@ void AboutDialog::on_copyButton_clicked() QTextStream str(&data); std::map& config = App::Application::Config(); std::map::iterator it; - QString exe = QString::fromLatin1(App::GetApplication().getExecutableName()); + QString exe = QString::fromStdString(App::Application::getExecutableName()); QString major = QString::fromLatin1(config["BuildVersionMajor"].c_str()); QString minor = QString::fromLatin1(config["BuildVersionMinor"].c_str()); diff --git a/src/Gui/TextDocumentEditorView.h b/src/Gui/TextDocumentEditorView.h index f53f03f7eb..193b70ec84 100644 --- a/src/Gui/TextDocumentEditorView.h +++ b/src/Gui/TextDocumentEditorView.h @@ -53,8 +53,8 @@ public: QPlainTextEdit* getEditor() const { return editor; } App::TextDocument* getTextObject() const { return textDocument; } - QStringList undoActions() const; - QStringList redoActions() const; + QStringList undoActions() const override; + QStringList redoActions() const override; protected: void showEvent(QShowEvent*) override; diff --git a/src/Gui/TextEdit.cpp b/src/Gui/TextEdit.cpp index d9cbda44bd..219a3d72fc 100644 --- a/src/Gui/TextEdit.cpp +++ b/src/Gui/TextEdit.cpp @@ -48,6 +48,21 @@ TextEdit::TextEdit(QWidget* parent) shortcut->setKey(Qt::CTRL+Qt::Key_Space); shortcut->setContext(Qt::WidgetShortcut); connect(shortcut, SIGNAL(activated()), this, SLOT(complete())); + + QShortcut* shortcutFind = new QShortcut(this); + shortcutFind->setKey(QKeySequence::Find); + shortcutFind->setContext(Qt::WidgetShortcut); + connect(shortcutFind, SIGNAL(activated()), this, SIGNAL(showSearchBar())); + + QShortcut* shortcutNext = new QShortcut(this); + shortcutNext->setKey(QKeySequence::FindNext); + shortcutNext->setContext(Qt::WidgetShortcut); + connect(shortcutNext, SIGNAL(activated()), this, SIGNAL(findNext())); + + QShortcut* shortcutPrev = new QShortcut(this); + shortcutPrev->setKey(QKeySequence::FindPrevious); + shortcutPrev->setContext(Qt::WidgetShortcut); + connect(shortcutPrev, SIGNAL(activated()), this, SIGNAL(findPrevious())); } /** Destroys the object and frees any allocated resources */ diff --git a/src/Gui/TextEdit.h b/src/Gui/TextEdit.h index ee85626323..ad7bded512 100644 --- a/src/Gui/TextEdit.h +++ b/src/Gui/TextEdit.h @@ -64,6 +64,11 @@ public: private Q_SLOTS: void complete(); +Q_SIGNALS: + void showSearchBar(); + void findNext(); + void findPrevious(); + protected: void keyPressEvent(QKeyEvent *); diff --git a/src/Gui/TinkerCADNavigationStyle.cpp b/src/Gui/TinkerCADNavigationStyle.cpp new file mode 100644 index 0000000000..50b28e47ad --- /dev/null +++ b/src/Gui/TinkerCADNavigationStyle.cpp @@ -0,0 +1,263 @@ +/*************************************************************************** + * 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 "InventorAll.h" +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#include +#include "NavigationStyle.h" +#include "View3DInventorViewer.h" +#include "Application.h" +#include "MenuManager.h" +#include "MouseSelection.h" + +using namespace Gui; + +// ---------------------------------------------------------------------------------- + +/* TRANSLATOR Gui::TinkerCADNavigationStyle */ + +TYPESYSTEM_SOURCE(Gui::TinkerCADNavigationStyle, Gui::UserNavigationStyle) + +TinkerCADNavigationStyle::TinkerCADNavigationStyle() +{ +} + +TinkerCADNavigationStyle::~TinkerCADNavigationStyle() +{ +} + +const char* TinkerCADNavigationStyle::mouseButtons(ViewerMode mode) +{ + switch (mode) { + case NavigationStyle::SELECTION: + return QT_TR_NOOP("Press left mouse button"); + case NavigationStyle::PANNING: + return QT_TR_NOOP("Press middle mouse button"); + case NavigationStyle::DRAGGING: + return QT_TR_NOOP("Press right mouse button"); + case NavigationStyle::ZOOMING: + return QT_TR_NOOP("Scroll middle mouse button"); + default: + return "No description"; + } +} + +SbBool TinkerCADNavigationStyle::processSoEvent(const SoEvent * const ev) +{ + // Events when in "ready-to-seek" mode are ignored, except those + // which influence the seek mode itself -- these are handled further + // up the inheritance hierarchy. + if (this->isSeekMode()) { return inherited::processSoEvent(ev); } + // Switch off viewing mode + if (!this->isSeekMode() && !this->isAnimating() && this->isViewing()) + this->setViewing(false); // by default disable viewing mode to render the scene + + const SoType type(ev->getTypeId()); + + const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion(); + const SbVec2s pos(ev->getPosition()); + const SbVec2f posn = normalizePixelPos(pos); + + const SbVec2f prevnormalized = this->lastmouseposition; + this->lastmouseposition = posn; + + // Set to true if any event processing happened. Note that it is not + // necessary to restrict ourselves to only do one "action" for an + // event, we only need this flag to see if any processing happened + // at all. + SbBool processed = false; + + const ViewerMode curmode = this->currentmode; + ViewerMode newmode = curmode; + + // Mismatches in state of the modifier keys happens if the user + // presses or releases them outside the viewer window. + syncModifierKeys(ev); + + // give the nodes in the foreground root the chance to handle events (e.g color bar) + if (!viewer->isEditing()) { + processed = handleEventInForeground(ev); + if (processed) + return true; + } + + // Keyboard handling + if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { + const SoKeyboardEvent * const event = static_cast(ev); + processed = processKeyboardEvent(event); + } + + // Mouse Button / Spaceball Button handling + if (type.isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) { + const SoMouseButtonEvent * const event = (const SoMouseButtonEvent *) ev; + const int button = event->getButton(); + const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; + SbBool canOpenPopupMenu = false; + + switch (button) { + case SoMouseButtonEvent::BUTTON1: + this->button1down = press; + if (press && (curmode == NavigationStyle::SEEK_WAIT_MODE)) { + newmode = NavigationStyle::SEEK_MODE; + this->seekToPoint(pos); // implicitly calls interactiveCountInc() + processed = true; + } + else if (viewer->isEditing() && (curmode == NavigationStyle::SPINNING)) { + processed = true; + } + else { + processed = processClickEvent(event); + } + break; + case SoMouseButtonEvent::BUTTON2: + // If we are in edit mode then simply ignore the RMB events + // to pass the event to the base class. + this->button2down = press; + if (press) { + mouseDownConsumedEvent = *event; + mouseDownConsumedEvent.setTime(ev->getTime()); + } + else if (mouseDownConsumedEvent.getButton() == SoMouseButtonEvent::BUTTON2) { + SbTime tmp = (ev->getTime() - mouseDownConsumedEvent.getTime()); + float dci = float(QApplication::doubleClickInterval())/1000.0f; + // time between press and release event + if (tmp.getValue() < dci) { + canOpenPopupMenu = true; + } + } + + // About to start rotating + if (press && (curmode == NavigationStyle::IDLE)) { + // Use this variable to spot move events + saveCursorPosition(ev); + this->centerTime = ev->getTime(); + processed = true; + } + else if (!press && (curmode == NavigationStyle::DRAGGING)) { + if (!viewer->isEditing() && canOpenPopupMenu) { + // If we are in drag mode but mouse hasn't been moved open the context-menu + if (this->isPopupMenuEnabled()) { + this->openPopupMenu(event->getPosition()); + } + } + newmode = NavigationStyle::IDLE; + processed = true; + } + break; + case SoMouseButtonEvent::BUTTON3: + this->button3down = press; + if (press) { + this->centerTime = ev->getTime(); + float ratio = vp.getViewportAspectRatio(); + SbViewVolume vv = viewer->getSoRenderManager()->getCamera()->getViewVolume(ratio); + this->panningplane = vv.getPlane(viewer->getSoRenderManager()->getCamera()->focalDistance.getValue()); + } + else if (curmode == NavigationStyle::PANNING) { + newmode = NavigationStyle::IDLE; + processed = true; + } + break; + default: + break; + } + } + + // Mouse Movement handling + if (type.isDerivedFrom(SoLocation2Event::getClassTypeId())) { + const SoLocation2Event * const event = (const SoLocation2Event *) ev; + if (curmode == NavigationStyle::PANNING) { + float ratio = vp.getViewportAspectRatio(); + panCamera(viewer->getSoRenderManager()->getCamera(), ratio, this->panningplane, posn, prevnormalized); + processed = true; + } + else if (curmode == NavigationStyle::DRAGGING) { + this->addToLog(event->getPosition(), event->getTime()); + this->spin(posn); + moveCursorPosition(); + processed = true; + } + } + + // Spaceball & Joystick handling + if (type.isDerivedFrom(SoMotion3Event::getClassTypeId())) { + const SoMotion3Event * const event = static_cast(ev); + if (event) + this->processMotionEvent(event); + processed = true; + } + + enum { + BUTTON1DOWN = 1 << 0, + BUTTON3DOWN = 1 << 1, + CTRLDOWN = 1 << 2, + SHIFTDOWN = 1 << 3, + BUTTON2DOWN = 1 << 4 + }; + unsigned int combo = + (this->button1down ? BUTTON1DOWN : 0) | + (this->button2down ? BUTTON2DOWN : 0) | + (this->button3down ? BUTTON3DOWN : 0) | + (this->ctrldown ? CTRLDOWN : 0) | + (this->shiftdown ? SHIFTDOWN : 0); + + switch (combo) { + case 0: + if (curmode == NavigationStyle::SPINNING) { break; } + newmode = NavigationStyle::IDLE; + break; + case BUTTON1DOWN: + newmode = NavigationStyle::SELECTION; + break; + case BUTTON2DOWN: + newmode = NavigationStyle::DRAGGING; + break; + case BUTTON3DOWN: + newmode = NavigationStyle::PANNING; + break; + default: + break; + } + + if (newmode != curmode) { + this->setViewingMode(newmode); + } + + // If not handled in this class, pass on upwards in the inheritance + // hierarchy. + if (!processed) + processed = inherited::processSoEvent(ev); + return processed; +} diff --git a/src/Gui/TouchpadNavigationStyle.cpp b/src/Gui/TouchpadNavigationStyle.cpp index 7b279eefc4..dbb2dc8b6c 100644 --- a/src/Gui/TouchpadNavigationStyle.cpp +++ b/src/Gui/TouchpadNavigationStyle.cpp @@ -88,12 +88,10 @@ SbBool TouchpadNavigationStyle::processSoEvent(const SoEvent * const ev) const SoType type(ev->getTypeId()); const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion(); - const SbVec2s size(vp.getViewportSizePixels()); - const SbVec2f prevnormalized = this->lastmouseposition; const SbVec2s pos(ev->getPosition()); - const SbVec2f posn((float) pos[0] / (float) std::max((int)(size[0] - 1), 1), - (float) pos[1] / (float) std::max((int)(size[1] - 1), 1)); + const SbVec2f posn = normalizePixelPos(pos); + const SbVec2f prevnormalized = this->lastmouseposition; this->lastmouseposition = posn; // Set to true if any event processing happened. Note that it is not @@ -107,15 +105,7 @@ SbBool TouchpadNavigationStyle::processSoEvent(const SoEvent * const ev) // Mismatches in state of the modifier keys happens if the user // presses or releases them outside the viewer window. - if (this->ctrldown != ev->wasCtrlDown()) { - this->ctrldown = ev->wasCtrlDown(); - } - if (this->shiftdown != ev->wasShiftDown()) { - this->shiftdown = ev->wasShiftDown(); - } - if (this->altdown != ev->wasAltDown()) { - this->altdown = ev->wasAltDown(); - } + syncModifierKeys(ev); // give the nodes in the foreground root the chance to handle events (e.g color bar) if (!viewer->isEditing()) { @@ -126,45 +116,8 @@ SbBool TouchpadNavigationStyle::processSoEvent(const SoEvent * const ev) // Keyboard handling if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { - const SoKeyboardEvent * const event = (const SoKeyboardEvent *) ev; - const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; - switch (event->getKey()) { - case SoKeyboardEvent::LEFT_CONTROL: - case SoKeyboardEvent::RIGHT_CONTROL: - this->ctrldown = press; - break; - case SoKeyboardEvent::LEFT_SHIFT: - case SoKeyboardEvent::RIGHT_SHIFT: - this->shiftdown = press; - break; - case SoKeyboardEvent::LEFT_ALT: - case SoKeyboardEvent::RIGHT_ALT: - this->altdown = press; - break; - case SoKeyboardEvent::H: - processed = true; - viewer->saveHomePosition(); - break; - case SoKeyboardEvent::S: - case SoKeyboardEvent::HOME: - case SoKeyboardEvent::LEFT_ARROW: - case SoKeyboardEvent::UP_ARROW: - case SoKeyboardEvent::RIGHT_ARROW: - case SoKeyboardEvent::DOWN_ARROW: - if (!this->isViewing()) - this->setViewing(true); - break; - case SoKeyboardEvent::PAGE_UP: - doZoom(viewer->getSoRenderManager()->getCamera(), getDelta(), posn); - processed = true; - break; - case SoKeyboardEvent::PAGE_DOWN: - doZoom(viewer->getSoRenderManager()->getCamera(), -getDelta(), posn); - processed = true; - break; - default: - break; - } + const SoKeyboardEvent * const event = static_cast(ev); + processed = processKeyboardEvent(event); } // Mouse Button / Spaceball Button handling @@ -193,30 +146,8 @@ SbBool TouchpadNavigationStyle::processSoEvent(const SoEvent * const ev) else if (viewer->isEditing() && (this->currentmode == NavigationStyle::SPINNING)) { processed = true; } - // issue #0002433: avoid to swallow the UP event if down the - // scene graph somewhere a dialog gets opened - else if (press) { - SbTime tmp = (ev->getTime() - mouseDownConsumedEvent.getTime()); - float dci = (float)QApplication::doubleClickInterval()/1000.0f; - // a double-click? - if (tmp.getValue() < dci) { - mouseDownConsumedEvent = *event; - mouseDownConsumedEvent.setTime(ev->getTime()); - processed = true; - } - else { - mouseDownConsumedEvent.setTime(ev->getTime()); - // 'ANY' is used to mark that we don't know yet if it will - // be a double-click event. - mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); - } - } - else if (!press) { - if (mouseDownConsumedEvent.getButton() == SoMouseButtonEvent::BUTTON1) { - // now handle the postponed event - inherited::processSoEvent(&mouseDownConsumedEvent); - mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); - } + else { + processed = processClickEvent(event); } break; case SoMouseButtonEvent::BUTTON2: @@ -346,8 +277,5 @@ SbBool TouchpadNavigationStyle::processSoEvent(const SoEvent * const ev) // hierarchy. if (!processed) processed = inherited::processSoEvent(ev); - else - return true; - return processed; } diff --git a/src/Gui/Tree.cpp b/src/Gui/Tree.cpp index 196ea983f2..6d400dbf66 100644 --- a/src/Gui/Tree.cpp +++ b/src/Gui/Tree.cpp @@ -472,6 +472,10 @@ TreeWidget::TreeWidget(const char *name, QWidget* parent) connect(this->finishEditingAction, SIGNAL(triggered()), this, SLOT(onFinishEditing())); + this->selectDependentsAction = new QAction(this); + connect(this->selectDependentsAction, SIGNAL(triggered()), + this, SLOT(onSelectDependents())); + this->closeDocAction = new QAction(this); connect(this->closeDocAction, SIGNAL(triggered()), this, SLOT(onCloseDoc())); @@ -843,6 +847,7 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e) break; } } + contextMenu.addAction(this->selectDependentsAction); this->skipRecomputeAction->setChecked(doc->testStatus(App::Document::SkipRecompute)); contextMenu.addAction(this->skipRecomputeAction); this->allowPartialRecomputeAction->setChecked(doc->testStatus(App::Document::AllowPartialRecompute)); @@ -857,24 +862,43 @@ void TreeWidget::contextMenuEvent (QContextMenuEvent * e) DocumentObjectItem* objitem = static_cast (this->contextItem); + // check that the selection is not across several documents + bool acrossDocuments = false; + auto SelectedObjectsList = Selection().getCompleteSelection(); + // get the object's document as reference App::Document* doc = objitem->object()->getObject()->getDocument(); + for (auto it = SelectedObjectsList.begin(); it != SelectedObjectsList.end(); ++it) { + if ((*it).pDoc != doc) { + acrossDocuments = true; + break; + } + } + showHiddenAction->setChecked(doc->ShowHidden.getValue()); contextMenu.addAction(this->showHiddenAction); hideInTreeAction->setChecked(!objitem->object()->showInTree()); contextMenu.addAction(this->hideInTreeAction); - if (objitem->object()->getObject()->isDerivedFrom(App::DocumentObjectGroup::getClassTypeId())) - contextMenu.addAction(this->createGroupAction); + if (!acrossDocuments) { // is only sensible for selections within one document + if (objitem->object()->getObject()->isDerivedFrom(App::DocumentObjectGroup::getClassTypeId())) + contextMenu.addAction(this->createGroupAction); + // if there are dependent objects in the selection, add context menu to add them to selection + if (CheckForDependents()) + contextMenu.addAction(this->selectDependentsAction); + } contextMenu.addSeparator(); contextMenu.addAction(this->markRecomputeAction); contextMenu.addAction(this->recomputeObjectAction); contextMenu.addSeparator(); - contextMenu.addAction(this->relabelObjectAction); + + // relabeling is only possible for a single selected document + if (SelectedObjectsList.size() == 1) + contextMenu.addAction(this->relabelObjectAction); auto selItems = this->selectedItems(); - // if only one item is selected setup the edit menu + // if only one item is selected, setup the edit menu if (selItems.size() == 1) { objitem->object()->setupContextMenu(&editMenu, this, SLOT(onStartEditing())); QList editAct = editMenu.actions(); @@ -1037,6 +1061,73 @@ void TreeWidget::onFinishEditing() } } +// check if selection has dependent objects +bool TreeWidget::CheckForDependents() +{ + // if the selected object is a document + if (this->contextItem && this->contextItem->type() == DocumentType) { + return true; + } + // it can be an object + else { + QList items = this->selectedItems(); + for (QList::iterator it = items.begin(); it != items.end(); ++it) { + if ((*it)->type() == ObjectType) { + DocumentObjectItem* objitem = static_cast(*it); + App::DocumentObject* obj = objitem->object()->getObject(); + // get dependents + auto subObjectList = obj->getOutList(); + if (subObjectList.size() > 0) + return true; + } + } + } + + return false; +} + +// adds an App::DocumentObject* and its dependent objects to the selection +void TreeWidget::addDependentToSelection(App::Document* doc, App::DocumentObject* docObject) +{ + // add the docObject to the selection + Selection().addSelection(doc->getName(), docObject->getNameInDocument()); + // get the dependent + auto subObjectList = docObject->getOutList(); + // the dependent can in turn have dependents, thus add them recursively + for (auto itDepend = subObjectList.begin(); itDepend != subObjectList.end(); ++itDepend) + addDependentToSelection(doc, (*itDepend)); +} + +// add dependents of the selected tree object to selection +void TreeWidget::onSelectDependents() +{ + // We only have this context menu entry if the selection is within one document but it + // might be not the active document. Therefore get the document not here but later by casting. + App::Document* doc; + + // if the selected object is a document + if (this->contextItem && this->contextItem->type() == DocumentType) { + DocumentItem* docitem = static_cast(this->contextItem); + doc = docitem->document()->getDocument(); + std::vector obj = doc->getObjects(); + for (std::vector::iterator it = obj.begin(); it != obj.end(); ++it) + Selection().addSelection(doc->getName(), (*it)->getNameInDocument()); + } + // it can be an object + else { + QList items = this->selectedItems(); + for (QList::iterator it = items.begin(); it != items.end(); ++it) { + if ((*it)->type() == ObjectType) { + DocumentObjectItem* objitem = static_cast(*it); + doc = objitem->object()->getObject()->getDocument(); + App::DocumentObject* obj = objitem->object()->getObject(); + // the dependents can also have dependents, thus add them recursively via a separate void + addDependentToSelection(doc, obj); + } + } + } +} + void TreeWidget::onSkipRecompute(bool on) { // if a document item is selected then touch all objects @@ -2666,6 +2757,9 @@ void TreeWidget::setupText() this->finishEditingAction->setText(tr("Finish editing")); this->finishEditingAction->setStatusTip(tr("Finish editing object")); + 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")); diff --git a/src/Gui/Tree.h b/src/Gui/Tree.h index aa9e4cc9e2..f644f81a64 100644 --- a/src/Gui/Tree.h +++ b/src/Gui/Tree.h @@ -164,6 +164,7 @@ protected Q_SLOTS: void onActivateDocument(QAction*); void onStartEditing(); void onFinishEditing(); + void onSelectDependents(); void onSkipRecompute(bool on); void onAllowPartialRecompute(bool on); void onReloadDoc(); @@ -208,10 +209,14 @@ private: void updateChildren(App::DocumentObject *obj, const std::set &data, bool output, bool force); + bool CheckForDependents(); + void addDependentToSelection(App::Document* doc, App::DocumentObject* docObject); + private: QAction* createGroupAction; QAction* relabelObjectAction; QAction* finishEditingAction; + QAction* selectDependentsAction; QAction* skipRecomputeAction; QAction* allowPartialRecomputeAction; QAction* markRecomputeAction; diff --git a/src/Gui/View3DPy.cpp b/src/Gui/View3DPy.cpp index 50e8325c3a..0576eae5da 100644 --- a/src/Gui/View3DPy.cpp +++ b/src/Gui/View3DPy.cpp @@ -115,6 +115,8 @@ void View3DInventorPy::init_type() add_varargs_method("stopAnimating",&View3DInventorPy::stopAnimating,"stopAnimating()"); add_varargs_method("setAnimationEnabled",&View3DInventorPy::setAnimationEnabled,"setAnimationEnabled()"); add_varargs_method("isAnimationEnabled",&View3DInventorPy::isAnimationEnabled,"isAnimationEnabled()"); + add_varargs_method("setPopupMenuEnabled",&View3DInventorPy::setPopupMenuEnabled,"setPopupMenuEnabled()"); + add_varargs_method("isPopupMenuEnabled",&View3DInventorPy::isPopupMenuEnabled,"isPopupMenuEnabled()"); add_varargs_method("dump",&View3DInventorPy::dump,"dump(filename, [onlyVisible=False])"); add_varargs_method("dumpNode",&View3DInventorPy::dumpNode,"dumpNode(node)"); add_varargs_method("setStereoType",&View3DInventorPy::setStereoType,"setStereoType()"); @@ -243,7 +245,7 @@ PyObject *View3DInventorPy::method_varargs_ext_handler(PyObject *_self_and_name_ catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } } @@ -307,7 +309,7 @@ Py::Object View3DInventorPy::message(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } return Py::None(); @@ -328,7 +330,7 @@ Py::Object View3DInventorPy::fitAll(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } return Py::None(); @@ -474,7 +476,7 @@ Py::Object View3DInventorPy::viewBottom(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -495,7 +497,7 @@ Py::Object View3DInventorPy::viewFront(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -516,7 +518,7 @@ Py::Object View3DInventorPy::viewLeft(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -537,7 +539,7 @@ Py::Object View3DInventorPy::viewRear(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -558,7 +560,7 @@ Py::Object View3DInventorPy::viewRight(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -579,7 +581,7 @@ Py::Object View3DInventorPy::viewTop(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -600,7 +602,7 @@ Py::Object View3DInventorPy::viewIsometric(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -621,7 +623,7 @@ Py::Object View3DInventorPy::viewDimetric(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -642,7 +644,7 @@ Py::Object View3DInventorPy::viewTrimetric(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -733,7 +735,7 @@ Py::Object View3DInventorPy::viewDefaultOrientation(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -759,7 +761,7 @@ Py::Object View3DInventorPy::viewRotateLeft(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -785,7 +787,7 @@ Py::Object View3DInventorPy::viewRotateRight(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -806,7 +808,7 @@ Py::Object View3DInventorPy::zoomIn(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -827,7 +829,7 @@ Py::Object View3DInventorPy::zoomOut(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -953,6 +955,23 @@ Py::Object View3DInventorPy::isAnimationEnabled(const Py::Tuple& args) return Py::Boolean(ok ? true : false); } +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); + return Py::None(); +} + +Py::Object View3DInventorPy::isPopupMenuEnabled(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + SbBool ok = _view->getViewer()->isPopupMenuEnabled(); + return Py::Boolean(ok ? true : false); +} + Py::Object View3DInventorPy::saveImage(const Py::Tuple& args) { char *cFileName,*cColor="Current",*cComment="$MIBA"; @@ -1073,7 +1092,7 @@ Py::Object View3DInventorPy::getCamera(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } } @@ -1092,7 +1111,7 @@ Py::Object View3DInventorPy::getViewDirection(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } } @@ -1126,7 +1145,7 @@ Py::Object View3DInventorPy::setViewDirection(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -1415,23 +1434,23 @@ Py::Object View3DInventorPy::getObjectInfo(const Py::Tuple& args) Gui::Document* doc = _view->getViewer()->getDocument(); ViewProvider *vp = doc ? doc->getViewProviderByPathFromHead(Point->getPath()) : _view->getViewer()->getViewProviderByPath(Point->getPath()); - if(vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { - if(!vp->isSelectable()) + if (vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { + if (!vp->isSelectable()) return ret; ViewProviderDocumentObject* vpd = static_cast(vp); - if(vp->useNewSelectionModel()) { + if (vp->useNewSelectionModel()) { std::string subname; - if(!vp->getElementPicked(Point,subname)) + if (!vp->getElementPicked(Point,subname)) return ret; auto obj = vpd->getObject(); - if(!obj) + if (!obj) return ret; - if(subname.size()) { + if (subname.size()) { std::pair elementName; auto sobj = App::GeoFeature::resolveElement(obj,subname.c_str(),elementName); - if(!sobj) + if (!sobj) return ret; - if(sobj!=obj) { + if (sobj != obj) { dict.setItem("ParentObject",Py::Object(obj->getPyObject(),true)); dict.setItem("SubName",Py::String(subname)); obj = sobj; @@ -1443,7 +1462,8 @@ Py::Object View3DInventorPy::getObjectInfo(const Py::Tuple& args) dict.setItem("Object", Py::String(obj->getNameInDocument())); dict.setItem("Component",Py::String(subname)); - } else { + } + else { dict.setItem("Document", Py::String(vpd->getObject()->getDocument()->getName())); dict.setItem("Object", @@ -1530,19 +1550,19 @@ Py::Object View3DInventorPy::getObjectsInfo(const Py::Tuple& args) if(!vp->isSelectable()) continue; ViewProviderDocumentObject* vpd = static_cast(vp); - if(vp->useNewSelectionModel()) { + if (vp->useNewSelectionModel()) { std::string subname; - if(!vp->getElementPicked(point,subname)) + if (!vp->getElementPicked(point,subname)) continue; auto obj = vpd->getObject(); - if(!obj) + if (!obj) continue; - if(subname.size()) { + if (subname.size()) { std::pair elementName; auto sobj = App::GeoFeature::resolveElement(obj,subname.c_str(),elementName); - if(!sobj) + if (!sobj) continue; - if(sobj!=obj) { + if (sobj != obj) { dict.setItem("ParentObject",Py::Object(obj->getPyObject(),true)); dict.setItem("SubName",Py::String(subname)); obj = sobj; @@ -1554,7 +1574,8 @@ Py::Object View3DInventorPy::getObjectsInfo(const Py::Tuple& args) dict.setItem("Object", Py::String(obj->getNameInDocument())); dict.setItem("Component",Py::String(subname)); - } else { + } + else { dict.setItem("Document", Py::String(vpd->getObject()->getDocument()->getName())); dict.setItem("Object", @@ -2492,21 +2513,23 @@ Py::Object View3DInventorPy::removeDraggerCallback(const Py::Tuple& args) Py::Object View3DInventorPy::setActiveObject(const Py::Tuple& args) { - PyObject* docObject = Py_None; - char* name; + PyObject* docObject = Py_None; + char* name; char *subname = 0; if (!PyArg_ParseTuple(args.ptr(), "s|Os", &name, &docObject, &subname)) - throw Py::Exception(); + throw Py::Exception(); - if (docObject == Py_None) - _view->setActiveObject(0, name); - else{ - if(!PyObject_TypeCheck(docObject, &App::DocumentObjectPy::Type)) + if (docObject == Py_None) { + _view->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); - } - return Py::None(); + App::DocumentObject* obj = static_cast(docObject)->getDocumentObjectPtr(); + _view->setActiveObject(obj, name, subname); + } + + return Py::None(); } Py::Object View3DInventorPy::getActiveObject(const Py::Tuple& args) @@ -2519,10 +2542,10 @@ Py::Object View3DInventorPy::getActiveObject(const Py::Tuple& args) App::DocumentObject *parent = 0; std::string subname; App::DocumentObject* obj = _view->getActiveObject(name,&parent,&subname); - if(!obj) + if (!obj) return Py::None(); - if(PyObject_IsTrue(resolve)) + if (PyObject_IsTrue(resolve)) return Py::asObject(obj->getPyObject()); return Py::TupleN( diff --git a/src/Gui/View3DPy.h b/src/Gui/View3DPy.h index 71e2557bad..d84c073c34 100644 --- a/src/Gui/View3DPy.h +++ b/src/Gui/View3DPy.h @@ -87,6 +87,8 @@ public: Py::Object stopAnimating(const Py::Tuple&); Py::Object setAnimationEnabled(const Py::Tuple&); Py::Object isAnimationEnabled(const Py::Tuple&); + Py::Object setPopupMenuEnabled(const Py::Tuple&); + Py::Object isPopupMenuEnabled(const Py::Tuple&); Py::Object dump(const Py::Tuple&); Py::Object dumpNode(const Py::Tuple&); Py::Object setStereoType(const Py::Tuple&); diff --git a/src/Gui/ViewProviderDocumentObject.cpp b/src/Gui/ViewProviderDocumentObject.cpp index 0230117675..e4cc2bbb2b 100644 --- a/src/Gui/ViewProviderDocumentObject.cpp +++ b/src/Gui/ViewProviderDocumentObject.cpp @@ -196,12 +196,19 @@ void ViewProviderDocumentObject::onChanged(const App::Property* prop) // this is undesired behaviour. So, if this change marks the document as // modified then it must be be reversed. if (!testStatus(Gui::ViewStatus::TouchDocument)) { - bool mod = false; - if (pcDocument) - mod = pcDocument->isModified(); + // Note: reverting document modified status like that is not + // appropriate because we can't tell if there is any other + // property being changed due to the change of Visibility here. + // Temporary setting the Visibility property as 'NoModify' is + // the proper way. + Base::ObjectStatusLocker guard( + App::Property::NoModify, &Visibility); + // bool mod = false; + // if (pcDocument) + // mod = pcDocument->isModified(); getObject()->Visibility.setValue(Visibility.getValue()); - if (pcDocument) - pcDocument->setModified(mod); + // if (pcDocument) + // pcDocument->setModified(mod); } else { getObject()->Visibility.setValue(Visibility.getValue()); @@ -215,7 +222,10 @@ void ViewProviderDocumentObject::onChanged(const App::Property* prop) } } - if (pcDocument && !pcDocument->isModified() && testStatus(Gui::ViewStatus::TouchDocument)) { + if (prop && !prop->testStatus(App::Property::NoModify) + && pcDocument + && !pcDocument->isModified() + && testStatus(Gui::ViewStatus::TouchDocument)) { if (prop) FC_LOG(prop->getFullName() << " changed"); pcDocument->setModified(true); diff --git a/src/Gui/WidgetFactory.cpp b/src/Gui/WidgetFactory.cpp index b68456e7a4..241c35f655 100644 --- a/src/Gui/WidgetFactory.cpp +++ b/src/Gui/WidgetFactory.cpp @@ -431,7 +431,7 @@ void PyResource::load(const char* name) // checks whether it's a relative path if (fi.isRelative()) { QString cwd = QDir::currentPath (); - QString home= QDir(QString::fromUtf8(App::GetApplication().getHomePath())).path(); + QString home= QDir(QString::fromStdString(App::Application::getHomePath())).path(); // search in cwd and home path for the file // diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index 8211cca9ad..d3e9cd8076 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -2092,6 +2092,302 @@ void PropertyMatrixItem::setA44(double A44) setData(QVariant::fromValue(Base::Matrix4D(getA11(),getA12(),getA13(),getA14(),getA21(),getA22(),getA23(),getA24(),getA31(),getA32(),getA33(),getA34(),getA41(),getA42(),getA43(),A44 ))); } +// --------------------------------------------------------------- + +RotationHelper::RotationHelper() + : init_axis(false) + , changed_value(false) + , rot_angle(0) + , rot_axis(0,0,1) +{ +} + +void RotationHelper::setChanged(bool value) +{ + changed_value = value; +} + +bool RotationHelper::hasChangedAndReset() +{ + if (!changed_value) + return false; + + changed_value = false; + return true; +} + +bool RotationHelper::isAxisInitialized() const +{ + return init_axis; +} + +void RotationHelper::setValue(const Base::Vector3d& axis, double angle) +{ + rot_axis = axis; + rot_angle = angle; + init_axis = true; +} + +void RotationHelper::getValue(Base::Vector3d& axis, double& angle) const +{ + axis = rot_axis; + angle = rot_angle; +} + +double RotationHelper::getAngle(const Base::Rotation& val) const +{ + double angle; + Base::Vector3d dir; + val.getRawValue(dir, angle); + if (dir * this->rot_axis < 0.0) + angle = -angle; + return angle; +} + +Base::Rotation RotationHelper::setAngle(double angle) +{ + Base::Rotation rot; + rot.setValue(this->rot_axis, Base::toRadians(angle)); + changed_value = true; + rot_angle = angle; + return rot; +} + +Base::Vector3d RotationHelper::getAxis() const +{ + // We must store the rotation axis in a member because + // if we read the value from the property we would always + // get a normalized vector which makes it quite unhandy + // to work with + return this->rot_axis; +} + +Base::Rotation RotationHelper::setAxis(const Base::Rotation& value, const Base::Vector3d& axis) +{ + this->rot_axis = axis; + Base::Rotation rot = value; + Base::Vector3d dummy; double angle; + rot.getValue(dummy, angle); + if (dummy * axis < 0.0) + angle = -angle; + rot.setValue(axis, angle); + changed_value = true; + return rot; +} + +void RotationHelper::assignProperty(const Base::Rotation& value, double eps) +{ + double angle; + Base::Vector3d dir; + value.getRawValue(dir, angle); + Base::Vector3d cross = this->rot_axis.Cross(dir); + double len2 = cross.Sqr(); + if (angle != 0) { + // vectors are not parallel + if (len2 > eps) + this->rot_axis = dir; + // vectors point into opposite directions + else if (this->rot_axis.Dot(dir) < 0) + this->rot_axis = -this->rot_axis; + } + this->rot_angle = Base::toDegrees(angle); +} + +// --------------------------------------------------------------- + +PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyRotationItem) + +PropertyRotationItem::PropertyRotationItem() +{ + m_a = static_cast(PropertyUnitItem::create()); + m_a->setParent(this); + m_a->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Angle"))); + this->appendChild(m_a); + m_d = static_cast(PropertyVectorItem::create()); + m_d->setParent(this); + m_d->setPropertyName(QLatin1String(QT_TRANSLATE_NOOP("App::Property", "Axis"))); + m_d->setReadOnly(true); + this->appendChild(m_d); +} + +PropertyRotationItem::~PropertyRotationItem() +{ +} + +Base::Quantity PropertyRotationItem::getAngle() const +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) + return Base::Quantity(0.0); + + const Base::Rotation& val = value.value(); + double angle = h.getAngle(val); + return Base::Quantity(Base::toDegrees(angle), Base::Unit::Angle); +} + +void PropertyRotationItem::setAngle(Base::Quantity angle) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) + return; + + Base::Rotation rot = h.setAngle(angle.getValue()); + setValue(QVariant::fromValue(rot)); +} + +Base::Vector3d PropertyRotationItem::getAxis() const +{ + return h.getAxis(); +} + +void PropertyRotationItem::setAxis(const Base::Vector3d& axis) +{ + QVariant value = data(1, Qt::EditRole); + if (!value.canConvert()) + return; + + Base::Rotation rot = value.value(); + rot = h.setAxis(rot, axis); + setValue(QVariant::fromValue(rot)); +} + +void PropertyRotationItem::assignProperty(const App::Property* prop) +{ + // Choose an adaptive epsilon to avoid changing the axis when they are considered to + // be equal. See https://forum.freecadweb.org/viewtopic.php?f=10&t=24662&start=10 + double eps = std::pow(10.0, -2*(decimals()+1)); + if (prop->getTypeId().isDerivedFrom(App::PropertyRotation::getClassTypeId())) { + const Base::Rotation& value = static_cast(prop)->getValue(); + h.assignProperty(value, eps); + } +} + +QVariant PropertyRotationItem::value(const App::Property* prop) const +{ + assert(prop && prop->getTypeId().isDerivedFrom(App::PropertyRotation::getClassTypeId())); + + const Base::Rotation& value = static_cast(prop)->getValue(); + double angle; + Base::Vector3d dir; + value.getRawValue(dir, angle); + if (!h.isAxisInitialized()) { + if (m_a->hasExpression()) { + QString str = m_a->expressionAsString(); + angle = str.toDouble(); + } + else { + angle = Base::toDegrees(angle); + } + + PropertyItem* x = m_d->child(0); + PropertyItem* y = m_d->child(1); + PropertyItem* z = m_d->child(2); + if (x->hasExpression()) { + QString str = x->expressionAsString(); + dir.x = str.toDouble(); + } + if (y->hasExpression()) { + QString str = y->expressionAsString(); + dir.y = str.toDouble(); + } + if (z->hasExpression()) { + QString str = z->expressionAsString(); + dir.z = str.toDouble(); + } + h.setValue(dir, angle); + } + return QVariant::fromValue(value); +} + +QVariant PropertyRotationItem::toolTip(const App::Property* prop) const +{ + assert(prop && prop->getTypeId().isDerivedFrom(App::PropertyRotation::getClassTypeId())); + + const Base::Rotation& p = static_cast(prop)->getValue(); + double angle; + Base::Vector3d dir; + p.getRawValue(dir, angle); + angle = Base::toDegrees(angle); + + QLocale loc; + QString data = QString::fromUtf8("Axis: (%1 %2 %3)\n" + "Angle: %4") + .arg(loc.toString(dir.x,'f',decimals()), + loc.toString(dir.y,'f',decimals()), + loc.toString(dir.z,'f',decimals()), + Base::Quantity(angle, Base::Unit::Angle).getUserString()); + return QVariant(data); +} + +QVariant PropertyRotationItem::toString(const QVariant& prop) const +{ + const Base::Rotation& p = prop.value(); + double angle; + Base::Vector3d dir; + p.getRawValue(dir, angle); + angle = Base::toDegrees(angle); + + QLocale loc; + QString data = QString::fromUtf8("[(%1 %2 %3); %4]") + .arg(loc.toString(dir.x,'f',2), + loc.toString(dir.y,'f',2), + loc.toString(dir.z,'f',2), + Base::Quantity(angle, Base::Unit::Angle).getUserString()); + return QVariant(data); +} + +void PropertyRotationItem::setValue(const QVariant& value) +{ + if (!value.canConvert()) + return; + // Accept this only if the user changed the axis, angle or position but + // not if >this< item loses focus + if (!h.hasChangedAndReset()) + return; + + Base::Vector3d axis; + double angle; + h.getValue(axis, angle); + Base::QuantityFormat format(Base::QuantityFormat::Fixed, decimals()); + QString data = QString::fromLatin1("App.Rotation(App.Vector(%1,%2,%3),%4)") + .arg(Base::UnitsApi::toNumber(axis.x, format)) + .arg(Base::UnitsApi::toNumber(axis.y, format)) + .arg(Base::UnitsApi::toNumber(axis.z, format)) + .arg(Base::UnitsApi::toNumber(angle, format)); + setPropertyValue(data); +} + +QWidget* PropertyRotationItem::createEditor(QWidget* parent, const QObject* receiver, const char* method) const +{ + Q_UNUSED(parent) + Q_UNUSED(receiver) + Q_UNUSED(method) + return nullptr; +} + +void PropertyRotationItem::setEditorData(QWidget *editor, const QVariant& data) const +{ + Q_UNUSED(editor) + Q_UNUSED(data) +} + +QVariant PropertyRotationItem::editorData(QWidget *editor) const +{ + Q_UNUSED(editor) + return QVariant(); +} + +void PropertyRotationItem::propertyBound() +{ + if (isBound()) { + m_a->bind(App::ObjectIdentifier(getPath())<bind(App::ObjectIdentifier(getPath())<(PropertyUnitItem::create()); m_a->setParent(this); @@ -2195,12 +2491,9 @@ Base::Quantity PropertyPlacementItem::getAngle() const QVariant value = data(1, Qt::EditRole); if (!value.canConvert()) return Base::Quantity(0.0); + const Base::Placement& val = value.value(); - double angle; - Base::Vector3d dir; - val.getRotation().getRawValue(dir, angle); - if (dir * this->rot_axis < 0.0) - angle = -angle; + double angle = h.getAngle(val.getRotation()); return Base::Quantity(Base::toDegrees(angle), Base::Unit::Angle); } @@ -2211,21 +2504,14 @@ void PropertyPlacementItem::setAngle(Base::Quantity angle) return; Base::Placement val = value.value(); - Base::Rotation rot; - rot.setValue(this->rot_axis, Base::toRadians(angle.getValue())); + Base::Rotation rot = h.setAngle(angle.getValue()); val.setRotation(rot); - changed_value = true; - rot_angle = angle.getValue(); setValue(QVariant::fromValue(val)); } Base::Vector3d PropertyPlacementItem::getAxis() const { - // We must store the rotation axis in a member because - // if we read the value from the property we would always - // get a normalized vector which makes it quite unhandy - // to work with - return this->rot_axis; + return h.getAxis(); } void PropertyPlacementItem::setAxis(const Base::Vector3d& axis) @@ -2233,16 +2519,11 @@ void PropertyPlacementItem::setAxis(const Base::Vector3d& axis) QVariant value = data(1, Qt::EditRole); if (!value.canConvert()) return; - this->rot_axis = axis; + Base::Placement val = value.value(); Base::Rotation rot = val.getRotation(); - Base::Vector3d dummy; double angle; - rot.getValue(dummy, angle); - if (dummy * axis < 0.0) - angle = -angle; - rot.setValue(axis, angle); + rot = h.setAxis(rot, axis); val.setRotation(rot); - changed_value = true; setValue(QVariant::fromValue(val)); } @@ -2260,9 +2541,10 @@ void PropertyPlacementItem::setPosition(const Base::Vector3d& pos) QVariant value = data(1, Qt::EditRole); if (!value.canConvert()) return; + Base::Placement val = value.value(); val.setPosition(pos); - changed_value = true; + h.setChanged(true); setValue(QVariant::fromValue(val)); } @@ -2273,20 +2555,7 @@ void PropertyPlacementItem::assignProperty(const App::Property* prop) double eps = std::pow(10.0, -2*(decimals()+1)); if (prop->getTypeId().isDerivedFrom(App::PropertyPlacement::getClassTypeId())) { const Base::Placement& value = static_cast(prop)->getValue(); - double angle; - Base::Vector3d dir; - value.getRotation().getRawValue(dir, angle); - Base::Vector3d cross = this->rot_axis.Cross(dir); - double len2 = cross.Sqr(); - if (angle != 0) { - // vectors are not parallel - if (len2 > eps) - this->rot_axis = dir; - // vectors point into opposite directions - else if (this->rot_axis.Dot(dir) < 0) - this->rot_axis = -this->rot_axis; - } - this->rot_angle = Base::toDegrees(angle); + h.assignProperty(value.getRotation(), eps); } } @@ -2298,13 +2567,13 @@ QVariant PropertyPlacementItem::value(const App::Property* prop) const double angle; Base::Vector3d dir; value.getRotation().getRawValue(dir, angle); - if (!init_axis) { + if (!h.isAxisInitialized()) { if (m_a->hasExpression()) { QString str = m_a->expressionAsString(); - const_cast(this)->rot_angle = str.toDouble(); + angle = str.toDouble(); } else { - const_cast(this)->rot_angle = Base::toDegrees(angle); + angle = Base::toDegrees(angle); } PropertyItem* x = m_d->child(0); @@ -2322,8 +2591,7 @@ QVariant PropertyPlacementItem::value(const App::Property* prop) const QString str = z->expressionAsString(); dir.z = str.toDouble(); } - const_cast(this)->rot_axis = dir; - const_cast(this)->init_axis = true; + h.setValue(dir, angle); } return QVariant::fromValue(value); } @@ -2380,12 +2648,16 @@ void PropertyPlacementItem::setValue(const QVariant& value) return; // Accept this only if the user changed the axis, angle or position but // not if >this< item loses focus - if (!changed_value) + if (!h.hasChangedAndReset()) return; - changed_value = false; + const Base::Placement& val = value.value(); Base::Vector3d pos = val.getPosition(); + Base::Vector3d axis; + double angle; + h.getValue(axis, angle); + Base::QuantityFormat format(Base::QuantityFormat::Fixed, decimals()); QString data = QString::fromLatin1("App.Placement(" "App.Vector(%1,%2,%3)," @@ -2393,10 +2665,10 @@ void PropertyPlacementItem::setValue(const QVariant& value) .arg(Base::UnitsApi::toNumber(pos.x, format)) .arg(Base::UnitsApi::toNumber(pos.y, format)) .arg(Base::UnitsApi::toNumber(pos.z, format)) - .arg(Base::UnitsApi::toNumber(rot_axis.x, format)) - .arg(Base::UnitsApi::toNumber(rot_axis.y, format)) - .arg(Base::UnitsApi::toNumber(rot_axis.z, format)) - .arg(Base::UnitsApi::toNumber(rot_angle, format)); + .arg(Base::UnitsApi::toNumber(axis.x, format)) + .arg(Base::UnitsApi::toNumber(axis.y, format)) + .arg(Base::UnitsApi::toNumber(axis.z, format)) + .arg(Base::UnitsApi::toNumber(angle, format)); setPropertyValue(data); } diff --git a/src/Gui/propertyeditor/PropertyItem.h b/src/Gui/propertyeditor/PropertyItem.h index 8fe76df91f..9483b4d3b6 100644 --- a/src/Gui/propertyeditor/PropertyItem.h +++ b/src/Gui/propertyeditor/PropertyItem.h @@ -40,6 +40,7 @@ #include #include #include +#include #ifdef Q_MOC_RUN Q_DECLARE_METATYPE(Base::Vector3f) @@ -47,6 +48,7 @@ Q_DECLARE_METATYPE(Base::Vector3d) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(Base::Matrix4D) Q_DECLARE_METATYPE(Base::Placement) +Q_DECLARE_METATYPE(Base::Rotation) Q_DECLARE_METATYPE(Base::Quantity) Q_DECLARE_METATYPE(QList) #endif @@ -656,6 +658,65 @@ private: PropertyFloatItem* m_a44; }; +class RotationHelper +{ +public: + RotationHelper(); + void setChanged(bool); + bool hasChangedAndReset(); + bool isAxisInitialized() const; + void setValue(const Base::Vector3d& axis, double angle); + void getValue(Base::Vector3d& axis, double& angle) const; + double getAngle(const Base::Rotation& val) const; + Base::Rotation setAngle(double); + Base::Vector3d getAxis() const; + Base::Rotation setAxis(const Base::Rotation& value, const Base::Vector3d& axis); + void assignProperty(const Base::Rotation& value, double eps); + +private: + bool init_axis; + bool changed_value; + double rot_angle; + Base::Vector3d rot_axis; +}; + +/** + * Edit properties of rotation type. + * \author Werner Mayer + */ +class GuiExport PropertyRotationItem: public PropertyItem +{ + Q_OBJECT + Q_PROPERTY(Base::Quantity Angle READ getAngle WRITE setAngle DESIGNABLE true USER true) + Q_PROPERTY(Base::Vector3d Axis READ getAxis WRITE setAxis DESIGNABLE true USER true) + PROPERTYITEM_HEADER + + virtual QWidget* createEditor(QWidget* parent, const QObject* receiver, const char* method) const; + virtual void setEditorData(QWidget *editor, const QVariant& data) const; + virtual QVariant editorData(QWidget *editor) const; + + virtual void propertyBound(); + virtual void assignProperty(const App::Property*); + + Base::Quantity getAngle() const; + void setAngle(Base::Quantity); + Base::Vector3d getAxis() const; + void setAxis(const Base::Vector3d&); + +protected: + PropertyRotationItem(); + ~PropertyRotationItem(); + virtual QVariant toolTip(const App::Property*) const; + virtual QVariant toString(const QVariant&) const; + virtual QVariant value(const App::Property*) const; + virtual void setValue(const QVariant&); + +private: + mutable RotationHelper h; + PropertyUnitItem * m_a; + PropertyVectorItem* m_d; +}; + class PlacementEditor : public Gui::LabelButton { Q_OBJECT @@ -711,10 +772,7 @@ protected: virtual void setValue(const QVariant&); private: - bool init_axis; - bool changed_value; - double rot_angle; - Base::Vector3d rot_axis; + mutable RotationHelper h; PropertyUnitItem * m_a; PropertyVectorItem* m_d; PropertyVectorDistanceItem* m_p; diff --git a/src/Main/MainCmd.cpp b/src/Main/MainCmd.cpp index fa230acacc..ae4e36ffd9 100644 --- a/src/Main/MainCmd.cpp +++ b/src/Main/MainCmd.cpp @@ -108,7 +108,7 @@ int main( int argc, char ** argv ) std::string appName = App::Application::Config()["ExeName"]; std::stringstream msg; msg << "While initializing " << appName << " the following exception occurred: '" << e.what() << "'\n\n"; - msg << "Python is searching for its runtime files in the following directories:\n" << Py_GetPath() << "\n\n"; + msg << "Python is searching for its runtime files in the following directories:\n" << Py_EncodeLocale(Py_GetPath(),nullptr) << "\n\n"; msg << "Python version information:\n" << Py_GetVersion() << "\n"; const char* pythonhome = getenv("PYTHONHOME"); if ( pythonhome ) { diff --git a/src/Mod/Arch/ArchStructure.py b/src/Mod/Arch/ArchStructure.py index e7f3e385b5..0b08fc4a73 100644 --- a/src/Mod/Arch/ArchStructure.py +++ b/src/Mod/Arch/ArchStructure.py @@ -677,9 +677,9 @@ class _Structure(ArchComponent.Component): if not "ComputedLength" in pl: obj.addProperty("App::PropertyDistance", "ComputedLength", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "The computed length of the extrusion path"), 1) if not "ToolOffsetFirst" in pl: - obj.addProperty("App::PropertyDistance", "ToolOffsetFirst", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "Start offset distance along the extrusion path (positive: extend, negative: trim")) + obj.addProperty("App::PropertyDistance", "ToolOffsetFirst", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "Start offset distance along the extrusion path (positive: extend, negative: trim)")) if not "ToolOffsetLast" in pl: - obj.addProperty("App::PropertyDistance", "ToolOffsetLast", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "End offset distance along the extrusion path (positive: extend, negative: trim")) + obj.addProperty("App::PropertyDistance", "ToolOffsetLast", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "End offset distance along the extrusion path (positive: extend, negative: trim)")) if not "BasePerpendicularToTool" in pl: obj.addProperty("App::PropertyBool", "BasePerpendicularToTool", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "Automatically align the Base of the Structure perpendicular to the Tool axis")) if not "BaseOffsetX" in pl: diff --git a/src/Mod/Arch/exportIFC.py b/src/Mod/Arch/exportIFC.py index 3043103f40..6c4fc01e52 100644 --- a/src/Mod/Arch/exportIFC.py +++ b/src/Mod/Arch/exportIFC.py @@ -961,7 +961,7 @@ def export(exportList, filename, colors=None, preferences=None): for c in objs: if c.Name in products and c.Name not in treated: prod = products[c.Name] - if prod.is_a()=='IfcSpace': + if prod.is_a() == 'IfcSpace': spaces.append(prod) else: buildingelements.append(prod) @@ -1193,7 +1193,7 @@ def export(exportList, filename, colors=None, preferences=None): if defaulthost: spaces, buildingelements = [],[] for entity in untreated: - if entity.is_a()=="IfcSpace": + if entity.is_a() == "IfcSpace": spaces.append(entity) else: buildingelements.append(entity) @@ -1214,7 +1214,7 @@ def export(exportList, filename, colors=None, preferences=None): '', buildingelements, defaulthost - ) + ) else: # no default host: aggregate unassigned objects directly under the IfcProject - WARNING: NON STANDARD if preferences['DEBUG']: print("WARNING - Default building generation is disabled. You are producing a non-standard file.") @@ -1258,7 +1258,7 @@ def export(exportList, filename, colors=None, preferences=None): rgb = tuple([float(f) for f in m.Material[colorslot].strip("()").split(",")]) break if rgb: - psa = ifcbin.createIfcPresentationStyleAssignment(l,rgb[0],rgb[1],rgb[2],ifc4=(preferences["SCHEMA"]=="IFC4")) + psa = ifcbin.createIfcPresentationStyleAssignment(l,rgb[0],rgb[1],rgb[2],ifc4=(preferences["SCHEMA"] == "IFC4")) isi = ifcfile.createIfcStyledItem(None,[psa],None) isr = ifcfile.createIfcStyledRepresentation(context,"Style","Material",[isi]) imd = ifcfile.createIfcMaterialDefinitionRepresentation(None,None,[isr],mat) @@ -1648,7 +1648,7 @@ def getIfcTypeFromObj(obj): if ifctype in translationtable.keys(): ifctype = translationtable[ifctype] - if not "::" in ifctype: + if "::" not in ifctype: ifctype = "Ifc" + ifctype elif ifctype == "IfcApp::DocumentObjctGroup": ifctype = "IfcGroup" @@ -1737,7 +1737,6 @@ def buildAddress(obj,ifcfile): def createCurve(ifcfile,wire,scaling=1.0): - "creates an IfcCompositeCurve from a shape" segments = [] @@ -1810,7 +1809,6 @@ def createCurve(ifcfile,wire,scaling=1.0): def getEdgesAngle(edge1, edge2): - """ getEdgesAngle(edge1, edge2): returns a angle between two edges.""" vec1 = vec(edge1) @@ -1821,7 +1819,6 @@ def getEdgesAngle(edge1, edge2): def checkRectangle(edges): - """ checkRectangle(edges=[]): This function checks whether the given form is a rectangle or not. It will return True when edges form a rectangular shape or return False when edges do not form a rectangular shape.""" @@ -1841,7 +1838,6 @@ def checkRectangle(edges): def getProfile(ifcfile,p): - """returns an IFC profile definition from a shape""" import Part @@ -1905,8 +1901,18 @@ def getProfile(ifcfile,p): return profile -def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1,colors=None,preferences=None,forceclone=False,skipshape=False): - +def getRepresentation( + ifcfile, + context, + obj, + forcebrep=False, + subtraction=False, + tessellation=1, + colors=None, + preferences=None, + forceclone=False, + skipshape=False +): """returns an IfcShapeRepresentation object or None. forceclone can be False (does nothing), "store" or True (stores the object as clone base) or a Vector (creates a clone)""" @@ -1924,7 +1930,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess if ((not subtraction) and (not forcebrep)) or forceclone: if forceclone: - if not obj.Name in clones: + if obj.Name not in clones: clones[obj.Name] = [] for k,v in clones.items(): if (obj.Name == k) or (obj.Name in v): @@ -2051,7 +2057,6 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess solidType = "SweptSolid" shapetype = "extrusion" - if (not shapes) and (not skipshape): # check if we keep a null shape (additions-only object) @@ -2382,7 +2387,6 @@ def getText(field,obj): def getAxisContext(ifcfile): - """gets or creates an axis context""" contexts = ifcfile.by_type("IfcGeometricRepresentationContext") @@ -2393,12 +2397,11 @@ def getAxisContext(ifcfile): if ctx.ContextIdentifier == "Axis": return ctx ctx = contexts[0] # arbitrarily take the first one... - nctx = ifcfile.createIfcGeometricRepresentationSubContext('Axis','Model',None,None,None,None,ctx,None,"MODEL_VIEW",None); + nctx = ifcfile.createIfcGeometricRepresentationSubContext('Axis','Model',None,None,None,None,ctx,None,"MODEL_VIEW",None) return nctx def createAxis(ifcfile,obj,preferences): - """Creates an axis for a given wall, if applicable""" if hasattr(obj,"Base") and hasattr(obj.Base,"Shape") and obj.Base.Shape: @@ -2412,7 +2415,6 @@ def createAxis(ifcfile,obj,preferences): def writeJson(filename,ifcfile): - """writes an .ifcjson file""" import json diff --git a/src/Mod/Arch/exportIFCHelper.py b/src/Mod/Arch/exportIFCHelper.py index c9479d66b4..fabf327516 100644 --- a/src/Mod/Arch/exportIFCHelper.py +++ b/src/Mod/Arch/exportIFCHelper.py @@ -38,18 +38,17 @@ def getObjectsOfIfcType(objects, ifcType): def writeUnits(ifcfile,unit="metre"): - """adds additional units settings to the given ifc file if needed""" # so far, only metre or foot possible (which is all revit knows anyway) if unit == "foot": - d1 = ifcfile.createIfcDimensionalExponents(1,0,0,0,0,0,0); + d1 = ifcfile.createIfcDimensionalExponents(1,0,0,0,0,0,0) d2 = ifcfile.createIfcMeasureWithUnit(ifcfile.createIfcRatioMeasure(0.3048),ifcfile[13]) d3 = ifcfile.createIfcConversionBasedUnit(d1,'LENGTHUNIT','FOOT',d2) - d4 = ifcfile.createIfcDimensionalExponents(2,0,0,0,0,0,0); + d4 = ifcfile.createIfcDimensionalExponents(2,0,0,0,0,0,0) d5 = ifcfile.createIfcMeasureWithUnit(ifcfile.createIfcRatioMeasure(0.09290304000000001),ifcfile[14]) d6 = ifcfile.createIfcConversionBasedUnit(d4,'AREAUNIT','SQUARE FOOT',d5) - d7 = ifcfile.createIfcDimensionalExponents(3,0,0,0,0,0,0); + d7 = ifcfile.createIfcDimensionalExponents(3,0,0,0,0,0,0) d8 = ifcfile.createIfcMeasureWithUnit(ifcfile.createIfcRatioMeasure(0.028316846592),ifcfile[15]) d9 = ifcfile.createIfcConversionBasedUnit(d7,'VOLUMEUNIT','CUBIC FOOT',d8) ifcfile.createIfcUnitAssignment((d3,d6,d9,ifcfile[18])) diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index 6162d3dfcd..718a43e5bf 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -170,11 +170,12 @@ def getPreferences(): 'MERGE_MODE_STRUCT': p.GetInt("ifcImportModeStruct", 1), 'CREATE_CLONES': p.GetBool("ifcCreateClones", True), 'IMPORT_PROPERTIES': p.GetBool("ifcImportProperties", False), - 'SPLIT_LAYERS': p.GetBool("ifcSplitLayers", False), + 'SPLIT_LAYERS': p.GetBool("ifcSplitLayers", False), # wall layer, not layer for visual props 'FITVIEW_ONIMPORT': p.GetBool("ifcFitViewOnImport", False), 'ALLOW_INVALID': p.GetBool("ifcAllowInvalid", False), 'REPLACE_PROJECT': p.GetBool("ifcReplaceProject", False), - 'MULTICORE': p.GetInt("ifcMulticore", 0) + 'MULTICORE': p.GetInt("ifcMulticore", 0), + 'IMPORT_LAYER': p.GetBool("ifcImportLayer", True) } if preferences['MERGE_MODE_ARCH'] > 0: @@ -414,7 +415,6 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): else: if preferences['DEBUG']: print(" no layer found", ptype,end="") - # checking for full FreeCAD parametric definition, overriding everything else if psets and FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("IfcImportFreeCADProperties",False): if "FreeCADPropertySet" in [ifcfile[pset].Name for pset in psets.keys()]: @@ -464,7 +464,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if ptype in preferences['SKIP']: # preferences-set type skip list if preferences['DEBUG']: print(" skipped.") continue - if preferences['REPLACE_PROJECT']: # options-enabled project/site/building skip + if preferences['REPLACE_PROJECT']: # options-enabled project/site/building skip if ptype in ['IfcProject','IfcSite']: if preferences['DEBUG']: print(" skipped.") continue @@ -521,7 +521,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if shape.isNull() and (not preferences['ALLOW_INVALID']): if preferences['DEBUG']: print("null shape ",end="") - elif not shape.isValid() and (not preferences['ALLOW_INVALID']): + elif not shape.isValid() and (not preferences['ALLOW_INVALID']): if preferences['DEBUG']: print("invalid shape ",end="") else: @@ -613,7 +613,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): baseface = Draft.makeCircle(ex[0].Edges[0]) else: # curves or holes? We just make a Part face - baseface = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_footprint") + baseface = doc.addObject("Part::Feature",name+"_footprint") # bug/feature in ifcopenshell? Some faces of a shell may have non-null placement # workaround to remove the bad placement: exporting/reimporting as step if not ex[0].Placement.isNull(): @@ -639,7 +639,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if profileid: # store for possible shared use profiles[profileid] = baseface - baseobj = FreeCAD.ActiveDocument.addObject("Part::Extrusion",name+"_body") + baseobj = doc.addObject("Part::Extrusion",name+"_body") baseobj.Base = baseface if addplacement: # apply delta placement (stored profile) @@ -650,7 +650,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if FreeCAD.GuiUp: baseface.ViewObject.hide() if (not baseobj): - baseobj = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_body") + baseobj = doc.addObject("Part::Feature",name+"_body") baseobj.Shape = shape else: # this object has no shape (storeys, etc...) @@ -702,7 +702,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): obj.Height = baseobj.Dir.Length obj.Normal = FreeCAD.Vector(baseobj.Dir).normalize() bn = baseobj.Name - FreeCAD.ActiveDocument.removeObject(bn) + doc.removeObject(bn) if (freecadtype in ["Structure","Wall"]) and not baseobj: # remove sizes to prevent auto shape creation for types that don't require a base object obj.Height = 0 @@ -823,7 +823,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if product.Elevation: obj.Placement.Base.z = product.Elevation * ifcscale elif baseobj: - obj = FreeCAD.ActiveDocument.addObject("Part::Feature",name) + obj = doc.addObject("Part::Feature",name) obj.Shape = shape elif pid in additions: # no baseobj but in additions, thus we make a BuildingPart container @@ -914,15 +914,15 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if (pid in colors) and colors[pid]: colordict[obj.Name] = colors[pid] if FreeCAD.GuiUp: - # if preferences['DEBUG']: print(" setting color: ",int(colors[pid][0]*255),"/",int(colors[pid][1]*255),"/",int(colors[pid][2]*255)) + # if preferences['DEBUG']: + # print(" setting color: ",int(colors[pid][0]*255),"/",int(colors[pid][1]*255),"/",int(colors[pid][2]*255)) if hasattr(obj.ViewObject,"ShapeColor"): obj.ViewObject.ShapeColor = tuple(colors[pid][0:3]) if hasattr(obj.ViewObject,"Transparency"): obj.ViewObject.Transparency = colors[pid][3] - # if preferences['DEBUG'] is on, recompute after each shape - if preferences['DEBUG']: FreeCAD.ActiveDocument.recompute() + if preferences['DEBUG']: doc.recompute() # attached 2D elements @@ -961,7 +961,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): # But that would actually be an invalid IFC file, because the magnitude # of the (twodimensional) direction vector for TrueNorth shall be greater than zero. (x, y) = modelRC.TrueNorth.DirectionRatios[:2] - obj.Declination = ((math.degrees(math.atan2(y,x))-90+180)%360)-180 + obj.Declination = ((math.degrees(math.atan2(y,x))-90+180) % 360)-180 if (FreeCAD.GuiUp): obj.ViewObject.CompassRotation.Value = obj.Declination @@ -970,11 +970,11 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): except(RuntimeError): print("Aborted.") progressbar.stop() - FreeCAD.ActiveDocument.recompute() + doc.recompute() return progressbar.stop() - FreeCAD.ActiveDocument.recompute() + doc.recompute() if preferences['MERGE_MODE_STRUCT'] == 2: @@ -990,11 +990,11 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if compound: name = ifcfile[host].Name or "AnalysisModel" if preferences['PREFIX_NUMBERS']: name = "ID" + str(host) + " " + name - obj = FreeCAD.ActiveDocument.addObject("Part::Feature",name) + obj = doc.addObject("Part::Feature",name) obj.Label = name obj.Shape = Part.makeCompound(compound) if structshapes: # remaining Structural shapes - obj = FreeCAD.ActiveDocument.addObject("Part::Feature","UnclaimedStruct") + obj = doc.addObject("Part::Feature","UnclaimedStruct") obj.Shape = Part.makeCompound(structshapes.values()) if preferences['DEBUG']: print("done") @@ -1008,7 +1008,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): for host,children in groups.items(): if ifcfile[host].is_a("IfcStructuralAnalysisModel"): # print(host, ' --> ', children) - obj = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup","AnalysisModel") + obj = doc.addObject("App::DocumentObjectGroup","AnalysisModel") objects[host] = obj if host in objects.keys(): cobs = [] @@ -1022,12 +1022,12 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if cobs: if preferences['DEBUG']: print("adding ",len(cobs), " object(s) to ", objects[host].Label) Arch.addComponents(cobs,objects[host]) - if preferences['DEBUG']: FreeCAD.ActiveDocument.recompute() + if preferences['DEBUG']: doc.recompute() if preferences['DEBUG']: print("done") if preferences['MERGE_MODE_ARCH'] > 2: # if ArchObj is compound or ArchObj not imported - FreeCAD.ActiveDocument.recompute() + doc.recompute() # cleaning bad shapes for obj in objects.values(): @@ -1048,7 +1048,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): grp_name = ifcfile[host].is_a() + "_" + str(ifcfile[host].id()) if six.PY2: grp_name = grp_name.encode("utf8") - grp = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup",grp_name) + grp = doc.addObject("App::DocumentObjectGroup",grp_name) grp.Label = grp_name objects[host] = grp for child in children: @@ -1079,11 +1079,11 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if compound: name = ifcfile[host].Name or "Floor" if preferences['PREFIX_NUMBERS']: name = "ID" + str(host) + " " + name - obj = FreeCAD.ActiveDocument.addObject("Part::Feature",name) + obj = doc.addObject("Part::Feature",name) obj.Label = name obj.Shape = Part.makeCompound(compound) if shapes: # remaining Arch shapes - obj = FreeCAD.ActiveDocument.addObject("Part::Feature","UnclaimedArch") + obj = doc.addObject("Part::Feature","UnclaimedArch") obj.Shape = Part.makeCompound(shapes.values()) if preferences['DEBUG']: print("done") @@ -1103,7 +1103,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): first = False if preferences['DEBUG']: print(" subtracting",objects[subtraction[0]].Label, "from", objects[subtraction[1]].Label) Arch.removeComponents(objects[subtraction[0]],objects[subtraction[1]]) - if preferences['DEBUG']: FreeCAD.ActiveDocument.recompute() + if preferences['DEBUG']: doc.recompute() # additions @@ -1122,17 +1122,21 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if preferences['DEBUG'] and first: print("") first = False - if preferences['DEBUG'] and (len(cobs) > 10) and (not(Draft.getType(objects[host]) in ["Site","Building","Floor","BuildingPart","Project"])): + if ( + preferences['DEBUG'] + and (len(cobs) > 10) + and (not(Draft.getType(objects[host]) in ["Site","Building","Floor","BuildingPart","Project"])) + ): # avoid huge fusions print("more than 10 shapes to add: skipping.") else: if preferences['DEBUG']: print(" adding",len(cobs), "object(s) to", objects[host].Label) Arch.addComponents(cobs,objects[host]) - if preferences['DEBUG']: FreeCAD.ActiveDocument.recompute() + if preferences['DEBUG']: doc.recompute() if preferences['DEBUG'] and first: print("done.") - FreeCAD.ActiveDocument.recompute() + doc.recompute() # cleaning bad shapes @@ -1141,7 +1145,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if obj.Shape.isNull() and not(Draft.getType(obj) in ["Site","Project"]): Arch.rebuildArchShape(obj) - FreeCAD.ActiveDocument.recompute() + doc.recompute() # 2D elements @@ -1217,7 +1221,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): for rep in annotation.Representation.Representations: if rep.RepresentationIdentifier in ["Annotation","FootPrint","Axis"]: sh = importIFCHelper.get2DShape(rep,ifcscale) - if sh in FreeCAD.ActiveDocument.Objects: + if sh in doc.Objects: # dirty hack: get2DShape might return an object directly if non-shape based (texts for ex) anno = sh else: @@ -1225,7 +1229,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if shapes2d: sh = Part.makeCompound(shapes2d) if preferences['DEBUG']: print(" shape") - anno = FreeCAD.ActiveDocument.addObject("Part::Feature",name) + anno = doc.addObject("Part::Feature",name) anno.Shape = sh p = importIFCHelper.getPlacement(annotation.ObjectPlacement,ifcscale) if p: # and annotation.is_a("IfcAnnotation"): @@ -1243,7 +1247,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): if (aid in children) and (host in objects.keys()): Arch.addComponents(anno,objects[host]) - FreeCAD.ActiveDocument.recompute() + doc.recompute() # Materials @@ -1252,48 +1256,69 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): # print("colors:",colors) # print("mattable:",mattable) # print("materials:",materials) - fcmats = {} + added_mats = [] for material in materials: # print(material.id()) - # get and set material name + + mdict = {} + # on ifc import only the "Description" and "DiffuseColor" (if it was read) of the material dictionary will be initialized + # on editing material in Arch Gui a lot more keys of the material dictionary are initialized even with empty values + # TODO: there should be a generic material obj init method which will be used by Arch Gui, import IFC, FEM, etc + + # get the material name name = "Material" if material.Name: name = material.Name if six.PY2: name = name.encode("utf8") + # mdict["Name"] = name on duplicate material names in IFC this could result in crash + # https://forum.freecadweb.org/viewtopic.php?f=23&t=63260 + # thus use "Description" + mdict["Description"] = name + # get material color - # the "DiffuseColor" of a material should never be 'None' + # the "DiffuseColor" of a material should never be "None" # values in colors are None if something went wrong # thus the "DiffuseColor" will only be set if the color is not None - mdict = {} + mat_color = None if material.id() in colors and colors[material.id()] is not None: - mdict["DiffuseColor"] = str(colors[material.id()]) + mat_color = str(colors[material.id()]) else: for o,m in mattable.items(): if m == material.id(): if o in colors and colors[o] is not None: - mdict["DiffuseColor"] = str(colors[o]) + mat_color = str(colors[o]) + if mat_color is not None: + mdict["DiffuseColor"] = mat_color + else: + if preferences['DEBUG']: print("/n no color for material: {}, ".format(str(material.id)),end="") + # merge materials with same name and color if setting in prefs is True add_material = True - if preferences['MERGE_MATERIALS']: - for key in list(fcmats.keys()): - if key.startswith(name) \ - and "DiffuseColor" in mdict and "DiffuseColor" in fcmats[key].Material \ - and mdict["DiffuseColor"] == fcmats[key].Material["DiffuseColor"]: - mat = fcmats[key] + if preferences["MERGE_MATERIALS"]: + for added_mat in added_mats: + if ( + "Description" in added_mat.Material + and "DiffuseColor" in added_mat.Material + and "DiffuseColor" in mdict # Description has been set thus it is in mdict + and added_mat.Material["Description"] == mdict["Description"] + and added_mat.Material["DiffuseColor"] == mdict["DiffuseColor"] + ): + matobj = added_mat add_material = False + break + # add a new material object if add_material is True: - mat = Arch.makeMaterial(name=name) - if mdict: - mat.Material = mdict - fcmats[mat.Name] = mat + matobj = Arch.makeMaterial(name=name) + matobj.Material = mdict + added_mats.append(matobj) # fill material attribute of the objects for o,m in mattable.items(): if m == material.id(): if o in objects: if hasattr(objects[o],"Material"): - objects[o].Material = mat + objects[o].Material = matobj if FreeCAD.GuiUp: # the reason behind ... # there are files around in which the material color is different from the shape color @@ -1312,44 +1337,58 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): print("\nobject color != material color for object: ", o) print(" material color is used (most software uses shape color)") print(" obj: ", o, "label: ", objects[o].Label, " col: ", sh_color) - print(" mat: ", m, "label: ", mat.Label, " col: ", ma_color) + print(" mat: ", m, "label: ", matobj.Label, " col: ", ma_color) # print(" ", ifcfile[o]) # print(" ", ifcfile[m]) # print(" colors:") # print(" ", o, ": ", colors[o]) # print(" ", m, ": ", colors[m]) - if preferences['DEBUG'] and materials: print("done") + # Grouping everything if required + + # has to be before the Layer + # if REPLACE_PROJECT and only one storey and one building both are omitted + # the pure objects do not belong to any container, they will be added here + # if after Layer they are linked by Layer and will not be added here + if preferences["REPLACE_PROJECT"] and filename: + rootgroup = doc.addObject("App::DocumentObjectGroup","Group") + rootgroup.Label = os.path.basename(filename) + # print(objects) + for key,obj in objects.items(): + # only add top-level objects + if not obj.InList: + rootgroup.addObject(obj) + # Layers if preferences['DEBUG'] and layers: print("Creating layers...", end="") # print(layers) for layer_name, layer_objects in layers.items(): + if preferences["IMPORT_LAYER"] is False: + continue + # the method make_layer does some nasty debug prints lay = Draft.make_layer(layer_name) + # ShapeColor and LineColor are not set, thus some some default values are used + # do not override the imported ShapeColor and LineColor with default layer values + if FreeCAD.GuiUp: + lay.ViewObject.OverrideLineColorChildren = False + lay.ViewObject.OverrideShapeColorChildren = False lay_grp = [] for lobj_id in layer_objects: if lobj_id in objects: lay_grp.append(objects[lobj_id]) lay.Group = lay_grp - FreeCAD.ActiveDocument.recompute() + doc.recompute() if preferences['DEBUG'] and layers: print("done") # restore links from full parametric definitions + for p in parametrics: - l = FreeCAD.ActiveDocument.getObject(p[2]) + l = doc.getObject(p[2]) if l: setattr(p[0],p[1],l) - # Grouping everything if required - if preferences['REPLACE_PROJECT'] and filename: - rootgroup = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup","Group") - rootgroup.Label = os.path.basename(filename) - for key,obj in objects.items(): - # only add top-level objects - if not obj.InList: - rootgroup.addObject(obj) - # Save colordict in non-GUI mode if colordict and not FreeCAD.GuiUp: import json @@ -1357,7 +1396,7 @@ def insert(srcfile, docname, skip=[], only=[], root=None, preferences=None): d["colordict"] = json.dumps(colordict) doc.Meta = d - FreeCAD.ActiveDocument.recompute() + doc.recompute() if FreeCAD.GuiUp and ZOOMOUT: Gui.SendMsgToActiveView("ViewFit") diff --git a/src/Mod/Arch/importIFCHelper.py b/src/Mod/Arch/importIFCHelper.py index 22e15360c5..e71cccd60a 100644 --- a/src/Mod/Arch/importIFCHelper.py +++ b/src/Mod/Arch/importIFCHelper.py @@ -396,6 +396,7 @@ def getColorFromProduct(product): if color: return color + def getColorFromMaterial(material): if material.HasRepresentation: @@ -540,6 +541,8 @@ def predefined_to_rgb(rgb_color): # ************************************************************************************************ # property related methods + + def buildRelProperties(ifcfile): """ Builds and returns a dictionary of {object:[properties]} from an IFC file @@ -814,6 +817,7 @@ def get2DShape(representation,scaling=1000): result.append(e) elif el.is_a("IfcIndexedPolyCurve"): coords = el.Points.CoordList + def index2points(segment): pts = [] for i in segment.wrappedValue: @@ -898,7 +902,6 @@ def isRectangle(verts): def createFromProperties(propsets,ifcfile,parametrics): - """ Creates a FreeCAD parametric object from a set of properties. """ @@ -986,7 +989,6 @@ def createFromProperties(propsets,ifcfile,parametrics): def applyColorDict(doc,colordict=None): - """applies the contents of a color dict to the objects in the given doc. If no colordict is given, the doc Meta property is searched for a "colordict" entry.""" @@ -1007,7 +1009,6 @@ def applyColorDict(doc,colordict=None): def getParents(ifcobj): - """finds the parent entities of an IFC entity""" parentlist = [] diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py index 842af542a3..b59bf0333b 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -473,9 +473,11 @@ class DraftToolBar: self.xValue.setText(FreeCAD.Units.Quantity(0,FreeCAD.Units.Length).UserString) self.labely = self._label("labely", yl) self.yValue = self._inputfield("yValue", yl) + self.yValue.installEventFilter(self.baseWidget) # Required to detect snap cycling in case of Y constraining. self.yValue.setText(FreeCAD.Units.Quantity(0,FreeCAD.Units.Length).UserString) self.labelz = self._label("labelz", zl) self.zValue = self._inputfield("zValue", zl) + self.zValue.installEventFilter(self.baseWidget) # Required to detect snap cycling in case of Z constraining. self.zValue.setText(FreeCAD.Units.Quantity(0,FreeCAD.Units.Length).UserString) self.pointButton = self._pushbutton("addButton", bl, icon="Draft_AddPoint") @@ -498,6 +500,7 @@ class DraftToolBar: self.layout.addLayout(al) self.labellength = self._label("labellength", ll) self.lengthValue = self._inputfield("lengthValue", ll) + self.lengthValue.installEventFilter(self.baseWidget) # Required to detect snap cycling if focusOnLength is True. self.lengthValue.setText(FreeCAD.Units.Quantity(0,FreeCAD.Units.Length).UserString) self.labelangle = self._label("labelangle", al) self.angleLock = self._checkbox("angleLock",al,checked=self.alock) diff --git a/src/Mod/Draft/Resources/ui/preferences-dwg.ui b/src/Mod/Draft/Resources/ui/preferences-dwg.ui index 4d97ab91cc..2f6e1572fd 100644 --- a/src/Mod/Draft/Resources/ui/preferences-dwg.ui +++ b/src/Mod/Draft/Resources/ui/preferences-dwg.ui @@ -38,7 +38,7 @@ - This is the method FreeCAD will use to convert DWG files to DXF. If "Automatic" is chosen, FreeCAD will try to find one of the following convertors in the same order as they are shown here. If FreeCAD is unable to find any, you might need to choose a specific convertor and indicate its path here under. Choose the "dwg2dxf" utility if using LibreDWG, "ODAFileConverter" if using the ODA file converter, or the "dwg2dwg" utility if using the pro version of QCAD. + This is the method FreeCAD will use to convert DWG files to DXF. If "Automatic" is chosen, FreeCAD will try to find one of the following converters in the same order as they are shown here. If FreeCAD is unable to find any, you might need to choose a specific converter and indicate its path here under. Choose the "dwg2dxf" utility if using LibreDWG, "ODAFileConverter" if using the ODA file converter, or the "dwg2dwg" utility if using the pro version of QCAD. DWGConversion @@ -82,7 +82,7 @@ - The path to your ODA (formerly Teigha) File Converter executable + The path to your DWG file converter executable TeighaFileConverter diff --git a/src/Mod/Draft/WorkingPlane.py b/src/Mod/Draft/WorkingPlane.py index af53f6695c..b155d74c13 100644 --- a/src/Mod/Draft/WorkingPlane.py +++ b/src/Mod/Draft/WorkingPlane.py @@ -521,7 +521,7 @@ class Plane: self.v = v2 self.axis = v3 - def alignToFace(self, shape, offset=0): + def alignToFace(self, shape, offset=0, parent=None): """Align the plane to a face. It uses the center of mass of the face as `position`, @@ -542,6 +542,10 @@ class Plane: Defaults to zero. A value which will be used to offset the plane in the direction of its `axis`. + parent : object + Defaults to None. The ParentGeoFeatureGroup of the object + the face belongs to. + Returns ------- bool @@ -554,17 +558,21 @@ class Plane: """ # Set face to the unique selected face, if found if shape.ShapeType == 'Face': - self.alignToPointAndAxis(shape.Faces[0].CenterOfMass, - shape.Faces[0].normalAt(0, 0), - offset) + if parent: + place = parent.getGlobalPlacement() + else: + place = FreeCAD.Placement() + cen = place.multVec(shape.Faces[0].CenterOfMass) + place.Base = FreeCAD.Vector(0, 0, 0) # Reset the Base for the conversion of the normal. + nor = place.multVec(shape.Faces[0].normalAt(0, 0)) + self.alignToPointAndAxis(cen, nor, offset) import DraftGeomUtils q = DraftGeomUtils.getQuad(shape) if q: - self.u = q[1] - self.v = q[2] + self.u = place.multVec(q[1]) + self.v = place.multVec(q[2]) if not DraftVecUtils.equals(self.u.cross(self.v), self.axis): - self.u = q[2] - self.v = q[1] + self.u, self.v = self.v, self.u if DraftVecUtils.equals(self.u, Vector(0, 0, 1)): # the X axis is vertical: rotate 90 degrees self.u, self.v = self.v.negative(), self.u @@ -644,14 +652,14 @@ class Plane: if not geom_is_shape: FreeCAD.Console.PrintError(translate( "draft", - "Object without Part.Shape geometry:'{}'\n".format( - obj.ObjectName))) + "Object without Part.Shape geometry:'{}'".format( + obj.ObjectName)) + "\n") return False if geom.isNull(): FreeCAD.Console.PrintError(translate( "draft", - "Object with null Part.Shape geometry:'{}'\n".format( - obj.ObjectName))) + "Object with null Part.Shape geometry:'{}'".format( + obj.ObjectName)) + "\n") return False if obj.HasSubObjects: shapes.extend(obj.SubObjects) @@ -664,7 +672,7 @@ class Plane: for n in range(len(shapes)): if not DraftGeomUtils.is_planar(shapes[n]): FreeCAD.Console.PrintError(translate( - "draft","'{}' object is not planar\n".format(names[n]))) + "draft", "'{}' object is not planar".format(names[n])) + "\n") return False if not normal: normal = DraftGeomUtils.get_normal(shapes[n]) @@ -675,8 +683,8 @@ class Plane: for n in range(len(shapes)): if not DraftGeomUtils.are_coplanar(shapes[shape_ref], shapes[n]): FreeCAD.Console.PrintError(translate( - "draft","{} and {} aren't coplanar\n".format( - names[shape_ref],names[n]))) + "draft", "{} and {} aren't coplanar".format( + names[shape_ref],names[n])) + "\n") return False else: # suppose all geometries are straight lines or points @@ -685,7 +693,7 @@ class Plane: poly = Part.makePolygon(points) if not DraftGeomUtils.is_planar(poly): FreeCAD.Console.PrintError(translate( - "draft","All Shapes must be coplanar\n")) + "draft", "All Shapes must be coplanar") + "\n") return False normal = DraftGeomUtils.get_normal(poly) else: @@ -693,7 +701,7 @@ class Plane: if not normal: FreeCAD.Console.PrintError(translate( - "draft","Selected Shapes must define a plane\n")) + "draft", "Selected Shapes must define a plane") + "\n") return False # set center of mass diff --git a/src/Mod/Draft/draftgeoutils/wires.py b/src/Mod/Draft/draftgeoutils/wires.py index fabc5c2601..683f87b037 100644 --- a/src/Mod/Draft/draftgeoutils/wires.py +++ b/src/Mod/Draft/draftgeoutils/wires.py @@ -354,7 +354,7 @@ def removeInterVertices(wire): def cleanProjection(shape, tessellate=True, seglength=0.05): - """Return a compound of edges, optionally tesselate ellipses, splines + """Return a compound of edges, optionally tessellate ellipses, splines and bezcurves. The function was formerly used to workaround bugs in the projection diff --git a/src/Mod/Draft/draftguitools/gui_dimensions.py b/src/Mod/Draft/draftguitools/gui_dimensions.py index 45f12e8991..d091568ea8 100644 --- a/src/Mod/Draft/draftguitools/gui_dimensions.py +++ b/src/Mod/Draft/draftguitools/gui_dimensions.py @@ -106,7 +106,7 @@ class Dimension(gui_base_original.Creator): self.arctrack = trackers.arcTracker() self.link = None self.edges = [] - self.pts = [] + self.angles = [] self.angledata = None self.indices = [] self.center = None @@ -397,7 +397,7 @@ class Dimension(gui_base_original.Creator): r = self.point.sub(self.center) self.arctrack.setRadius(r.Length) a = self.arctrack.getAngle(self.point) - pair = DraftGeomUtils.getBoundaryAngles(a, self.pts) + pair = DraftGeomUtils.getBoundaryAngles(a, self.angles) if not (pair[0] < a < pair[1]): self.angledata = [4 * math.pi - pair[0], 2 * math.pi - pair[1]] @@ -504,8 +504,15 @@ class Dimension(gui_base_original.Creator): self.arctrack.setCenter(self.center) self.arctrack.on() for e in self.edges: - for v in e.Vertexes: - self.pts.append(self.arctrack.getAngle(v.Point)) + if e.Length < 0.00003: # Edge must be long enough for the tolerance of 0.00001mm to make sense. + _msg(translate("draft", "Edge too short!")) + self.finish() + return + for i in [0, 1]: + pt = e.Vertexes[i].Point + if pt.isEqual(self.center, 0.00001): # A relatively high tolerance is required. + pt = e.Vertexes[i - 1].Point # Use the other point instead. + self.angles.append(self.arctrack.getAngle(pt)) self.link = [self.link[0], ob] else: _msg(translate("draft", "Edges don't intersect!")) diff --git a/src/Mod/Draft/draftguitools/gui_edit_sketcher_objects.py b/src/Mod/Draft/draftguitools/gui_edit_sketcher_objects.py index 20b94192f7..e6211d6b2b 100644 --- a/src/Mod/Draft/draftguitools/gui_edit_sketcher_objects.py +++ b/src/Mod/Draft/draftguitools/gui_edit_sketcher_objects.py @@ -49,8 +49,11 @@ class SketcherSketchObjectGuiTools(GuiTools): 0 : startpoint 1 : endpoint """ + import Part editpoints = [] - if obj.GeometryCount == 1: + if (obj.ConstraintCount == 0 + and obj.GeometryCount == 1 + and type(obj.Geometry[0]) == Part.LineSegment): editpoints.append(obj.getPoint(0,1)) editpoints.append(obj.getPoint(0,2)) return editpoints @@ -60,7 +63,6 @@ class SketcherSketchObjectGuiTools(GuiTools): App.Console.PrintWarning(_wrn + "\n") return None - def update_object_from_edit_points(self, obj, node_idx, v, alt_edit_mode=0): """Move a single line sketch vertex a certain displacement. @@ -69,11 +71,12 @@ class SketcherSketchObjectGuiTools(GuiTools): 0 : startpoint 1 : endpoint """ - import Sketcher + line = obj.Geometry[0] if node_idx == 0: - obj.movePoint(0, 1, v) + line.StartPoint = v elif node_idx == 1: - obj.movePoint(0, 2, v) + line.EndPoint = v + obj.Geometry = [line] obj.recompute() ## @} diff --git a/src/Mod/Draft/draftguitools/gui_selectplane.py b/src/Mod/Draft/draftguitools/gui_selectplane.py index adf753db5c..22d97e722d 100644 --- a/src/Mod/Draft/draftguitools/gui_selectplane.py +++ b/src/Mod/Draft/draftguitools/gui_selectplane.py @@ -209,7 +209,9 @@ class Draft_SelectPlane: if len(sel.SubElementNames) == 1: # look for a face or a plane if "Face" in sel.SubElementNames[0]: - FreeCAD.DraftWorkingPlane.alignToFace(sel.SubObjects[0], self.getOffset()) + FreeCAD.DraftWorkingPlane.alignToFace(sel.SubObjects[0], + self.getOffset(), + sel.Object.getParentGeoFeatureGroup()) self.display(FreeCAD.DraftWorkingPlane.axis) return True elif sel.SubElementNames[0] == "Plane": diff --git a/src/Mod/Draft/draftmake/make_sketch.py b/src/Mod/Draft/draftmake/make_sketch.py index 52d6fde7e1..b136c944bc 100644 --- a/src/Mod/Draft/draftmake/make_sketch.py +++ b/src/Mod/Draft/draftmake/make_sketch.py @@ -100,13 +100,15 @@ def make_sketch(objects_list, autoconstraints=False, addTo=None, if isinstance(obj,Part.Shape): shape = obj elif not hasattr(obj,'Shape'): - App.Console.PrintError(translate("draft","No shape found\n")) + App.Console.PrintError(translate("draft", + "No shape found")+"\n") return None else: shape = obj.Shape if not DraftGeomUtils.is_planar(shape, tol): - App.Console.PrintError(translate("draft","All Shapes must be planar\n")) + App.Console.PrintError(translate("draft", + "All Shapes must be planar")+"\n") return None if DraftGeomUtils.get_normal(shape, tol): @@ -121,7 +123,8 @@ def make_sketch(objects_list, autoconstraints=False, addTo=None, if len(shape_norm_yes) >= 1: for shape in shapes_list[1:]: if not DraftGeomUtils.are_coplanar(shapes_list[0], shape, tol): - App.Console.PrintError(translate("draft","All Shapes must be coplanar\n")) + App.Console.PrintError(translate("draft", + "All Shapes must be coplanar")+"\n") return None # define sketch normal normal = DraftGeomUtils.get_normal(shapes_list[0], tol) @@ -132,7 +135,8 @@ def make_sketch(objects_list, autoconstraints=False, addTo=None, if len(points) >= 2: poly = Part.makePolygon(points) if not DraftGeomUtils.is_planar(poly, tol): - App.Console.PrintError(translate("draft","All Shapes must be coplanar\n")) + App.Console.PrintError(translate("draft", + "All Shapes must be coplanar")+"\n") return None normal = DraftGeomUtils.get_normal(poly, tol) if not normal: diff --git a/src/Mod/Draft/draftobjects/array.py b/src/Mod/Draft/draftobjects/array.py index 946d474804..8292c60751 100644 --- a/src/Mod/Draft/draftobjects/array.py +++ b/src/Mod/Draft/draftobjects/array.py @@ -287,7 +287,7 @@ class Array(DraftLink): _tip = QT_TRANSLATE_NOOP("App::Property", "A parameter that determines " "how many symmetry planes " - " the circular array will have.") + "the circular array will have.") obj.addProperty("App::PropertyInteger", "Symmetry", "Circular array", @@ -380,7 +380,7 @@ class Array(DraftLink): obj.setPropertyStatus(pr, "Hidden") def execute(self, obj): - """Execture when the object is created or recomputed.""" + """Execute when the object is created or recomputed.""" if not obj.Base: return diff --git a/src/Mod/Draft/draftobjects/dimension.py b/src/Mod/Draft/draftobjects/dimension.py index a7143a655b..bc4bcbecdb 100644 --- a/src/Mod/Draft/draftobjects/dimension.py +++ b/src/Mod/Draft/draftobjects/dimension.py @@ -163,7 +163,7 @@ class DimensionBase(DraftAnnotation): "There are various possibilities:\n" "- An object, and one of its edges.\n" "- An object, and two of its vertices.\n" - "- An arc object, and its edge.\n") + "- An arc object, and its edge.") obj.addProperty("App::PropertyLinkSubList", "LinkedGeometry", "Dimension", diff --git a/src/Mod/Draft/draftviewproviders/view_layer.py b/src/Mod/Draft/draftviewproviders/view_layer.py index b7231ae1f1..1f88700105 100644 --- a/src/Mod/Draft/draftviewproviders/view_layer.py +++ b/src/Mod/Draft/draftviewproviders/view_layer.py @@ -1,6 +1,7 @@ # *************************************************************************** # * Copyright (c) 2014 Yorik van Havre * # * Copyright (c) 2020 Eliud Cabrera Castillo * +# * Copyright (c) 2021 FreeCAD Developers * # * * # * This file is part of the FreeCAD CAx development system. * # * * @@ -37,7 +38,7 @@ import FreeCAD as App import FreeCADGui as Gui from draftutils.messages import _msg -from draftutils.translate import translate +from draftutils.translate import _tr from draftobjects.layer import Layer @@ -355,13 +356,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"), - translate("draft", "Activate this layer"), + _tr("Activate this layer"), menu) action1.triggered.connect(self.activate) menu.addAction(action1) action2 = QtGui.QAction(QtGui.QIcon(":/icons/Draft_SelectGroup.svg"), - translate("draft", "Select layer contents"), + _tr("Select layer contents"), menu) action2.triggered.connect(self.select_contents) menu.addAction(action2) @@ -399,80 +400,76 @@ 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"), - translate("Draft", "Merge layer duplicates"), + _tr("Merge layer duplicates"), menu) action1.triggered.connect(self.merge_by_name) menu.addAction(action1) action2 = QtGui.QAction(QtGui.QIcon(":/icons/Draft_NewLayer.svg"), - translate("Draft", "Add new layer"), + _tr("Add new layer"), menu) action2.triggered.connect(self.add_layer) menu.addAction(action2) def merge_by_name(self): - """Merge the layers that have the same name.""" + """Merge the layers that have the same base label.""" if not hasattr(self, "Object") or not hasattr(self.Object, "Group"): return - obj = self.Object + doc = App.ActiveDocument + doc.openTransaction(_tr("Merge layer duplicates")) - layers = list() - for iobj in obj.Group: - if hasattr(iobj, "Proxy") and isinstance(iobj.Proxy, Layer): - layers.append(iobj) + layer_container = self.Object + layers = [] + for obj in layer_container.Group: + if hasattr(obj, "Proxy") and isinstance(obj.Proxy, Layer): + layers.append(obj) - to_delete = list() + to_delete = [] for layer in layers: - # Test the last three characters of the layer's Label to see - # if it's a number, like `'Layer017'` - if (layer.Label[-1].isdigit() - and layer.Label[-2].isdigit() - and layer.Label[-3].isdigit()): - # If the object inside the layer has the same Label - # as the layer, save this object - orig = None - for ol in layer.OutList: - if ol.Label == layer.Label[:-3].strip(): - orig = ol - break + # Remove trailing digits (usually 3 but there might be more) and + # trailing spaces from Label before comparing: + base_label = layer.Label.rstrip("0123456789 ") - # Go into the objects that reference this layer object - # and set the layer property with the previous `orig` - # object found - # Editor: when is this possible? Maybe if a layer is inside - # another layer? Currently the code doesn't allow this - # so maybe this was a previous behavior that was disabled - # in `ViewProviderLayer`. - if orig: - for par in layer.InList: - for prop in par.PropertiesList: - if getattr(par, prop) == layer: - _msg("Changed property '" + prop - + "' of object " + par.Label - + " from " + layer.Label - + " to " + orig.Label) - setattr(par, prop, orig) - to_delete.append(layer) + # Try to find the `'base'` layer: + base = None + for other_layer in layers: + if ((not other_layer in to_delete) # Required if there are duplicate labels. + and other_layer != layer + and other_layer.Label.upper() == base_label.upper()): + base = other_layer + break + + if base: + if layer.Group: + base_group = base.Group + for obj in layer.Group: + if not obj in base_group: + base_group.append(obj) + base.Group = base_group + to_delete.append(layer) + elif layer.Label != base_label: + _msg(_tr("Relabeling layer:") + + " '{}' -> '{}'".format(layer.Label, base_label)) + layer.Label = base_label for layer in to_delete: - if not layer.InList: - _msg("Merging duplicate layer: " + layer.Label) - App.ActiveDocument.removeObject(layer.Name) - elif len(layer.InList) == 1: - first = layer.InList[0] + _msg(_tr("Merging layer:") + " '{}'".format(layer.Label)) + doc.removeObject(layer.Name) - if first.isDerivedFrom("App::DocumentObjectGroup"): - _msg("Merging duplicate layer: " + layer.Label) - App.ActiveDocument.removeObject(layer.Name) - else: - _msg("InList not empty. " - "Unable to delete layer: " + layer.Label) + doc.recompute() + doc.commitTransaction() def add_layer(self): """Creates a new layer""" import Draft + + doc = App.ActiveDocument + doc.openTransaction(_tr("Add new layer")) + Draft.make_layer() - App.ActiveDocument.recompute() + + doc.recompute() + doc.commitTransaction() def __getstate__(self): """Return a tuple of objects to save or None.""" diff --git a/src/Mod/Draft/importDWG.py b/src/Mod/Draft/importDWG.py index 288dcb0a86..4fb042784f 100644 --- a/src/Mod/Draft/importDWG.py +++ b/src/Mod/Draft/importDWG.py @@ -249,7 +249,7 @@ def convertToDxf(dwgfilename): outdir = tempfile.mkdtemp() basename = os.path.basename(dwgfilename) result = outdir + os.sep + os.path.splitext(basename)[0] + ".dxf" - proc = subprocess.Popen((path, "-o", result, dwgfilename)) + proc = subprocess.Popen((path, "-f", "-o", result, dwgfilename)) proc.communicate() return result except Exception: diff --git a/src/Mod/Fem/femcommands/commands.py b/src/Mod/Fem/femcommands/commands.py index 782fffc61f..051be66cb3 100644 --- a/src/Mod/Fem/femcommands/commands.py +++ b/src/Mod/Fem/femcommands/commands.py @@ -31,17 +31,21 @@ __url__ = "https://www.freecadweb.org" import FreeCAD import FreeCADGui +from FreeCAD import Qt from .manager import CommandManager from femtools.femutils import is_of_type -# Python command definitions +# Python command definitions: # for C++ command definitions see src/Mod/Fem/Command.cpp # TODO, may be even more generic class creation # with type() and identifier instead of class for # the commands which add new document objects. # see https://www.python-course.eu/python3_classes_and_type.php +# Translation: +# some information in the regard of translation can be found in forum post +# https://forum.freecadweb.org/viewtopic.php?f=18&t=62449&p=543845#p543593 class _Analysis(CommandManager): @@ -49,9 +53,12 @@ class _Analysis(CommandManager): def __init__(self): super(_Analysis, self).__init__() - self.menutext = "Analysis container" + self.menutext = Qt.QT_TRANSLATE_NOOP("FEM_Analysis", "Analysis container") self.accel = "S, A" - self.tooltip = "Creates an analysis container with standard solver CalculiX" + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_Analysis", + "Creates an analysis container with standard solver CalculiX" + ) self.is_active = "with_document" def Activated(self): @@ -74,8 +81,11 @@ class _ClippingPlaneAdd(CommandManager): def __init__(self): super(_ClippingPlaneAdd, self).__init__() - self.menutext = "Clipping plane on face" - self.tooltip = "Add a clipping plane on a selected face" + self.menutext = Qt.QT_TRANSLATE_NOOP("FEM_ClippingPlaneAdd", "Clipping plane on face") + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ClippingPlaneAdd", + "Add a clipping plane on a selected face" + ) self.is_active = "with_document" def Activated(self): @@ -123,8 +133,14 @@ class _ClippingPlaneRemoveAll(CommandManager): def __init__(self): super(_ClippingPlaneRemoveAll, self).__init__() - self.menutext = "Remove all clipping planes" - self.tooltip = "Remove all clipping planes" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ClippingPlaneRemoveAll", + "Remove all clipping planes" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ClippingPlaneRemoveAll", + "Remove all clipping planes" + ) self.is_active = "with_document" def Activated(self): @@ -143,8 +159,14 @@ class _ConstantVacuumPermittivity(CommandManager): def __init__(self): super(_ConstantVacuumPermittivity, self).__init__() self.pixmap = "fem-solver-analysis-thermomechanical.svg" - self.menutext = "Constant vacuum permittivity" - self.tooltip = "Creates a FEM constant vacuum permittivity to overwrite standard value" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstantVacuumPermittivity", + "Constant vacuum permittivity" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstantVacuumPermittivity", + "Creates a FEM constant vacuum permittivity to overwrite standard value" + ) self.is_active = "with_document" self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_noset_edit" @@ -156,8 +178,14 @@ class _ConstraintBodyHeatSource(CommandManager): def __init__(self): super(_ConstraintBodyHeatSource, self).__init__() self.pixmap = "FEM_ConstraintHeatflux" # the heatflux icon is used - self.menutext = "Constraint body heat source" - self.tooltip = "Creates a FEM constraint body heat source" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintBodyHeatSource", + "Constraint body heat source" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintBodyHeatSource", + "Creates a FEM constraint body heat source" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_noset_edit" @@ -167,8 +195,14 @@ class _ConstraintCentrif(CommandManager): def __init__(self): super(_ConstraintCentrif, self).__init__() - self.menutext = "Constraint centrif" - self.tooltip = "Creates a FEM constraint centrif" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintCentrif", + "Constraint centrif" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintCentrif", + "Creates a FEM constraint centrif" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_set_edit" @@ -178,8 +212,14 @@ class _ConstraintElectrostaticPotential(CommandManager): def __init__(self): super(_ConstraintElectrostaticPotential, self).__init__() - self.menutext = "Constraint electrostatic potential" - self.tooltip = "Creates a FEM constraint electrostatic potential" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintElectrostaticPotential", + "Constraint electrostatic potential" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintElectrostaticPotential", + "Creates a FEM constraint electrostatic potential" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_set_edit" @@ -189,8 +229,14 @@ class _ConstraintFlowVelocity(CommandManager): def __init__(self): super(_ConstraintFlowVelocity, self).__init__() - self.menutext = "Constraint flow velocity" - self.tooltip = "Creates a FEM constraint flow velocity" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintFlowVelocity", + "Constraint flow velocity" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintFlowVelocity", + "Creates a FEM constraint flow velocity" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_set_edit" @@ -200,8 +246,14 @@ class _ConstraintInitialFlowVelocity(CommandManager): def __init__(self): super(_ConstraintInitialFlowVelocity, self).__init__() - self.menutext = "Constraint initial flow velocity" - self.tooltip = "Creates a FEM constraint initial flow velocity" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintInitialFlowVelocity", + "Constraint initial flow velocity" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintInitialFlowVelocity", + "Creates a FEM constraint initial flow velocity" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_set_edit" @@ -211,8 +263,14 @@ class _ConstraintSectionPrint(CommandManager): def __init__(self): super(_ConstraintSectionPrint, self).__init__() - self.menutext = "Constraint sectionprint" - self.tooltip = "Creates a FEM constraint sectionprint" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintSectionPrint", + "Constraint sectionprint" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintSectionPrint", + "Creates a FEM constraint sectionprint" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_set_edit" @@ -222,8 +280,14 @@ class _ConstraintSelfWeight(CommandManager): def __init__(self): super(_ConstraintSelfWeight, self).__init__() - self.menutext = "Constraint self weight" - self.tooltip = "Creates a FEM constraint self weight" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintSelfWeight", + "Constraint self weight" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintSelfWeight", + "Creates a FEM constraint self weight" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_noset_edit" @@ -233,8 +297,14 @@ class _ConstraintTie(CommandManager): def __init__(self): super(_ConstraintTie, self).__init__() - self.menutext = "Constraint tie" - self.tooltip = "Creates a FEM constraint tie" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintTie", + "Constraint tie" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ConstraintTie", + "Creates a FEM constraint tie" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_set_edit" @@ -244,8 +314,14 @@ class _ElementFluid1D(CommandManager): def __init__(self): super(_ElementFluid1D, self).__init__() - self.menutext = "Fluid section for 1D flow" - self.tooltip = "Creates a FEM fluid section for 1D flow" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ElementFluid1D", + "Fluid section for 1D flow" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ElementFluid1D", + "Creates a FEM fluid section for 1D flow" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_set_edit" @@ -255,8 +331,14 @@ class _ElementGeometry1D(CommandManager): def __init__(self): super(_ElementGeometry1D, self).__init__() - self.menutext = "Beam cross section" - self.tooltip = "Creates a FEM beam cross section" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ElementGeometry1D", + "Beam cross section" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ElementGeometry1D", + "Creates a FEM beam cross section" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_set_edit" @@ -266,8 +348,14 @@ class _ElementGeometry2D(CommandManager): def __init__(self): super(_ElementGeometry2D, self).__init__() - self.menutext = "Shell plate thickness" - self.tooltip = "Creates a FEM shell plate thickness" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ElementGeometry2D", + "Shell plate thickness" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ElementGeometry2D", + "Creates a FEM shell plate thickness" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_set_edit" @@ -277,8 +365,14 @@ class _ElementRotation1D(CommandManager): def __init__(self): super(_ElementRotation1D, self).__init__() - self.menutext = "Beam rotation" - self.tooltip = "Creates a FEM beam rotation" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ElementRotation1D", + "Beam rotation" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ElementRotation1D", + "Creates a FEM beam rotation" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_noset_edit" @@ -288,8 +382,14 @@ class _EquationElectrostatic(CommandManager): def __init__(self): super(_EquationElectrostatic, self).__init__() - self.menutext = "Electrostatic equation" - self.tooltip = "Creates a FEM equation for electrostatic" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationElectrostatic", + "Electrostatic equation" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationElectrostatic", + "Creates a FEM equation for electrostatic" + ) self.is_active = "with_solver_elmer" self.do_activated = "add_obj_on_gui_selobj_noset_edit" @@ -299,8 +399,14 @@ class _EquationElasticity(CommandManager): def __init__(self): super(_EquationElasticity, self).__init__() - self.menutext = "Elasticity equation" - self.tooltip = "Creates a FEM equation for elasticity" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationElasticity", + "Elasticity equation" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationElasticity", + "Creates a FEM equation for elasticity" + ) self.is_active = "with_solver_elmer" self.do_activated = "add_obj_on_gui_selobj_noset_edit" @@ -310,8 +416,14 @@ class _EquationFlow(CommandManager): def __init__(self): super(_EquationFlow, self).__init__() - self.menutext = "Flow equation" - self.tooltip = "Creates a FEM equation for flow" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationFlow", + "Flow equation" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationFlow", + "Creates a FEM equation for flow" + ) self.is_active = "with_solver_elmer" self.do_activated = "add_obj_on_gui_selobj_noset_edit" @@ -321,8 +433,14 @@ class _EquationFlux(CommandManager): def __init__(self): super(_EquationFlux, self).__init__() - self.menutext = "Flux equation" - self.tooltip = "Creates a FEM equation for flux" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationFlux", + "Flux equation" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationFlux", + "Creates a FEM equation for flux" + ) self.is_active = "with_solver_elmer" self.do_activated = "add_obj_on_gui_selobj_noset_edit" @@ -332,8 +450,14 @@ class _EquationElectricforce(CommandManager): def __init__(self): super(_EquationElectricforce, self).__init__() - self.menutext = "Electricforce equation" - self.tooltip = "Creates a FEM equation for electric forces" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationElectricforce", + "Electricforce equation" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationElectricforce", + "Creates a FEM equation for electric forces" + ) self.is_active = "with_solver_elmer" self.do_activated = "add_obj_on_gui_selobj_noset_edit" @@ -343,8 +467,14 @@ class _EquationHeat(CommandManager): def __init__(self): super(_EquationHeat, self).__init__() - self.menutext = "Heat equation" - self.tooltip = "Creates a FEM equation for heat" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationHeat", + "Heat equation" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationHeat", + "Creates a FEM equation for heat" + ) self.is_active = "with_solver_elmer" self.do_activated = "add_obj_on_gui_selobj_noset_edit" @@ -355,8 +485,14 @@ class _Examples(CommandManager): def __init__(self): super(_Examples, self).__init__() self.pixmap = "FemWorkbench" - self.menutext = "Open FEM examples" - self.tooltip = "Open FEM examples" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_Examples", + "Open FEM examples" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_Examples", + "Open FEM examples" + ) self.is_active = "always" def Activated(self): @@ -370,8 +506,14 @@ class _MaterialEditor(CommandManager): def __init__(self): super(_MaterialEditor, self).__init__() self.pixmap = "Arch_Material_Group" - self.menutext = "Material editor" - self.tooltip = "Opens the FreeCAD material editor" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_MaterialEditor", + "Material editor" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_MaterialEditor", + "Opens the FreeCAD material editor" + ) self.is_active = "always" def Activated(self): @@ -384,8 +526,14 @@ class _MaterialFluid(CommandManager): def __init__(self): super(_MaterialFluid, self).__init__() - self.menutext = "Material for fluid" - self.tooltip = "Creates a FEM material for fluid" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_MaterialFluid", + "Material for fluid" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_MaterialFluid", + "Creates a FEM material for fluid" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_set_edit" @@ -395,8 +543,14 @@ class _MaterialMechanicalNonlinear(CommandManager): def __init__(self): super(_MaterialMechanicalNonlinear, self).__init__() - self.menutext = "Nonlinear mechanical material" - self.tooltip = "Creates a nonlinear mechanical material" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_MaterialMechanicalNonlinear", + "Nonlinear mechanical material" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_MaterialMechanicalNonlinear", + "Creates a nonlinear mechanical material" + ) self.is_active = "with_material_solid" def Activated(self): @@ -459,8 +613,14 @@ class _MaterialReinforced(CommandManager): def __init__(self): super(_MaterialReinforced, self).__init__() - self.menutext = "Reinforced material (concrete)" - self.tooltip = "Creates a material for reinforced matrix material such as concrete" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_MaterialReinforced", + "Reinforced material (concrete)" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_MaterialReinforced", + "Creates a material for reinforced matrix material such as concrete" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_set_edit" @@ -470,9 +630,15 @@ class _MaterialSolid(CommandManager): def __init__(self): super(_MaterialSolid, self).__init__() - self.menutext = "Material for solid" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_MaterialSolid", + "Material for solid" + ) self.accel = "M, S" - self.tooltip = "Creates a FEM material for solid" + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_MaterialSolid", + "Creates a FEM material for solid" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_set_edit" @@ -482,8 +648,14 @@ class _FEMMesh2Mesh(CommandManager): def __init__(self): super(_FEMMesh2Mesh, self).__init__() - self.menutext = "FEM mesh to mesh" - self.tooltip = "Convert the surface of a FEM mesh to a mesh" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_FEMMesh2Mesh", + "FEM mesh to mesh" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_FEMMesh2Mesh", + "Convert the surface of a FEM mesh to a mesh" + ) self.is_active = "with_femmesh_andor_res" def Activated(self): @@ -523,8 +695,14 @@ class _MeshBoundaryLayer(CommandManager): def __init__(self): super(_MeshBoundaryLayer, self).__init__() - self.menutext = "FEM mesh boundary layer" - self.tooltip = "Creates a FEM mesh boundary layer" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshBoundaryLayer", + "FEM mesh boundary layer" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshBoundaryLayer", + "Creates a FEM mesh boundary layer" + ) self.is_active = "with_gmsh_femmesh" self.do_activated = "add_obj_on_gui_selobj_set_edit" @@ -534,8 +712,14 @@ class _MeshClear(CommandManager): def __init__(self): super(_MeshClear, self).__init__() - self.menutext = "Clear FEM mesh" - self.tooltip = "Clear the Mesh of a FEM mesh object" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshClear", + "Clear FEM mesh" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshClear", + "Clear the Mesh of a FEM mesh object" + ) self.is_active = "with_femmesh" def Activated(self): @@ -553,8 +737,14 @@ class _MeshDisplayInfo(CommandManager): def __init__(self): super(_MeshDisplayInfo, self).__init__() - self.menutext = "Display FEM mesh info" - self.tooltip = "Display FEM mesh info" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshDisplayInfo", + "Display FEM mesh info" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshDisplayInfo", + "Display FEM mesh info" + ) self.is_active = "with_femmesh" def Activated(self): @@ -576,8 +766,14 @@ class _MeshGmshFromShape(CommandManager): def __init__(self): super(_MeshGmshFromShape, self).__init__() - self.menutext = "FEM mesh from shape by Gmsh" - self.tooltip = "Create a FEM mesh from a shape by Gmsh mesher" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshGmshFromShape", + "FEM mesh from shape by Gmsh" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshGmshFromShape", + "Create a FEM mesh from a shape by Gmsh mesher" + ) self.is_active = "with_part_feature" def Activated(self): @@ -615,8 +811,14 @@ class _MeshGroup(CommandManager): def __init__(self): super(_MeshGroup, self).__init__() - self.menutext = "FEM mesh group" - self.tooltip = "Creates a FEM mesh group" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshGroup", + "FEM mesh group" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshGroup", + "Creates a FEM mesh group" + ) self.is_active = "with_gmsh_femmesh" self.do_activated = "add_obj_on_gui_selobj_set_edit" @@ -626,8 +828,14 @@ class _MeshNetgenFromShape(CommandManager): def __init__(self): super(_MeshNetgenFromShape, self).__init__() - self.menutext = "FEM mesh from shape by Netgen" - self.tooltip = "Create a FEM mesh from a solid or face shape by Netgen internal mesher" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshNetgenFromShape", + "FEM mesh from shape by Netgen" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshNetgenFromShape", + "Create a FEM mesh from a solid or face shape by Netgen internal mesher" + ) self.is_active = "with_part_feature" def Activated(self): @@ -665,8 +873,14 @@ class _MeshRegion(CommandManager): def __init__(self): super(_MeshRegion, self).__init__() - self.menutext = "FEM mesh region" - self.tooltip = "Creates a FEM mesh region" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshRegion", + "FEM mesh region" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_MeshRegion", + "Creates a FEM mesh region" + ) self.is_active = "with_gmsh_femmesh" self.do_activated = "add_obj_on_gui_selobj_set_edit" @@ -676,9 +890,15 @@ class _ResultShow(CommandManager): def __init__(self): super(_ResultShow, self).__init__() - self.menutext = "Show result" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ResultShow", + "Show result" + ) self.accel = "R, S" - self.tooltip = "Shows and visualizes selected result data" + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ResultShow", + "Shows and visualizes selected result data" + ) self.is_active = "with_selresult" def Activated(self): @@ -690,9 +910,15 @@ class _ResultsPurge(CommandManager): def __init__(self): super(_ResultsPurge, self).__init__() - self.menutext = "Purge results" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_ResultsPurge", + "Purge results" + ) self.accel = "R, P" - self.tooltip = "Purges all results from active analysis" + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_ResultsPurge", + "Purges all results from active analysis" + ) self.is_active = "with_results" def Activated(self): @@ -706,9 +932,15 @@ class _SolverCxxtools(CommandManager): def __init__(self): super(_SolverCxxtools, self).__init__() self.pixmap = "FEM_SolverStandard" - self.menutext = "Solver CalculiX Standard" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_SolverCxxtools", + "Solver CalculiX Standard" + ) self.accel = "S, X" - self.tooltip = "Creates a standard FEM solver CalculiX with ccx tools" + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_SolverCxxtools", + "Creates a standard FEM solver CalculiX with ccx tools" + ) self.is_active = "with_analysis" def Activated(self): @@ -741,9 +973,15 @@ class _SolverCalculix(CommandManager): def __init__(self): super(_SolverCalculix, self).__init__() self.pixmap = "FEM_SolverStandard" - self.menutext = "Solver CalculiX (new framework)" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_SolverCalculix", + "Solver CalculiX (new framework)" + ) self.accel = "S, C" - self.tooltip = "Creates a FEM solver CalculiX new framework (less result error handling)" + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_SolverCalculix", + "Creates a FEM solver CalculiX new framework (less result error handling)" + ) self.is_active = "with_analysis" self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_noset_edit" @@ -754,9 +992,15 @@ class _SolverControl(CommandManager): def __init__(self): super(_SolverControl, self).__init__() - self.menutext = "Solver job control" + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_SolverControl", + "Solver job control" + ) self.accel = "S, T" - self.tooltip = "Changes solver attributes and runs the calculations for the selected solver" + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_SolverControl", + "Changes solver attributes and runs the calculations for the selected solver" + ) self.is_active = "with_solver" def Activated(self): @@ -768,9 +1012,12 @@ class _SolverElmer(CommandManager): def __init__(self): super(_SolverElmer, self).__init__() - self.menutext = "Solver Elmer" + self.menutext = Qt.QT_TRANSLATE_NOOP("FEM_SolverElmer", "Solver Elmer") self.accel = "S, E" - self.tooltip = "Creates a FEM solver Elmer" + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_SolverElmer", + "Creates a FEM solver Elmer" + ) self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_noset_edit" @@ -781,9 +1028,9 @@ class _SolverMystran(CommandManager): def __init__(self): super(_SolverMystran, self).__init__() self.pixmap = "FEM_SolverStandard" - self.menutext = "Solver Mystran" + self.menutext = Qt.QT_TRANSLATE_NOOP("FEM_SolverMystran", "Solver Mystran") self.accel = "S, M" - self.tooltip = "Creates a FEM solver Mystran" + self.tooltip = Qt.QT_TRANSLATE_NOOP("FEM_SolverMystran", "Creates a FEM solver Mystran") self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_noset_edit" @@ -793,9 +1040,12 @@ class _SolverRun(CommandManager): def __init__(self): super(_SolverRun, self).__init__() - self.menutext = "Run solver calculations" + self.menutext = Qt.QT_TRANSLATE_NOOP("FEM_SolverRun", "Run solver calculations") self.accel = "S, R" - self.tooltip = "Runs the calculations for the selected solver" + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_SolverRun", + "Runs the calculations for the selected solver" + ) self.is_active = "with_solver" def Activated(self): @@ -810,9 +1060,9 @@ class _SolverZ88(CommandManager): def __init__(self): super(_SolverZ88, self).__init__() - self.menutext = "Solver Z88" + self.menutext = Qt.QT_TRANSLATE_NOOP("FEM_SolverZ88", "Solver Z88") self.accel = "S, Z" - self.tooltip = "Creates a FEM solver Z88" + self.tooltip = Qt.QT_TRANSLATE_NOOP("FEM_SolverZ88", "Creates a FEM solver Z88") self.is_active = "with_analysis" self.do_activated = "add_obj_on_gui_noset_edit" diff --git a/src/Mod/Fem/femtaskpanels/task_material_common.py b/src/Mod/Fem/femtaskpanels/task_material_common.py index 1ce0320928..4ceb7fa941 100644 --- a/src/Mod/Fem/femtaskpanels/task_material_common.py +++ b/src/Mod/Fem/femtaskpanels/task_material_common.py @@ -549,7 +549,7 @@ class _TaskPanel: # for example PoissonRatio value = Units.Quantity(inputfield_text).Value old_value = Units.Quantity(self.material[matProperty]).Value - # value = float(inputfield_text) # this fails on locale with komma + # value = float(inputfield_text) # this fails on locale with comma # https://forum.freecadweb.org/viewtopic.php?f=18&t=56912&p=523313#p523313 if value: if not (1 - variation < float(old_value) / value < 1 + variation): diff --git a/src/Mod/Import/App/AppImportPy.cpp b/src/Mod/Import/App/AppImportPy.cpp index f76e59bf15..88e7f2be6e 100644 --- a/src/Mod/Import/App/AppImportPy.cpp +++ b/src/Mod/Import/App/AppImportPy.cpp @@ -368,7 +368,7 @@ private: //makeHeader.SetName(new TCollection_HAsciiString((Standard_CString)Utf8Name.c_str())); makeHeader.SetAuthorValue (1, new TCollection_HAsciiString(hGrp->GetASCII("Author", "Author").c_str())); makeHeader.SetOrganizationValue (1, new TCollection_HAsciiString(hGrp->GetASCII("Company").c_str())); - makeHeader.SetOriginatingSystem(new TCollection_HAsciiString(App::GetApplication().getExecutableName())); + makeHeader.SetOriginatingSystem(new TCollection_HAsciiString(App::Application::getExecutableName().c_str())); makeHeader.SetDescriptionValue(1, new TCollection_HAsciiString("FreeCAD Model")); IFSelect_ReturnStatus ret = writer.Write(name8bit.c_str()); if (ret == IFSelect_RetError || ret == IFSelect_RetFail || ret == IFSelect_RetStop) { diff --git a/src/Mod/Import/Gui/AppImportGuiPy.cpp b/src/Mod/Import/Gui/AppImportGuiPy.cpp index 774c40e2f9..8f855a3a32 100644 --- a/src/Mod/Import/Gui/AppImportGuiPy.cpp +++ b/src/Mod/Import/Gui/AppImportGuiPy.cpp @@ -668,7 +668,7 @@ private: //makeHeader.SetName(new TCollection_HAsciiString((Standard_CString)Utf8Name.c_str())); makeHeader.SetAuthorValue (1, new TCollection_HAsciiString(hGrp->GetASCII("Author", "Author").c_str())); makeHeader.SetOrganizationValue (1, new TCollection_HAsciiString(hGrp->GetASCII("Company").c_str())); - makeHeader.SetOriginatingSystem(new TCollection_HAsciiString(App::GetApplication().getExecutableName())); + makeHeader.SetOriginatingSystem(new TCollection_HAsciiString(App::Application::getExecutableName().c_str())); makeHeader.SetDescriptionValue(1, new TCollection_HAsciiString("FreeCAD Model")); IFSelect_ReturnStatus ret = writer.Write(name8bit.c_str()); if (ret == IFSelect_RetError || ret == IFSelect_RetFail || ret == IFSelect_RetStop) { diff --git a/src/Mod/Import/stepZ.py b/src/Mod/Import/stepZ.py index afec487d45..a6f0034a02 100644 --- a/src/Mod/Import/stepZ.py +++ b/src/Mod/Import/stepZ.py @@ -22,7 +22,7 @@ from PySide import QtGui, QtCore import tempfile import shutil -___stpZversion___ = "1.3.8" +___stpZversion___ = "1.3.9" # support both gz and zipfile archives # Catia seems to use gz, Inventor zipfile # improved import, open and export @@ -162,25 +162,21 @@ def export(objs,filename): if os.path.exists(outfpathT_stp): - sayzw("File cannot be compressed because a file with the same name exists '"+ outfpathT_stp +"'") - QtGui.QApplication.restoreOverrideCursor() - reply = QtGui.QMessageBox.information(None,"info", "File cannot be compressed because\na file with the same name exists\n'"+ outfpath_stp + "'") + os.remove(outfpathT_stp) + sayzw("Old temp file with the same name removed '"+ outfpathT_stp +"'") + ImportGui.export(objs,outfpathT_stp) + with builtin.open(outfpathT_stp, 'rb') as f_in: + file_content = f_in.read() + new_f_content = file_content + f_in.close() + with gz.open(outfpathT_str, 'wb') as f_out: + f_out.write(new_f_content) + f_out.close() + if os.path.exists(outfpath): + shutil.move(outfpathT_str, outfpath) + #os.remove(outfpathT_stp) else: - ImportGui.export(objs,outfpathT_stp) - with builtin.open(outfpathT_stp, 'rb') as f_in: - file_content = f_in.read() - new_f_content = file_content - f_in.close() - with gz.open(outfpathT_str, 'wb') as f_out: - f_out.write(new_f_content) - f_out.close() - if os.path.exists(outfpath): - os.remove(outfpath) - shutil.move(outfpathT_str, outfpath) - #os.remove(outfpathT_stp) - else: - shutil.move(outfpathT_str, outfpath) - #os.remove(outfpathT_stp) + shutil.move(outfpathT_str, outfpath) + #os.remove(outfpathT_stp) #### - diff --git a/src/Mod/Mesh/App/AppMeshPy.cpp b/src/Mod/Mesh/App/AppMeshPy.cpp index 92a6524e7f..ccc80fe665 100644 --- a/src/Mod/Mesh/App/AppMeshPy.cpp +++ b/src/Mod/Mesh/App/AppMeshPy.cpp @@ -308,19 +308,32 @@ private: } Py::Object createBox(const Py::Tuple& args) { - float length = 10.0f; - float width = 10.0f; - float height = 10.0f; - float edgelen = -1.0f; - if (!PyArg_ParseTuple(args.ptr(), "|ffff",&length,&width,&height,&edgelen)) - throw Py::Exception(); + MeshObject* mesh = nullptr; - MeshObject* mesh; - if (edgelen < 0.0f) - mesh = MeshObject::createCube(length, width, height); - else - mesh = MeshObject::createCube(length, width, height, edgelen); + do { + float length = 10.0f; + float width = 10.0f; + float height = 10.0f; + float edgelen = -1.0f; + if (PyArg_ParseTuple(args.ptr(), "|ffff",&length,&width,&height,&edgelen)) { + if (edgelen < 0.0f) + mesh = MeshObject::createCube(length, width, height); + else + mesh = MeshObject::createCube(length, width, height, edgelen); + break; + } + PyErr_Clear(); + PyObject* box; + if (PyArg_ParseTuple(args.ptr(), "O!",&Base::BoundBoxPy::Type, &box)) { + Py::BoundingBox bbox(box, false); + mesh = MeshObject::createCube(bbox.getValue()); + break; + } + + throw Py::TypeError("Must be real numbers or BoundBox"); + } + while (false); if (!mesh) { throw Py::Exception(Base::BaseExceptionFreeCADError, "Creation of box failed"); } diff --git a/src/Mod/Mesh/App/Core/Approximation.h b/src/Mod/Mesh/App/Core/Approximation.h index 99d73be059..524fca7d25 100644 --- a/src/Mod/Mesh/App/Core/Approximation.h +++ b/src/Mod/Mesh/App/Core/Approximation.h @@ -514,7 +514,7 @@ public: dKoeff[ ct ] = pKoef[ ct ]; } /** - * Destruktor. Deletes the ImpicitSurface instance + * Destructor. Deletes the ImpicitSurface instance * of the WildMagic library */ ~FunctionContainer(){ delete pImplSurf; } diff --git a/src/Mod/Mesh/App/Core/Elements.cpp b/src/Mod/Mesh/App/Core/Elements.cpp index 4e8cad49d3..53bc252add 100644 --- a/src/Mod/Mesh/App/Core/Elements.cpp +++ b/src/Mod/Mesh/App/Core/Elements.cpp @@ -180,22 +180,22 @@ MeshFacetArray& MeshFacetArray::operator = (const MeshFacetArray &rclFAry) bool MeshGeomEdge::ContainedByOrIntersectBoundingBox ( const Base::BoundBox3f &rclBB ) const { - // Test, ob alle Eckpunkte der Edge sich auf einer der 6 Seiten der BB befinden + // Test whether all corner points of the Edge are on one of the 6 sides of the BB if ((GetBoundBox() && rclBB) == false) return false; - // Test, ob Edge-BB komplett in BB liegt + // Test whether Edge-BB is completely in BB if (rclBB.IsInBox(GetBoundBox())) return true; - // Test, ob einer der Eckpunkte in BB liegt + // Test whether one of the corner points is in BB for (int i=0;i<2;i++) { if (rclBB.IsInBox(_aclPoints[i])) return true; } - // "echter" Test auf Schnitt + // "real" test for cut if (IntersectBoundingBox(rclBB)) return true; @@ -487,7 +487,7 @@ bool MeshGeomFacet::IsPointOf (const Base::Vector3f &rclPoint, float fDistance) clProjPt.ProjectToPlane(_aclPoints[0], clNorm); - // Kante P0 --> P1 + // Edge P0 --> P1 clEdge = clP1 - clP0; fLP = clProjPt.DistanceToLine(clP0, clEdge); if (fLP > 0.0f) @@ -500,9 +500,9 @@ bool MeshGeomFacet::IsPointOf (const Base::Vector3f &rclPoint, float fDistance) } else return false; - } + } - // Kante P0 --> P2 + // Edge P0 --> P2 clEdge = clP2 - clP0; fLP = clProjPt.DistanceToLine(clP0, clEdge); if (fLP > 0.0f) @@ -515,9 +515,9 @@ bool MeshGeomFacet::IsPointOf (const Base::Vector3f &rclPoint, float fDistance) } else return false; - } + } - // Kante P1 --> P2 + // Edge P1 --> P2 clEdge = clP2 - clP1; fLP = clProjPt.DistanceToLine(clP1, clEdge); if (fLP > 0.0f) @@ -537,7 +537,7 @@ bool MeshGeomFacet::IsPointOf (const Base::Vector3f &rclPoint, float fDistance) bool MeshGeomFacet::IsPointOfFace (const Base::Vector3f& rclP, float fDistance) const { - // effektivere Implementierung als in MeshGeomFacet::IsPointOf + // more effective implementation than in MeshGeomFacet::IsPointOf // Base::Vector3f a(_aclPoints[0].x, _aclPoints[0].y, _aclPoints[0].z); Base::Vector3f b(_aclPoints[1].x, _aclPoints[1].y, _aclPoints[1].z); @@ -907,7 +907,7 @@ bool MeshGeomFacet::Foraminate (const Base::Vector3f &P, const Base::Vector3f &d bool MeshGeomFacet::IntersectPlaneWithLine (const Base::Vector3f &rclPt, const Base::Vector3f &rclDir, Base::Vector3f &rclRes) const { - // berechne den Schnittpunkt Gerade <-> Ebene + // calculate the intersection of the straight line <-> plane if ( fabs(rclDir * GetNormal()) < 1e-3f ) return false; // line and plane are parallel @@ -979,7 +979,7 @@ void MeshGeomFacet::SubSample (float fStep, std::vector &rclPoin Base::Vector3f clVecAC(C - A); Base::Vector3f clVecBC(C - B); - // laengste Achse entspricht AB + // longest axis corresponds to AB float fLenAB = clVecAB.Length(); float fLenAC = clVecAC.Length(); float fLenBC = clVecBC.Length(); @@ -1036,6 +1036,15 @@ void MeshGeomFacet::SubSample (float fStep, std::vector &rclPoin rclPoints.insert(rclPoints.end(), clPoints.begin(), clPoints.end()); } +bool MeshGeomFacet::IsCoplanar(const MeshGeomFacet &facet) const +{ + const float eps = 1e-06f; + const float unit = 0.9995f; + float mult = fabs(this->GetNormal() * facet.GetNormal()); + float dist = fabs(DistancePlaneToPoint(facet._aclPoints[0])); + return (mult >= unit) && (dist <= eps); +} + /** * Fast Triangle-Triangle Intersection Test by Tomas Moeller * http://www.acm.org/jgt/papers/Moller97/tritri.html @@ -1068,6 +1077,39 @@ int MeshGeomFacet::IntersectWithFacet (const MeshGeomFacet& rclFacet, Base::Vector3f& rclPt0, Base::Vector3f& rclPt1) const { + // Note: tri_tri_intersect_with_isection() does not return line of + // intersection when triangles are coplanar. See tritritest.h:18 and 658. + if (IsCoplanar(rclFacet)) { + // Since tri_tri_intersect_with_isection may return garbage values try to get + // sensible values with edge/edge intersections + std::vector intersections; + for (short i=0; i<3; i++) { + MeshGeomEdge edge1 = GetEdge(i); + for (short j=0; j<3; j++) { + MeshGeomEdge edge2 = rclFacet.GetEdge(j); + Base::Vector3f point; + if (edge1.IntersectWithEdge(edge2, point)) { + intersections.push_back(point); + } + } + } + + // If triangles overlap there can be more than two intersection points + // In that case use any two of them. + if (intersections.size() >= 2) { + rclPt0 = intersections[0]; + rclPt1 = intersections[1]; + return 2; + } + else if (intersections.size() == 1) { + rclPt0 = intersections[0]; + rclPt1 = intersections[0]; + return 1; + } + + return 0; + } + float V[3][3], U[3][3]; int coplanar = 0; float isectpt1[3], isectpt2[3]; @@ -1089,45 +1131,17 @@ int MeshGeomFacet::IntersectWithFacet (const MeshGeomFacet& rclFacet, rclPt0.x = isectpt1[0]; rclPt0.y = isectpt1[1]; rclPt0.z = isectpt1[2]; rclPt1.x = isectpt2[0]; rclPt1.y = isectpt2[1]; rclPt1.z = isectpt2[2]; - // Note: tri_tri_intersect_with_isection() does not return line of - // intersection when triangles are coplanar. See tritritest.h:18 and 658. - if (coplanar) { - // Since tri_tri_intersect_with_isection may return garbage values try to get - // sensible values with edge/edge intersections - std::vector intersections; - for (short i=0; i<3; i++) { - MeshGeomEdge edge1 = GetEdge(i); - for (short j=0; j<3; j++) { - MeshGeomEdge edge2 = rclFacet.GetEdge(j); - Base::Vector3f point; - if (edge1.IntersectWithEdge(edge2, point)) { - intersections.push_back(point); - } - } - } - - // If triangles overlap there can be more than two intersection points - // In that case use any two of them. - if (intersections.size() >= 2) { - rclPt0 = intersections[0]; - rclPt1 = intersections[1]; - } - else if (intersections.size() == 1) { - rclPt0 = intersections[0]; - rclPt1 = intersections[0]; - } - return 2; - } - // With extremely acute-angled triangles it may happen that the algorithm // claims an intersection but the intersection points are far outside the // model. So, a plausibility check is to verify that the intersection points // are inside the bounding boxes of both triangles. Base::BoundBox3f box1 = this->GetBoundBox(); + box1.Enlarge(0.001f); if (!box1.IsInBox(rclPt0) || !box1.IsInBox(rclPt1)) return 0; Base::BoundBox3f box2 = rclFacet.GetBoundBox(); + box2.Enlarge(0.001f); if (!box2.IsInBox(rclPt0) || !box2.IsInBox(rclPt1)) return 0; diff --git a/src/Mod/Mesh/App/Core/Elements.h b/src/Mod/Mesh/App/Core/Elements.h index a0af29e49c..513c4adacb 100644 --- a/src/Mod/Mesh/App/Core/Elements.h +++ b/src/Mod/Mesh/App/Core/Elements.h @@ -546,6 +546,10 @@ public: /** Apply a transformation on the triangle. */ void Transform(const Base::Matrix4D&); + /** + * Checks if the two triangles are coplanar. + */ + bool IsCoplanar(const MeshGeomFacet &facet) const; protected: Base::Vector3f _clNormal; /**< Normal of the facet. */ diff --git a/src/Mod/Mesh/App/Core/MeshIO.cpp b/src/Mod/Mesh/App/Core/MeshIO.cpp index 09d1ad91dc..9fde9d8515 100644 --- a/src/Mod/Mesh/App/Core/MeshIO.cpp +++ b/src/Mod/Mesh/App/Core/MeshIO.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -1947,6 +1948,7 @@ std::vector MeshOutput::supportedMeshFormats() fmt.emplace_back("wrz"); fmt.emplace_back("amf"); fmt.emplace_back("asy"); + fmt.emplace_back("3mf"); return fmt; } @@ -2004,6 +2006,9 @@ MeshIO::Format MeshOutput::GetFormat(const char* FileName) else if (file.hasExtension("amf")) { return MeshIO::AMF; } + else if (file.hasExtension("3mf")) { + return MeshIO::ThreeMF; + } else if (file.hasExtension("smf")) { return MeshIO::SMF; } @@ -2113,6 +2118,11 @@ bool MeshOutput::SaveAny(const char* FileName, MeshIO::Format format) const if (!SaveX3DOM(str)) throw Base::FileException("Export of X3DOM failed",FileName); } + else if (fileformat == MeshIO::ThreeMF) { + // write file + if (!Save3MF(str)) + throw Base::FileException("Export of 3MF failed",FileName); + } else if (fileformat == MeshIO::PY) { // write file if (!SavePython(str)) @@ -2183,6 +2193,8 @@ bool MeshOutput::SaveFormat(std::ostream &str, MeshIO::Format fmt) const case MeshIO::WRZ: // it's up to the client to create the needed stream return SaveVRML(str); + case MeshIO::ThreeMF: + return Save3MF(str); case MeshIO::NAS: return SaveNastran(str); case MeshIO::PLY: @@ -2974,6 +2986,99 @@ void MeshOutput::SaveXML (Base::Writer &writer) const writer.decInd(); } +/** Saves the mesh object into a 3MF file. */ +bool MeshOutput::Save3MF(std::ostream &str) const +{ + zipios::ZipOutputStream zip(str); + zip.putNextEntry("/3D/3dmodel.model"); + if (!Save3MFModel(zip)) + return false; + zip.closeEntry(); + + zip.putNextEntry("_rels/.rels"); + if (!Save3MFRels(zip)) + return false; + zip.closeEntry(); + + zip.putNextEntry("[Content_Types].xml"); + if (!Save3MFContent(zip)) + return false; + zip.closeEntry(); + return true; +} + +bool MeshOutput::Save3MFRels(std::ostream &str) const +{ + str << "\n" + << "" + "" + ""; + return true; +} + +bool MeshOutput::Save3MFContent(std::ostream &str) const +{ + str << "\n" + << "" + "" + "" + ""; + return true; +} + +bool MeshOutput::Save3MFModel (std::ostream &str) const +{ + const MeshPointArray& rPoints = _rclMesh.GetPoints(); + const MeshFacetArray& rFacets = _rclMesh.GetFacets(); + + if (!str || str.bad() == true) + return false; + + str << "\n" + << "\n" + << "FreeCAD\n"; + str << Base::blanks(2) << "\n"; + str << Base::blanks(4) << "\n"; + str << Base::blanks(6) << "\n"; + + // vertices + str << Base::blanks(8) << "\n"; + Base::Vector3f pt; + std::size_t index = 0; + for (MeshPointArray::_TConstIterator it = rPoints.begin(); it != rPoints.end(); ++it, ++index) { + pt.Set(it->x, it->y, it->z); + if (this->apply_transform) { + this->_transform.multVec(pt, pt); + } + str << Base::blanks(10) << "\n"; + } + str << Base::blanks(8) << "\n"; + + // facet indices + str << Base::blanks(8) << "\n"; + for (MeshFacetArray::_TConstIterator it = rFacets.begin(); it != rFacets.end(); ++it) { + str << Base::blanks(10) << "_aulPoints[0] + << "\" v2=\"" << it->_aulPoints[1] + << "\" v3=\"" << it->_aulPoints[2] + << "\" />\n"; + } + str << Base::blanks(8) << "\n"; + + str << Base::blanks(6) << "\n"; + str << Base::blanks(4) << "\n"; + str << Base::blanks(2) << "\n"; + str << Base::blanks(2) << "\n"; + str << Base::blanks(4) << "\n"; + str << Base::blanks(2) << "\n"; + str << "\n"; + return true; +} + /** Writes an IDTF file. */ bool MeshOutput::SaveIDTF (std::ostream &str) const { diff --git a/src/Mod/Mesh/App/Core/MeshIO.h b/src/Mod/Mesh/App/Core/MeshIO.h index fdb369c348..4e2fc61f0c 100644 --- a/src/Mod/Mesh/App/Core/MeshIO.h +++ b/src/Mod/Mesh/App/Core/MeshIO.h @@ -60,7 +60,8 @@ namespace MeshIO { PY, AMF, SMF, - ASY + ASY, + ThreeMF }; enum Binding { OVERALL, @@ -197,6 +198,8 @@ public: bool SaveAsymptote (std::ostream &rstrOut) const; /** Saves the mesh object into an XML file. */ void SaveXML (Base::Writer &writer) const; + /** Saves the mesh object into a 3MF file. */ + bool Save3MF (std::ostream &str) const; /** Saves a node to an OpenInventor file. */ bool SaveMeshNode (std::ostream &rstrIn); /** Writes an IDTF file. */ @@ -223,6 +226,9 @@ public: protected: /** Writes an X3D file. */ bool SaveX3DContent (std::ostream &rstrOut, bool exportViewpoints) const; + bool Save3MFModel(std::ostream &str) const; + bool Save3MFRels(std::ostream &str) const; + bool Save3MFContent(std::ostream &str) const; protected: const MeshKernel &_rclMesh; /**< reference to mesh data structure */ diff --git a/src/Mod/Mesh/App/Mesh.cpp b/src/Mod/Mesh/App/Mesh.cpp index e64d463218..463d6f19ba 100644 --- a/src/Mod/Mesh/App/Mesh.cpp +++ b/src/Mod/Mesh/App/Mesh.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -1833,6 +1834,37 @@ MeshObject* MeshObject::createCube(float length, float width, float height, floa return nullptr; } +MeshObject* MeshObject::createCube(const Base::BoundBox3d& bbox) +{ + std::vector facets; + auto createFacet = [&bbox](int i, int j, int k) { + MeshCore::MeshGeomFacet facet; + facet._aclPoints[0] = Base::convertTo(bbox.CalcPoint(i)); + facet._aclPoints[1] = Base::convertTo(bbox.CalcPoint(j)); + facet._aclPoints[2] = Base::convertTo(bbox.CalcPoint(k)); + facet.CalcNormal(); + return facet; + }; + + facets.push_back(createFacet(0, 1, 2)); + facets.push_back(createFacet(0, 2, 3)); + facets.push_back(createFacet(0, 5, 1)); + facets.push_back(createFacet(0, 4, 5)); + facets.push_back(createFacet(0, 3, 7)); + facets.push_back(createFacet(0, 7, 4)); + facets.push_back(createFacet(4, 6, 5)); + facets.push_back(createFacet(4, 7, 6)); + facets.push_back(createFacet(1, 6, 2)); + facets.push_back(createFacet(1, 5, 6)); + facets.push_back(createFacet(2, 7, 3)); + facets.push_back(createFacet(2, 6, 7)); + + Base::EmptySequencer seq; + std::unique_ptr mesh(new MeshObject); + mesh->getKernel() = facets; + return mesh.release(); +} + void MeshObject::addSegment(const Segment& s) { addSegment(s.getIndices()); diff --git a/src/Mod/Mesh/App/Mesh.h b/src/Mod/Mesh/App/Mesh.h index 6d531802db..ea3f54ca1a 100644 --- a/src/Mod/Mesh/App/Mesh.h +++ b/src/Mod/Mesh/App/Mesh.h @@ -338,6 +338,7 @@ public: static MeshObject* createTorus(float, float, int); static MeshObject* createCube(float, float, float); static MeshObject* createCube(float, float, float, float); + static MeshObject* createCube(const Base::BoundBox3d&); //@} public: diff --git a/src/Mod/Mesh/App/MeshPyImp.cpp b/src/Mod/Mesh/App/MeshPyImp.cpp index 4f47e92368..3876f0ff91 100644 --- a/src/Mod/Mesh/App/MeshPyImp.cpp +++ b/src/Mod/Mesh/App/MeshPyImp.cpp @@ -222,6 +222,7 @@ PyObject* MeshPy::write(PyObject *args, PyObject *kwds) ext["APLY" ] = MeshCore::MeshIO::APLY; ext["PY" ] = MeshCore::MeshIO::PY; ext["ASY" ] = MeshCore::MeshIO::ASY; + ext["3MF" ] = MeshCore::MeshIO::ThreeMF; static char* keywords_path[] = {"Filename","Format","Name","Material",NULL}; if (PyArg_ParseTupleAndKeywords(args, kwds, "et|ssO", keywords_path, "utf-8", diff --git a/src/Mod/Mesh/App/MeshTestsApp.py b/src/Mod/Mesh/App/MeshTestsApp.py index d467b1e016..4c32fc6570 100644 --- a/src/Mod/Mesh/App/MeshTestsApp.py +++ b/src/Mod/Mesh/App/MeshTestsApp.py @@ -226,6 +226,59 @@ class MeshGeoTestCases(unittest.TestCase): res=f1.intersect(f2) self.assertTrue(len(res) == 0) + def testIntersectionOfTransformedMesh(self): + self.planarMesh.append( [0.0,10.0,10.0] ) + self.planarMesh.append( [10.0,0.0,10.0] ) + self.planarMesh.append( [10.0,10.0,10.0] ) + self.planarMesh.append( [6.0,8.0,10.0] ) + self.planarMesh.append( [16.0,8.0,10.0] ) + self.planarMesh.append( [6.0,18.0,10.0] ) + planarMeshObject = Mesh.Mesh(self.planarMesh) + + mat = Base.Matrix() + mat.rotateX(1.0) + mat.rotateY(1.0) + mat.rotateZ(1.0) + planarMeshObject.transformGeometry(mat) + + f1 = planarMeshObject.Facets[0] + f2 = planarMeshObject.Facets[1] + res=f1.intersect(f2) + self.assertEqual(len(res), 2) + + def testIntersectionOfParallelTriangles(self): + self.planarMesh.append( [0.0,10.0,10.0] ) + self.planarMesh.append( [10.0,0.0,10.0] ) + self.planarMesh.append( [10.0,10.0,10.0] ) + self.planarMesh.append( [6.0,8.0,10.1] ) + self.planarMesh.append( [16.0,8.0,10.1] ) + self.planarMesh.append( [6.0,18.0,10.1] ) + planarMeshObject = Mesh.Mesh(self.planarMesh) + + mat = Base.Matrix() + mat.rotateX(1.0) + mat.rotateY(1.0) + mat.rotateZ(1.0) + planarMeshObject.transformGeometry(mat) + + f1 = planarMeshObject.Facets[0] + f2 = planarMeshObject.Facets[1] + res=f1.intersect(f2) + self.assertTrue(len(res) == 0) + + def testIntersectionOnEdge(self): + self.planarMesh.append( [5.0, -1.9371663331985474, 0.49737977981567383] ) + self.planarMesh.append( [4.0, -1.9371663331985474, 0.49737977981567383] ) + self.planarMesh.append( [5.0, -1.9842294454574585, 0.25066646933555603] ) + self.planarMesh.append( [4.6488823890686035, -1.7827962636947632, 0.4577442705631256] ) + self.planarMesh.append( [4.524135112762451, -2.0620131492614746, 0.5294350385665894] ) + self.planarMesh.append( [4.6488823890686035, -1.8261089324951172, 0.23069120943546295] ) + planarMeshObject = Mesh.Mesh(self.planarMesh) + f1 = planarMeshObject.Facets[0] + f2 = planarMeshObject.Facets[1] + res = f1.intersect(f2) + self.assertEqual(len(res), 2) + def testIntersectionCoplanar(self): self.planarMesh.append( [0.,10.,10.] ) self.planarMesh.append( [10.,0.,10.] ) diff --git a/src/Mod/Mesh/Gui/Command.cpp b/src/Mod/Mesh/Gui/Command.cpp index f92ca46735..52f078b099 100644 --- a/src/Mod/Mesh/Gui/Command.cpp +++ b/src/Mod/Mesh/Gui/Command.cpp @@ -524,6 +524,7 @@ void CmdMeshExport::activated(int) ext << qMakePair(QString::fromLatin1("%1 (*.nas *.bdf)").arg(QObject::tr("Nastran")), "NAS"); ext << qMakePair(QString::fromLatin1("%1 (*.py)").arg(QObject::tr("Python module def")), "PY"); ext << qMakePair(QString::fromLatin1("%1 (*.asy)").arg(QObject::tr("Asymptote Format")), "ASY"); + ext << qMakePair(QString::fromLatin1("%1 (*.3mf)").arg(QObject::tr("3D Manufacturing Format")), "3MF"); ext << qMakePair(QString::fromLatin1("%1 (*.*)").arg(QObject::tr("All Files")), ""); // Undefined QStringList filter; for (QList >::iterator it = ext.begin(); it != ext.end(); ++it) diff --git a/src/Mod/Mesh/Gui/DlgEvaluateMeshImp.cpp b/src/Mod/Mesh/Gui/DlgEvaluateMeshImp.cpp index a14fc796e6..cdac9bd8e1 100644 --- a/src/Mod/Mesh/Gui/DlgEvaluateMeshImp.cpp +++ b/src/Mod/Mesh/Gui/DlgEvaluateMeshImp.cpp @@ -25,6 +25,7 @@ #ifndef _PreComp_ # include # include +# include #endif #include "DlgEvaluateMeshImp.h" @@ -1273,11 +1274,18 @@ bool DockEvaluateMeshImp::hasInstance() DockEvaluateMeshImp::DockEvaluateMeshImp( QWidget* parent, Qt::WindowFlags fl ) : DlgEvaluateMeshImp( parent, fl ) { + scrollArea = new QScrollArea(); + scrollArea->setObjectName(QLatin1String("scrollArea")); + scrollArea->setFrameShape(QFrame::NoFrame); + scrollArea->setFrameShadow(QFrame::Plain); + scrollArea->setWidgetResizable(true); + scrollArea->setWidget(this); + // embed this dialog into a dockable widget container Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance(); // use Qt macro for preparing for translation stuff (but not translating yet) QDockWidget* dw = pDockMgr->addDockWindow("Evaluate & Repair Mesh", - this, Qt::RightDockWidgetArea); + scrollArea, Qt::RightDockWidgetArea); //dw->setAttribute(Qt::WA_DeleteOnClose); dw->setFeatures(QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetFloatable); dw->show(); @@ -1298,7 +1306,11 @@ void DockEvaluateMeshImp::closeEvent(QCloseEvent*) { // closes the dock window Gui::DockWindowManager* pDockMgr = Gui::DockWindowManager::instance(); - pDockMgr->removeDockWindow(this); + pDockMgr->removeDockWindow(scrollArea); + + // make sure to also delete the scroll area + scrollArea->setWidget(nullptr); + scrollArea->deleteLater(); } /** diff --git a/src/Mod/Mesh/Gui/DlgEvaluateMeshImp.h b/src/Mod/Mesh/Gui/DlgEvaluateMeshImp.h index e663e795ae..fffd94e34e 100644 --- a/src/Mod/Mesh/Gui/DlgEvaluateMeshImp.h +++ b/src/Mod/Mesh/Gui/DlgEvaluateMeshImp.h @@ -34,6 +34,7 @@ #include class QAbstractButton; +class QScrollArea; namespace Gui { class View3DInventor; @@ -159,6 +160,7 @@ public: QSize sizeHint () const; private: + QScrollArea* scrollArea; static DockEvaluateMeshImp* _instance; }; diff --git a/src/Mod/Mesh/Gui/Resources/Mesh.qrc b/src/Mod/Mesh/Gui/Resources/Mesh.qrc index 3c358b6c78..5dbfb6d409 100644 --- a/src/Mod/Mesh/Gui/Resources/Mesh.qrc +++ b/src/Mod/Mesh/Gui/Resources/Mesh.qrc @@ -42,6 +42,8 @@ icons/RegularSolids/Mesh_Ellipsoid.svg icons/RegularSolids/Mesh_Sphere.svg icons/RegularSolids/Mesh_Torus.svg + + translations/Mesh_af.qm translations/Mesh_de.qm translations/Mesh_fi.qm diff --git a/src/Mod/MeshPart/App/AppMeshPart.cpp b/src/Mod/MeshPart/App/AppMeshPart.cpp index fbd6d1da92..e4cc0d004b 100644 --- a/src/Mod/MeshPart/App/AppMeshPart.cpp +++ b/src/Mod/MeshPart/App/AppMeshPart.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (c) 2008 Jürgen Riegel (juergen.riegel@web.de) * + * Copyright (c) 2008 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * diff --git a/src/Mod/MeshPart/App/AppMeshPartPy.cpp b/src/Mod/MeshPart/App/AppMeshPartPy.cpp index 489ae76933..a1b96ff51e 100644 --- a/src/Mod/MeshPart/App/AppMeshPartPy.cpp +++ b/src/Mod/MeshPart/App/AppMeshPartPy.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (c) 2008 Jürgen Riegel (juergen.riegel@web.de) * + * Copyright (c) 2008 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * diff --git a/src/Mod/MeshPart/App/CurveProjector.cpp b/src/Mod/MeshPart/App/CurveProjector.cpp index e559834e66..5731521aa3 100644 --- a/src/Mod/MeshPart/App/CurveProjector.cpp +++ b/src/Mod/MeshPart/App/CurveProjector.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (c) Juergen Riegel * + * Copyright (c) 2008 Juergen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * diff --git a/src/Mod/MeshPart/App/CurveProjector.h b/src/Mod/MeshPart/App/CurveProjector.h index 20d2479fea..5efcd7566d 100644 --- a/src/Mod/MeshPart/App/CurveProjector.h +++ b/src/Mod/MeshPart/App/CurveProjector.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (c) Juergen Riegel * + * Copyright (c) 2008 Juergen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * diff --git a/src/Mod/MeshPart/App/MeshAlgos.cpp b/src/Mod/MeshPart/App/MeshAlgos.cpp index 0f1f2439d2..3551766207 100644 --- a/src/Mod/MeshPart/App/MeshAlgos.cpp +++ b/src/Mod/MeshPart/App/MeshAlgos.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (c) Juergen Riegel * + * Copyright (c) 2008 Juergen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * diff --git a/src/Mod/MeshPart/App/MeshAlgos.h b/src/Mod/MeshPart/App/MeshAlgos.h index d524f7effd..a0f482421d 100644 --- a/src/Mod/MeshPart/App/MeshAlgos.h +++ b/src/Mod/MeshPart/App/MeshAlgos.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (c) Juergen Riegel * + * Copyright (c) 2008 Juergen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * diff --git a/src/Mod/MeshPart/App/PreCompiled.cpp b/src/Mod/MeshPart/App/PreCompiled.cpp index 46269e9671..1e5d389dd2 100644 --- a/src/Mod/MeshPart/App/PreCompiled.cpp +++ b/src/Mod/MeshPart/App/PreCompiled.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (c) 2008 Jürgen Riegel (juergen.riegel@web.de) * + * Copyright (c) 2008 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * diff --git a/src/Mod/MeshPart/App/PreCompiled.h b/src/Mod/MeshPart/App/PreCompiled.h index 07eac4d2e8..754261f3a7 100644 --- a/src/Mod/MeshPart/App/PreCompiled.h +++ b/src/Mod/MeshPart/App/PreCompiled.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (c) 2008 Jürgen Riegel (juergen.riegel@web.de) * + * Copyright (c) 2008 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * diff --git a/src/Mod/MeshPart/Gui/AppMeshPartGui.cpp b/src/Mod/MeshPart/Gui/AppMeshPartGui.cpp index 5bd034c4eb..64aa198feb 100644 --- a/src/Mod/MeshPart/Gui/AppMeshPartGui.cpp +++ b/src/Mod/MeshPart/Gui/AppMeshPartGui.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (c) 2008 Jürgen Riegel (juergen.riegel@web.de) * + * Copyright (c) 2008 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * diff --git a/src/Mod/MeshPart/Gui/Command.cpp b/src/Mod/MeshPart/Gui/Command.cpp index 22f2ff929e..f91ff09030 100644 --- a/src/Mod/MeshPart/Gui/Command.cpp +++ b/src/Mod/MeshPart/Gui/Command.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (c) 2008 Jürgen Riegel (juergen.riegel@web.de) * + * Copyright (c) 2008 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * diff --git a/src/Mod/MeshPart/Gui/MeshFlatteningCommand.py b/src/Mod/MeshPart/Gui/MeshFlatteningCommand.py index ff442d2d90..3b4a910de4 100644 --- a/src/Mod/MeshPart/Gui/MeshFlatteningCommand.py +++ b/src/Mod/MeshPart/Gui/MeshFlatteningCommand.py @@ -1,24 +1,24 @@ -#************************************************************************** -#* Copyright (c) 2017 Lorenz Lechner * -#* * -#* 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 * -#* * -#**************************************************************************/ +#*************************************************************************** +#* Copyright (c) 2017 Lorenz Lechner * +#* * +#* 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 * +#* * +#***************************************************************************/ import Mesh import FreeCAD as App diff --git a/src/Mod/MeshPart/Gui/PreCompiled.cpp b/src/Mod/MeshPart/Gui/PreCompiled.cpp index 46269e9671..1e5d389dd2 100644 --- a/src/Mod/MeshPart/Gui/PreCompiled.cpp +++ b/src/Mod/MeshPart/Gui/PreCompiled.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (c) 2008 Jürgen Riegel (juergen.riegel@web.de) * + * Copyright (c) 2008 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * diff --git a/src/Mod/MeshPart/Gui/PreCompiled.h b/src/Mod/MeshPart/Gui/PreCompiled.h index 7cef9ef237..9f90acb0d8 100644 --- a/src/Mod/MeshPart/Gui/PreCompiled.h +++ b/src/Mod/MeshPart/Gui/PreCompiled.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (c) 2008 Jürgen Riegel (juergen.riegel@web.de) * + * Copyright (c) 2008 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * diff --git a/src/Mod/MeshPart/Init.py b/src/Mod/MeshPart/Init.py index c9c37c802b..dd8dea3aee 100644 --- a/src/Mod/MeshPart/Init.py +++ b/src/Mod/MeshPart/Init.py @@ -1,8 +1,8 @@ -# FreeCAD init script of the MeshPart module +# FreeCAD init script of the MeshPart module # (c) 2001 Juergen Riegel #*************************************************************************** -#* (c) Juergen Riegel (juergen.riegel@web.de) 2002 * +#* Copyright (c) 2002 Juergen Riegel * #* * #* This file is part of the FreeCAD CAx development system. * #* * @@ -13,14 +13,13 @@ #* for detail see the LICENCE text file. * #* * #* FreeCAD is distributed in the hope that it will be useful, * -#* but WITHOUT ANY WARRANTY; without even the implied warranty of * +#* but WITHOUT ANY WARRANTY; without even the implied warranty of * #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * #* GNU Lesser General Public License for more details. * #* * #* You should have received a copy of the GNU Library General Public * -#* License along with FreeCAD; if not, write to the Free Software * +#* License along with FreeCAD; if not, write to the Free Software * #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * #* USA * #* * -#* Juergen Riegel 2002 * #***************************************************************************/ diff --git a/src/Mod/MeshPart/InitGui.py b/src/Mod/MeshPart/InitGui.py index 82a917f2ea..17d0102f1f 100644 --- a/src/Mod/MeshPart/InitGui.py +++ b/src/Mod/MeshPart/InitGui.py @@ -1,4 +1,4 @@ -# MeshPart gui init module +# MeshPart gui init module # (c) 2003 Juergen Riegel # # Gathering all the information to start FreeCAD @@ -6,7 +6,7 @@ # runs when the gui is up #*************************************************************************** -#* (c) Juergen Riegel (juergen.riegel@web.de) 2002 * +#* Copyright (c) 2002 Juergen Riegel * #* * #* This file is part of the FreeCAD CAx development system. * #* * @@ -26,44 +26,46 @@ #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * #* USA * #* * -#* Juergen Riegel 2002 * #***************************************************************************/ class MeshPartWorkbench ( Workbench ): - "MeshPart workbench object" - Icon = """ - /* XPM */ - static const char *MeshPart_Box[]={ - "16 16 3 1", - ". c None", - "# c #000000", - "a c #c6c642", - "................", - ".......#######..", - "......#aaaaa##..", - ".....#aaaaa###..", - "....#aaaaa##a#..", - "...#aaaaa##aa#..", - "..#aaaaa##aaa#..", - ".########aaaa#..", - ".#aaaaa#aaaaa#..", - ".#aaaaa#aaaa##..", - ".#aaaaa#aaa##...", - ".#aaaaa#aa##....", - ".#aaaaa#a##... .", - ".#aaaaa###......", - ".########.......", - "................"}; - """ - MenuText = "MeshPart" - ToolTip = "MeshPart workbench" + "MeshPart workbench object" + Icon = """ + /* XPM */ + static const char *MeshPart_Box[]={ + "16 16 3 1", + ". c None", + "# c #000000", + "a c #c6c642", + "................", + ".......#######..", + "......#aaaaa##..", + ".....#aaaaa###..", + "....#aaaaa##a#..", + "...#aaaaa##aa#..", + "..#aaaaa##aaa#..", + ".########aaaa#..", + ".#aaaaa#aaaaa#..", + ".#aaaaa#aaaa##..", + ".#aaaaa#aaa##...", + ".#aaaaa#aa##....", + ".#aaaaa#a##... .", + ".#aaaaa###......", + ".########.......", + "................"}; + """ + MenuText = "MeshPart" + ToolTip = "MeshPart workbench" - def Initialize(self): - # load the module - import MeshPartGui - import MeshPart - def GetClassName(self): - return "MeshPartGui::Workbench" -#Gui.addWorkbench(MeshPartWorkbench()) + def Initialize(self): + # load the module + import MeshPartGui + import MeshPart + + + def GetClassName(self): + return "MeshPartGui::Workbench" + +# Gui.addWorkbench(MeshPartWorkbench()) diff --git a/src/Mod/OpenSCAD/OpenSCADFeatures.py b/src/Mod/OpenSCAD/OpenSCADFeatures.py index 93332b67a2..4f3f4d21d8 100644 --- a/src/Mod/OpenSCAD/OpenSCADFeatures.py +++ b/src/Mod/OpenSCAD/OpenSCADFeatures.py @@ -200,13 +200,11 @@ class Resize : self.createGeometry(fp) def createGeometry(self, fp) : - print("Resize create Geometry") import FreeCAD mat = FreeCAD.Matrix() mat.A11 = self.Vector[0] mat.A22 = self.Vector[1] mat.A33 = self.Vector[2] - print(mat) fp.Shape = self.Target.Shape.transformGeometry(mat) def __getstate__(self): @@ -387,7 +385,7 @@ class Frustum: fp.Placement = plm class Twist: - def __init__(self, obj,child=None,h=1.0,angle=0.0,scale=[1.0,1.0]): + def __init__(self, obj, child=None, h=1.0, angle=0.0, scale=[1.0,1.0]): import FreeCAD obj.addProperty("App::PropertyLink","Base","Base", "The base object that must be transformed") @@ -406,10 +404,10 @@ class Twist: self.createGeometry(fp) def onChanged(self, fp, prop): - if prop in ["Angle","Height"]: + if prop in ["Angle","Height","Scale"]: self.createGeometry(fp) - def createGeometry(self,fp): + def createGeometry(self, fp): import FreeCAD,Part,math,sys if fp.Base and fp.Height and fp.Base.Shape.isValid(): solids = [] @@ -443,7 +441,6 @@ class Twist: pipe_shell.add(wire1) pipe_shell.add(wire2) pipe_shell.setAuxiliarySpine(auxiliary_spine,True,0) - print(pipe_shell.getStatus()) assert(pipe_shell.isReady()) pipe_shell.build() faces.extend(pipe_shell.shape().Faces) @@ -534,10 +531,9 @@ class PrismaticToroid: solid = Part.makeSolid (clean_shell) if solid.Volume < 0: solid.reverse() - print (f"Solid volume is {solid.Volume}") solids.append(solid) except Part.OCCError: - print ("Could not create solid: creating compound instead") + FreeCAD.Console.PrintWarning("Could not create solid: creating compound instead") solids.append(Part.Compound(faces)) fp.Shape = Part.Compound(solids) @@ -587,139 +583,146 @@ class CGALFeature: raise ValueError def makeSurfaceVolume(filename): - import FreeCAD,Part,sys + import FreeCAD + import Part + import sys + coords = [] with open(filename) as f1: - coords = [] min_z = sys.float_info.max for line in f1.readlines(): - sline=line.strip() + sline = line.strip() if sline and not sline.startswith('#'): - ycoord=len(coords) - lcoords=[] + ycoord = len(coords) + lcoords = [] for xcoord, num in enumerate(sline.split()): - fnum=float(num) + fnum = float(num) lcoords.append(FreeCAD.Vector(float(xcoord),float(ycoord),fnum)) min_z = min(fnum,min_z) coords.append(lcoords) - - num_rows = len(coords) - num_cols = len(coords[0]) - # OpenSCAD does not spline this surface, so neither do we: just create a bunch of faces, - # using four triangles per quadrilateral - faces = [] - for row in range(num_rows-1): - for col in range(num_cols-1): - a = coords[row+0][col+0] - b = coords[row+0][col+1] - c = coords[row+1][col+1] - d = coords[row+1][col+0] - centroid = 0.25 * (a + b + c + d) - ab = Part.makeLine(a,b) - bc = Part.makeLine(b,c) - cd = Part.makeLine(c,d) - da = Part.makeLine(d,a) + num_rows = len(coords) + if num_rows == 0: + FreeCAD.Console.PrintWarning(f"No data found in surface file {filename}") + return None,0,0 + num_cols = len(coords[0]) - diag_a = Part.makeLine(a, centroid) - diag_b = Part.makeLine(b, centroid) - diag_c = Part.makeLine(c, centroid) - diag_d = Part.makeLine(d, centroid) + # OpenSCAD does not spline this surface, so neither do we: just create a + # bunch of faces, + # using four triangles per quadrilateral + faces = [] + for row in range(num_rows - 1): + for col in range(num_cols - 1): + a = coords[row + 0][col + 0] + b = coords[row + 0][col + 1] + c = coords[row + 1][col + 1] + d = coords[row + 1][col + 0] + centroid = 0.25 * (a + b + c + d) + ab = Part.makeLine(a,b) + bc = Part.makeLine(b,c) + cd = Part.makeLine(c,d) + da = Part.makeLine(d,a) - wire1 = Part.Wire([ab,diag_a,diag_b]) - wire2 = Part.Wire([bc,diag_b,diag_c]) - wire3 = Part.Wire([cd,diag_c,diag_d]) - wire4 = Part.Wire([da,diag_d,diag_a]) + diag_a = Part.makeLine(a, centroid) + diag_b = Part.makeLine(b, centroid) + diag_c = Part.makeLine(c, centroid) + diag_d = Part.makeLine(d, centroid) - try: - face = Part.Face(wire1) - faces.append(face) - face = Part.Face(wire2) - faces.append(face) - face = Part.Face(wire3) - faces.append(face) - face = Part.Face(wire4) - faces.append(face) - except Exception: - print ("Failed to create the face from {},{},{},{}".format(coords[row+0][col+0],\ - coords[row+0][col+1],coords[row+1][col+1],coords[row+1][col+0])) - - last_row = num_rows-1 - last_col = num_cols-1 + wire1 = Part.Wire([ab,diag_a,diag_b]) + wire2 = Part.Wire([bc,diag_b,diag_c]) + wire3 = Part.Wire([cd,diag_c,diag_d]) + wire4 = Part.Wire([da,diag_d,diag_a]) - # Create the face to close off the y-min border: OpenSCAD places the lower surface of the shell - # at 1 unit below the lowest coordinate in the surface - lines = [] - corner1 = FreeCAD.Vector(coords[0][0].x, coords[0][0].y, min_z-1) - lines.append (Part.makeLine(corner1,coords[0][0])) - for col in range(num_cols-1): - a = coords[0][col] - b = coords[0][col+1] - lines.append (Part.makeLine(a, b)) - corner2 = FreeCAD.Vector(coords[0][last_col].x, coords[0][last_col].y, min_z-1) - lines.append (Part.makeLine(corner2,coords[0][last_col])) - lines.append (Part.makeLine(corner1,corner2)) - wire = Part.Wire(lines) - face = Part.Face(wire) - faces.append(face) - - # Create the face to close off the y-max border - lines = [] - corner1 = FreeCAD.Vector(coords[last_row][0].x, coords[last_row][0].y, min_z-1) - lines.append (Part.makeLine(corner1,coords[last_row][0])) - for col in range(num_cols-1): - a = coords[last_row][col] - b = coords[last_row][col+1] - lines.append (Part.makeLine(a, b)) - corner2 = FreeCAD.Vector(coords[last_row][last_col].x, coords[last_row][last_col].y, min_z-1) - lines.append (Part.makeLine(corner2,coords[last_row][last_col])) - lines.append (Part.makeLine(corner1,corner2)) - wire = Part.Wire(lines) - face = Part.Face(wire) - faces.append(face) + try: + face = Part.Face(wire1) + faces.append(face) + face = Part.Face(wire2) + faces.append(face) + face = Part.Face(wire3) + faces.append(face) + face = Part.Face(wire4) + faces.append(face) + except Exception: + FreeCAD.Console.PrintWarning("Failed to create the face from {},{},{},{}".format(coords[row + 0][col + 0],\ + coords[row + 0][col + 1],coords[row + 1][col + 1],coords[row + 1][col + 0])) - # Create the face to close off the x-min border - lines = [] - corner1 = FreeCAD.Vector(coords[0][0].x, coords[0][0].y, min_z-1) - lines.append (Part.makeLine(corner1,coords[0][0])) - for row in range(num_rows-1): - a = coords[row][0] - b = coords[row+1][0] - lines.append (Part.makeLine(a, b)) - corner2 = FreeCAD.Vector(coords[last_row][0].x, coords[last_row][0].y, min_z-1) - lines.append (Part.makeLine(corner2,coords[last_row][0])) - lines.append (Part.makeLine(corner1,corner2)) - wire = Part.Wire(lines) - face = Part.Face(wire) - faces.append(face) + last_row = num_rows - 1 + last_col = num_cols - 1 - # Create the face to close off the x-max border - lines = [] - corner1 = FreeCAD.Vector(coords[0][last_col].x, coords[0][last_col].y, min_z-1) - lines.append (Part.makeLine(corner1,coords[0][last_col])) - for row in range(num_rows-1): - a = coords[row][last_col] - b = coords[row+1][last_col] - lines.append (Part.makeLine(a, b)) - corner2 = FreeCAD.Vector(coords[last_row][last_col].x, coords[last_row][last_col].y, min_z-1) - lines.append (Part.makeLine(corner2,coords[last_row][last_col])) - lines.append (Part.makeLine(corner1,corner2)) - wire = Part.Wire(lines) - face = Part.Face(wire) - faces.append(face) + # Create the face to close off the y-min border: OpenSCAD places the lower + # surface of the shell + # at 1 unit below the lowest coordinate in the surface + lines = [] + corner1 = FreeCAD.Vector(coords[0][0].x, coords[0][0].y, min_z - 1) + lines.append(Part.makeLine(corner1,coords[0][0])) + for col in range(num_cols - 1): + a = coords[0][col] + b = coords[0][col + 1] + lines.append(Part.makeLine(a, b)) + corner2 = FreeCAD.Vector(coords[0][last_col].x, coords[0][last_col].y, min_z - 1) + lines.append(Part.makeLine(corner2,coords[0][last_col])) + lines.append(Part.makeLine(corner1,corner2)) + wire = Part.Wire(lines) + face = Part.Face(wire) + faces.append(face) - # Create a bottom surface to close off the shell - a = FreeCAD.Vector(coords[0][0].x, coords[0][0].y, min_z-1) - b = FreeCAD.Vector(coords[0][last_col].x, coords[0][last_col].y, min_z-1) - c = FreeCAD.Vector(coords[last_row][last_col].x, coords[last_row][last_col].y, min_z-1) - d = FreeCAD.Vector(coords[last_row][0].x, coords[last_row][0].y, min_z-1) - ab = Part.makeLine(a,b) - bc = Part.makeLine(b,c) - cd = Part.makeLine(c,d) - da = Part.makeLine(d,a) - wire = Part.Wire([ab,bc,cd,da]) - face = Part.Face(wire) - faces.append(face) + # Create the face to close off the y-max border + lines = [] + corner1 = FreeCAD.Vector(coords[last_row][0].x, coords[last_row][0].y, min_z - 1) + lines.append(Part.makeLine(corner1,coords[last_row][0])) + for col in range(num_cols - 1): + a = coords[last_row][col] + b = coords[last_row][col + 1] + lines.append(Part.makeLine(a, b)) + corner2 = FreeCAD.Vector(coords[last_row][last_col].x, coords[last_row][last_col].y, min_z - 1) + lines.append(Part.makeLine(corner2,coords[last_row][last_col])) + lines.append(Part.makeLine(corner1,corner2)) + wire = Part.Wire(lines) + face = Part.Face(wire) + faces.append(face) - s = Part.Shell(faces) - solid = Part.Solid(s) - return solid,last_col,last_row + # Create the face to close off the x-min border + lines = [] + corner1 = FreeCAD.Vector(coords[0][0].x, coords[0][0].y, min_z - 1) + lines.append(Part.makeLine(corner1,coords[0][0])) + for row in range(num_rows - 1): + a = coords[row][0] + b = coords[row + 1][0] + lines.append(Part.makeLine(a, b)) + corner2 = FreeCAD.Vector(coords[last_row][0].x, coords[last_row][0].y, min_z - 1) + lines.append(Part.makeLine(corner2,coords[last_row][0])) + lines.append(Part.makeLine(corner1,corner2)) + wire = Part.Wire(lines) + face = Part.Face(wire) + faces.append(face) + + # Create the face to close off the x-max border + lines = [] + corner1 = FreeCAD.Vector(coords[0][last_col].x, coords[0][last_col].y, min_z - 1) + lines.append(Part.makeLine(corner1,coords[0][last_col])) + for row in range(num_rows - 1): + a = coords[row][last_col] + b = coords[row + 1][last_col] + lines.append(Part.makeLine(a, b)) + corner2 = FreeCAD.Vector(coords[last_row][last_col].x, coords[last_row][last_col].y, min_z - 1) + lines.append(Part.makeLine(corner2,coords[last_row][last_col])) + lines.append(Part.makeLine(corner1,corner2)) + wire = Part.Wire(lines) + face = Part.Face(wire) + faces.append(face) + + # Create a bottom surface to close off the shell + a = FreeCAD.Vector(coords[0][0].x, coords[0][0].y, min_z - 1) + b = FreeCAD.Vector(coords[0][last_col].x, coords[0][last_col].y, min_z - 1) + c = FreeCAD.Vector(coords[last_row][last_col].x, coords[last_row][last_col].y, min_z - 1) + d = FreeCAD.Vector(coords[last_row][0].x, coords[last_row][0].y, min_z - 1) + ab = Part.makeLine(a,b) + bc = Part.makeLine(b,c) + cd = Part.makeLine(c,d) + da = Part.makeLine(d,a) + wire = Part.Wire([ab,bc,cd,da]) + face = Part.Face(wire) + faces.append(face) + + s = Part.Shell(faces) + solid = Part.Solid(s) + return solid,last_col,last_row diff --git a/src/Mod/OpenSCAD/OpenSCADTest/app/test_importCSG.py b/src/Mod/OpenSCAD/OpenSCADTest/app/test_importCSG.py index 59759a7c47..24dda152a2 100644 --- a/src/Mod/OpenSCAD/OpenSCADTest/app/test_importCSG.py +++ b/src/Mod/OpenSCAD/OpenSCADTest/app/test_importCSG.py @@ -38,6 +38,7 @@ __url__ = "https://www.freecadweb.org" class TestImportCSG(unittest.TestCase): MODULE = 'test_importCSG' # file name without extension + temp_dir = tempfile.TemporaryDirectory() def setUp(self): @@ -73,113 +74,78 @@ class TestImportCSG(unittest.TestCase): FreeCAD.closeDocument("CSG") + def utility_create_scad(self, scadCode, name): + filename = self.temp_dir.name + os.path.sep + name + ".scad" + print (f"Creating {filename}") + f = open(filename,"w+") + f.write(scadCode) + f.close() + return importCSG.open(filename) + def test_import_sphere(self): - with tempfile.TemporaryDirectory() as temp_dir: - filename = temp_dir + os.path.sep + "sphere.scad" - f = open(filename,"w+") - f.write("sphere(10.0);") - f.close() - doc = importCSG.open(filename) - sphere = doc.getObject("sphere") - self.assertTrue (sphere is not None) - self.assertTrue (sphere.Radius == 10.0) - FreeCAD.closeDocument(doc.Name) + doc = self.utility_create_scad("sphere(10.0);","sphere") + sphere = doc.getObject("sphere") + self.assertTrue (sphere is not None) + self.assertTrue (sphere.Radius == 10.0) + FreeCAD.closeDocument(doc.Name) def test_import_cylinder(self): - with tempfile.TemporaryDirectory() as temp_dir: - filename = temp_dir + os.path.sep + "cylinder.scad" - f = open(filename,"w+") - f.write("cylinder(50.0,d=10.0);") - f.close() - doc = importCSG.open(filename) - cylinder = doc.getObject("cylinder") - self.assertTrue (cylinder is not None) - self.assertTrue (cylinder.Radius == 5.0) - self.assertTrue (cylinder.Height == 50.0) - FreeCAD.closeDocument(doc.Name) + doc = self.utility_create_scad("cylinder(50.0,d=10.0);","cylinder") + cylinder = doc.getObject("cylinder") + self.assertTrue (cylinder is not None) + self.assertTrue (cylinder.Radius == 5.0) + self.assertTrue (cylinder.Height == 50.0) + FreeCAD.closeDocument(doc.Name) def test_import_cube(self): - with tempfile.TemporaryDirectory() as temp_dir: - filename = temp_dir + os.path.sep + "cube.scad" - f = open(filename,"w+") - f.write("cube([1.0,2.0,3.0]);") - f.close() - doc = importCSG.open(filename) - cube = doc.getObject("cube") - self.assertTrue (cube is not None) - self.assertTrue (cube.Length == 1.0) - self.assertTrue (cube.Width == 2.0) - self.assertTrue (cube.Height == 3.0) - FreeCAD.closeDocument(doc.Name) + doc = self.utility_create_scad("cube([1.0,2.0,3.0]);","cube") + cube = doc.getObject("cube") + self.assertTrue (cube is not None) + self.assertTrue (cube.Length == 1.0) + self.assertTrue (cube.Width == 2.0) + self.assertTrue (cube.Height == 3.0) + FreeCAD.closeDocument(doc.Name) def test_import_circle(self): - with tempfile.TemporaryDirectory() as temp_dir: - filename = temp_dir + os.path.sep + "circle.scad" - f = open(filename,"w+") - f.write("circle(10.0);") - f.close() - doc = importCSG.open(filename) - circle = doc.getObject("circle") - self.assertTrue (circle is not None) - self.assertTrue (circle.Radius == 10.0) - FreeCAD.closeDocument(doc.Name) + doc = self.utility_create_scad("circle(10.0);","circle") + circle = doc.getObject("circle") + self.assertTrue (circle is not None) + self.assertTrue (circle.Radius == 10.0) + FreeCAD.closeDocument(doc.Name) def test_import_square(self): - with tempfile.TemporaryDirectory() as temp_dir: - filename = temp_dir + os.path.sep + "square.scad" - f = open(filename,"w+") - f.write("square([1.0,2.0]);") - f.close() - doc = importCSG.open(filename) - square = doc.getObject("square") - self.assertTrue (square is not None) - self.assertTrue (square.Length == 1.0) - self.assertTrue (square.Width == 2.0) - FreeCAD.closeDocument(doc.Name) + doc = self.utility_create_scad("square([1.0,2.0]);","square") + square = doc.getObject("square") + self.assertTrue (square is not None) + self.assertTrue (square.Length == 1.0) + self.assertTrue (square.Width == 2.0) + FreeCAD.closeDocument(doc.Name) def test_import_text(self): - with tempfile.TemporaryDirectory() as temp_dir: - filename = temp_dir + os.path.sep + "text.scad" - f = open(filename,"w+") - f.write("text(\"X\");") # Keep it short to keep the test fast-ish - f.close() - try: - doc = importCSG.open(filename) - text = doc.getObject("text") - self.assertTrue (text is not None) - FreeCAD.closeDocument(doc.Name) - except Exception: - pass # We may not have the DXF importer available + try: + doc = self.utility_create_scad("text(\"X\");","text") # Keep it short to keep the test fast-ish + text = doc.getObject("text") + self.assertTrue (text is not None) + FreeCAD.closeDocument(doc.Name) + except Exception: + pass # We may not have the DXF importer available def test_import_polygon_nopath(self): - with tempfile.TemporaryDirectory() as temp_dir: - filename = temp_dir + os.path.sep + "polygon_nopath.scad" - f = open(filename,"w+") - f.write("polygon(points=[[0,0],[100,0],[130,50],[30,50]]);") - f.close() - doc = importCSG.open(filename) - polygon = doc.getObject("polygon") - self.assertTrue (polygon is not None) - self.assertAlmostEqual (polygon.Shape.Area, 5000.0) - FreeCAD.closeDocument(doc.Name) + doc = self.utility_create_scad("polygon(points=[[0,0],[100,0],[130,50],[30,50]]);","polygon_nopath") + polygon = doc.getObject("polygon") + self.assertTrue (polygon is not None) + self.assertAlmostEqual (polygon.Shape.Area, 5000.0) + FreeCAD.closeDocument(doc.Name) def test_import_polygon_path(self): - with tempfile.TemporaryDirectory() as temp_dir: - filename = temp_dir + os.path.sep + "polygon_path.scad" - f = open(filename,"w+") - f.write("polygon([[0,0],[100,0],[130,50],[30,50]], paths=[[0,1,2,3]]);") - f.close() - doc = importCSG.open(filename) - wire = doc.ActiveObject # With paths, the polygon gets created as a wire... - self.assertTrue (wire is not None) - self.assertAlmostEqual (wire.Shape.Area, 5000.0) - FreeCAD.closeDocument(doc.Name) + doc = self.utility_create_scad("polygon([[0,0],[100,0],[130,50],[30,50]], paths=[[0,1,2,3]]);","polygon_path") + wire = doc.ActiveObject # With paths, the polygon gets created as a wire... + self.assertTrue (wire is not None) + self.assertAlmostEqual (wire.Shape.Area, 5000.0) + FreeCAD.closeDocument(doc.Name) def test_import_polyhedron(self): - with tempfile.TemporaryDirectory() as temp_dir: - filename = temp_dir + os.path.sep + "polyhedron.scad" - f = open(filename,"w+") - f.write( + doc = self.utility_create_scad( """ polyhedron( points=[ [10,10,0],[10,-10,0],[-10,-10,0],[-10,10,0], // the four points at base @@ -187,22 +153,12 @@ polyhedron( faces=[ [0,1,4],[1,2,4],[2,3,4],[3,0,4], // each triangle side [1,0,3],[2,1,3] ] // two triangles for square base ); -""" +""","polyhedron" ) - f.close() - doc = importCSG.open(filename) - polyhedron = doc.ActiveObject # With paths, the polygon gets created as a wire... - self.assertTrue (polyhedron is not None) - self.assertAlmostEqual (polyhedron.Shape.Volume, 1333.3333, 4) - FreeCAD.closeDocument(doc.Name) - - def utility_create_scad(self, scadCode, name): - with tempfile.TemporaryDirectory() as temp_dir: - filename = temp_dir + os.path.sep + name + ".scad" - f = open(filename,"w+") - f.write(scadCode) - f.close() - return importCSG.open(filename) + polyhedron = doc.ActiveObject # With paths, the polygon gets created as a wire... + self.assertTrue (polyhedron is not None) + self.assertAlmostEqual (polyhedron.Shape.Volume, 1333.3333, 4) + FreeCAD.closeDocument(doc.Name) def test_import_difference(self): doc = self.utility_create_scad("difference() { cube(15, center=true); sphere(10); }", "difference") diff --git a/src/Mod/OpenSCAD/importCSG.py b/src/Mod/OpenSCAD/importCSG.py index c59dc778ce..9007750f34 100644 --- a/src/Mod/OpenSCAD/importCSG.py +++ b/src/Mod/OpenSCAD/importCSG.py @@ -79,7 +79,7 @@ def setColorRecursively(obj, color, transp): "Part::Common", "Part::MultiCommon"] if obj.TypeId in boolean_features: for currentObject in obj.OutList: - print(f"Fixing up colors for: {currentObject.FullName}") + if printverbose: print(f"Fixing up colors for: {currentObject.FullName}") if currentObject not in hassetcolor: setColorRecursively(currentObject, color, transp) @@ -767,14 +767,15 @@ def p_linear_extrude_with_transform(p): 'linear_extrude_with_transform : linear_extrude LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE' if printverbose: print("Linear Extrude With Transform") h = float(p[3]['height']) - s = 1.0 + if printverbose: print("Height : ",h) + s = [1.0,1.0] t = 0.0 - if printverbose: print("Twist : ",p[3]) if 'scale' in p[3]: s = [float(p[3]['scale'][0]), float(p[3]['scale'][1])] - print ("Scale: " + str(s)) + if printverbose: print ("Scale: " + str(s)) if 'twist' in p[3]: t = float(p[3]['twist']) + if printverbose: print("Twist : ",t) # Test if null object like from null text if (len(p[6]) == 0) : p[0] = [] @@ -783,7 +784,7 @@ def p_linear_extrude_with_transform(p): obj = fuse(p[6],"Linear Extrude Union") else : obj = p[6][0] - if t != 0.0 or s != 1.0: + if t != 0.0 or s[0] != 1.0 or s[1] != 1.0: newobj = process_linear_extrude_with_transform(obj,h,t,s) else: newobj = process_linear_extrude(obj,h) @@ -1286,11 +1287,7 @@ def p_polyhedron_action(p) : pp =[v2(v[k]) for k in i] # Add first point to end of list to close polygon pp.append(pp[0]) - print("pp") - print(pp) w = Part.makePolygon(pp) - print("w") - print(w) try: f = Part.Face(w) except Exception: @@ -1315,7 +1312,6 @@ def p_projection_action(p) : for shape in p[6]: shape.Shape.tessellate(0.05) bbox.add(shape.Shape.BoundBox) - print (bbox) plane = doc.addObject("Part::Plane","xy_plane_used_for_projection") plane.Length = bbox.XLength plane.Width = bbox.YLength diff --git a/src/Mod/Part/App/Tools.cpp b/src/Mod/Part/App/Tools.cpp index d95e49eaf4..7c6792a2ef 100644 --- a/src/Mod/Part/App/Tools.cpp +++ b/src/Mod/Part/App/Tools.cpp @@ -25,6 +25,11 @@ # include # include # include +# include +# include +# include +# include +# include # include # include # include @@ -590,3 +595,64 @@ void Part::Tools::applyTransformationOnNormals(const TopLoc_Location& loc, std:: } } } + +Handle (Poly_Triangulation) Part::Tools::triangulationOfFace(const TopoDS_Face& face) +{ + TopLoc_Location loc; + Handle (Poly_Triangulation) mesh = BRep_Tool::Triangulation(face, loc); + if (!mesh.IsNull()) + return mesh; + + // If no triangulation exists then the shape is probably infinite + BRepAdaptor_Surface adapt(face); + double u1 = adapt.FirstUParameter(); + double u2 = adapt.LastUParameter(); + double v1 = adapt.FirstVParameter(); + double v2 = adapt.LastVParameter(); + + // recreate a face with a clear boundary + u1 = std::max(-50.0, u1); + u2 = std::min( 50.0, u2); + v1 = std::max(-50.0, v1); + v2 = std::min( 50.0, v2); + + Handle(Geom_Surface) surface = BRep_Tool::Surface(face); + BRepBuilderAPI_MakeFace mkBuilder(surface, u1, u2, v1, v2 +#if OCC_VERSION_HEX >= 0x060502 + , Precision::Confusion() +#endif + ); + + TopoDS_Shape shape = mkBuilder.Shape(); + shape.Location(loc); + + BRepMesh_IncrementalMesh(shape, 0.1); + return BRep_Tool::Triangulation(TopoDS::Face(shape), loc); +} + +Handle(Poly_Polygon3D) Part::Tools::polygonOfEdge(const TopoDS_Edge& edge, TopLoc_Location& loc) +{ + BRepAdaptor_Curve adapt(edge); + double u = adapt.FirstParameter(); + double v = adapt.LastParameter(); + Handle(Poly_Polygon3D) aPoly = BRep_Tool::Polygon3D(edge, loc); + if (!aPoly.IsNull() && !Precision::IsInfinite(u) && !Precision::IsInfinite(v)) + return aPoly; + + // recreate an edge with a clear range + u = std::max(-50.0, u); + v = std::min( 50.0, v); + + double uv; + Handle(Geom_Curve) curve = BRep_Tool::Curve(edge, uv, uv); + + BRepBuilderAPI_MakeEdge mkBuilder(curve, u, v); + TopoDS_Shape shape = mkBuilder.Shape(); + // why do we have to set the inverted location here? + TopLoc_Location inv = loc.Inverted(); + shape.Location(inv); + + BRepMesh_IncrementalMesh(shape, 0.1); + TopLoc_Location tmp; + return BRep_Tool::Polygon3D(TopoDS::Edge(shape), tmp); +} diff --git a/src/Mod/Part/App/Tools.h b/src/Mod/Part/App/Tools.h index 425032a12d..3bb543804e 100644 --- a/src/Mod/Part/App/Tools.h +++ b/src/Mod/Part/App/Tools.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ #include #include #include +#include class gp_Lin; class gp_Pln; @@ -177,6 +179,22 @@ public: * \param normals */ static void applyTransformationOnNormals(const TopLoc_Location& loc, std::vector& normals); + /*! + * \brief triangulationOfInfinite + * Returns the triangulation of the face of the tessellated shape. In case the face has infinite lengths + * the triangulation of a limited parameter range is computed. + * \param edge + * \param loc + */ + static Handle (Poly_Triangulation) triangulationOfFace(const TopoDS_Face& face); + /*! + * \brief polygonOfEdge + * Returns the polygon of the edge of the tessellated shape. In case the edge has infinite length + * the polygon of a limited parameter range is computed. + * \param edge + * \param loc + */ + static Handle(Poly_Polygon3D) polygonOfEdge(const TopoDS_Edge& edge, TopLoc_Location& loc); }; } //namespace Part diff --git a/src/Mod/Part/App/TopoShapePyImp.cpp b/src/Mod/Part/App/TopoShapePyImp.cpp index 806d24d8ee..c6a25d3d74 100644 --- a/src/Mod/Part/App/TopoShapePyImp.cpp +++ b/src/Mod/Part/App/TopoShapePyImp.cpp @@ -26,6 +26,7 @@ # include # include # include +# include # include # include # include @@ -2198,26 +2199,49 @@ PyObject* TopoShapePy::isInside(PyObject *args) PyObject* checkFace = Py_False; TopAbs_State stateIn = TopAbs_IN; if (!PyArg_ParseTuple(args, "O!dO!", &(Base::VectorPy::Type), &point, &tolerance, &PyBool_Type, &checkFace)) - return NULL; + return nullptr; + try { TopoDS_Shape shape = getTopoShapePtr()->getShape(); - BRepClass3d_SolidClassifier solidClassifier(shape); + if (shape.IsNull()) { + PyErr_SetString(PartExceptionOCCError, "Cannot handle null shape"); + return nullptr; + } + Base::Vector3d pnt = static_cast(point)->value(); gp_Pnt vertex = gp_Pnt(pnt.x,pnt.y,pnt.z); - solidClassifier.Perform(vertex, tolerance); - Standard_Boolean test = (solidClassifier.State() == stateIn); - if (PyObject_IsTrue(checkFace) && (solidClassifier.IsOnAFace())) - test = Standard_True; - return Py_BuildValue("O", (test ? Py_True : Py_False)); + if (shape.ShapeType() == TopAbs_VERTEX || + shape.ShapeType() == TopAbs_EDGE || + shape.ShapeType() == TopAbs_WIRE) { + + BRepBuilderAPI_MakeVertex mkVertex(vertex); + BRepExtrema_DistShapeShape extss; + extss.LoadS1(mkVertex.Vertex()); + extss.LoadS2(shape); + if (!extss.Perform()) { + PyErr_SetString(PartExceptionOCCError, "Failed to determine distance to shape"); + return nullptr; + } + Standard_Boolean test = (extss.Value() <= tolerance); + return Py_BuildValue("O", (test ? Py_True : Py_False)); + } + else { + BRepClass3d_SolidClassifier solidClassifier(shape); + solidClassifier.Perform(vertex, tolerance); + Standard_Boolean test = (solidClassifier.State() == stateIn); + + if (PyObject_IsTrue(checkFace) && (solidClassifier.IsOnAFace())) + test = Standard_True; + return Py_BuildValue("O", (test ? Py_True : Py_False)); + } } catch (Standard_Failure& e) { - PyErr_SetString(PartExceptionOCCError, e.GetMessageString()); - return NULL; + return nullptr; } catch (const std::exception& e) { PyErr_SetString(PartExceptionOCCError, e.what()); - return NULL; + return nullptr; } } diff --git a/src/Mod/Part/App/modelRefine.cpp b/src/Mod/Part/App/modelRefine.cpp index 91c9a3d2e9..90dabaea99 100644 --- a/src/Mod/Part/App/modelRefine.cpp +++ b/src/Mod/Part/App/modelRefine.cpp @@ -1061,6 +1061,11 @@ bool FaceUniter::process() TopoDS_Face newFace = (*typeIt)->buildFace(adjacencySplitter.getGroup(adjacentIndex)); if (!newFace.IsNull()) { + // the created face should have the same orientation as the input faces + const FaceVectorType& faces = adjacencySplitter.getGroup(adjacentIndex); + if (!faces.empty() && newFace.Orientation() != faces[0].Orientation()) { + newFace.Orientation(faces[0].Orientation()); + } facesToSew.push_back(newFace); if (facesToRemove.capacity() <= facesToRemove.size() + adjacencySplitter.getGroup(adjacentIndex).size()) facesToRemove.reserve(facesToRemove.size() + adjacencySplitter.getGroup(adjacentIndex).size()); diff --git a/src/Mod/Part/Gui/TaskAttacher.cpp b/src/Mod/Part/Gui/TaskAttacher.cpp index 8f608f7f8a..0c7273c858 100644 --- a/src/Mod/Part/Gui/TaskAttacher.cpp +++ b/src/Mod/Part/Gui/TaskAttacher.cpp @@ -1060,7 +1060,8 @@ TaskDlgAttacher::~TaskDlgAttacher() void TaskDlgAttacher::open() { Gui::Document* document = Gui::Application::Instance->getDocument(ViewProvider->getObject()->getDocument()); - document->openCommand(QT_TRANSLATE_NOOP("Command", "Edit attachment")); + if (!document->hasPendingCommand()) + document->openCommand(QT_TRANSLATE_NOOP("Command", "Edit attachment")); } void TaskDlgAttacher::clicked(int) diff --git a/src/Mod/Part/Gui/ViewProvider2DObject.cpp b/src/Mod/Part/Gui/ViewProvider2DObject.cpp index 3c1ad047a4..a19a47803c 100644 --- a/src/Mod/Part/Gui/ViewProvider2DObject.cpp +++ b/src/Mod/Part/Gui/ViewProvider2DObject.cpp @@ -274,7 +274,7 @@ void ViewProvider2DObjectGrid::onChanged(const App::Property* prop) ViewProviderPart::onChanged(prop); if (prop == &ShowGrid || prop == &ShowOnlyInEditMode || prop == &Visibility) { - if (ShowGrid.getValue() && Visibility.getValue() && !(ShowOnlyInEditMode.getValue() && !this->isEditing())) + if (ShowGrid.getValue() && ((Visibility.getValue() && !ShowOnlyInEditMode.getValue()) || this->isEditing())) createGrid(); else Gui::coinRemoveAllChildren(GridRoot); diff --git a/src/Mod/Part/Gui/ViewProvider2DObject.h b/src/Mod/Part/Gui/ViewProvider2DObject.h index a6fd7b9221..135265d0b8 100644 --- a/src/Mod/Part/Gui/ViewProvider2DObject.h +++ b/src/Mod/Part/Gui/ViewProvider2DObject.h @@ -21,8 +21,8 @@ ***************************************************************************/ -#ifndef PARTGUI_IEWPROVIDER2DOBJECT_H -#define PARTGUI_IEWPROVIDER2DOBJECT_H +#ifndef PARTGUI_VIEWPROVIDER2DOBJECT_H +#define PARTGUI_VIEWPROVIDER2DOBJECT_H #include "ViewProvider.h" #include @@ -103,5 +103,5 @@ typedef Gui::ViewProviderPythonFeatureT ViewProvider2DObje } // namespace PartGui -#endif // PARTGUI_IEWPROVIDER2DOBJECT_H +#endif // PARTGUI_VIEWPROVIDER2DOBJECT_H diff --git a/src/Mod/Part/Gui/ViewProviderExt.cpp b/src/Mod/Part/Gui/ViewProviderExt.cpp index daf355e909..8fb9c9fe0d 100644 --- a/src/Mod/Part/Gui/ViewProviderExt.cpp +++ b/src/Mod/Part/Gui/ViewProviderExt.cpp @@ -104,6 +104,7 @@ #include #include #include +#include #include #include @@ -396,6 +397,12 @@ void ViewProviderPartExt::onChanged(const App::Property* prop) // if the object was invisible and has been changed, recreate the visual if (prop == &Visibility && (isUpdateForced() || Visibility.getValue()) && VisualTouched) { updateVisual(); + // updateVisual() may not be triggered by any change (e.g. + // triggered by an external object through forceUpdate()). And + // since DiffuseColor is not changed here either, do not falsely set + // the document modified status + Base::ObjectStatusLocker guard( + App::Property::NoModify, &DiffuseColor); // The material has to be checked again (#0001736) onChanged(&DiffuseColor); } @@ -985,6 +992,9 @@ void ViewProviderPartExt::updateVisual() TopExp::MapShapes(cShape, TopAbs_FACE, faceMap); for (int i=1; i <= faceMap.Extent(); i++) { Handle (Poly_Triangulation) mesh = BRep_Tool::Triangulation(TopoDS::Face(faceMap(i)), aLoc); + if (mesh.IsNull()) { + mesh = Part::Tools::triangulationOfFace(TopoDS::Face(faceMap(i))); + } // Note: we must also count empty faces if (!mesh.IsNull()) { numTriangles += mesh->NbTriangles(); @@ -1024,7 +1034,7 @@ void ViewProviderPartExt::updateVisual() // a free edge. int hash = aEdge.HashCode(INT_MAX); if (faceEdges.find(hash) == faceEdges.end()) { - Handle(Poly_Polygon3D) aPoly = BRep_Tool::Polygon3D(aEdge, aLoc); + Handle(Poly_Polygon3D) aPoly = Part::Tools::polygonOfEdge(aEdge, aLoc); if (!aPoly.IsNull()) { int nbNodesInEdge = aPoly->NbNodes(); numNodes += nbNodesInEdge; @@ -1058,6 +1068,9 @@ void ViewProviderPartExt::updateVisual() const TopoDS_Face &actFace = TopoDS::Face(faceMap(i)); // get the mesh of the shape Handle (Poly_Triangulation) mesh = BRep_Tool::Triangulation(actFace,aLoc); + if (mesh.IsNull()) { + mesh = Part::Tools::triangulationOfFace(actFace); + } if (mesh.IsNull()) { parts[ii] = 0; continue; @@ -1220,7 +1233,7 @@ void ViewProviderPartExt::updateVisual() // handling of the free edge that are not associated to a face int hash = aEdge.HashCode(INT_MAX); if (faceEdges.find(hash) == faceEdges.end()) { - Handle(Poly_Polygon3D) aPoly = BRep_Tool::Polygon3D(aEdge, aLoc); + Handle(Poly_Polygon3D) aPoly = Part::Tools::polygonOfEdge(aEdge, aLoc); if (!aPoly.IsNull()) { if (!aLoc.IsIdentity()) { identity = false; @@ -1292,8 +1305,8 @@ void ViewProviderPartExt::updateVisual() void ViewProviderPartExt::forceUpdate(bool enable) { if(enable) { if(++forceUpdateCount == 1) { - if(!isShow()) - Visibility.touch(); + if(!isShow() && VisualTouched) + updateVisual(); } }else if(forceUpdateCount) --forceUpdateCount; diff --git a/src/Mod/Part/Gui/ViewProviderExt.h b/src/Mod/Part/Gui/ViewProviderExt.h index 2cb55c1873..4dcbd6ac5c 100644 --- a/src/Mod/Part/Gui/ViewProviderExt.h +++ b/src/Mod/Part/Gui/ViewProviderExt.h @@ -34,6 +34,7 @@ #include #include #include +#include class TopoDS_Shape; class TopoDS_Edge; diff --git a/src/Mod/Part/Gui/ViewProviderSpline.cpp b/src/Mod/Part/Gui/ViewProviderSpline.cpp index e9d1ce23c4..fa6d67a912 100644 --- a/src/Mod/Part/Gui/ViewProviderSpline.cpp +++ b/src/Mod/Part/Gui/ViewProviderSpline.cpp @@ -63,10 +63,9 @@ namespace bp = boost::placeholders; PROPERTY_SOURCE(PartGui::ViewProviderSpline, PartGui::ViewProviderPartExt) ViewProviderSpline::ViewProviderSpline() - : pcControlPoints(0) { sPixmap = "Part_Spline_Parametric"; - ADD_PROPERTY(ControlPoints,(false)); + extension.initExtension(this); } ViewProviderSpline::~ViewProviderSpline() @@ -78,243 +77,6 @@ QIcon ViewProviderSpline::getIcon(void) const return Gui::BitmapFactory().pixmap(sPixmap); } -void ViewProviderSpline::setupContextMenu(QMenu* menu, QObject* receiver, const char* member) -{ - ViewProviderPartExt::setupContextMenu(menu, receiver, member); - - // toggle command to display components - Gui::ActionFunction* func = new Gui::ActionFunction(menu); - QAction* act = menu->addAction(QObject::tr("Show control points")); - act->setCheckable(true); - act->setChecked(ControlPoints.getValue()); - func->toggle(act, boost::bind(&ViewProviderSpline::toggleControlPoints, this, bp::_1)); -} - -void ViewProviderSpline::toggleControlPoints(bool on) -{ - ControlPoints.setValue(on); -} - -void ViewProviderSpline::updateData(const App::Property* prop) -{ - ViewProviderPartExt::updateData(prop); - if (prop->getTypeId() == Part::PropertyPartShape::getClassTypeId() && strcmp(prop->getName(), "Shape") == 0) { - // update control points if there - if (pcControlPoints) { - Gui::coinRemoveAllChildren(pcControlPoints); - showControlPoints(this->ControlPoints.getValue(), prop); - } - } -} - -void ViewProviderSpline::onChanged(const App::Property* prop) -{ - if (prop == &ControlPoints) { - App::DocumentObject* obj = this->pcObject; - App::Property* shape = obj->getPropertyByName("Shape"); - showControlPoints(ControlPoints.getValue(), shape); - } - else { - ViewProviderPartExt::onChanged(prop); - } -} - -void ViewProviderSpline::showControlPoints(bool show, const App::Property* prop) -{ - if (!pcControlPoints && show) { - pcControlPoints = new SoSwitch(); - pcRoot->addChild(pcControlPoints); - } - - if (pcControlPoints) { - pcControlPoints->whichChild = (show ? SO_SWITCH_ALL : SO_SWITCH_NONE); - } - - if (!show || !pcControlPoints || pcControlPoints->getNumChildren() > 0) - return; - - // ask for the property we are interested in - if (prop && prop->getTypeId() == Part::PropertyPartShape::getClassTypeId()) { - const TopoDS_Shape& shape = static_cast(prop)->getValue(); - if (shape.IsNull()) - return; // empty shape - - for (TopExp_Explorer xp(shape, TopAbs_SHELL); xp.More(); xp.Next()) { - const TopoDS_Shell& shell = TopoDS::Shell(xp.Current()); - for (TopExp_Explorer xp2(shell, TopAbs_FACE); xp2.More(); xp2.Next()) { - const TopoDS_Face& face = TopoDS::Face(xp2.Current()); - showControlPointsOfFace(face); - } - } - for (TopExp_Explorer xp(shape, TopAbs_FACE, TopAbs_SHELL); xp.More(); xp.Next()) { - const TopoDS_Face& face = TopoDS::Face(xp.Current()); - showControlPointsOfFace(face); - } - for (TopExp_Explorer xp(shape, TopAbs_WIRE, TopAbs_FACE); xp.More(); xp.Next()) { - const TopoDS_Wire& wire = TopoDS::Wire(xp.Current()); - for (TopExp_Explorer xp2(wire, TopAbs_EDGE); xp2.More(); xp2.Next()) { - const TopoDS_Edge& edge = TopoDS::Edge(xp2.Current()); - showControlPointsOfEdge(edge); - } - } - for (TopExp_Explorer xp(shape, TopAbs_EDGE, TopAbs_WIRE); xp.More(); xp.Next()) { - const TopoDS_Edge& edge = TopoDS::Edge(xp.Current()); - showControlPointsOfEdge(edge); - } - } -} - -void ViewProviderSpline::showControlPointsOfEdge(const TopoDS_Edge& edge) -{ - std::list poles, knots; - Standard_Integer nCt=0; - - TopoDS_Edge edge_loc(edge); - TopLoc_Location aLoc; - edge_loc.Location(aLoc); - - BRepAdaptor_Curve curve(edge_loc); - switch (curve.GetType()) - { - case GeomAbs_BezierCurve: - { - Handle(Geom_BezierCurve) hBezier = curve.Bezier(); - nCt = hBezier->NbPoles(); - for (Standard_Integer i = 1; i <= nCt; i++) - poles.push_back(hBezier->Pole(i)); - if (hBezier->IsClosed()) { - nCt++; - poles.push_back(hBezier->Pole(1)); - } - } break; - case GeomAbs_BSplineCurve: - { - Handle(Geom_BSplineCurve) hBSpline = curve.BSpline(); - nCt = hBSpline->NbPoles(); - for (Standard_Integer i = 1; i <= nCt; i++) - poles.push_back(hBSpline->Pole(i)); - if (hBSpline->IsClosed()) { - nCt++; - poles.push_back(hBSpline->Pole(1)); - } - for (Standard_Integer i = hBSpline->FirstUKnotIndex()+1; i <= hBSpline->LastUKnotIndex()-1; i++) - knots.push_back(hBSpline->Value(hBSpline->Knot(i))); - } break; - default: - break; - } - - if (poles.empty()) - return; // nothing to do - - SoCoordinate3 * controlcoords = new SoCoordinate3; - controlcoords->point.setNum(nCt + knots.size()); - - int index=0; - SbVec3f* verts = controlcoords->point.startEditing(); - for (std::list::iterator p = poles.begin(); p != poles.end(); ++p) { - verts[index++].setValue((float)p->X(), (float)p->Y(), (float)p->Z()); - } - for (std::list::iterator k = knots.begin(); k != knots.end(); ++k) { - verts[index++].setValue((float)k->X(), (float)k->Y(), (float)k->Z()); - } - controlcoords->point.finishEditing(); - - - SoFCControlPoints* controlpoints = new SoFCControlPoints(); - controlpoints->numPolesU = nCt; - controlpoints->numPolesV = 1; - - SoSeparator* nodes = new SoSeparator(); - nodes->addChild(controlcoords); - nodes->addChild(controlpoints); - - pcControlPoints->addChild(nodes); -} - -void ViewProviderSpline::showControlPointsOfFace(const TopoDS_Face& face) -{ - std::list knots; - std::vector > poles; - Standard_Integer nCtU=0, nCtV=0; - - TopoDS_Face face_loc(face); - TopLoc_Location aLoc; - face_loc.Location(aLoc); - - BRepAdaptor_Surface surface(face_loc); - switch (surface.GetType()) - { - case GeomAbs_BezierSurface: - { - Handle(Geom_BezierSurface) hBezier = surface.Bezier(); - nCtU = hBezier->NbUPoles(); - nCtV = hBezier->NbVPoles(); - poles.resize(nCtU); - for (Standard_Integer u = 1; u <= nCtU; u++) { - poles[u-1].resize(nCtV); - for (Standard_Integer v = 1; v <= nCtV; v++) - poles[u-1][v-1] = hBezier->Pole(u, v); - } - } break; - case GeomAbs_BSplineSurface: - { - Handle(Geom_BSplineSurface) hBSpline = surface.BSpline(); - nCtU = hBSpline->NbUPoles(); - nCtV = hBSpline->NbVPoles(); - poles.resize(nCtU); - for (Standard_Integer u = 1; u <= nCtU; u++) { - poles[u-1].resize(nCtV); - for (Standard_Integer v = 1; v <= nCtV; v++) - poles[u-1][v-1] = hBSpline->Pole(u, v); - } - - //Standard_Integer nKnU = hBSpline->NbUKnots(); - //Standard_Integer nKnV = hBSpline->NbVKnots(); - for (Standard_Integer u = 1; u <= hBSpline->NbUKnots(); u++) { - for (Standard_Integer v = 1; v <= hBSpline->NbVKnots(); v++) - knots.push_back(hBSpline->Value(hBSpline->UKnot(u), hBSpline->VKnot(v))); - } - } break; - default: - break; - } - - if (poles.empty()) - return; // nothing to do - - SoCoordinate3 * coords = new SoCoordinate3; - coords->point.setNum(nCtU * nCtV + knots.size()); - - int index=0; - SbVec3f* verts = coords->point.startEditing(); - for (std::vector >::iterator u = poles.begin(); u != poles.end(); ++u) { - for (std::vector::iterator v = u->begin(); v != u->end(); ++v) { - verts[index++].setValue((float)v->X(), (float)v->Y(), (float)v->Z()); - } - } - for (std::list::iterator k = knots.begin(); k != knots.end(); ++k) { - verts[index++].setValue((float)k->X(), (float)k->Y(), (float)k->Z()); - } - coords->point.finishEditing(); - - - SoFCControlPoints* control = new SoFCControlPoints(); - control->numPolesU = nCtU; - control->numPolesV = nCtV; - - //if (knots.size() > 0) { - // control->numKnotsU = nKnU; - // control->numKnotsV = nKnV; - //} - - SoSeparator* nodes = new SoSeparator(); - nodes->addChild(coords); - nodes->addChild(control); - - pcControlPoints->addChild(nodes); -} - // ---------------------------------------------------------------------------- EXTENSION_PROPERTY_SOURCE(PartGui::ViewProviderSplineExtension, Gui::ViewProviderExtension) diff --git a/src/Mod/Part/Gui/ViewProviderSpline.h b/src/Mod/Part/Gui/ViewProviderSpline.h index 6861a2cdfe..2786e9fe01 100644 --- a/src/Mod/Part/Gui/ViewProviderSpline.h +++ b/src/Mod/Part/Gui/ViewProviderSpline.h @@ -23,46 +23,19 @@ #ifndef PARTGUI_VIEWPROVIDERPARTSPLINE_H #define PARTGUI_VIEWPROVIDERPARTSPLINE_H -#include "ViewProviderExt.h" +#include #include namespace PartGui { -class PartGuiExport ViewProviderSpline : public ViewProviderPartExt -{ - PROPERTY_HEADER(PartGui::ViewProviderSpline); - -public: - /// constructor - ViewProviderSpline(); - /// destructor - virtual ~ViewProviderSpline(); - - // Display properties - App::PropertyBool ControlPoints; - - QIcon getIcon(void) const; - void updateData(const App::Property* prop); - void setupContextMenu(QMenu* menu, QObject* receiver, const char* member); - -protected: - void onChanged(const App::Property* prop); - void toggleControlPoints(bool); - void showControlPoints(bool, const App::Property* prop); - void showControlPointsOfEdge(const TopoDS_Edge&); - void showControlPointsOfFace(const TopoDS_Face&); - - SoSwitch *pcControlPoints; -}; - class PartGuiExport ViewProviderSplineExtension : public Gui::ViewProviderExtension { EXTENSION_PROPERTY_HEADER_WITH_OVERRIDE(PartGui::ViewProviderSplineExtension); public: /// Constructor - ViewProviderSplineExtension(void); + ViewProviderSplineExtension(); virtual ~ViewProviderSplineExtension() = default; App::PropertyBool ControlPoints; @@ -80,6 +53,22 @@ protected: SoSwitch *pcControlPoints; }; +class PartGuiExport ViewProviderSpline : public ViewProviderPartExt +{ + PROPERTY_HEADER(PartGui::ViewProviderSpline); + +public: + /// constructor + ViewProviderSpline(); + /// destructor + virtual ~ViewProviderSpline(); + + QIcon getIcon() const; + +private: + ViewProviderSplineExtension extension; +}; + typedef Gui::ViewProviderExtensionPythonT ViewProviderSplineExtensionPython; } //namespace PartGui diff --git a/src/Mod/Part/PartGlobal.h b/src/Mod/Part/PartGlobal.h new file mode 100644 index 0000000000..a6d29ec10c --- /dev/null +++ b/src/Mod/Part/PartGlobal.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * 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 + +#ifndef PART_GLOBAL_H +#define PART_GLOBAL_H + + +// Part +#ifndef PartExport +#ifdef Part_EXPORTS +# define PartExport FREECAD_DECL_EXPORT +#else +# define PartExport FREECAD_DECL_IMPORT +#endif +#endif + +// PartGui +#ifndef PartGuiExport +#ifdef PartGui_EXPORTS +# define PartGuiExport FREECAD_DECL_EXPORT +#else +# define PartGuiExport FREECAD_DECL_IMPORT +#endif +#endif + +#endif //PART_GLOBAL_H diff --git a/src/Mod/PartDesign/Gui/Command.cpp b/src/Mod/PartDesign/Gui/Command.cpp index 8a52a037c7..5e74f23473 100644 --- a/src/Mod/PartDesign/Gui/Command.cpp +++ b/src/Mod/PartDesign/Gui/Command.cpp @@ -107,7 +107,7 @@ void UnifiedDatumCommand(Gui::Command &cmd, Base::Type type, std::string name) bEditSelected = true; } - PartDesign::Body *pcActiveBody = PartDesignGui::getBody(/*messageIfNot = */false); + PartDesign::Body *pcActiveBody = PartDesignGui::getBody(/*messageIfNot = */true); if (bEditSelected) { std::string tmp = std::string("Edit ")+name; diff --git a/src/Mod/PartDesign/Gui/DlgActiveBody.cpp b/src/Mod/PartDesign/Gui/DlgActiveBody.cpp index aa916d6208..fb4841d94f 100644 --- a/src/Mod/PartDesign/Gui/DlgActiveBody.cpp +++ b/src/Mod/PartDesign/Gui/DlgActiveBody.cpp @@ -28,6 +28,7 @@ # include #endif +#include #include #include "DlgActiveBody.h" @@ -74,6 +75,14 @@ DlgActiveBody::DlgActiveBody(QWidget *parent, App::Document*& doc, // TODO: Any other logic (hover, select effects on view etc.) } + + if (!bodyOfActiveObject) { + // by default select the first item so that the user + // can continue by clicking Ok without further action + QListWidgetItem* first = ui->bodySelect->item(0); + if (first) + first->setSelected(true); + } } void DlgActiveBody::accept() @@ -84,10 +93,15 @@ void DlgActiveBody::accept() App::DocumentObject* selectedBody = selectedItems[0]->data(Qt::UserRole).value(); - if (selectedBody) + if (selectedBody) { activeBody = makeBodyActive(selectedBody, _doc); - else + } + else { + // A transaction must be created as otherwise the undo/redo is broken + App::GetApplication().setActiveTransaction(QT_TRANSLATE_NOOP("Command", "Add a Body"), true); activeBody = makeBody(_doc); + App::GetApplication().closeActiveTransaction(); + } QDialog::accept(); } diff --git a/src/Mod/PartDesign/Gui/TaskLoftParameters.cpp b/src/Mod/PartDesign/Gui/TaskLoftParameters.cpp index 36cab259a2..8872cd069e 100644 --- a/src/Mod/PartDesign/Gui/TaskLoftParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskLoftParameters.cpp @@ -55,7 +55,7 @@ using namespace Gui; /* TRANSLATOR PartDesignGui::TaskLoftParameters */ -TaskLoftParameters::TaskLoftParameters(ViewProviderLoft *LoftView,bool /*newObj*/, QWidget *parent) +TaskLoftParameters::TaskLoftParameters(ViewProviderLoft *LoftView, bool /*newObj*/, QWidget *parent) : TaskSketchBasedParameters(LoftView, parent, "PartDesign_AdditiveLoft", tr("Loft parameters")) , ui(new Ui_TaskLoftParameters) { @@ -69,7 +69,7 @@ TaskLoftParameters::TaskLoftParameters(ViewProviderLoft *LoftView,bool /*newObj* connect(ui->buttonRefAdd, SIGNAL(toggled(bool)), this, SLOT(onRefButtonAdd(bool))); connect(ui->buttonRefRemove, SIGNAL(toggled(bool)), - this, SLOT(onRefButtonRemvove(bool))); + this, SLOT(onRefButtonRemove(bool))); connect(ui->checkBoxRuled, SIGNAL(toggled(bool)), this, SLOT(onRuled(bool))); connect(ui->checkBoxClosed, SIGNAL(toggled(bool)), @@ -121,24 +121,23 @@ TaskLoftParameters::TaskLoftParameters(ViewProviderLoft *LoftView,bool /*newObj* ui->checkBoxRuled->setChecked(loft->Ruled.getValue()); ui->checkBoxClosed->setChecked(loft->Closed.getValue()); - if (!loft->Sections.getValues().empty()) { - LoftView->makeTemporaryVisible(true); - } - // activate and de-activate dialog elements as appropriate for (QWidget* child : proxy->findChildren()) child->blockSignals(false); - updateUI(0); + updateUI(); } TaskLoftParameters::~TaskLoftParameters() { } -void TaskLoftParameters::updateUI(int index) +void TaskLoftParameters::updateUI() { - Q_UNUSED(index); + // we must assure the changed loft is kept visible on section changes, + // see https://forum.freecadweb.org/viewtopic.php?f=3&t=63252 + PartDesign::Loft* loft = static_cast(vp->getObject()); + vp->makeTemporaryVisible(!loft->Sections.getValues().empty()); } void TaskLoftParameters::onSelectionChanged(const Gui::SelectionChanges& msg) @@ -173,6 +172,7 @@ void TaskLoftParameters::onSelectionChanged(const Gui::SelectionChanges& msg) clearButtons(); exitSelectionMode(); + updateUI(); } } @@ -256,6 +256,7 @@ void TaskLoftParameters::onDeleteSection() //static_cast(vp)->highlightReferences(false, true); recomputeFeature(); + updateUI(); } } } @@ -279,6 +280,7 @@ void TaskLoftParameters::indexesMoved() loft->Sections.setValues(originals); recomputeFeature(); + updateUI(); } void TaskLoftParameters::clearButtons() { @@ -324,7 +326,7 @@ void TaskLoftParameters::onRefButtonAdd(bool checked) { } } -void TaskLoftParameters::onRefButtonRemvove(bool checked) { +void TaskLoftParameters::onRefButtonRemove(bool checked) { if (checked) { Gui::Selection().clearSelection(); @@ -359,7 +361,7 @@ bool TaskDlgLoftParameters::accept() // TODO Fill this with commands (2015-09-11, Fat-Zer) PartDesign::Loft* pcLoft = static_cast(vp->getObject()); - for(App::DocumentObject* obj : pcLoft->Sections.getValues()) { + for (App::DocumentObject* obj : pcLoft->Sections.getValues()) { FCMD_OBJ_HIDE(obj); } diff --git a/src/Mod/PartDesign/Gui/TaskLoftParameters.h b/src/Mod/PartDesign/Gui/TaskLoftParameters.h index b99a771edf..a3dd8c5ccb 100644 --- a/src/Mod/PartDesign/Gui/TaskLoftParameters.h +++ b/src/Mod/PartDesign/Gui/TaskLoftParameters.h @@ -50,13 +50,13 @@ class TaskLoftParameters : public TaskSketchBasedParameters Q_OBJECT public: - TaskLoftParameters(ViewProviderLoft *LoftView,bool newObj=false,QWidget *parent = 0); + TaskLoftParameters(ViewProviderLoft *LoftView, bool newObj=false, QWidget *parent = 0); ~TaskLoftParameters(); private Q_SLOTS: void onProfileButton(bool); void onRefButtonAdd(bool); - void onRefButtonRemvove(bool); + void onRefButtonRemove(bool); void onClosed(bool); void onRuled(bool); void onDeleteSection(); @@ -67,7 +67,7 @@ protected: private: void onSelectionChanged(const Gui::SelectionChanges& msg); - void updateUI(int index); + void updateUI(); bool referenceSelected(const Gui::SelectionChanges& msg) const; void removeFromListWidget(QListWidget*w, QString name); void clearButtons(); diff --git a/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp b/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp index 66465f3814..6dd611b873 100644 --- a/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp +++ b/src/Mod/PartDesign/Gui/TaskPipeParameters.cpp @@ -94,6 +94,7 @@ TaskPipeParameters::TaskPipeParameters(ViewProviderPipe *PipeView, bool /*newObj // Create context menu QAction* remove = new QAction(tr("Remove"), this); remove->setShortcut(QKeySequence::Delete); + remove->setShortcutContext(Qt::WidgetShortcut); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // display shortcut behind the context menu entry remove->setShortcutVisibleInContextMenu(true); @@ -587,6 +588,7 @@ TaskPipeOrientation::TaskPipeOrientation(ViewProviderPipe* PipeView, bool /*newO // Create context menu QAction* remove = new QAction(tr("Remove"), this); remove->setShortcut(QKeySequence::Delete); + remove->setShortcutContext(Qt::WidgetShortcut); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // display shortcut behind the context menu entry remove->setShortcutVisibleInContextMenu(true); @@ -902,6 +904,7 @@ TaskPipeScaling::TaskPipeScaling(ViewProviderPipe* PipeView, bool /*newObj*/, QW // Create context menu QAction* remove = new QAction(tr("Remove"), this); remove->setShortcut(QKeySequence::Delete); + remove->setShortcutContext(Qt::WidgetShortcut); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) // display shortcut behind the context menu entry remove->setShortcutVisibleInContextMenu(true); diff --git a/src/Mod/PartDesign/Gui/ViewProvider.cpp b/src/Mod/PartDesign/Gui/ViewProvider.cpp index dff2defa82..156424678f 100644 --- a/src/Mod/PartDesign/Gui/ViewProvider.cpp +++ b/src/Mod/PartDesign/Gui/ViewProvider.cpp @@ -31,6 +31,7 @@ #include #endif +#include #include #include #include @@ -64,26 +65,10 @@ ViewProvider::~ViewProvider() bool ViewProvider::doubleClicked(void) { -#if 0 - // TODO May be move to setEdit()? (2015-07-26, Fat-Zer) - if (body != NULL) { - // Drop into insert mode so that the user doesn't see all the geometry that comes later in the tree - // Also, this way the user won't be tempted to use future geometry as external references for the sketch - oldTip = body->Tip.getValue(); - if (oldTip != this->pcObject) - Gui::Command::doCommand(Gui::Command::Gui,"FreeCADGui.runCommand('PartDesign_MoveTip')"); - else - oldTip = NULL; - } else { - oldTip = NULL; - } -#endif - try { - PartDesign::Body* body = PartDesign::Body::findBodyOf(getObject()); - std::string Msg("Edit "); - Msg += this->pcObject->Label.getValue(); - Gui::Command::openCommand(Msg.c_str()); + 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); } catch (const Base::Exception&) { @@ -92,6 +77,25 @@ bool ViewProvider::doubleClicked(void) return true; } +void ViewProvider::startDefaultEditMode() +{ + QString text = QObject::tr("Edit %1").arg(QString::fromUtf8(getObject()->Label.getValue())); + Gui::Command::openCommand(text.toUtf8()); + + Gui::Document* document = this->getDocument(); + if (document) { + document->setEdit(this, ViewProvider::Default); + } +} + +void ViewProvider::addDefaultAction(QMenu* menu, const QString& text) +{ + QAction* act = menu->addAction(text); + act->setData(QVariant((int)ViewProvider::Default)); + Gui::ActionFunction* func = new Gui::ActionFunction(menu); + func->trigger(act, boost::bind(&ViewProvider::startDefaultEditMode, this)); +} + void ViewProvider::setupContextMenu(QMenu* menu, QObject* receiver, const char* member) { QAction* act = menu->addAction(QObject::tr("Set colors..."), receiver, member); diff --git a/src/Mod/PartDesign/Gui/ViewProvider.h b/src/Mod/PartDesign/Gui/ViewProvider.h index 1506f5eef2..6ff7ffccb4 100644 --- a/src/Mod/PartDesign/Gui/ViewProvider.h +++ b/src/Mod/PartDesign/Gui/ViewProvider.h @@ -76,6 +76,8 @@ protected: virtual void setupContextMenu(QMenu* menu, QObject* receiver, const char* member) override; virtual bool setEdit(int ModNum) override; virtual void unsetEdit(int ModNum) override; + void startDefaultEditMode(); + void addDefaultAction(QMenu*, const QString&); virtual bool onDelete(const std::vector &) override; diff --git a/src/Mod/PartDesign/Gui/ViewProviderGroove.cpp b/src/Mod/PartDesign/Gui/ViewProviderGroove.cpp index 782effe822..b173676cbc 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderGroove.cpp +++ b/src/Mod/PartDesign/Gui/ViewProviderGroove.cpp @@ -47,9 +47,7 @@ ViewProviderGroove::~ViewProviderGroove() void ViewProviderGroove::setupContextMenu(QMenu* menu, QObject* receiver, const char* member) { - QAction* act; - act = menu->addAction(QObject::tr("Edit groove"), receiver, member); - act->setData(QVariant((int)ViewProvider::Default)); + addDefaultAction(menu, QObject::tr("Edit groove")); PartDesignGui::ViewProviderSketchBased::setupContextMenu(menu, receiver, member); } diff --git a/src/Mod/PartDesign/Gui/ViewProviderLoft.cpp b/src/Mod/PartDesign/Gui/ViewProviderLoft.cpp index 8cbb387b47..6f260f3b78 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderLoft.cpp +++ b/src/Mod/PartDesign/Gui/ViewProviderLoft.cpp @@ -81,11 +81,6 @@ void ViewProviderLoft::setupContextMenu(QMenu* menu, QObject* receiver, const ch PartDesignGui::ViewProvider::setupContextMenu(menu, receiver, member); } -bool ViewProviderLoft::doubleClicked(void) -{ - return PartDesignGui::setEdit(pcObject); -} - bool ViewProviderLoft::setEdit(int ModNum) { if (ModNum == ViewProvider::Default) diff --git a/src/Mod/PartDesign/Gui/ViewProviderLoft.h b/src/Mod/PartDesign/Gui/ViewProviderLoft.h index dcec346ee3..01f24e76c5 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderLoft.h +++ b/src/Mod/PartDesign/Gui/ViewProviderLoft.h @@ -41,7 +41,6 @@ public: /// grouping handling std::vector claimChildren(void)const; void setupContextMenu(QMenu*, QObject*, const char*); - bool doubleClicked(); virtual bool onDelete(const std::vector &); void highlightReferences(const bool on, bool auxiliary); diff --git a/src/Mod/PartDesign/Gui/ViewProviderPad.cpp b/src/Mod/PartDesign/Gui/ViewProviderPad.cpp index 93e5cc63d2..e7eeeaa3d1 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderPad.cpp +++ b/src/Mod/PartDesign/Gui/ViewProviderPad.cpp @@ -47,12 +47,7 @@ ViewProviderPad::~ViewProviderPad() void ViewProviderPad::setupContextMenu(QMenu* menu, QObject* receiver, const char* member) { - // Note: This methode couldn't be unified with others because menu entry string - // should present united in sources for proper translation and shouldn't be - // constructed on runtime. - QAction* act; - act = menu->addAction(QObject::tr("Edit pad"), receiver, member); - act->setData(QVariant((int)ViewProvider::Default)); + addDefaultAction(menu, QObject::tr("Edit pad")); PartDesignGui::ViewProviderSketchBased::setupContextMenu(menu, receiver, member); } diff --git a/src/Mod/PartDesign/Gui/ViewProviderPipe.cpp b/src/Mod/PartDesign/Gui/ViewProviderPipe.cpp index 490bddf704..5c875153be 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderPipe.cpp +++ b/src/Mod/PartDesign/Gui/ViewProviderPipe.cpp @@ -89,11 +89,6 @@ void ViewProviderPipe::setupContextMenu(QMenu* menu, QObject* receiver, const ch PartDesignGui::ViewProvider::setupContextMenu(menu, receiver, member); } -bool ViewProviderPipe::doubleClicked(void) -{ - return PartDesignGui::setEdit(pcObject); -} - bool ViewProviderPipe::setEdit(int ModNum) { if (ModNum == ViewProvider::Default ) setPreviewDisplayMode(true); diff --git a/src/Mod/PartDesign/Gui/ViewProviderPipe.h b/src/Mod/PartDesign/Gui/ViewProviderPipe.h index 7fbf6647ed..e3efe3a695 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderPipe.h +++ b/src/Mod/PartDesign/Gui/ViewProviderPipe.h @@ -49,7 +49,6 @@ public: /// grouping handling std::vector claimChildren(void)const; void setupContextMenu(QMenu*, QObject*, const char*); - bool doubleClicked(); virtual bool onDelete(const std::vector &); void highlightReferences(Reference mode, bool on); diff --git a/src/Mod/PartDesign/Gui/ViewProviderPocket.cpp b/src/Mod/PartDesign/Gui/ViewProviderPocket.cpp index e610196a89..8343ceb8fd 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderPocket.cpp +++ b/src/Mod/PartDesign/Gui/ViewProviderPocket.cpp @@ -49,9 +49,7 @@ ViewProviderPocket::~ViewProviderPocket() void ViewProviderPocket::setupContextMenu(QMenu* menu, QObject* receiver, const char* member) { - QAction* act; - act = menu->addAction(QObject::tr("Edit pocket"), receiver, member); - act->setData(QVariant((int)ViewProvider::Default)); + addDefaultAction(menu, QObject::tr("Edit pocket")); PartDesignGui::ViewProviderSketchBased::setupContextMenu(menu, receiver, member); } diff --git a/src/Mod/PartDesign/Gui/ViewProviderRevolution.cpp b/src/Mod/PartDesign/Gui/ViewProviderRevolution.cpp index 1cf70ebbb1..80c869e6a9 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderRevolution.cpp +++ b/src/Mod/PartDesign/Gui/ViewProviderRevolution.cpp @@ -47,9 +47,7 @@ ViewProviderRevolution::~ViewProviderRevolution() void ViewProviderRevolution::setupContextMenu(QMenu* menu, QObject* receiver, const char* member) { - QAction* act; - act = menu->addAction(QObject::tr("Edit revolution"), receiver, member); - act->setData(QVariant((int)ViewProvider::Default)); + addDefaultAction(menu, QObject::tr("Edit revolution")); PartDesignGui::ViewProviderSketchBased::setupContextMenu(menu, receiver, member); } diff --git a/src/Mod/PartDesign/Gui/Workbench.cpp b/src/Mod/PartDesign/Gui/Workbench.cpp index 94cd370dc7..4ae7939dda 100644 --- a/src/Mod/PartDesign/Gui/Workbench.cpp +++ b/src/Mod/PartDesign/Gui/Workbench.cpp @@ -65,6 +65,8 @@ namespace bp = boost::placeholders; qApp->translate("Gui::TaskView::TaskWatcherCommands", "Create Geometry"); // qApp->translate("Workbench", "Measure"); + qApp->translate("Workbench", "Refresh"); + qApp->translate("Workbench", "Toggle 3D"); qApp->translate("Workbench", "Part Design Helper"); qApp->translate("Workbench", "Part Design Modeling"); #endif diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 11f11fc63f..d91652788f 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -1230,7 +1230,7 @@ int Area::project(TopoDS_Shape &shape_out, const TopoDS_Shape *work_plane) { FC_TIME_INIT2(t,t1); - Handle_HLRBRep_Algo brep_hlr = NULL; + Handle_HLRBRep_Algo brep_hlr; gp_Dir dir(0,0,1); try { brep_hlr = new HLRBRep_Algo(); diff --git a/src/Mod/Path/Gui/AppPathGuiPy.cpp b/src/Mod/Path/Gui/AppPathGuiPy.cpp index a442244b41..062da3a069 100644 --- a/src/Mod/Path/Gui/AppPathGuiPy.cpp +++ b/src/Mod/Path/Gui/AppPathGuiPy.cpp @@ -82,7 +82,7 @@ private: wc.restoreCursor(); try { - std::string path = App::GetApplication().getHomePath(); + std::string path = App::Application::getHomePath(); path += "Mod/Path/PathScripts/post/"; QDir dir1(QString::fromUtf8(path.c_str()), QString::fromLatin1("*_pre.py")); std::string cMacroPath = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro") @@ -149,7 +149,7 @@ private: wc.restoreCursor(); try { - std::string path = App::GetApplication().getHomePath(); + std::string path = App::Application::getHomePath(); path += "Mod/Path/PathScripts/post/"; QDir dir1(QString::fromUtf8(path.c_str()), QString::fromLatin1("*_pre.py")); std::string cMacroPath = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro") @@ -225,7 +225,7 @@ private: if (objlist.size() == 0) throw Py::RuntimeError("No object to export"); - std::string path = App::GetApplication().getHomePath(); + std::string path = App::Application::getHomePath(); path += "Mod/Path/PathScripts/post/"; QDir dir1(QString::fromUtf8(path.c_str()), QString::fromLatin1("*_post.py")); std::string cMacroPath = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Macro") diff --git a/src/Mod/Raytracing/App/AppRaytracingPy.cpp b/src/Mod/Raytracing/App/AppRaytracingPy.cpp index 5b124e91fc..dc6a528415 100644 --- a/src/Mod/Raytracing/App/AppRaytracingPy.cpp +++ b/src/Mod/Raytracing/App/AppRaytracingPy.cpp @@ -222,7 +222,7 @@ private: if (! PyArg_ParseTuple(args.ptr(), "ss",&FileName,&DestDir)) throw Py::Exception(); - std::string resName = App::GetApplication().getHomePath(); + std::string resName = App::Application::getHomePath(); resName += "Mod"; resName += PATHSEP ; resName += "Raytracing"; diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index dc165de246..e737bf4925 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -277,7 +277,7 @@ int SketchObject::solve(bool updateGeoAfterSolving/*=true*/) // Some examples: // Redundant: a vertical line, a horizontal line and an angle constraint of 90 degrees between the two lines // Conflicting: a 80 degrees angle between a vertical line and another line, then adding a horizontal constraint to that other line - // OverConstrained: a conflicting constraint when all other DoF are already constraint (it has more constrains than parameters and the extra constraints are not redundant) + // OverConstrained: a conflicting constraint when all other DoF are already constrained (it has more constraints than parameters and the extra constraints are not redundant) solverNeedsUpdate=false; diff --git a/src/Mod/Sketcher/Gui/CMakeLists.txt b/src/Mod/Sketcher/Gui/CMakeLists.txt index 317d91c208..9e55568413 100644 --- a/src/Mod/Sketcher/Gui/CMakeLists.txt +++ b/src/Mod/Sketcher/Gui/CMakeLists.txt @@ -32,7 +32,7 @@ else() endif() set(SketcherGui_UIC_SRCS - TaskSketcherConstrains.ui + TaskSketcherConstraints.ui TaskSketcherElements.ui TaskSketcherGeneral.ui TaskSketcherMessages.ui @@ -78,10 +78,10 @@ SET(SketcherGui_SRCS SoDatumLabel.h PropertyConstraintListItem.h PropertyConstraintListItem.cpp - TaskSketcherConstrains.ui - TaskSketcherConstrains.cpp + TaskSketcherConstraints.ui + TaskSketcherConstraints.cpp ConstraintFilters.h - TaskSketcherConstrains.h + TaskSketcherConstraints.h TaskSketcherElements.ui TaskSketcherElements.cpp TaskSketcherElements.h diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 716cdba5ea..f6a4f0b57b 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -7137,7 +7137,7 @@ bool CmdSketcherConstrainSnellsLaw::isActive(void) DEF_STD_CMD_A(CmdSketcherConstrainInternalAlignment) // NOTE: This command is deprecated. Nobody seriously uses today manual creation of an internal alignment constraint -// The only reason this code remains is the extremelly unlikely scenario that some user macro may rely on it. +// The only reason this code remains is the extremely unlikely scenario that some user macro may rely on it. CmdSketcherConstrainInternalAlignment::CmdSketcherConstrainInternalAlignment() :Command("Sketcher_ConstrainInternalAlignment") { diff --git a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp index e0c931fa81..ba1496e225 100644 --- a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp +++ b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp @@ -107,6 +107,9 @@ Base::Vector2d GetCircleCenter (const Base::Vector2d &p1, const Base::Vector2d & double vv = v*v; double ww = w*w; + if (uu * vv * ww == 0) + THROWM(Base::ValueError,"Two points are coincident"); + double uv = -(u*v); double vw = -(v*w); double uw = -(u*w); @@ -552,7 +555,8 @@ public: "conList.append(Sketcher.Constraint('Horizontal',%i))\n" "conList.append(Sketcher.Constraint('Vertical',%i))\n" "conList.append(Sketcher.Constraint('Vertical',%i))\n" - "%s.addConstraint(conList)\n", + "%s.addConstraint(conList)\n" + "del geoList, conList\n", EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y, // line 1 EditCurve[1].x,EditCurve[1].y,EditCurve[2].x,EditCurve[2].y, // line 2 EditCurve[2].x,EditCurve[2].y,EditCurve[3].x,EditCurve[3].y, // line 3 @@ -591,7 +595,8 @@ public: "conList.append(Sketcher.Constraint('Vertical',%i))\n" "conList.append(Sketcher.Constraint('Vertical',%i))\n" "conList.append(Sketcher.Constraint('Symmetric',%i,2,%i,1,%i,1))\n" - "%s.addConstraint(conList)\n", + "%s.addConstraint(conList)\n" + "del geoList, conList\n", EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y, // line 1 EditCurve[1].x,EditCurve[1].y,EditCurve[2].x,EditCurve[2].y, // line 2 EditCurve[2].x,EditCurve[2].y,EditCurve[3].x,EditCurve[3].y, // line 3 @@ -927,7 +932,8 @@ public: "conList.append(Sketcher.Constraint('Equal', %i, %i))\n" "conList.append(Sketcher.Constraint('Equal', %i, %i))\n" "conList.append(Sketcher.Constraint('Equal', %i, %i))\n" - "%s.addConstraint(conList)\n", + "%s.addConstraint(conList)\n" + "del geoList, conList\n", StartPos.x + (signX * radius), StartPos.y + (signY * radius), // center of the arc 1 radius, start, end, // start and end angle of arc1 @@ -981,7 +987,8 @@ public: "conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n" "conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n" "conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n" - "%s.addConstraint(conList)\n", + "%s.addConstraint(conList)\n" + "del geoList, conList\n", StartPos.x, StartPos.y, // point at StartPos EndPos.x, EndPos.y, // point at EndPos Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch @@ -4812,7 +4819,7 @@ public: // autoconstraints were added to the circles of the poles, which is ok because they must go to the // right position, or the user will freak-out if they appear out of the autoconstrained position. - // However, autoconstrains on the first and last pole, in normal non-periodic b-splines (with appropriate endpoint knot multiplicity) + // However, autoconstraints on the first and last pole, in normal non-periodic b-splines (with appropriate endpoint knot multiplicity) // as the ones created by this tool are intended for the b-spline endpoints, and not for the poles, // so here we retrieve any autoconstraint on those poles' center and mangle it to the endpoint. if (ConstrMethod == 0) { @@ -4840,6 +4847,7 @@ public: } cstream << Gui::Command::getObjectCmd(sketchgui->getObject()) << ".addConstraint(conList)\n"; + cstream << "del conList\n"; Gui::Command::doCommand(Gui::Command::Doc, cstream.str().c_str()); @@ -7126,7 +7134,8 @@ 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.addConstraint(conList)\n", + "%s.addConstraint(conList)\n" + "del geoList, conList\n", StartPos.x, StartPos.y, // center of the arc1 r, // radius arc1 start, end, // start and end angle of arc1 diff --git a/src/Mod/Sketcher/Gui/TaskDlgEditSketch.cpp b/src/Mod/Sketcher/Gui/TaskDlgEditSketch.cpp index 8fbdf26459..f522949c9e 100644 --- a/src/Mod/Sketcher/Gui/TaskDlgEditSketch.cpp +++ b/src/Mod/Sketcher/Gui/TaskDlgEditSketch.cpp @@ -43,7 +43,7 @@ TaskDlgEditSketch::TaskDlgEditSketch(ViewProviderSketch *sketchView) : TaskDialog(),sketchView(sketchView) { assert(sketchView); - Constraints = new TaskSketcherConstrains(sketchView); + Constraints = new TaskSketcherConstraints(sketchView); Elements = new TaskSketcherElements(sketchView); General = new TaskSketcherGeneral(sketchView); Messages = new TaskSketcherMessages(sketchView); diff --git a/src/Mod/Sketcher/Gui/TaskDlgEditSketch.h b/src/Mod/Sketcher/Gui/TaskDlgEditSketch.h index e99900e13d..924ce5737a 100644 --- a/src/Mod/Sketcher/Gui/TaskDlgEditSketch.h +++ b/src/Mod/Sketcher/Gui/TaskDlgEditSketch.h @@ -27,7 +27,7 @@ #include #include "ViewProviderSketch.h" -#include "TaskSketcherConstrains.h" +#include "TaskSketcherConstraints.h" #include "TaskSketcherElements.h" #include "TaskSketcherGeneral.h" #include "TaskSketcherMessages.h" @@ -72,7 +72,7 @@ protected: protected: ViewProviderSketch *sketchView; - TaskSketcherConstrains *Constraints; + TaskSketcherConstraints *Constraints; TaskSketcherElements *Elements; TaskSketcherGeneral *General; TaskSketcherMessages *Messages; diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp b/src/Mod/Sketcher/Gui/TaskSketcherConstraints.cpp similarity index 92% rename from src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp rename to src/Mod/Sketcher/Gui/TaskSketcherConstraints.cpp index 7e3d84711e..93ec845a5e 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstraints.cpp @@ -37,8 +37,8 @@ # include #endif -#include "TaskSketcherConstrains.h" -#include "ui_TaskSketcherConstrains.h" +#include "TaskSketcherConstraints.h" +#include "ui_TaskSketcherConstraints.h" #include "EditDatumDialog.h" #include "ViewProviderSketch.h" @@ -379,7 +379,7 @@ public: class ExpressionDelegate : public QStyledItemDelegate { public: - ExpressionDelegate(QListWidget * _view) : view(_view) { } + ExpressionDelegate(QListWidget * _view) : QStyledItemDelegate(_view), view(_view) { } protected: QPixmap getIcon(const char* name, const QSize& size) const { @@ -565,7 +565,7 @@ void ConstraintView::modifyCurrentItem() void ConstraintView::renameCurrentItem() { - // See also TaskSketcherConstrains::on_listWidgetConstraints_itemChanged + // See also TaskSketcherConstraints::on_listWidgetConstraints_itemChanged QListWidgetItem* item = currentItem(); if (item) editItem(item); @@ -631,10 +631,10 @@ void ConstraintView::swapNamedOfSelectedItems() // ---------------------------------------------------------------------------- -TaskSketcherConstrains::TaskSketcherConstrains(ViewProviderSketch *sketchView) : +TaskSketcherConstraints::TaskSketcherConstraints(ViewProviderSketch *sketchView) : TaskBox(Gui::BitmapFactory().pixmap("document-new"), tr("Constraints"), true, 0), sketchView(sketchView), inEditMode(false), - ui(new Ui_TaskSketcherConstrains) + ui(new Ui_TaskSketcherConstraints) { // we need a separate container widget to add all controls to proxy = new QWidget(this); @@ -709,7 +709,7 @@ TaskSketcherConstrains::TaskSketcherConstrains(ViewProviderSketch *sketchView) : ); connectionConstraintsChanged = sketchView->signalConstraintsChanged.connect( - boost::bind(&SketcherGui::TaskSketcherConstrains::slotConstraintsChanged, this)); + boost::bind(&SketcherGui::TaskSketcherConstraints::slotConstraintsChanged, this)); this->groupLayout()->addWidget(proxy); @@ -718,13 +718,13 @@ TaskSketcherConstrains::TaskSketcherConstrains(ViewProviderSketch *sketchView) : slotConstraintsChanged(); } -TaskSketcherConstrains::~TaskSketcherConstrains() +TaskSketcherConstraints::~TaskSketcherConstraints() { connectionConstraintsChanged.disconnect(); } -void TaskSketcherConstrains::createVisibilityButtonActions() +void TaskSketcherConstraints::createVisibilityButtonActions() { QAction* action = new QAction(QString::fromLatin1("Show only filtered Constraints"),this); @@ -741,7 +741,7 @@ void TaskSketcherConstrains::createVisibilityButtonActions() ui->visibilityButton->addAction(action); } -void TaskSketcherConstrains::updateSelectionFilter() +void TaskSketcherConstraints::updateSelectionFilter() { // Snapshot current selection auto items = ui->listWidgetConstraints->selectedItems(); @@ -752,7 +752,7 @@ void TaskSketcherConstrains::updateSelectionFilter() selectionFilter.push_back(static_cast(item)->ConstraintNbr); } -void TaskSketcherConstrains::updateAssociatedConstraintsFilter() +void TaskSketcherConstraints::updateAssociatedConstraintsFilter() { associatedConstraintsFilter.clear(); @@ -793,7 +793,7 @@ void TaskSketcherConstrains::updateAssociatedConstraintsFilter() updateList(); } -void TaskSketcherConstrains::updateList() +void TaskSketcherConstraints::updateList() { // enforce constraint visibility ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); @@ -805,7 +805,7 @@ void TaskSketcherConstrains::updateList() slotConstraintsChanged(); } -void TaskSketcherConstrains::on_multipleFilterButton_clicked(bool) +void TaskSketcherConstraints::on_multipleFilterButton_clicked(bool) { ConstraintMultiFilterDialog mf; @@ -825,7 +825,7 @@ void TaskSketcherConstrains::on_multipleFilterButton_clicked(bool) } } -void TaskSketcherConstrains::on_settingsDialogButton_clicked(bool) +void TaskSketcherConstraints::on_settingsDialogButton_clicked(bool) { ConstraintSettingsDialog cs; @@ -845,7 +845,7 @@ void TaskSketcherConstrains::on_settingsDialogButton_clicked(bool) cs.exec(); // The dialog reacted on any change, so the result of running the dialog is already reflected on return. } -void TaskSketcherConstrains::changeFilteredVisibility(bool show, ActionTarget target) +void TaskSketcherConstraints::changeFilteredVisibility(bool show, ActionTarget target) { assert(sketchView); const Sketcher::SketchObject * sketch = sketchView->getSketchObject(); @@ -918,27 +918,27 @@ void TaskSketcherConstrains::changeFilteredVisibility(bool show, ActionTarget ta } -void TaskSketcherConstrains::on_showAllButton_clicked(bool) +void TaskSketcherConstraints::on_showAllButton_clicked(bool) { changeFilteredVisibility(true); } -void TaskSketcherConstrains::on_hideAllButton_clicked(bool) +void TaskSketcherConstraints::on_hideAllButton_clicked(bool) { changeFilteredVisibility(false); } -void TaskSketcherConstrains::on_listWidgetConstraints_emitHideSelection3DVisibility() +void TaskSketcherConstraints::on_listWidgetConstraints_emitHideSelection3DVisibility() { changeFilteredVisibility(false, ActionTarget::Selected); } -void TaskSketcherConstrains::on_listWidgetConstraints_emitShowSelection3DVisibility() +void TaskSketcherConstraints::on_listWidgetConstraints_emitShowSelection3DVisibility() { changeFilteredVisibility(true, ActionTarget::Selected); } -void TaskSketcherConstrains::onSelectionChanged(const Gui::SelectionChanges& msg) +void TaskSketcherConstraints::onSelectionChanged(const Gui::SelectionChanges& msg) { assert(sketchView); @@ -1002,7 +1002,7 @@ void TaskSketcherConstrains::onSelectionChanged(const Gui::SelectionChanges& msg if(geoid != Sketcher::Constraint::GeoUndef && pointpos == Sketcher::none){ // It is not possible to update on single addition/removal of a geometric element, // as one removal may imply removing a constraint that should be added by a different element - // that is still selected. The necessary checks outweight a full rebuild of the filter. + // that is still selected. The necessary checks outweigh a full rebuild of the filter. updateAssociatedConstraintsFilter(); } } @@ -1014,7 +1014,7 @@ void TaskSketcherConstrains::onSelectionChanged(const Gui::SelectionChanges& msg } } -void TaskSketcherConstrains::getSelectionGeoId(QString expr, int & geoid, Sketcher::PointPos & pointpos) +void TaskSketcherConstraints::getSelectionGeoId(QString expr, int & geoid, Sketcher::PointPos & pointpos) { QRegExp rxEdge(QString::fromLatin1("^Edge(\\d+)$")); int pos = expr.indexOf(rxEdge); @@ -1043,7 +1043,7 @@ void TaskSketcherConstrains::getSelectionGeoId(QString expr, int & geoid, Sketch } } -void TaskSketcherConstrains::on_comboBoxFilter_currentIndexChanged(int filterindex) +void TaskSketcherConstraints::on_comboBoxFilter_currentIndexChanged(int filterindex) { selectionFilter.clear(); // reset the stored selection filter associatedConstraintsFilter.clear(); @@ -1058,13 +1058,13 @@ void TaskSketcherConstrains::on_comboBoxFilter_currentIndexChanged(int filterind updateList(); } -void TaskSketcherConstrains::on_filterInternalAlignment_stateChanged(int state) +void TaskSketcherConstraints::on_filterInternalAlignment_stateChanged(int state) { Q_UNUSED(state); slotConstraintsChanged(); } -void TaskSketcherConstrains::on_visualisationTrackingFilter_stateChanged(int state) +void TaskSketcherConstraints::on_visualisationTrackingFilter_stateChanged(int state) { // Synchronise button drop state { @@ -1078,7 +1078,7 @@ void TaskSketcherConstrains::on_visualisationTrackingFilter_stateChanged(int sta change3DViewVisibilityToTrackFilter(); } -void TaskSketcherConstrains::on_visibilityButton_trackingaction_changed() +void TaskSketcherConstraints::on_visibilityButton_trackingaction_changed() { // synchronise VisualisationTrackingFilter parameter ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); @@ -1095,23 +1095,23 @@ void TaskSketcherConstrains::on_visibilityButton_trackingaction_changed() change3DViewVisibilityToTrackFilter(); } -void TaskSketcherConstrains::on_visibilityButton_clicked(bool) +void TaskSketcherConstraints::on_visibilityButton_clicked(bool) { change3DViewVisibilityToTrackFilter(); } -void TaskSketcherConstrains::on_extendedInformation_stateChanged(int state) +void TaskSketcherConstraints::on_extendedInformation_stateChanged(int state) { Q_UNUSED(state); slotConstraintsChanged(); } -void TaskSketcherConstrains::on_listWidgetConstraints_emitCenterSelectedItems() +void TaskSketcherConstraints::on_listWidgetConstraints_emitCenterSelectedItems() { sketchView->centerSelection(); } -void TaskSketcherConstrains::on_listWidgetConstraints_itemSelectionChanged(void) +void TaskSketcherConstraints::on_listWidgetConstraints_itemSelectionChanged(void) { std::string doc_name = sketchView->getSketchObject()->getDocument()->getName(); std::string obj_name = sketchView->getSketchObject()->getNameInDocument(); @@ -1132,7 +1132,7 @@ void TaskSketcherConstrains::on_listWidgetConstraints_itemSelectionChanged(void) this->blockConnection(block); } -void TaskSketcherConstrains::on_listWidgetConstraints_itemActivated(QListWidgetItem *item) +void TaskSketcherConstraints::on_listWidgetConstraints_itemActivated(QListWidgetItem *item) { ConstraintItem *it = dynamic_cast(item); if (!it) return; @@ -1145,7 +1145,7 @@ void TaskSketcherConstrains::on_listWidgetConstraints_itemActivated(QListWidgetI } } -void TaskSketcherConstrains::on_listWidgetConstraints_updateDrivingStatus(QListWidgetItem *item, bool status) +void TaskSketcherConstraints::on_listWidgetConstraints_updateDrivingStatus(QListWidgetItem *item, bool status) { Q_UNUSED(status); ConstraintItem *citem = dynamic_cast(item); @@ -1155,7 +1155,7 @@ void TaskSketcherConstrains::on_listWidgetConstraints_updateDrivingStatus(QListW slotConstraintsChanged(); } -void TaskSketcherConstrains::on_listWidgetConstraints_updateActiveStatus(QListWidgetItem *item, bool status) +void TaskSketcherConstraints::on_listWidgetConstraints_updateActiveStatus(QListWidgetItem *item, bool status) { Q_UNUSED(status); ConstraintItem *citem = dynamic_cast(item); @@ -1165,7 +1165,7 @@ void TaskSketcherConstrains::on_listWidgetConstraints_updateActiveStatus(QListWi slotConstraintsChanged(); } -void TaskSketcherConstrains::on_listWidgetConstraints_itemChanged(QListWidgetItem *item) +void TaskSketcherConstraints::on_listWidgetConstraints_itemChanged(QListWidgetItem *item) { const ConstraintItem *it = dynamic_cast(item); if (!it || inEditMode) @@ -1224,7 +1224,7 @@ void TaskSketcherConstrains::on_listWidgetConstraints_itemChanged(QListWidgetIte inEditMode = false; } -void TaskSketcherConstrains::change3DViewVisibilityToTrackFilter() +void TaskSketcherConstraints::change3DViewVisibilityToTrackFilter() { assert(sketchView); // Build up ListView with the constraints @@ -1302,7 +1302,7 @@ void TaskSketcherConstrains::change3DViewVisibilityToTrackFilter() } -bool TaskSketcherConstrains::isConstraintFiltered(QListWidgetItem * item) +bool TaskSketcherConstraints::isConstraintFiltered(QListWidgetItem * item) { assert(sketchView); const Sketcher::SketchObject * sketch = sketchView->getSketchObject(); @@ -1407,7 +1407,7 @@ bool TaskSketcherConstrains::isConstraintFiltered(QListWidgetItem * item) return !visible; } -void TaskSketcherConstrains::slotConstraintsChanged(void) +void TaskSketcherConstraints::slotConstraintsChanged(void) { assert(sketchView); // Build up ListView with the constraints @@ -1460,7 +1460,7 @@ void TaskSketcherConstrains::slotConstraintsChanged(void) } } -void TaskSketcherConstrains::changeEvent(QEvent *e) +void TaskSketcherConstraints::changeEvent(QEvent *e) { TaskBox::changeEvent(e); if (e->type() == QEvent::LanguageChange) { @@ -1469,10 +1469,10 @@ void TaskSketcherConstrains::changeEvent(QEvent *e) } template -bool TaskSketcherConstrains::isFilter(T filterValue) { +bool TaskSketcherConstraints::isFilter(T filterValue) { return isFilterMatch(filterValue, ui->comboBoxFilter->currentIndex()); } -#include "moc_TaskSketcherConstrains.cpp" +#include "moc_TaskSketcherConstraints.cpp" diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h b/src/Mod/Sketcher/Gui/TaskSketcherConstraints.h similarity index 91% rename from src/Mod/Sketcher/Gui/TaskSketcherConstrains.h rename to src/Mod/Sketcher/Gui/TaskSketcherConstraints.h index 8a04dbfe87..5f2941aa35 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstraints.h @@ -21,8 +21,8 @@ ***************************************************************************/ -#ifndef GUI_TASKVIEW_TaskSketcherConstrains_H -#define GUI_TASKVIEW_TaskSketcherConstrains_H +#ifndef GUI_TASKVIEW_TaskSketcherConstraints_H +#define GUI_TASKVIEW_TaskSketcherConstraints_H #include #include @@ -40,7 +40,7 @@ class Property; namespace SketcherGui { class ViewProviderSketch; -class Ui_TaskSketcherConstrains; +class Ui_TaskSketcherConstraints; class ConstraintView : public QListWidget { @@ -73,7 +73,7 @@ protected Q_SLOTS: void hideConstraints(); }; -class TaskSketcherConstrains : public Gui::TaskView::TaskBox, public Gui::SelectionObserver +class TaskSketcherConstraints : public Gui::TaskView::TaskBox, public Gui::SelectionObserver { Q_OBJECT @@ -83,8 +83,8 @@ class TaskSketcherConstrains : public Gui::TaskView::TaskBox, public Gui::Select }; public: - TaskSketcherConstrains(ViewProviderSketch *sketchView); - ~TaskSketcherConstrains(); + TaskSketcherConstraints(ViewProviderSketch *sketchView); + ~TaskSketcherConstraints(); /// Observer message from the Selection void onSelectionChanged(const Gui::SelectionChanges& msg); @@ -133,7 +133,7 @@ protected: private: QWidget* proxy; bool inEditMode; - std::unique_ptr ui; + std::unique_ptr ui; ConstraintFilter::FilterValueBitset multiFilterStatus; // Stores the filters to be aggregated to form the multifilter. std::vector selectionFilter; // holds the constraint ids of the selected constraints std::vector associatedConstraintsFilter; // holds the constraint ids of the constraints associated with the selected geometry diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui b/src/Mod/Sketcher/Gui/TaskSketcherConstraints.ui similarity index 99% rename from src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui rename to src/Mod/Sketcher/Gui/TaskSketcherConstraints.ui index 7730655b4a..eb9fb18c50 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstraints.ui @@ -1,7 +1,7 @@ - SketcherGui::TaskSketcherConstrains - + SketcherGui::TaskSketcherConstraints + 0 diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.ui b/src/Mod/Sketcher/Gui/TaskSketcherElements.ui index 777e9ce69e..d2321f6930 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.ui +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.ui @@ -10,6 +10,12 @@ 401 + + + 0 + 400 + + Form diff --git a/src/Mod/Sketcher/Gui/TaskSketcherGeneral.cpp b/src/Mod/Sketcher/Gui/TaskSketcherGeneral.cpp index bebd6a5844..81a2ec0077 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherGeneral.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherGeneral.cpp @@ -264,9 +264,6 @@ void TaskSketcherGeneral::onChangedSketchView(const Gui::ViewProvider& vp, QSignalBlocker block(widget); widget->checkGridView(sketchView->ShowGrid.getValue()); widget->enableGridSettings(sketchView->ShowGrid.getValue()); - if (sketchView->ShowGrid.getValue()) { - sketchView->createGrid(); - } } else if (&sketchView->GridSize == &prop) { QSignalBlocker block(widget); @@ -293,7 +290,6 @@ void TaskSketcherGeneral::onToggleGridView(bool on) Base::ConnectionBlocker block(changedSketchView); sketchView->ShowGrid.setValue(on); widget->enableGridSettings(on); - if (on) sketchView->createGrid(); } void TaskSketcherGeneral::onSetGridSize(double val) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp b/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp index afd57cf1ba..11b9eb0cd2 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp @@ -107,6 +107,9 @@ void SketcherValidation::changeEvent(QEvent *e) void SketcherValidation::on_findButton_clicked() { + if (sketch.expired()) + return; + double prec = Precision::Confusion(); bool ok; double conv; @@ -150,6 +153,9 @@ void SketcherValidation::on_findButton_clicked() void SketcherValidation::on_fixButton_clicked() { + if (sketch.expired()) + return; + // undo command open App::Document* doc = sketch->getDocument(); doc->openTransaction("add coincident constraint"); @@ -167,6 +173,9 @@ void SketcherValidation::on_fixButton_clicked() void SketcherValidation::on_highlightButton_clicked() { + if (sketch.expired()) + return; + std::vector points; points = sketchAnalyser.getOpenVertices(); @@ -178,6 +187,9 @@ void SketcherValidation::on_highlightButton_clicked() void SketcherValidation::on_findConstraint_clicked() { + if (sketch.expired()) + return; + if (sketch->evaluateConstraints()) { QMessageBox::information(this, tr("No invalid constraints"), tr("No invalid constraints found")); @@ -192,12 +204,18 @@ void SketcherValidation::on_findConstraint_clicked() void SketcherValidation::on_fixConstraint_clicked() { + if (sketch.expired()) + return; + sketch->validateConstraints(); ui->fixConstraint->setEnabled(false); } void SketcherValidation::on_findReversed_clicked() { + if (sketch.expired()) + return; + std::vector points; const std::vector& geom = sketch->getExternalGeometry(); for (std::size_t i=0; igetDocument(); doc->openTransaction("Sketch porting"); @@ -255,6 +276,9 @@ void SketcherValidation::on_swapReversed_clicked() void SketcherValidation::on_orientLockEnable_clicked() { + if (sketch.expired()) + return; + App::Document* doc = sketch->getDocument(); doc->openTransaction("Constraint orientation lock"); @@ -269,6 +293,9 @@ void SketcherValidation::on_orientLockEnable_clicked() void SketcherValidation::on_orientLockDisable_clicked() { + if (sketch.expired()) + return; + App::Document* doc = sketch->getDocument(); doc->openTransaction("Constraint orientation unlock"); @@ -284,6 +311,9 @@ void SketcherValidation::on_orientLockDisable_clicked() void SketcherValidation::on_delConstrExtr_clicked() { + if (sketch.expired()) + return; + int reply; reply = QMessageBox::question(this, tr("Delete constraints to external geom."), @@ -337,21 +367,28 @@ void SketcherValidation::showPoints(const std::vector& pts) } coords->point.finishEditing(); - Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(sketch); - vp->getRoot()->addChild(coincidenceRoot); + if (!sketch.expired()) { + Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(sketch.get()); + vp->getRoot()->addChild(coincidenceRoot); + } } void SketcherValidation::hidePoints() { if (coincidenceRoot) { - Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(sketch); - vp->getRoot()->removeChild(coincidenceRoot); - coincidenceRoot = 0; + if (!sketch.expired()) { + Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(sketch.get()); + vp->getRoot()->removeChild(coincidenceRoot); + } + coincidenceRoot = nullptr; } } void SketcherValidation::on_findDegenerated_clicked() { + if (sketch.expired()) + return; + double prec = Precision::Confusion(); int count = sketchAnalyser.detectDegeneratedGeometries(prec); @@ -369,6 +406,9 @@ void SketcherValidation::on_findDegenerated_clicked() void SketcherValidation::on_fixDegenerated_clicked() { + if (sketch.expired()) + return; + // undo command open App::Document* doc = sketch->getDocument(); doc->openTransaction("Remove degenerated geometry"); diff --git a/src/Mod/Sketcher/Gui/TaskSketcherValidation.h b/src/Mod/Sketcher/Gui/TaskSketcherValidation.h index 127a561e65..c787587385 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherValidation.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherValidation.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -67,7 +68,7 @@ private: private: std::unique_ptr ui; - Sketcher::SketchObject* sketch; + App::WeakPtrT sketch; Sketcher::SketchAnalysis sketchAnalyser; SoGroup* coincidenceRoot; }; diff --git a/src/Mod/Sketcher/Gui/TaskSketcherValidation.ui b/src/Mod/Sketcher/Gui/TaskSketcherValidation.ui index 4cf647b8cf..bde339ba91 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherValidation.ui +++ b/src/Mod/Sketcher/Gui/TaskSketcherValidation.ui @@ -6,32 +6,57 @@ 0 0 - 311 - 453 + 266 + 684 Sketcher validation - - + + + + Reversed external geometry + + + + + + Finds reversed external geometries + + + Find + + + + + + + Fixes found reversed external geometries by swapping their endpoints + + + Swap endpoints in constraints + + + + + + + + + + Fixes found missing coincidences by adding extra coincident constrains + Missing coincidences - - - - Tolerance: - - - - - - + + If checked, construction geometries are ignored in the search + Ignore construction geometry @@ -42,11 +67,22 @@ + + Finds and displays missing coincidences found in the sketch +This is done by analyzing the sketch geometries and constraints + Find + + + + Tolerance: + + + @@ -54,17 +90,17 @@ - - - - Highlight open vertexes + + + + Defines the X/Y tolerance inside which missing coincidences are searched. - + Invalid constraints @@ -72,6 +108,9 @@ + + Finds invalid/malformed constrains in the sketch + Find @@ -79,6 +118,9 @@ + + Tries to fix found invalid constraints + Fix @@ -86,6 +128,9 @@ + + Deletes constrains refering to external geometry + Delete constraints to external geom. @@ -94,7 +139,39 @@ - + + + + Open and non-manifold vertexes + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + Highlights open and non-manifold vertexes that could lead to error if sketch is used to generate solids +This is purely based on topological shape of the sketch and not on its geometry/constrain set. + + + Highlight troublesome vertexes + + + + + + + Degenerated geometry @@ -102,6 +179,9 @@ + + Finds degenerated geometries in the sketch + Find @@ -109,6 +189,9 @@ + + Tries to fix found degenerated geometries + Fix @@ -117,30 +200,7 @@ - - - - Reversed external geometry - - - - - - Find - - - - - - - Swap endpoints in constraints - - - - - - - + Constraint orientation locking @@ -148,6 +208,9 @@ + + Enables/updates constraint orientation locking + Enable/Update @@ -155,6 +218,9 @@ + + Disables constraint orientation locking + Disable diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 698dadd0a4..e22254d2af 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -3193,7 +3193,7 @@ QString ViewProviderSketch::getPresentationString(const Constraint *constraint) // Hide units if user has requested it, is being displayed in the base // units, and the schema being used has a clear base unit in the first // place. Otherwise, display units. - if( iHideUnits ) + if( iHideUnits && constraint->Type != Sketcher::Angle ) { // Only hide the default length unit. Right now there is not an easy way // to get that from the Unit system so we have to manually add it here. @@ -5766,7 +5766,7 @@ Restart: break; SoDatumLabel *asciiText = static_cast(sep->getChild(CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL)); - asciiText->string = SbString(Constr->getPresentationValue().getUserString().toUtf8().constData()); + asciiText->string = SbString( getPresentationString(Constr).toUtf8().constData() ); asciiText->datumtype = SoDatumLabel::ANGLE; asciiText->param1 = Constr->LabelDistance; asciiText->param2 = startangle; @@ -6376,6 +6376,7 @@ bool ViewProviderSketch::setEdit(int ModNum) " tv.sketchClipPlane(ActiveSketch, ActiveSketch.ViewObject.SectionView)\n" "tv.hide(ActiveSketch)\n" "del(tv)\n" + "del(ActiveSketch)\n" ).arg(QString::fromLatin1(getDocument()->getDocument()->getName()), QString::fromLatin1(getSketchObject()->getNameInDocument()), QString::fromLatin1(Gui::Command::getObjectCmd(editObj).c_str()), @@ -6907,6 +6908,7 @@ void ViewProviderSketch::unsetEdit(int ModNum) " tv.restore()\n" "ActiveSketch.ViewObject.TempoVis = None\n" "del(tv)\n" + "del(ActiveSketch)\n" ).arg(QString::fromLatin1(getDocument()->getDocument()->getName())).arg( QString::fromLatin1(getSketchObject()->getNameInDocument())); QByteArray cmdstr_bytearray = cmdstr.toLatin1(); diff --git a/src/Mod/Spreadsheet/App/PropertySheet.cpp b/src/Mod/Spreadsheet/App/PropertySheet.cpp index 286b72890f..265f8d7697 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.cpp +++ b/src/Mod/Spreadsheet/App/PropertySheet.cpp @@ -351,7 +351,7 @@ void PropertySheet::copyCells(Base::Writer& writer, const std::vector& ra writer.incInd(); do { auto cell = getValue(*range); - if (cell) { + if (cell && cell->isUsed()) { cell->save(writer); } else { diff --git a/src/Mod/Spreadsheet/App/SheetPy.xml b/src/Mod/Spreadsheet/App/SheetPy.xml index a2c4bf6330..d968809ea7 100644 --- a/src/Mod/Spreadsheet/App/SheetPy.xml +++ b/src/Mod/Spreadsheet/App/SheetPy.xml @@ -176,7 +176,7 @@ recomputeCells(from, to=None) Manually recompute cells in the given range with the given order without -following depedency order. +following dependency order. diff --git a/src/Mod/Spreadsheet/Gui/Resources/translations/Spreadsheet_de.qm b/src/Mod/Spreadsheet/Gui/Resources/translations/Spreadsheet_de.qm index 74beda1985..955e330f2d 100644 Binary files a/src/Mod/Spreadsheet/Gui/Resources/translations/Spreadsheet_de.qm and b/src/Mod/Spreadsheet/Gui/Resources/translations/Spreadsheet_de.qm differ diff --git a/src/Mod/Spreadsheet/Gui/Resources/translations/Spreadsheet_de.ts b/src/Mod/Spreadsheet/Gui/Resources/translations/Spreadsheet_de.ts index 3ba265ee4c..6ac480beef 100644 --- a/src/Mod/Spreadsheet/Gui/Resources/translations/Spreadsheet_de.ts +++ b/src/Mod/Spreadsheet/Gui/Resources/translations/Spreadsheet_de.ts @@ -630,5 +630,17 @@ Tabelle.my_Alias_name anstelle von Tabelle.B1 Spreadsheet Kalkulationstabelle + + &Spreadsheet + &Kalkulationstabelle + + + &Alignment + &Ausrichtung + + + &Styles + &Stile + diff --git a/src/Mod/Spreadsheet/Gui/Sheet.ui b/src/Mod/Spreadsheet/Gui/Sheet.ui index 5704291d1e..68aa09998e 100644 --- a/src/Mod/Spreadsheet/Gui/Sheet.ui +++ b/src/Mod/Spreadsheet/Gui/Sheet.ui @@ -27,7 +27,7 @@ - + false @@ -44,7 +44,7 @@ - + false @@ -68,9 +68,9 @@ Spreadsheet.my_alias_name instead of Spreadsheet.B1
SheetTableView.h
- SpreadsheetGui::LineEdit + Gui::ExpressionLineEdit QLineEdit -
SpreadsheetView.h
+
Gui/ExpressionCompleter.h
diff --git a/src/Mod/Spreadsheet/Gui/SheetModel.cpp b/src/Mod/Spreadsheet/Gui/SheetModel.cpp index 341466214f..c289dcc121 100644 --- a/src/Mod/Spreadsheet/Gui/SheetModel.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetModel.cpp @@ -277,18 +277,34 @@ QVariant SheetModel::data(const QModelIndex &index, int role) const return QVariant::fromValue(f); } - if (!prop) { + auto dirtyCells = sheet->getCells()->getDirty(); + auto dirty = (dirtyCells.find(CellAddress(row,col)) != dirtyCells.end()); + + if (!prop || dirty) { switch (role) { case Qt::ForegroundRole: { - return QColor(0, 0, 255.0); + return QColor(0, 0, 255.0); // TODO: Remove this hardcoded color, replace with preference } case Qt::TextAlignmentRole: { qtAlignment = Qt::AlignHCenter | Qt::AlignVCenter; return QVariant::fromValue(qtAlignment); } case Qt::DisplayRole: - if(cell->getExpression()) - return QVariant(QLatin1String("#PENDING")); + if(cell->getExpression()) { + std::string str; + if (cell->getStringContent(str)) + if (str.size() > 0 && str[0] == '=') + // If this is a real computed value, indicate that a recompute is + // needed before we can display it + return QVariant(QLatin1String("#PENDING")); + else + // If it's just a simple value, display the new value, but still + // format it as a pending value to indicate to the user that + // a recompute is needed + return QVariant(QString::fromUtf8(str.c_str())); + else + return QVariant(); + } else return QVariant(); default: @@ -531,6 +547,16 @@ bool SheetModel::setData(const QModelIndex & index, const QVariant & value, int try { QString str = value.toString(); + + // Check to see if this is already the value in the cell, and skip the update if so + auto cell = sheet->getCell(address); + if (cell) { + std::string oldContent; + cell->getStringContent(oldContent); + if (str == QString::fromStdString(oldContent)) + return true; + } + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Edit cell")); // Because of possible complication of recursively escaped // characters, let's take a shortcut and bypass the command diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp index 634a30e815..e65365c3eb 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp @@ -163,8 +163,8 @@ SheetTableView::SheetTableView(QWidget *parent) auto cellProperties = new QAction(tr("Properties..."), this); addAction(cellProperties); - horizontalHeader()->setContextMenuPolicy(Qt::ActionsContextMenu); - verticalHeader()->setContextMenuPolicy(Qt::ActionsContextMenu); + horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + verticalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); contextMenu = new QMenu(this); diff --git a/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp b/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp index 3d2056cb3f..4103c2fd91 100644 --- a/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp +++ b/src/Mod/Spreadsheet/Gui/SpreadsheetView.cpp @@ -99,10 +99,8 @@ SheetView::SheetView(Gui::Document *pcDocument, App::DocumentObject *docObj, QWi this, SLOT(rowResized(int, int, int))); connect(delegate, &SpreadsheetDelegate::finishedWithKey, this, &SheetView::editingFinishedWithKey); - connect(ui->cellContent, &LineEdit::finishedWithKey, this, [this](int, Qt::KeyboardModifiers) {confirmContentChanged(ui->cellContent->text()); }); - connect(ui->cellContent, &LineEdit::returnPressed, this, [this]() {confirmContentChanged(ui->cellContent->text()); }); - connect(ui->cellAlias, &LineEdit::finishedWithKey, this, [this](int, Qt::KeyboardModifiers) {confirmAliasChanged(ui->cellAlias->text()); }); - connect(ui->cellAlias, &LineEdit::returnPressed, this, [this]() {confirmAliasChanged(ui->cellAlias->text()); }); + connect(ui->cellContent, &ExpressionLineEdit::returnPressed, this, [this]() {confirmContentChanged(ui->cellContent->text()); }); + connect(ui->cellAlias, &ExpressionLineEdit::returnPressed, this, [this]() {confirmAliasChanged(ui->cellAlias->text()); }); connect(ui->cellAlias, &LineEdit::textEdited, this, &SheetView::aliasChanged); columnWidthChangedConnection = sheet->columnWidthChanged.connect(bind(&SheetView::resizeColumn, this, bp::_1, bp::_2)); @@ -314,7 +312,6 @@ void SheetView::editingFinishedWithKey(int key, Qt::KeyboardModifiers modifiers) QModelIndex i = ui->cells->currentIndex(); if (i.isValid()) { - ui->cells->model()->setData(i, QVariant(ui->cellContent->text()), Qt::EditRole); ui->cells->finishEditWithMove(key, modifiers); } } diff --git a/src/Mod/Spreadsheet/Gui/Workbench.cpp b/src/Mod/Spreadsheet/Gui/Workbench.cpp index ec6cec5e77..b7c2a4a978 100644 --- a/src/Mod/Spreadsheet/Gui/Workbench.cpp +++ b/src/Mod/Spreadsheet/Gui/Workbench.cpp @@ -47,6 +47,9 @@ using namespace Spreadsheet; #if 0 // needed for Qt's lupdate utility qApp->translate("Workbench", "Spreadsheet"); + qApp->translate("Workbench", "&Spreadsheet"); + qApp->translate("Workbench", "&Alignment"); + qApp->translate("Workbench", "&Styles"); #endif /// @namespace ImageGui @class Workbench diff --git a/src/Mod/Surface/App/FeatureFilling.cpp b/src/Mod/Surface/App/FeatureFilling.cpp index 2b3b9a4a20..d0b17c2362 100644 --- a/src/Mod/Surface/App/FeatureFilling.cpp +++ b/src/Mod/Surface/App/FeatureFilling.cpp @@ -298,6 +298,7 @@ App::DocumentObjectExecReturn *Filling::execute(void) } // Add the constraints of border curves/faces (bound) + int numBoundaries = BoundaryEdges.getSize(); addConstraints(builder, BoundaryEdges, BoundaryFaces, BoundaryOrder, Standard_True); // Add additional edge constraints if available (unbound) @@ -316,7 +317,8 @@ App::DocumentObjectExecReturn *Filling::execute(void) } //Build the face - builder.Build(); + if (numBoundaries > 1) + builder.Build(); if (!builder.IsDone()) { Standard_Failure::Raise("Failed to create a face from constraints"); } @@ -327,7 +329,6 @@ App::DocumentObjectExecReturn *Filling::execute(void) return App::DocumentObject::StdReturn; } catch (Standard_Failure& e) { - return new App::DocumentObjectExecReturn(e.GetMessageString()); } } diff --git a/src/Mod/Surface/Gui/ViewProviderExtend.cpp b/src/Mod/Surface/Gui/ViewProviderExtend.cpp index ed5b5f261f..5d575c48f6 100644 --- a/src/Mod/Surface/Gui/ViewProviderExtend.cpp +++ b/src/Mod/Surface/Gui/ViewProviderExtend.cpp @@ -35,7 +35,7 @@ namespace SurfaceGui { QIcon ViewProviderExtend::getIcon(void) const { - return Gui::BitmapFactory().pixmap("Surface_Extend"); + return Gui::BitmapFactory().pixmap("Surface_ExtendFace"); } } //namespace SurfaceGui diff --git a/src/Mod/Test/BaseTests.py b/src/Mod/Test/BaseTests.py index 61a47a1b44..857e55d41b 100644 --- a/src/Mod/Test/BaseTests.py +++ b/src/Mod/Test/BaseTests.py @@ -244,6 +244,58 @@ class ParameterTestCase(unittest.TestCase): r.invert() self.assertTrue(r.isSame(s)) + # gimbal lock (north pole) + r=FreeCAD.Rotation() + r.setYawPitchRoll(20, 90, 10) + a=r.getYawPitchRoll() + s=FreeCAD.Rotation() + s.setYawPitchRoll(*a) + self.assertAlmostEqual(a[0], 0.0) + self.assertAlmostEqual(a[1], 90.0) + self.assertAlmostEqual(a[2], -10.0) + self.assertTrue(r.isSame(s, 1e-12)) + + # gimbal lock (south pole) + r=FreeCAD.Rotation() + r.setYawPitchRoll(20, -90, 10) + a=r.getYawPitchRoll() + s=FreeCAD.Rotation() + s.setYawPitchRoll(*a) + self.assertAlmostEqual(a[0], 0.0) + self.assertAlmostEqual(a[1], -90.0) + self.assertAlmostEqual(a[2], 30.0) + self.assertTrue(r.isSame(s, 1e-12)) + + def testYawPitchRoll(self): + def getYPR1(yaw, pitch, roll): + r = FreeCAD.Rotation() + r.setYawPitchRoll(yaw, pitch, roll) + return r + def getYPR2(yaw, pitch, roll): + rx = FreeCAD.Rotation() + ry = FreeCAD.Rotation() + rz = FreeCAD.Rotation() + + rx.Axis = FreeCAD.Vector(1,0,0) + ry.Axis = FreeCAD.Vector(0,1,0) + rz.Axis = FreeCAD.Vector(0,0,1) + + rx.Angle = math.radians(roll) + ry.Angle = math.radians(pitch) + rz.Angle = math.radians(yaw) + + return rz.multiply(ry).multiply(rx) + + angles = [] + angles.append((10,10,10)) + angles.append((13,45,-24)) + angles.append((10,-90,20)) + + for i in angles: + r = getYPR1(*i) + s = getYPR2(*i) + self.assertTrue(r.isSame(s, 1e-12)) + def testBounding(self): b=FreeCAD.BoundBox() b.setVoid() diff --git a/src/Mod/Tux/NavigationIndicatorGui.py b/src/Mod/Tux/NavigationIndicatorGui.py index 516c17258b..c49048c42c 100644 --- a/src/Mod/Tux/NavigationIndicatorGui.py +++ b/src/Mod/Tux/NavigationIndicatorGui.py @@ -288,6 +288,42 @@ def retranslateUi(): """ + global t9 + t9 = "

OpenSCAD " + text06 + """

+ + + + + + + + + + + + + + + +
""" + text01 + """""" + text02 + """""" + text02 + """""" + text03 + """""" + text04 + """
""" + + global t10 + t10 = "

TinkerCAD " + text06 + """

+ + + + + + + + + + + + + +
""" + text01 + """""" + text02 + """""" + text03 + """""" + text04 + """
""" + menuSettings.setTitle(translate("NavigationIndicator", "Settings")) menuOrbit.setTitle(translate("NavigationIndicator", "Orbit style")) aCompact.setText(translate("NavigationIndicator", "Compact")) @@ -384,6 +420,18 @@ a8.setText("OpenCascade") a8.setData("Gui::OpenCascadeNavigationStyle") a8.setObjectName("Indicator_NavigationOpenCascade") +a9 = QtGui.QAction(gStyle) +a9.setIcon(QtGui.QIcon(":/icons/NavigationOpenSCAD_dark.svg")) +a9.setText("OpenSCAD") +a9.setData("Gui::OpenSCADNavigationStyle") +a9.setObjectName("Indicator_NavigationOpenSCAD") + +a10 = QtGui.QAction(gStyle) +a10.setIcon(QtGui.QIcon(":/icons/NavigationTinkerCAD_dark.svg")) +a10.setText("TinkerCAD") +a10.setData("Gui::TinkerCADNavigationStyle") +a10.setObjectName("Indicator_NavigationTinkerCAD") + menu.addMenu(menuSettings) menu.addSeparator() menu.addAction(a0) @@ -395,6 +443,8 @@ menu.addAction(a5) menu.addAction(a6) menu.addAction(a7) menu.addAction(a8) +menu.addAction(a9) +menu.addAction(a10) def onCompact(): @@ -430,6 +480,8 @@ def onTooltip(): a6.setToolTip(t6) a7.setToolTip(t7) a8.setToolTip(t8) + a9.setToolTip(t9) + a10.setToolTip(t10) p.SetBool("Tooltip", 1) else: for i in gStyle.actions(): diff --git a/src/Mod/Tux/Resources/Tux.qrc b/src/Mod/Tux/Resources/Tux.qrc index e503675307..471f97fcc8 100644 --- a/src/Mod/Tux/Resources/Tux.qrc +++ b/src/Mod/Tux/Resources/Tux.qrc @@ -58,6 +58,17 @@ icons/NavigationOpenInventor_dark.svg icons/NavigationOpenInventor_ZoomAlt.svg icons/NavigationOpenInventor_Zoom.svg + icons/NavigationCAD_dark.svg + icons/NavigationOpenCascade_Select.svg + icons/NavigationOpenCascade_PanAlt.svg + icons/NavigationOpenCascade_PanAlt.svg + icons/NavigationOpenCascade_Select.svg + icons/NavigationGesture_Pan.svg + icons/NavigationCAD_dark.svg + icons/NavigationOpenCascade_Select.svg + icons/NavigationOpenCascade_Zoom.svg + icons/NavigationGesture_Pan.svg + icons/NavigationOpenCascade_PanAlt.svg icons/NavigationRevit_Pan.svg icons/NavigationRevit_Rotate.svg icons/NavigationRevit_light.svg diff --git a/src/Tools/makedist.py b/src/Tools/makedist.py index f4118fe68c..c054534fe8 100644 --- a/src/Tools/makedist.py +++ b/src/Tools/makedist.py @@ -5,23 +5,29 @@ # Python script to make source tarballs. # -import sys, os, getopt, tarfile, gzip, time, io, platform, shutil +import sys, os, getopt, tarfile, gzip, time, io, platform, shutil, subprocess def main(): bindir="." + major="0" + minor="0" dfsg=False check=False - wta="" + wta=None try: - opts, args = getopt.getopt(sys.argv[1:], "sb:", ["srcdir=","bindir=","dfsg", "check"]) + opts, args = getopt.getopt(sys.argv[1:], "sb:", ["srcdir=","bindir=","major=","minor=","dfsg", "check"]) except getopt.GetoptError: pass for o, a in opts: if o in ("-s", "--srcdir"): - print("%s is deprecated -- ignoring" % (o)) + print("{} is deprecated -- ignoring".format(o)) if o in ("-b", "--bindir"): bindir = a + if o in ("--major"): + major = a + if o in ("--minor"): + minor = a if o in ("--dfsg"): dfsg = True wta = "--worktree-attributes" @@ -41,14 +47,15 @@ def main(): info=os.popen("git rev-list HEAD").read() revision='%04d' % (info.count('\n')) - verfile = open("%s/src/Build/Version.h" % (bindir), 'r') - verstream = io.StringIO(verfile.read()) + verfile = open("{}/src/Build/Version.h".format(bindir), 'rb') + verstream = io.BytesIO(verfile.read()) verfile.close() - version_minor = verstream.getvalue().split('FCVersionMinor "')[1][:2] + version_major = major + version_minor = minor PACKAGE_NAME = 'freecad' - version = "0.%s.%s" % (version_minor, revision) + version = "{}.{}.{}".format(version_major, version_minor, revision) DIRNAME = "%(p)s-%(v)s" % {'p': PACKAGE_NAME, 'v': version} TARNAME = DIRNAME + '.tar' @@ -61,10 +68,13 @@ def main(): verinfo.size = len(verstream.getvalue()) verinfo.mtime = time.time() - print(("git archive %s --prefix=%s/ HEAD" % (wta, DIRNAME))) + if wta is None: + print(("git archive --prefix={}/ HEAD".format(DIRNAME))) + else: + print(("git archive {} --prefix={}/ HEAD".format(wta, DIRNAME))) + if platform.system() == 'Windows': - os.popen("git archive %s --prefix=%s/ --output=%s HEAD" - % (wta, DIRNAME, TARNAME)).read() + os.popen("git archive {} --prefix={}/ --output={} HEAD".format(wta, DIRNAME, TARNAME)).read() tar = tarfile.TarFile(mode="a", name=TARNAME) tar.addfile(verinfo, verstream) @@ -77,9 +87,15 @@ def main(): tardata.close() os.remove(TARNAME) else: - tardata = os.popen("git archive %s --prefix=%s/ HEAD" - % (wta, DIRNAME)).read() - tarstream = io.StringIO(tardata) + cmd_line = ["git", "archive"] + if not wta is None: + cmd_line.append(wta) + cmd_line.append("--prefix={}/".format(DIRNAME)) + cmd_line.append("HEAD") + + tardata = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out,err = tardata.communicate() + tarstream = io.BytesIO(out) tar = tarfile.TarFile(mode="a", fileobj=tarstream) tar.addfile(verinfo, verstream) diff --git a/src/WindowsInstaller/README.md b/src/WindowsInstaller/README.md index 542f264de7..777ce80e43 100644 --- a/src/WindowsInstaller/README.md +++ b/src/WindowsInstaller/README.md @@ -20,7 +20,7 @@ To build the installer you can do the following: (You can alternatively get nsProcess from https://nsis.sourceforge.io/NsProcess_plugin) 7. Copy all FreeCAD files to the folder "~\FreeCAD" e.g. "C:\FreeCAD\Installer\FreeCAD" -8. If you use a version of FreeCAD that was compiled using another MSVC version than MSVC 2017, +8. If you use a version of FreeCAD that was compiled using another MSVC version than MSVC 2019, copy its distributable DLLs to the folder FILES_DEPS (see step 3). 9. Right-click on the file FreeCAD-installer.nsi and choose "Compile NSIS script" to compile the installer. diff --git a/src/WindowsInstaller/include/declarations.nsh b/src/WindowsInstaller/include/declarations.nsh index 03e26d9b6a..c63da6e971 100644 --- a/src/WindowsInstaller/include/declarations.nsh +++ b/src/WindowsInstaller/include/declarations.nsh @@ -26,9 +26,9 @@ Configuration and variables of FreeCAD installer !define APP_DIR_USERDATA ${APP_NAME} #!define APP_DIR_USERDATA "${APP_NAME}${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}" !define APP_INFO "${APP_NAME} - Your Own 3D Parametric Modeler" -!define APP_WEBPAGE "https://freecadweb.org/" +!define APP_WEBPAGE "https://freecad.org/" !define APP_WEBPAGE_INFO "${APP_NAME} Website" -!define APP_WIKI "https://www.freecadweb.org/wiki/Main_Page" +!define APP_WIKI "https://www.freecad.org/wiki/Main_Page" !define APP_WIKI_INFO "${APP_NAME} Wiki" !define APP_COPYRIGHT "${APP_NAME} is Copyright © 2001-${COPYRIGHT_YEAR} by the ${APP_NAME} Team" diff --git a/src/WindowsInstaller/include/gui.nsh b/src/WindowsInstaller/include/gui.nsh index ac0d6b5c80..16a285284f 100644 --- a/src/WindowsInstaller/include/gui.nsh +++ b/src/WindowsInstaller/include/gui.nsh @@ -66,7 +66,7 @@ BrandingText " " !define MUI_FINISHPAGE_SHOWREADME_FUNCTION StartFreeCAD !define MUI_FINISHPAGE_SHOWREADME_TEXT $(FinishPageRun) !define MUI_FINISHPAGE_LINK $(TEXT_FINISH_WEBSITE) -!define MUI_FINISHPAGE_LINK_LOCATION "https://freecadweb.org/" +!define MUI_FINISHPAGE_LINK_LOCATION "https://freecad.org/" #!define MUI_PAGE_CUSTOMFUNCTION_SHOW CheckDesktopShortcut !insertmacro MUI_PAGE_FINISH diff --git a/src/WindowsInstaller/lang/arabic.nsh b/src/WindowsInstaller/lang/arabic.nsh index a97346e770..f7771f51af 100644 --- a/src/WindowsInstaller/lang/arabic.nsh +++ b/src/WindowsInstaller/lang/arabic.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "هذا المساعد سوف يرشدك خلال #${LangFileString} TEXT_CONFIGURE_PYTHON "بناء سكربتات بايثون..." ${LangFileString} TEXT_FINISH_DESKTOP "إنشاء اختصار سطح المكتب" -${LangFileString} TEXT_FINISH_WEBSITE "زيارة freecadweb.org لمشاهدة آخر الاخبار, الدعم والأفكار" +${LangFileString} TEXT_FINISH_WEBSITE "زيارة freecad.org لمشاهدة آخر الاخبار, الدعم والأفكار" #${LangFileString} FileTypeTitle "مستند - ليك" diff --git a/src/WindowsInstaller/lang/basque.nsh b/src/WindowsInstaller/lang/basque.nsh index 981d11bf20..7f4bfe2c86 100644 --- a/src/WindowsInstaller/lang/basque.nsh +++ b/src/WindowsInstaller/lang/basque.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Morroi honek $(^NameDA) aplikazioaren instalazio #${LangFileString} TEXT_CONFIGURE_PYTHON "Python script-ak konpilatzen..." ${LangFileString} TEXT_FINISH_DESKTOP "Sortu mahaigaineko lasterbidea" -${LangFileString} TEXT_FINISH_WEBSITE "Bisitatu freecadweb.org azken berriak, aholkuak eta laguntza lortzeko" +${LangFileString} TEXT_FINISH_WEBSITE "Bisitatu freecad.org azken berriak, aholkuak eta laguntza lortzeko" #${LangFileString} FileTypeTitle "FreeCAD-dokumentua" diff --git a/src/WindowsInstaller/lang/catalan.nsh b/src/WindowsInstaller/lang/catalan.nsh index 12a77adc31..2c9c5d32dc 100644 --- a/src/WindowsInstaller/lang/catalan.nsh +++ b/src/WindowsInstaller/lang/catalan.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Aquest assistent us guiarà en la instal·lació #${LangFileString} TEXT_CONFIGURE_PYTHON "Compiling Python scripts..." ${LangFileString} TEXT_FINISH_DESKTOP "Create desktop shortcut" -${LangFileString} TEXT_FINISH_WEBSITE "Visit freecadweb.org for the latest news, support and tips" +${LangFileString} TEXT_FINISH_WEBSITE "Visit freecad.org for the latest news, support and tips" #${LangFileString} FileTypeTitle "Document FreeCAD" diff --git a/src/WindowsInstaller/lang/czech.nsh b/src/WindowsInstaller/lang/czech.nsh index 807cf7c82e..399945e327 100644 --- a/src/WindowsInstaller/lang/czech.nsh +++ b/src/WindowsInstaller/lang/czech.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Tento pomocník vás provede instalací FreeCADu #${LangFileString} TEXT_CONFIGURE_PYTHON "Compiling Python scripts..." ${LangFileString} TEXT_FINISH_DESKTOP "Create desktop shortcut" -${LangFileString} TEXT_FINISH_WEBSITE "Visit freecadweb.org for the latest news, support and tips" +${LangFileString} TEXT_FINISH_WEBSITE "Visit freecad.org for the latest news, support and tips" #${LangFileString} FileTypeTitle "FreeCAD-dokumentů" diff --git a/src/WindowsInstaller/lang/danish.nsh b/src/WindowsInstaller/lang/danish.nsh index 9c43044e6f..9ef681a349 100644 --- a/src/WindowsInstaller/lang/danish.nsh +++ b/src/WindowsInstaller/lang/danish.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Denne guide vil installere FreeCAD på din compu #${LangFileString} TEXT_CONFIGURE_PYTHON "Compiling Python scripts..." ${LangFileString} TEXT_FINISH_DESKTOP "Create desktop shortcut" -${LangFileString} TEXT_FINISH_WEBSITE "Visit freecadweb.org for the latest news, support and tips" +${LangFileString} TEXT_FINISH_WEBSITE "Visit freecad.org for the latest news, support and tips" #${LangFileString} FileTypeTitle "FreeCAD-Dokument" diff --git a/src/WindowsInstaller/lang/dutch.nsh b/src/WindowsInstaller/lang/dutch.nsh index e3cc15322f..07ddce249d 100644 --- a/src/WindowsInstaller/lang/dutch.nsh +++ b/src/WindowsInstaller/lang/dutch.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Dit installatie programma zal FreeCAD op uw syst #${LangFileString} TEXT_CONFIGURE_PYTHON "Compiling Python scripts..." ${LangFileString} TEXT_FINISH_DESKTOP "Create desktop shortcut" -${LangFileString} TEXT_FINISH_WEBSITE "Visit freecadweb.org for the latest news, support and tips" +${LangFileString} TEXT_FINISH_WEBSITE "Visit freecad.org for the latest news, support and tips" #${LangFileString} FileTypeTitle "FreeCAD-Document" diff --git a/src/WindowsInstaller/lang/english.nsh b/src/WindowsInstaller/lang/english.nsh index 138c5aada0..fcf401586c 100644 --- a/src/WindowsInstaller/lang/english.nsh +++ b/src/WindowsInstaller/lang/english.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "This wizard will guide you through the installat #${LangFileString} TEXT_CONFIGURE_PYTHON "Compiling Python scripts..." ${LangFileString} TEXT_FINISH_DESKTOP "Create desktop shortcut" -${LangFileString} TEXT_FINISH_WEBSITE "Visit freecadweb.org/ for the latest news, support and tips" +${LangFileString} TEXT_FINISH_WEBSITE "Visit freecad.org/ for the latest news, support and tips" #${LangFileString} FileTypeTitle "FreeCAD-Document" diff --git a/src/WindowsInstaller/lang/french.nsh b/src/WindowsInstaller/lang/french.nsh index 5b4132444f..c75e5b9ce5 100644 --- a/src/WindowsInstaller/lang/french.nsh +++ b/src/WindowsInstaller/lang/french.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Cet assistant va vous guider tout au long de l'i #${LangFileString} TEXT_CONFIGURE_PYTHON "Compilation des scripts Python..." ${LangFileString} TEXT_FINISH_DESKTOP "Créer un raccourci sur le bureau" -${LangFileString} TEXT_FINISH_WEBSITE "Consulter les dernières nouvelles, trucs et astuces sur le site freecadweb.org" +${LangFileString} TEXT_FINISH_WEBSITE "Consulter les dernières nouvelles, trucs et astuces sur le site freecad.org" #${LangFileString} FileTypeTitle "Document FreeCAD" diff --git a/src/WindowsInstaller/lang/galician.nsh b/src/WindowsInstaller/lang/galician.nsh index 48d684f2d9..f66e93e863 100644 --- a/src/WindowsInstaller/lang/galician.nsh +++ b/src/WindowsInstaller/lang/galician.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Este asistente vai-no guiar na instalación do F #${LangFileString} TEXT_CONFIGURE_PYTHON "Compiling Python scripts..." ${LangFileString} TEXT_FINISH_DESKTOP "Create desktop shortcut" -${LangFileString} TEXT_FINISH_WEBSITE "Visit freecadweb.org for the latest news, support and tips" +${LangFileString} TEXT_FINISH_WEBSITE "Visit freecad.org for the latest news, support and tips" #${LangFileString} FileTypeTitle "Documento FreeCAD" diff --git a/src/WindowsInstaller/lang/german.nsh b/src/WindowsInstaller/lang/german.nsh index 44e831d58e..dbb04b10df 100644 --- a/src/WindowsInstaller/lang/german.nsh +++ b/src/WindowsInstaller/lang/german.nsh @@ -15,7 +15,7 @@ ${LangFileString} TEXT_WELCOME "Dieser Assistent wird Sie durch die Installation #${LangFileString} TEXT_CONFIGURE_PYTHON "Kompiliere Python Skripte..." ${LangFileString} TEXT_FINISH_DESKTOP "Ein Symbol auf der Arbeitsoberfläche erzeugen" -${LangFileString} TEXT_FINISH_WEBSITE "Besuchen Sie freecadweb.org für aktuelle Neuigkeiten" +${LangFileString} TEXT_FINISH_WEBSITE "Besuchen Sie freecad.org für aktuelle Neuigkeiten" #${LangFileString} FileTypeTitle "FreeCAD-Dokument" diff --git a/src/WindowsInstaller/lang/hungarian.nsh b/src/WindowsInstaller/lang/hungarian.nsh index 5230dc66a4..55e6ac43d5 100644 --- a/src/WindowsInstaller/lang/hungarian.nsh +++ b/src/WindowsInstaller/lang/hungarian.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "A varázsló segítségével tudja telepíteni a #${LangFileString} TEXT_CONFIGURE_PYTHON "Python parancsfájlok fordítása..." ${LangFileString} TEXT_FINISH_DESKTOP "Indítóikon létrehozása Asztalon" -${LangFileString} TEXT_FINISH_WEBSITE "Látogasson el a freecadweb.org oldalra az aktuális hírekért, támogatásért és tippekért" +${LangFileString} TEXT_FINISH_WEBSITE "Látogasson el a freecad.org oldalra az aktuális hírekért, támogatásért és tippekért" #${LangFileString} FileTypeTitle "FreeCAD-dokumentum" diff --git a/src/WindowsInstaller/lang/indonesian.nsh b/src/WindowsInstaller/lang/indonesian.nsh index 1b92dd13b3..09282cc38a 100644 --- a/src/WindowsInstaller/lang/indonesian.nsh +++ b/src/WindowsInstaller/lang/indonesian.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Program ini akan memandu anda dalam melakukan in #${LangFileString} TEXT_CONFIGURE_PYTHON "Proses kompilasi skrip Python ..." ${LangFileString} TEXT_FINISH_DESKTOP "Membuat pintasan ikon di destop" -${LangFileString} TEXT_FINISH_WEBSITE "Kunjungi freecadweb.org untuk berita terbaru serta dukungan" +${LangFileString} TEXT_FINISH_WEBSITE "Kunjungi freecad.org untuk berita terbaru serta dukungan" #${LangFileString} FileTypeTitle "Dokumen-FreeCAD" diff --git a/src/WindowsInstaller/lang/italian.nsh b/src/WindowsInstaller/lang/italian.nsh index 589ac1e945..4eff2860c0 100644 --- a/src/WindowsInstaller/lang/italian.nsh +++ b/src/WindowsInstaller/lang/italian.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Verrete guidati nell'installazione di $(^NameDA) #${LangFileString} TEXT_CONFIGURE_PYTHON "Compilazione degli script Python in corso..." ${LangFileString} TEXT_FINISH_DESKTOP "Crea icona sul desktop" -${LangFileString} TEXT_FINISH_WEBSITE "Visitate freecadweb.org per ultime novità, aiuto e suggerimenti" +${LangFileString} TEXT_FINISH_WEBSITE "Visitate freecad.org per ultime novità, aiuto e suggerimenti" #${LangFileString} FileTypeTitle "Documento di FreeCAD" diff --git a/src/WindowsInstaller/lang/japanese.nsh b/src/WindowsInstaller/lang/japanese.nsh index b973095539..7320c9cfdc 100644 --- a/src/WindowsInstaller/lang/japanese.nsh +++ b/src/WindowsInstaller/lang/japanese.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "このウィザードが、あなたのFreeCAD #${LangFileString} TEXT_CONFIGURE_PYTHON "Pythonスクリプトをコンパイルしています..." ${LangFileString} TEXT_FINISH_DESKTOP "デスクトップにショートカットを作成する" -${LangFileString} TEXT_FINISH_WEBSITE "freecadweb.orgを開いて最新ニュースやサポート、ヒントなどを入手する" +${LangFileString} TEXT_FINISH_WEBSITE "freecad.orgを開いて最新ニュースやサポート、ヒントなどを入手する" #${LangFileString} FileTypeTitle "FreeCAD文書" diff --git a/src/WindowsInstaller/lang/norwegian.nsh b/src/WindowsInstaller/lang/norwegian.nsh index 6069b9278f..2e3293b4f1 100644 --- a/src/WindowsInstaller/lang/norwegian.nsh +++ b/src/WindowsInstaller/lang/norwegian.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Denne veiviseren installerer FreeCAD på datamas #${LangFileString} TEXT_CONFIGURE_PYTHON "Kompilerer Python script..." ${LangFileString} TEXT_FINISH_DESKTOP "Lager snarveg på skrivebordet" -${LangFileString} TEXT_FINISH_WEBSITE "Besøk freecadweb.org for de seneste nyhetene, hjelp og støtte" +${LangFileString} TEXT_FINISH_WEBSITE "Besøk freecad.org for de seneste nyhetene, hjelp og støtte" #${LangFileString} FileTypeTitle "FreeCAD-dokument" diff --git a/src/WindowsInstaller/lang/polish.nsh b/src/WindowsInstaller/lang/polish.nsh index 861959f5a4..8e9ad43d90 100644 --- a/src/WindowsInstaller/lang/polish.nsh +++ b/src/WindowsInstaller/lang/polish.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Kreator przeprowadzi Ciebie przez proces instala #${LangFileString} TEXT_CONFIGURE_PYTHON "Kompilowanie skryptów Python..." ${LangFileString} TEXT_FINISH_DESKTOP "Utwórz skrót na pulpicie" -${LangFileString} TEXT_FINISH_WEBSITE "Odwiedź freecadweb.org by poznać wiadomości i wskazówki lub skorzystać ze wsparcia" +${LangFileString} TEXT_FINISH_WEBSITE "Odwiedź freecad.org by poznać wiadomości i wskazówki lub skorzystać ze wsparcia" #${LangFileString} FileTypeTitle "Dokument FreeCAD" diff --git a/src/WindowsInstaller/lang/portuguese.nsh b/src/WindowsInstaller/lang/portuguese.nsh index df7aa3fbb5..24757e9b59 100644 --- a/src/WindowsInstaller/lang/portuguese.nsh +++ b/src/WindowsInstaller/lang/portuguese.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Este assistente de instalação irá guiá-lo at #${LangFileString} TEXT_CONFIGURE_PYTHON "Compilando os scripts de Python..." ${LangFileString} TEXT_FINISH_DESKTOP "Criar um atalho no ambiente de trabalho" -${LangFileString} TEXT_FINISH_WEBSITE "Visite freecadweb.org para as últimas notícias, suporte e dicas" +${LangFileString} TEXT_FINISH_WEBSITE "Visite freecad.org para as últimas notícias, suporte e dicas" #${LangFileString} FileTypeTitle "Documento FreeCAD" diff --git a/src/WindowsInstaller/lang/portugueseBR.nsh b/src/WindowsInstaller/lang/portugueseBR.nsh index b2925508e4..2658c70d98 100644 --- a/src/WindowsInstaller/lang/portugueseBR.nsh +++ b/src/WindowsInstaller/lang/portugueseBR.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Este assistente guiará você durante a instala #${LangFileString} TEXT_CONFIGURE_PYTHON "Compilando scripts Python..." ${LangFileString} TEXT_FINISH_DESKTOP "Criar atalho na área de trabalho" -${LangFileString} TEXT_FINISH_WEBSITE "Visite freecadweb.org para ver as últimas novidades do FreeCAD!" +${LangFileString} TEXT_FINISH_WEBSITE "Visite freecad.org para ver as últimas novidades do FreeCAD!" #${LangFileString} FileTypeTitle "Documento-FreeCAD" diff --git a/src/WindowsInstaller/lang/romanian.nsh b/src/WindowsInstaller/lang/romanian.nsh index c95950fe2c..2dadf8a9c5 100644 --- a/src/WindowsInstaller/lang/romanian.nsh +++ b/src/WindowsInstaller/lang/romanian.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Acest asistent vă va ghida în procesul de inst #${LangFileString} TEXT_CONFIGURE_PYTHON "Compiling Python scripts..." ${LangFileString} TEXT_FINISH_DESKTOP "Create desktop shortcut" -${LangFileString} TEXT_FINISH_WEBSITE "Visit freecadweb.org for the latest news, support and tips" +${LangFileString} TEXT_FINISH_WEBSITE "Visit freecad.org for the latest news, support and tips" #${LangFileString} FileTypeTitle "Document FreeCAD" diff --git a/src/WindowsInstaller/lang/russian.nsh b/src/WindowsInstaller/lang/russian.nsh index 92cc4169fd..879ebce14f 100644 --- a/src/WindowsInstaller/lang/russian.nsh +++ b/src/WindowsInstaller/lang/russian.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Этот мастер проведет вас ч #${LangFileString} TEXT_CONFIGURE_PYTHON "Компиляция скриптов Python..." ${LangFileString} TEXT_FINISH_DESKTOP "Создать ярлык на рабочем столе" -${LangFileString} TEXT_FINISH_WEBSITE "Перейти на freecadweb.org за новостями, поддержкой и советами" +${LangFileString} TEXT_FINISH_WEBSITE "Перейти на freecad.org за новостями, поддержкой и советами" #${LangFileString} FileTypeTitle "FreeCAD-Document" diff --git a/src/WindowsInstaller/lang/slovak.nsh b/src/WindowsInstaller/lang/slovak.nsh index 14e0aef61e..910538a125 100644 --- a/src/WindowsInstaller/lang/slovak.nsh +++ b/src/WindowsInstaller/lang/slovak.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Tento sprievodca Vám pomáha inštalovať FreeC #${LangFileString} TEXT_CONFIGURE_PYTHON "Kompilácia Python skriptov..." ${LangFileString} TEXT_FINISH_DESKTOP "Vytvoriť skratku pre pracovnú plochu" -${LangFileString} TEXT_FINISH_WEBSITE "Navštívte freecadweb.org pre posledné novinky, podporu a tipy" +${LangFileString} TEXT_FINISH_WEBSITE "Navštívte freecad.org pre posledné novinky, podporu a tipy" #${LangFileString} FileTypeTitle "FreeCAD dokument" diff --git a/src/WindowsInstaller/lang/spanish.nsh b/src/WindowsInstaller/lang/spanish.nsh index eb6c5a2079..3540bf480b 100644 --- a/src/WindowsInstaller/lang/spanish.nsh +++ b/src/WindowsInstaller/lang/spanish.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Este programa instalará FreeCAD en su ordenador #${LangFileString} TEXT_CONFIGURE_PYTHON "Compilando guiones Python..." ${LangFileString} TEXT_FINISH_DESKTOP "Crear acceso directo en el escritorio" -${LangFileString} TEXT_FINISH_WEBSITE "Visite freecadweb.org para últimas noticias, ayuda y consejos" +${LangFileString} TEXT_FINISH_WEBSITE "Visite freecad.org para últimas noticias, ayuda y consejos" #${LangFileString} FileTypeTitle "Documento FreeCAD" diff --git a/src/WindowsInstaller/lang/swedish.nsh b/src/WindowsInstaller/lang/swedish.nsh index 22bb333d20..0176d07b3f 100644 --- a/src/WindowsInstaller/lang/swedish.nsh +++ b/src/WindowsInstaller/lang/swedish.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Denna guide tar dig igenom installationen av $(^ #${LangFileString} TEXT_CONFIGURE_PYTHON "Kompilerar Pythonskript..." ${LangFileString} TEXT_FINISH_DESKTOP "Skapa skrivbordsgenväg" -${LangFileString} TEXT_FINISH_WEBSITE "Besök freecadweb.org för de senaste nyheterna, support och tips" +${LangFileString} TEXT_FINISH_WEBSITE "Besök freecad.org för de senaste nyheterna, support och tips" #${LangFileString} FileTypeTitle "FreeCAD-dokument" diff --git a/src/WindowsInstaller/lang/turkish.nsh b/src/WindowsInstaller/lang/turkish.nsh index dd1511abcf..12f4eedab2 100644 --- a/src/WindowsInstaller/lang/turkish.nsh +++ b/src/WindowsInstaller/lang/turkish.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "Bu sihirbaz size FreeCAD programını kuracak.$\ #${LangFileString} TEXT_CONFIGURE_PYTHON "Compiling Python scripts..." ${LangFileString} TEXT_FINISH_DESKTOP "Create desktop shortcut" -${LangFileString} TEXT_FINISH_WEBSITE "Visit freecadweb.org for the latest news, support and tips" +${LangFileString} TEXT_FINISH_WEBSITE "Visit freecad.org for the latest news, support and tips" #${LangFileString} FileTypeTitle "FreeCAD-Document" diff --git a/src/WindowsInstaller/lang/ukrainian.nsh b/src/WindowsInstaller/lang/ukrainian.nsh index d1e4f975f2..fac5191ab4 100644 --- a/src/WindowsInstaller/lang/ukrainian.nsh +++ b/src/WindowsInstaller/lang/ukrainian.nsh @@ -14,7 +14,7 @@ ${LangFileString} TEXT_WELCOME "За допомогою цього майстр #${LangFileString} TEXT_CONFIGURE_PYTHON "Обробка скриптів Python..." ${LangFileString} TEXT_FINISH_DESKTOP "Створити значок на стільниці" -${LangFileString} TEXT_FINISH_WEBSITE "Відвідати freecadweb.org, щоб ознайомитися з новинами, довідковими матеріалами та підказками" +${LangFileString} TEXT_FINISH_WEBSITE "Відвідати freecad.org, щоб ознайомитися з новинами, довідковими матеріалами та підказками" #${LangFileString} FileTypeTitle "Документ FreeCAD" diff --git a/src/WindowsInstaller/setup/configure.nsh b/src/WindowsInstaller/setup/configure.nsh index dae07512f0..d42ef4d6bb 100644 --- a/src/WindowsInstaller/setup/configure.nsh +++ b/src/WindowsInstaller/setup/configure.nsh @@ -45,9 +45,9 @@ Section -InstallData WriteRegStr SHCTX ${APP_UNINST_KEY} "DisplayVersion" "${APP_VERSION}" WriteRegStr SHCTX ${APP_UNINST_KEY} "DisplayIcon" "$INSTDIR\${APP_RUN}" WriteRegStr SHCTX ${APP_UNINST_KEY} "URLUpdateInfo" "${APP_WEBPAGE}" - WriteRegStr SHCTX ${APP_UNINST_KEY} "URLInfoAbout" "https://www.freecadweb.org/" + WriteRegStr SHCTX ${APP_UNINST_KEY} "URLInfoAbout" "https://www.freecad.org/" WriteRegStr SHCTX ${APP_UNINST_KEY} "Publisher" "${APP_NAME} Team" - WriteRegStr SHCTX ${APP_UNINST_KEY} "HelpLink" "https://forum.freecadweb.org/" + WriteRegStr SHCTX ${APP_UNINST_KEY} "HelpLink" "https://forum.freecad.org/" WriteRegDWORD SHCTX ${APP_UNINST_KEY} "NoModify" 0x00000001 WriteRegDWORD SHCTX ${APP_UNINST_KEY} "NoRepair" 0x00000001 WriteRegStr SHCTX ${APP_UNINST_KEY} "StartMenu" "$SMPROGRAMS\$StartmenuFolder"