From 213bef18f42c69c286f24e0906e562746f290581 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Fri, 17 Sep 2021 15:47:00 +0200 Subject: [PATCH 01/21] AddonManager: Fixed path of gitlab readme --- src/Mod/AddonManager/addonmanager_utilities.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index e86fff54c2..cfb63ccbb6 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -266,8 +266,10 @@ def get_zip_url(baseurl): def get_readme_url(url): "Returns the location of a readme file" - if "github" in url or "framagit" in url or "gitlab" in url: + if "github" in url or "framagit" in url: return url+"/raw/master/README.md" + elif "gitlab" in url: + return url+"/-/raw/master/README.md" else: print("Debug: addonmanager_utilities.get_readme_url: Unknown git host:", url) return None From f9f08f8805fde18c9295adf3da3cf593950d2154 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 18 Sep 2021 22:39:19 -0500 Subject: [PATCH 02/21] [Mesh] Implement GRID* input in NASTRAN LGTM complained about two empty blocks in the Mesh NASTRAN reader: those blocks related to the code skipping the input of the high-precision GRID element. This commit adds support for that element. --- src/Mod/Mesh/App/Core/MeshIO.cpp | 75 ++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/src/Mod/Mesh/App/Core/MeshIO.cpp b/src/Mod/Mesh/App/Core/MeshIO.cpp index 7a18de2d54..f0a1e0b7e7 100644 --- a/src/Mod/Mesh/App/Core/MeshIO.cpp +++ b/src/Mod/Mesh/App/Core/MeshIO.cpp @@ -49,6 +49,8 @@ #include #include #include +#include +#include using namespace MeshCore; @@ -1656,31 +1658,88 @@ bool MeshInput::LoadNastran (std::istream &rstrIn) while (std::getline(rstrIn, line)) { upper(ltrim(line)); - if (line.find("GRID*") == 0) { + if (line.empty()) { + // Skip all the following tests } - else if (line.find('*') == 0) { + else if (line.rfind("GRID*", 0) == 0) { + // This element is the 16-digit-precision GRID element, which occupies two lines of the card. Note that + // FreeCAD discards the extra precision, downcasting to an four-byte float. + // + // The two lines are: + // 1 8 24 40 56 + // GRID* Index(16) Blank(16) x(16) y(at least one) + // * z(at least one) + // + // The first character is typically the sign, and may be omitted for positive numbers, + // so it is possible for a field to begin with a blank. Trailing zeros may be omitted, so + // a field may also end with blanks. No space or other delimiter is required between + // the numbers. The following is a valid NASTRAN GRID* element: + // + // GRID* 1 0.1234567890120. + // * 1. + // + if (line.length() < 8 + 16 + 16 + 16 + 1) // Element type(8), index(16), empty(16), x(16), y(>=1) + continue; + auto indexView = std::string_view(&line[8], 16); + auto blankView = std::string_view(&line[8+16], 16); + auto xView = std::string_view(&line[8+16+16], 16); + auto yView = std::string_view(&line[8+16+16+16]); + + std::string line2; + std::getline(rstrIn, line2); + if ((!line2.empty() && line2[0] != '*') || + line2.length() < 9) + continue; // File format error: second line is not a continuation line + auto zView = std::string_view(&line2[8]); + + // We have to strip off any whitespace (technically really just any *trailing* whitespace): + auto indexString = boost::trim_copy(std::string(indexView)); + auto xString = boost::trim_copy(std::string(xView)); + auto yString = boost::trim_copy(std::string(yView)); + auto zString = boost::trim_copy(std::string(zView)); + + auto converter = boost::cnv::spirit(); + auto indexCheck = boost::convert(indexString, converter); + if (!indexCheck.is_initialized()) + // File format error: index couldn't be converted to an integer + continue; + index = indexCheck.get(); + + // Get the high-precision versions first + auto x = boost::convert(xString, converter); + auto y = boost::convert(yString, converter); + auto z = boost::convert(zString, converter); + + if (!x.is_initialized() || !y.is_initialized() || !z.is_initialized()) + // File format error: x, y or z could not be converted + continue; + + // Now drop precision: + mNode[index].x = (float)x.get(); + mNode[index].y = (float)y.get(); + mNode[index].z = (float)z.get(); } - // insert the read-in vertex into a map to preserve the order - else if (line.find("GRID") == 0) { + else if (line.rfind("GRID", 0) == 0) { if (boost::regex_match(line.c_str(), what, rx_p)) { + // insert the read-in vertex into a map to preserve the order index = std::atol(what[1].first)-1; mNode[index].x = (float)std::atof(what[2].first); mNode[index].y = (float)std::atof(what[5].first); mNode[index].z = (float)std::atof(what[8].first); } } - // insert the read-in triangle into a map to preserve the order - else if (line.find("CTRIA3 ") == 0) { + else if (line.rfind("CTRIA3 ", 0) == 0) { if (boost::regex_match(line.c_str(), what, rx_t)) { + // insert the read-in triangle into a map to preserve the order index = std::atol(what[1].first)-1; mTria[index].iV[0] = std::atol(what[3].first)-1; mTria[index].iV[1] = std::atol(what[4].first)-1; mTria[index].iV[2] = std::atol(what[5].first)-1; } } - // insert the read-in quadrangle into a map to preserve the order - else if (line.find("CQUAD4") == 0) { + else if (line.rfind("CQUAD4", 0) == 0) { if (boost::regex_match(line.c_str(), what, rx_q)) { + // insert the read-in quadrangle into a map to preserve the order index = std::atol(what[1].first)-1; mQuad[index].iV[0] = std::atol(what[3].first)-1; mQuad[index].iV[1] = std::atol(what[4].first)-1; From 26dfd72143ed7d3dc3975c2836229958f98ed1e5 Mon Sep 17 00:00:00 2001 From: Przemo Firszt Date: Sun, 4 Jul 2021 19:11:19 +0100 Subject: [PATCH 03/21] [gitlab-ci] gitlab CI on docker initial commit --- ci/.gitlab-ci.yml | 56 ++++++++++++++++++++++ ci/Dockerfile | 118 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 ci/.gitlab-ci.yml create mode 100644 ci/Dockerfile diff --git a/ci/.gitlab-ci.yml b/ci/.gitlab-ci.yml new file mode 100644 index 0000000000..edb7bd10b9 --- /dev/null +++ b/ci/.gitlab-ci.yml @@ -0,0 +1,56 @@ +# This file is a template, and might need editing before it works on your project. +# To contribute improvements to CI/CD templates, please follow the Development guide at: +# https://docs.gitlab.com/ee/development/cicd/templates.html +# This specific template is located at: +# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml + +# This is a sample GitLab CI/CD configuration file that should run without any modifications. +# It demonstrates a basic 3 stage CI/CD pipeline. Instead of real tests or scripts, +# it uses echo commands to simulate the pipeline execution. +# +# A pipeline is composed of independent jobs that run scripts, grouped into stages. +# Stages run in sequential order, but jobs within stages run in parallel. +# +# For more information, see: https://docs.gitlab.com/ee/ci/yaml/README.html#stages + +# this image is on dockerhub. Dockerfile is here: https://gitlab.com/PrzemoF/FreeCAD/-/blob/gitlab-v1/ci/Dockerfile +image: freecadci/runner + +stages: # List of stages for jobs, and their order of execution + - build + - test + +before_script: + - apt-get update -yqq + # CCache Config + - mkdir -p ccache + - export CCACHE_BASEDIR=${PWD} + - export CCACHE_DIR=${PWD}/ccache + +cache: + paths: + - ccache/ + +build-job: # This job runs in the build stage, which runs first. + stage: build + + script: + - echo "Compiling the code..." + - mkdir build + - cd build + - ccache cmake ../ + - ccache cmake --build ./ -j$(nproc) + - echo "Compile complete." + + artifacts: + paths: + - build/ + +test-job: # This job runs in the test stage. + stage: test # It only starts when the job in the build stage completes successfully. + script: + - echo "Running unit tests... " + - cd build/bin/ + # Testing currently doesn't work due to problems with libraries ot being visible by the binary. + - ./FreeCADCmd -t 0 + diff --git a/ci/Dockerfile b/ci/Dockerfile new file mode 100644 index 0000000000..fb5196ed4d --- /dev/null +++ b/ci/Dockerfile @@ -0,0 +1,118 @@ +FROM ubuntu:20.04 +MAINTAINER Przemo Firszt +# This is the docker image definition used to build FreeCAD. It's currently accessible on: +# https://hub.docker.com/repository/docker/freecadci/runner +# on under name freecadci/runner when using docker + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update -y +RUN apt-get update -y && apt-get install -y gnupg2 +RUN echo "deb http://ppa.launchpad.net/freecad-maintainers/freecad-daily/ubuntu focal main" >> /etc/apt/sources.list.d/freecad-daily.list +RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 83193AA3B52FF6FCF10A1BBF005EAE8119BB5BCA +RUN apt-get update -y + +# those 3 are for debugging purposes only. Not required to build FreeCAD +RUN apt-get install -y \ + vim \ + nano \ + bash + +# Main set of FreeCAD dependencies. To be verified. +RUN apt-get install -y \ + ccache \ + cmake \ + debhelper \ + dh-exec \ + dh-python \ + doxygen \ + git \ + graphviz \ + libboost-date-time-dev \ + libboost-dev \ + libboost-filesystem-dev \ + libboost-filesystem1.71-dev \ + libboost-graph-dev \ + libboost-iostreams-dev \ + libboost-program-options-dev \ + libboost-program-options1.71-dev \ + libboost-python1.71-dev \ + libboost-regex-dev \ + libboost-regex1.71-dev \ + libboost-serialization-dev \ + libboost-system1.71-dev \ + libboost-thread-dev \ + libboost-thread1.71-dev \ + libboost1.71-dev \ + libcoin-dev \ + libdouble-conversion-dev \ + libeigen3-dev \ + libglew-dev \ + libgts-bin \ + libgts-dev \ + libkdtree++-dev \ + liblz4-dev \ + libmedc-dev \ + libmetis-dev \ + libnglib-dev \ + libocct-data-exchange-dev \ + libocct-ocaf-dev \ + libocct-visualization-dev \ + libopencv-dev \ + libproj-dev \ + libpyside2-dev \ + libqt5opengl5 \ + libqt5opengl5-dev \ + libqt5svg5-dev \ + libqt5webkit5 \ + libqt5webkit5-dev \ + libqt5x11extras5-dev \ + libqt5xmlpatterns5-dev \ + libshiboken2-dev \ + libspnav-dev \ + libvtk7-dev \ + libvtk7.1p \ + libvtk7.1p-qt \ + libx11-dev \ + libxerces-c-dev \ + libzipios++-dev \ + lsb-release \ + nastran \ + netgen \ + netgen-headers \ + occt-draw \ + pybind11-dev \ + pyqt5-dev-tools \ + pyside2-tools \ + python3-dev \ + python3-matplotlib \ + python3-pivy \ + python3-ply \ + python3-pyqt5 \ + python3-pyside2.* \ + python3-pyside2.qtcore \ + python3-pyside2.qtgui \ + python3-pyside2.qtsvg \ + python3-pyside2.qtuitools \ + python3-pyside2.qtwidgets \ + python3-pyside2.qtxml \ + python3-requests \ + python3-yaml \ + qt5-default \ + qt5-qmake \ + qtbase5-dev \ + qttools5-dev \ + qtwebengine5-dev \ + swig + +RUN apt-get update -y --fix-missing + +# Clean +RUN apt-get clean \ + && rm /var/lib/apt/lists/* \ + /usr/share/doc/* \ + /usr/share/locale/* \ + /usr/share/man/* \ + /usr/share/info/* -fR + + From 1d8b334e88c4481b038f3dfb427576003d227291 Mon Sep 17 00:00:00 2001 From: Przemo Firszt Date: Mon, 26 Jul 2021 23:17:55 +0100 Subject: [PATCH 04/21] [UnitTest] Replace deg symbol with deg unittest cannot handle the symbol on some systems. deg is a safe solution. Signed-off-by: Przemo Firszt --- src/Mod/Path/PathTests/TestPathDeburr.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathTests/TestPathDeburr.py b/src/Mod/Path/PathTests/TestPathDeburr.py index 2dcf446018..6d103e7ab0 100644 --- a/src/Mod/Path/PathTests/TestPathDeburr.py +++ b/src/Mod/Path/PathTests/TestPathDeburr.py @@ -52,7 +52,7 @@ class TestPathDeburr(PathTestUtils.PathTestBase): self.assertFalse(info) def test01(self): - '''Verify chamfer depth and offset for a 90° v-bit.''' + '''Verify chamfer depth and offset for a 90 deg v-bit.''' tool = Path.Tool() tool.FlatRadius = 0 tool.CuttingEdgeAngle = 90 @@ -68,7 +68,7 @@ class TestPathDeburr(PathTestUtils.PathTestBase): self.assertFalse(info) def test02(self): - '''Verify chamfer depth and offset for a 90° v-bit with non 0 flat radius.''' + '''Verify chamfer depth and offset for a 90 deg v-bit with non 0 flat radius.''' tool = Path.Tool() tool.FlatRadius = 0.3 tool.CuttingEdgeAngle = 90 @@ -84,7 +84,7 @@ class TestPathDeburr(PathTestUtils.PathTestBase): self.assertFalse(info) def test03(self): - '''Verify chamfer depth and offset for a 60° v-bit with non 0 flat radius.''' + '''Verify chamfer depth and offset for a 60 deg v-bit with non 0 flat radius.''' tool = Path.Tool() tool.FlatRadius = 10 tool.CuttingEdgeAngle = 60 From c942d950a9e9dc47538c1d993516784d2f13d6ce Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Tue, 21 Sep 2021 15:45:32 +0200 Subject: [PATCH 05/21] Web: Extended openBrowserHTML to allow custom icon --- src/Mod/Web/Gui/AppWebGui.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Mod/Web/Gui/AppWebGui.cpp b/src/Mod/Web/Gui/AppWebGui.cpp index c665318dff..a82508b93d 100644 --- a/src/Mod/Web/Gui/AppWebGui.cpp +++ b/src/Mod/Web/Gui/AppWebGui.cpp @@ -27,6 +27,7 @@ # include # include # include +# include #endif #include @@ -63,7 +64,7 @@ public: add_varargs_method("openBrowserWindow",&Module::openBrowserWindow ); add_varargs_method("open",&Module::openBrowser, - "open(string)\n" + "open(htmlcode,baseurl,[title,iconpath])\n" "Load a local (X)HTML file." ); add_varargs_method("insert",&Module::openBrowser, @@ -99,8 +100,9 @@ private: { const char* HtmlCode; const char* BaseUrl; + const char* IconPath; char* TabName = nullptr; - if (! PyArg_ParseTuple(args.ptr(), "ss|et", &HtmlCode, &BaseUrl, "utf-8", &TabName)) + if (! PyArg_ParseTuple(args.ptr(), "ss|ets", &HtmlCode, &BaseUrl, "utf-8", &TabName, &IconPath)) throw Py::Exception(); std::string EncodedName = "Browser"; @@ -114,6 +116,8 @@ private: pcBrowserView->resize(400, 300); pcBrowserView->setHtml(QString::fromUtf8(HtmlCode),QUrl(QString::fromLatin1(BaseUrl))); pcBrowserView->setWindowTitle(QString::fromUtf8(EncodedName.c_str())); + if (IconPath) + pcBrowserView->setWindowIcon(QIcon(QString::fromUtf8(IconPath))); Gui::getMainWindow()->addWindow(pcBrowserView); if (!Gui::getMainWindow()->activeWindow()) Gui::getMainWindow()->setActiveWindow(pcBrowserView); From 6a9b27df1e6230f18ec07ff7af474e947c726da6 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 21 Sep 2021 11:16:57 -0500 Subject: [PATCH 06/21] Tools: Corrected arguments to git.extractInfo --- src/Tools/WinVersion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/WinVersion.py b/src/Tools/WinVersion.py index eb02f62229..0e15568759 100644 --- a/src/Tools/WinVersion.py +++ b/src/Tools/WinVersion.py @@ -26,7 +26,7 @@ def main(): output = a git = SubWCRev.GitControl() - if(git.extractInfo(input)): + if(git.extractInfo(input, "")): print(git.hash) print(git.branch) print(git.rev[0:4]) From f7bdd2c5e857e59e3d1bf8f4da1a533904a67b51 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Tue, 21 Sep 2021 18:23:59 +0200 Subject: [PATCH 07/21] CI gitlab: update readme --- ci/.gitlab-ci.yml | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/ci/.gitlab-ci.yml b/ci/.gitlab-ci.yml index edb7bd10b9..92200a8d8e 100644 --- a/ci/.gitlab-ci.yml +++ b/ci/.gitlab-ci.yml @@ -1,17 +1,4 @@ -# This file is a template, and might need editing before it works on your project. -# To contribute improvements to CI/CD templates, please follow the Development guide at: -# https://docs.gitlab.com/ee/development/cicd/templates.html -# This specific template is located at: -# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml - -# This is a sample GitLab CI/CD configuration file that should run without any modifications. -# It demonstrates a basic 3 stage CI/CD pipeline. Instead of real tests or scripts, -# it uses echo commands to simulate the pipeline execution. -# -# A pipeline is composed of independent jobs that run scripts, grouped into stages. -# Stages run in sequential order, but jobs within stages run in parallel. -# -# For more information, see: https://docs.gitlab.com/ee/ci/yaml/README.html#stages +# gitlab CI config file # this image is on dockerhub. Dockerfile is here: https://gitlab.com/PrzemoF/FreeCAD/-/blob/gitlab-v1/ci/Dockerfile image: freecadci/runner From 4f2d65a178929779dd3b90e5999a14399e1a1018 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 21 Sep 2021 12:56:34 -0500 Subject: [PATCH 08/21] Web: Fix MSVC compile error in BrowserView --- src/Mod/Web/Gui/BrowserView.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Mod/Web/Gui/BrowserView.h b/src/Mod/Web/Gui/BrowserView.h index 67efacdceb..fae6c608b0 100644 --- a/src/Mod/Web/Gui/BrowserView.h +++ b/src/Mod/Web/Gui/BrowserView.h @@ -97,7 +97,9 @@ public: bool onMsg(const char* pMsg,const char** ppReturn); bool onHasMsg(const char* pMsg) const; - bool canClose(void); +#ifdef QTWEBENGINE + void setWindowIcon(const QIcon &icon); +#endif protected Q_SLOTS: void onLoadStarted(); @@ -107,7 +109,6 @@ protected Q_SLOTS: void urlFilter(const QUrl &url); #ifdef QTWEBENGINE void onDownloadRequested(QWebEngineDownloadItem *request); - void setWindowIcon(const QIcon &icon); void onLinkHovered(const QString& url); #else void onDownloadRequested(const QNetworkRequest& request); From 7b77ea52e7850b73cf0ae888a557b0dce7874e2f Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 21 Sep 2021 13:08:29 -0500 Subject: [PATCH 09/21] Web: Fix MSVC compile error in BrowserView (part 2) --- src/Mod/Web/Gui/BrowserView.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/Web/Gui/BrowserView.h b/src/Mod/Web/Gui/BrowserView.h index fae6c608b0..b36e505b32 100644 --- a/src/Mod/Web/Gui/BrowserView.h +++ b/src/Mod/Web/Gui/BrowserView.h @@ -97,6 +97,8 @@ public: bool onMsg(const char* pMsg,const char** ppReturn); bool onHasMsg(const char* pMsg) const; + bool canClose (void); + #ifdef QTWEBENGINE void setWindowIcon(const QIcon &icon); #endif From d008d44a34f8f87a935e6b45702a3685b331afa2 Mon Sep 17 00:00:00 2001 From: Przemo Firszt Date: Sat, 24 Jul 2021 21:00:20 +0100 Subject: [PATCH 10/21] [UnitTest] Fix UnitTests - use utf-8 On some systems there is a problem with utf-8 during testing. This commit is trying to address it. Tested only on ubuntu 20.04 Signed-off-by: Przemo Firszt --- src/Mod/Test/UnitTests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Mod/Test/UnitTests.py b/src/Mod/Test/UnitTests.py index 5b896da80f..6584ae6fc5 100644 --- a/src/Mod/Test/UnitTests.py +++ b/src/Mod/Test/UnitTests.py @@ -125,9 +125,10 @@ class UnitBasicCases(unittest.TestCase): try: q2 = FreeCAD.Units.Quantity(t[0]) if math.fabs(q1.Value - q2.Value) > 0.01: - print (q1, " : ", q2, " : ", t, " : ", i, " : ", val) + print (u" {} : {} : {} : {} : {}".format(q1,q2, t, i, val).encode("utf-8").strip()) except Exception as e: - print ("{}: {}".format(str(e), t[0])) + s = "{}: {}".format(e, t[0]) + print (u" ".join(e).encode("utf-8").strip()) def testVoltage(self): q1 = FreeCAD.Units.Quantity("1e20 V") From 70c5505a75ad545cb671eb73f29d5e1626aebf78 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Wed, 22 Sep 2021 08:06:47 +0200 Subject: [PATCH 11/21] Test: remove not needed u before py3 unicode string --- src/Mod/Test/UnitTests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Test/UnitTests.py b/src/Mod/Test/UnitTests.py index 6584ae6fc5..5c6ffea7e6 100644 --- a/src/Mod/Test/UnitTests.py +++ b/src/Mod/Test/UnitTests.py @@ -125,10 +125,10 @@ class UnitBasicCases(unittest.TestCase): try: q2 = FreeCAD.Units.Quantity(t[0]) if math.fabs(q1.Value - q2.Value) > 0.01: - print (u" {} : {} : {} : {} : {}".format(q1,q2, t, i, val).encode("utf-8").strip()) + print (" {} : {} : {} : {} : {}".format(q1, q2, t, i, val).encode("utf-8").strip()) except Exception as e: s = "{}: {}".format(e, t[0]) - print (u" ".join(e).encode("utf-8").strip()) + print (" ".join(e).encode("utf-8").strip()) def testVoltage(self): q1 = FreeCAD.Units.Quantity("1e20 V") From fc83fd1fc83353988fc8fb1f21152c8658573936 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 22 Sep 2021 14:09:07 +0200 Subject: [PATCH 12/21] port to MSYS2/clang: suppress 'undefined-var-template' warnings --- src/Mod/Mesh/App/CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Mod/Mesh/App/CMakeLists.txt b/src/Mod/Mesh/App/CMakeLists.txt index 187b2115de..2d661aea12 100644 --- a/src/Mod/Mesh/App/CMakeLists.txt +++ b/src/Mod/Mesh/App/CMakeLists.txt @@ -372,6 +372,15 @@ SET(Mesh_SRCS Segment.h ) +# Suppress -Wundefined-var-template +if (MINGW AND CMAKE_COMPILER_IS_CLANGXX) + unset(_flag_found CACHE) + check_cxx_compiler_flag("-Wno-undefined-var-template" _flag_found) + if (_flag_found) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-undefined-var-template") + endif() +endif() + if(FREECAD_USE_PCH) add_definitions(-D_PreComp_) GET_MSVC_PRECOMPILED_SOURCE("PreCompiled.cpp" PCH_SRCS ${Core_SRCS} ${Mesh_SRCS}) From 42437906272eb3aa664a8cf2105b578bfb847a2a Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 19 Sep 2021 18:45:10 +0200 Subject: [PATCH 13/21] Sketcher: Constraint Widget extended filter =========================================== - Remove "Normal" as it did exactly the same as "All". - Add "Geometric" to filter only Geometric (non datum) constraints - Add all individual constraint types to filter. As per request: https://forum.freecadweb.org/viewtopic.php?p=534176#p534176 --- .../Sketcher/Gui/TaskSketcherConstrains.cpp | 56 ++++++++--- src/Mod/Sketcher/Gui/TaskSketcherConstrains.h | 27 ++++++ .../Sketcher/Gui/TaskSketcherConstrains.ui | 97 ++++++++++++++++++- 3 files changed, 164 insertions(+), 16 deletions(-) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp index b55fba8077..2d1a44b447 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp @@ -912,45 +912,71 @@ void TaskSketcherConstrains::slotConstraintsChanged(void) ConstraintItem * it = static_cast(ui->listWidgetConstraints->item(i)); bool visible = true; - /* Filter - 0 <=> All - 1 <=> Normal - 2 <=> Datums - 3 <=> Named - 4 <=> Non-Driving - */ - - bool showNormal = (Filter < 2); - bool showDatums = (Filter < 3); - bool showNamed = (Filter == 3 && !(constraint->Name.empty())); - bool showNonDriving = (Filter == 4 && !constraint->isDriving); + bool showAll = (Filter == FilterValue::All); + bool showGeometric = (Filter == FilterValue::Geometric); + bool showDatums = (Filter == FilterValue::Datums); + bool showNamed = (Filter == FilterValue::Named && !(constraint->Name.empty())); + bool showNonDriving = (Filter == FilterValue::NonDriving && !constraint->isDriving); bool hideInternalAlignment = this->ui->filterInternalAlignment->isChecked(); switch(constraint->Type) { case Sketcher::Horizontal: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Horizontal); + break; case Sketcher::Vertical: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Vertical); + break; case Sketcher::Coincident: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Coincident); + break; case Sketcher::PointOnObject: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::PointOnObject); + break; case Sketcher::Parallel: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Parallel); + break; case Sketcher::Perpendicular: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Perpendicular); + break; case Sketcher::Tangent: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Tangent); + break; case Sketcher::Equal: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Equality); + break; case Sketcher::Symmetric: + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Symmetric); + break; case Sketcher::Block: - visible = showNormal || showNamed; + visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Block); break; case Sketcher::Distance: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::Distance); + break; case Sketcher::DistanceX: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::HorizontalDistance); + break; case Sketcher::DistanceY: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::VerticalDistance); + break; case Sketcher::Radius: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::Radius); + break; case Sketcher::Weight: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::Weight); + break; case Sketcher::Diameter: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::Diameter); + break; case Sketcher::Angle: + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::Angle); + break; case Sketcher::SnellsLaw: - visible = (showDatums || showNamed || showNonDriving); + visible = ( showAll || showDatums || showNamed || showNonDriving) || (Filter == FilterValue::SnellsLaw); break; case Sketcher::InternalAlignment: - visible = ((showNormal || showNamed) && !hideInternalAlignment); + visible = (( showAll || showGeometric || showNamed || Filter == FilterValue::InternalAlignment) && + (!hideInternalAlignment || (Filter == FilterValue::InternalAlignment))); default: break; } diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h index 27864995d9..a0f304e352 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h @@ -71,6 +71,33 @@ class TaskSketcherConstrains : public Gui::TaskView::TaskBox, public Gui::Select { Q_OBJECT + enum FilterValue { + All = 0, + Geometric = 1, + Datums = 2, + Named = 3, + NonDriving = 4, + Horizontal = 5, + Vertical = 6, + Coincident = 7, + PointOnObject = 8, + Parallel = 9, + Perpendicular = 10, + Tangent = 11, + Equality = 12, + Symmetric = 13, + Block = 14, + Distance = 15, + HorizontalDistance = 16, + VerticalDistance = 17, + Radius = 18, + Weight = 19, + Diameter = 20, + Angle = 21, + SnellsLaw = 22, + InternalAlignment = 23 + }; + public: TaskSketcherConstrains(ViewProviderSketch *sketchView); ~TaskSketcherConstrains(); diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui index 8752bc40dd..ecfb596dd9 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui @@ -47,7 +47,7 @@ - Normal + Geometric @@ -65,6 +65,101 @@ Reference + + + Horizontal + + + + + Vertical + + + + + Coincident + + + + + Point on Object + + + + + Parallel + + + + + Perpendicular + + + + + Tangent + + + + + Equality + + + + + Symmetric + + + + + Block + + + + + Distance + + + + + Horizontal Distance + + + + + Vertical Distance + + + + + Radius + + + + + Weight + + + + + Diameter + + + + + Angle + + + + + Snell's Law + + + + + Internal Alignment + + From ff93cb9295d5868c44631211b2c4a9d091ce7abc Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Mon, 20 Sep 2021 19:46:49 +0200 Subject: [PATCH 14/21] Sketcher: Tracking of constraint visibility via contraint widget filter ======================================================================= Add new option so that the 3D view constraint visibility track the constraint widget filter selection. It maintains internally two mutually exclusive virtual spaces and the ability to select one as visible (the other remaining hiden). --- .../Sketcher/Gui/TaskSketcherConstrains.cpp | 157 ++++++++++++---- src/Mod/Sketcher/Gui/TaskSketcherConstrains.h | 2 + .../Sketcher/Gui/TaskSketcherConstrains.ui | 175 ++++++++++++++---- 3 files changed, 261 insertions(+), 73 deletions(-) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp index 2d1a44b447..76c6cc73b2 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp @@ -743,7 +743,13 @@ void TaskSketcherConstrains::onSelectionChanged(const Gui::SelectionChanges& msg void TaskSketcherConstrains::on_comboBoxFilter_currentIndexChanged(int) { - slotConstraintsChanged(); + // enforce constraint visibility + bool visibilityTracksFilter = ui->visualisationTrackingFilter->isChecked(); + + if(visibilityTracksFilter) + change3DViewVisibilityToTrackFilter(); // it will call slotConstraintChanged via update mechanism + else + slotConstraintsChanged(); } void TaskSketcherConstrains::on_filterInternalAlignment_stateChanged(int state) @@ -872,54 +878,85 @@ void TaskSketcherConstrains::on_listWidgetConstraints_itemChanged(QListWidgetIte inEditMode = false; } -void TaskSketcherConstrains::slotConstraintsChanged(void) +void TaskSketcherConstrains::change3DViewVisibilityToTrackFilter() { assert(sketchView); // Build up ListView with the constraints const Sketcher::SketchObject * sketch = sketchView->getSketchObject(); const std::vector< Sketcher::Constraint * > &vals = sketch->Constraints.getValues(); - /* Update constraint number and virtual space check status */ - for (int i = 0; i < ui->listWidgetConstraints->count(); ++i) { - ConstraintItem * it = dynamic_cast(ui->listWidgetConstraints->item(i)); + bool doCommit = false; - assert(it != 0); + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Update constraint's virtual space")); - it->ConstraintNbr = i; - it->value = QVariant(); - } - - /* Remove entries, if any */ - for (std::size_t i = ui->listWidgetConstraints->count(); i > vals.size(); --i) - delete ui->listWidgetConstraints->takeItem(i - 1); - - /* Add new entries, if any */ - for (std::size_t i = ui->listWidgetConstraints->count(); i < vals.size(); ++i) - ui->listWidgetConstraints->addItem(new ConstraintItem(sketch, sketchView, i)); - - /* Update the states */ - ui->listWidgetConstraints->blockSignals(true); - for (int i = 0; i < ui->listWidgetConstraints->count(); ++i) { - ConstraintItem * it = static_cast(ui->listWidgetConstraints->item(i)); - it->updateVirtualSpaceStatus(); - } - ui->listWidgetConstraints->blockSignals(false); - - /* Update filtering */ - int Filter = ui->comboBoxFilter->currentIndex(); for(std::size_t i = 0; i < vals.size(); ++i) { - const Sketcher::Constraint * constraint = vals[i]; ConstraintItem * it = static_cast(ui->listWidgetConstraints->item(i)); - bool visible = true; - bool showAll = (Filter == FilterValue::All); - bool showGeometric = (Filter == FilterValue::Geometric); - bool showDatums = (Filter == FilterValue::Datums); - bool showNamed = (Filter == FilterValue::Named && !(constraint->Name.empty())); - bool showNonDriving = (Filter == FilterValue::NonDriving && !constraint->isDriving); - bool hideInternalAlignment = this->ui->filterInternalAlignment->isChecked(); + bool visible = !isConstraintFiltered(it); - switch(constraint->Type) { + // If the constraint is filteredout and it was previously shown in 3D view + if( !visible && it->isInVirtualSpace() == sketchView->getIsShownVirtualSpace()) { + try { + Gui::cmdAppObjectArgs(sketch, "setVirtualSpace(%d, %s)", + it->ConstraintNbr, + "True"); + + doCommit = true; + + } + catch (const Base::Exception & e) { + Gui::Command::abortCommand(); + + QMessageBox::critical(Gui::MainWindow::getInstance(), tr("Error"), + QString::fromLatin1("Impossible to update visibility tracking"), QMessageBox::Ok, QMessageBox::Ok); + + return; + } + } + else if( visible && it->isInVirtualSpace() != sketchView->getIsShownVirtualSpace() ) { + try { + Gui::cmdAppObjectArgs(sketch, "setVirtualSpace(%d, %s)", + it->ConstraintNbr, + "False"); + + doCommit = true; + + } + catch (const Base::Exception & e) { + Gui::Command::abortCommand(); + + QMessageBox::critical(Gui::MainWindow::getInstance(), tr("Error"), + QString::fromLatin1("Impossible to update visibility tracking"), QMessageBox::Ok, QMessageBox::Ok); + + return; + } + } + } + + if(doCommit) + Gui::Command::commitCommand(); + +} + +bool TaskSketcherConstrains::isConstraintFiltered(QListWidgetItem * item) +{ + assert(sketchView); + const Sketcher::SketchObject * sketch = sketchView->getSketchObject(); + const std::vector< Sketcher::Constraint * > &vals = sketch->Constraints.getValues(); + ConstraintItem * it = static_cast(item); + const Sketcher::Constraint * constraint = vals[it->ConstraintNbr]; + + int Filter = ui->comboBoxFilter->currentIndex(); + bool hideInternalAlignment = this->ui->filterInternalAlignment->isChecked(); + + bool visible = true; + bool showAll = (Filter == FilterValue::All); + bool showGeometric = (Filter == FilterValue::Geometric); + bool showDatums = (Filter == FilterValue::Datums); + bool showNamed = (Filter == FilterValue::Named && !(constraint->Name.empty())); + bool showNonDriving = (Filter == FilterValue::NonDriving && !constraint->isDriving); + + switch(constraint->Type) { case Sketcher::Horizontal: visible = showAll || showGeometric || showNamed || (Filter == FilterValue::Horizontal); break; @@ -979,7 +1016,50 @@ void TaskSketcherConstrains::slotConstraintsChanged(void) (!hideInternalAlignment || (Filter == FilterValue::InternalAlignment))); default: break; - } + } + + return !visible; +} + +void TaskSketcherConstrains::slotConstraintsChanged(void) +{ + assert(sketchView); + // Build up ListView with the constraints + const Sketcher::SketchObject * sketch = sketchView->getSketchObject(); + const std::vector< Sketcher::Constraint * > &vals = sketch->Constraints.getValues(); + + /* Update constraint number and virtual space check status */ + for (int i = 0; i < ui->listWidgetConstraints->count(); ++i) { + ConstraintItem * it = dynamic_cast(ui->listWidgetConstraints->item(i)); + + assert(it != 0); + + it->ConstraintNbr = i; + it->value = QVariant(); + } + + /* Remove entries, if any */ + for (std::size_t i = ui->listWidgetConstraints->count(); i > vals.size(); --i) + delete ui->listWidgetConstraints->takeItem(i - 1); + + /* Add new entries, if any */ + for (std::size_t i = ui->listWidgetConstraints->count(); i < vals.size(); ++i) + ui->listWidgetConstraints->addItem(new ConstraintItem(sketch, sketchView, i)); + + /* Update the states */ + ui->listWidgetConstraints->blockSignals(true); + for (int i = 0; i < ui->listWidgetConstraints->count(); ++i) { + ConstraintItem * it = static_cast(ui->listWidgetConstraints->item(i)); + it->updateVirtualSpaceStatus(); + } + ui->listWidgetConstraints->blockSignals(false); + + /* Update filtering */ + for(std::size_t i = 0; i < vals.size(); ++i) { + const Sketcher::Constraint * constraint = vals[i]; + ConstraintItem * it = static_cast(ui->listWidgetConstraints->item(i)); + + bool visible = !isConstraintFiltered(it); // block signals as there is no need to invoke the // on_listWidgetConstraints_itemChanged() slot in @@ -990,6 +1070,7 @@ void TaskSketcherConstrains::slotConstraintsChanged(void) it->setHidden(!visible); it->setData(Qt::EditRole, Base::Tools::fromStdString(constraint->Name)); model->blockSignals(block); + } } diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h index a0f304e352..efcafbc9fc 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h @@ -107,6 +107,8 @@ public: private: void slotConstraintsChanged(void); + bool isConstraintFiltered(QListWidgetItem * item); + void change3DViewVisibilityToTrackFilter(); public Q_SLOTS: void on_comboBoxFilter_currentIndexChanged(int); diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui index ecfb596dd9..72111ff806 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui @@ -6,8 +6,8 @@ 0 0 - 212 - 288 + 234 + 388 @@ -19,7 +19,7 @@ 16777215 - 288 + 388 @@ -165,41 +165,146 @@ - + + + + 0 + 0 + + + + + 0 + 95 + + - Internal alignments will be hidden + - - Hide internal alignment - - - true - - - HideInternalAlignment - - - Mod/Sketcher - - - - - - - Extended information will be added to the list - - - Extended information - - - false - - - ExtendedConstraintInformation - - - Mod/Sketcher + + 1 + + + + 0 + 0 + + + + + 0 + 0 + + + + Controls widget list behaviour + + + List control + + + + + 0 + 30 + 189 + 36 + + + + + 0 + 0 + + + + Extended information will be added to the list + + + Extended information + + + false + + + ExtendedConstraintInformation + + + Mod/Sketcher + + + + + + 0 + 0 + 189 + 36 + + + + + 0 + 0 + + + + Internal alignments will be hidden + + + Hide internal alignment + + + true + + + HideInternalAlignment + + + Mod/Sketcher + + + + + + Controls visualisation in the 3D view + + + Visualisation + + + + + 0 + 0 + 189 + 36 + + + + + 0 + 0 + + + + Constraint visualisation tracks filter selection so that filtered out constraints are hidden + + + Track filter selection + + + false + + + VisualisationTrackingFilter + + + Mod/Sketcher + + + From 3e644ed2af8357b42d91b634bd92f3cda2ee4bf1 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Tue, 21 Sep 2021 13:45:52 +0200 Subject: [PATCH 15/21] Sketcher: Constraint hiding/showing not working properly for combined constraint icons ====================================================================================== Hiden constraint icons should not be grouped into combined icons. This may also improve selection of combined (stacked) icons. fixes #4590 --- src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 55 ++++++++++++--------- src/Mod/Sketcher/Gui/ViewProviderSketch.h | 2 + 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index cfeb1c0e59..3459526a49 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -3455,6 +3455,7 @@ void ViewProviderSketch::drawConstraintIcons() thisIcon.position = absPos; thisIcon.destination = coinIconPtr; thisIcon.infoPtr = infoPtr; + thisIcon.visible = (*it)->isInVirtualSpace == getIsShownVirtualSpace(); if ((*it)->Type==Symmetric) { Base::Vector3d startingpoint = getSketchObject()->getPoint((*it)->First,(*it)->FirstPos); @@ -3539,36 +3540,44 @@ void ViewProviderSketch::combineConstraintIcons(IconQueue iconQueue) iconQueue.pop_back(); // we group only icons not being Symmetry icons, because we want those on the line - if(init.type != QString::fromLatin1("Constraint_Symmetric")){ + // and only icons that are visible + if(init.type != QString::fromLatin1("Constraint_Symmetric") && init.visible){ IconQueue::iterator i = iconQueue.begin(); + + while(i != iconQueue.end()) { - bool addedToGroup = false; + if((*i).visible) { + bool addedToGroup = false; - for(IconQueue::iterator j = thisGroup.begin(); - j != thisGroup.end(); ++j) { - float distSquared = pow(i->position[0]-j->position[0],2) + pow(i->position[1]-j->position[1],2); - if(distSquared <= maxDistSquared && (*i).type != QString::fromLatin1("Constraint_Symmetric")) { - // Found an icon in iconQueue that's close enough to - // a member of thisGroup, so move it into thisGroup - thisGroup.push_back(*i); - i = iconQueue.erase(i); - addedToGroup = true; - break; + for(IconQueue::iterator j = thisGroup.begin(); + j != thisGroup.end(); ++j) { + float distSquared = pow(i->position[0]-j->position[0],2) + pow(i->position[1]-j->position[1],2); + if(distSquared <= maxDistSquared && (*i).type != QString::fromLatin1("Constraint_Symmetric")) { + // Found an icon in iconQueue that's close enough to + // a member of thisGroup, so move it into thisGroup + thisGroup.push_back(*i); + i = iconQueue.erase(i); + addedToGroup = true; + break; + } } - } - if(addedToGroup) { - if(i == iconQueue.end()) - // We just got the last icon out of iconQueue - break; - else - // Start looking through the iconQueue again, in case - // we have an icon that's now close enough to thisGroup - i = iconQueue.begin(); - } else - ++i; + if(addedToGroup) { + if(i == iconQueue.end()) + // We just got the last icon out of iconQueue + break; + else + // Start looking through the iconQueue again, in case + // we have an icon that's now close enough to thisGroup + i = iconQueue.begin(); + } else + ++i; + } + else // if !visible we skip it + i++; } + } if(thisGroup.size() == 1) { diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.h b/src/Mod/Sketcher/Gui/ViewProviderSketch.h index 2486301a62..598ff154c9 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.h +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.h @@ -370,6 +370,8 @@ protected: /// Angle to rotate an icon double iconRotation; + + bool visible; }; /// Internal type used for drawing constraint icons From 6343836689933bc0cd283c8ad9bcfac482ca7c5b Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Tue, 21 Sep 2021 16:51:56 +0200 Subject: [PATCH 16/21] Sketcher: Show All and Hide All buttons on Constraint Widget --- .../Sketcher/Gui/TaskSketcherConstrains.cpp | 64 ++++++++ src/Mod/Sketcher/Gui/TaskSketcherConstrains.h | 3 + .../Sketcher/Gui/TaskSketcherConstrains.ui | 141 ++++++++++++------ 3 files changed, 165 insertions(+), 43 deletions(-) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp index 76c6cc73b2..4ebb8bdc97 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp @@ -679,6 +679,14 @@ TaskSketcherConstrains::TaskSketcherConstrains(ViewProviderSketch *sketchView) : ui->extendedInformation, SIGNAL(stateChanged(int)), this , SLOT (on_extendedInformation_stateChanged(int)) ); + QObject::connect( + ui->showAllButton, SIGNAL(clicked(bool)), + this , SLOT (on_showAllButton_clicked(bool)) + ); + QObject::connect( + ui->hideAllButton, SIGNAL(clicked(bool)), + this , SLOT (on_hideAllButton_clicked(bool)) + ); connectionConstraintsChanged = sketchView->signalConstraintsChanged.connect( boost::bind(&SketcherGui::TaskSketcherConstrains::slotConstraintsChanged, this)); @@ -698,6 +706,62 @@ TaskSketcherConstrains::~TaskSketcherConstrains() connectionConstraintsChanged.disconnect(); } +void TaskSketcherConstrains::changeFilteredVisibility(bool show) +{ + assert(sketchView); + const Sketcher::SketchObject * sketch = sketchView->getSketchObject(); + + bool doCommit = false; + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Update constraint's virtual space")); + + for(int i = 0; i < ui->listWidgetConstraints->count(); ++i) + { + QListWidgetItem* item = ui->listWidgetConstraints->item(i); + + if(!item->isHidden()) { // The item is shown in the filtered list + const ConstraintItem *it = dynamic_cast(item); + + if (!it) + continue; + + // must change state is shown and is to be hidden or hidden and must change state is shown + if((it->isInVirtualSpace() == sketchView->getIsShownVirtualSpace() && !show) || + (it->isInVirtualSpace() != sketchView->getIsShownVirtualSpace() && show)) { + + + try { + Gui::cmdAppObjectArgs(sketch, "setVirtualSpace(%d, %s)", + it->ConstraintNbr, + show?"False":"True"); + + doCommit = true; + } + catch (const Base::Exception & e) { + Gui::Command::abortCommand(); + + QMessageBox::critical(Gui::MainWindow::getInstance(), tr("Error"), + QString::fromLatin1("Impossible to update visibility tracking"), QMessageBox::Ok, QMessageBox::Ok); + + return; + } + } + } + } + + if(doCommit) + Gui::Command::commitCommand(); +} + +void TaskSketcherConstrains::on_showAllButton_clicked(bool) +{ + changeFilteredVisibility(true); +} +void TaskSketcherConstrains::on_hideAllButton_clicked(bool) +{ + changeFilteredVisibility(false); +} + void TaskSketcherConstrains::onSelectionChanged(const Gui::SelectionChanges& msg) { std::string temp; diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h index efcafbc9fc..e0f2fd6d5c 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h @@ -109,6 +109,7 @@ private: void slotConstraintsChanged(void); bool isConstraintFiltered(QListWidgetItem * item); void change3DViewVisibilityToTrackFilter(); + void changeFilteredVisibility(bool show); public Q_SLOTS: void on_comboBoxFilter_currentIndexChanged(int); @@ -120,6 +121,8 @@ public Q_SLOTS: void on_listWidgetConstraints_emitCenterSelectedItems(void); void on_filterInternalAlignment_stateChanged(int state); void on_extendedInformation_stateChanged(int state); + void on_showAllButton_clicked(bool); + void on_hideAllButton_clicked(bool); protected: void changeEvent(QEvent *e); diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui index 72111ff806..11bfdaf064 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.ui @@ -6,7 +6,7 @@ 0 0 - 234 + 299 388 @@ -167,7 +167,7 @@ - + 0 0 @@ -182,8 +182,102 @@ - 1 + 0 + + + + 0 + 0 + + + + View + + + + + 10 + 10 + 125 + 27 + + + + + 0 + 0 + + + + Shows all the constraints in the list + + + Show All + + + + + + 140 + 10 + 125 + 27 + + + + + 0 + 0 + + + + Hides all the constraints in the list + + + Hide All + + + + + + Controls visualisation in the 3D view + + + Automation + + + + + 0 + 0 + 189 + 36 + + + + + 0 + 0 + + + + Constraint visualisation tracks filter selection so that filtered out constraints are hidden + + + Track filter selection + + + false + + + VisualisationTrackingFilter + + + Mod/Sketcher + + + @@ -201,7 +295,7 @@ Controls widget list behaviour - List control + List @@ -266,45 +360,6 @@ - - - Controls visualisation in the 3D view - - - Visualisation - - - - - 0 - 0 - 189 - 36 - - - - - 0 - 0 - - - - Constraint visualisation tracks filter selection so that filtered out constraints are hidden - - - Track filter selection - - - false - - - VisualisationTrackingFilter - - - Mod/Sketcher - - - From f3a5217468af949fe541cb95e65d0e62f814fb1a Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Wed, 22 Sep 2021 14:49:04 +0200 Subject: [PATCH 17/21] Sketcher: Fix hide/show operations on Constraint Widget taking too long ======================================================================= Fixes delay reported here: https://forum.freecadweb.org/viewtopic.php?f=17&t=60569#p519685 --- .../Sketcher/Gui/TaskSketcherConstrains.cpp | 47 ++++++++++++++----- src/Mod/Sketcher/Gui/TaskSketcherConstrains.h | 11 ++++- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp index 4ebb8bdc97..2344a87757 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp @@ -541,20 +541,12 @@ void ConstraintView::updateActiveStatus() void ConstraintView::showConstraints() { - QList items = selectedItems(); - for (auto it : items) { - if (it->checkState() != Qt::Checked) - it->setCheckState(Qt::Checked); - } + Q_EMIT emitShowSelection3DVisibility(); } void ConstraintView::hideConstraints() { - QList items = selectedItems(); - for (auto it : items) { - if (it->checkState() != Qt::Unchecked) - it->setCheckState(Qt::Unchecked); - } + Q_EMIT emitHideSelection3DVisibility(); } void ConstraintView::modifyCurrentItem() @@ -687,6 +679,14 @@ TaskSketcherConstrains::TaskSketcherConstrains(ViewProviderSketch *sketchView) : ui->hideAllButton, SIGNAL(clicked(bool)), this , SLOT (on_hideAllButton_clicked(bool)) ); + QObject::connect( + ui->listWidgetConstraints, SIGNAL(emitHideSelection3DVisibility()), + this , SLOT (on_listWidgetConstraints_emitHideSelection3DVisibility()) + ); + QObject::connect( + ui->listWidgetConstraints, SIGNAL(emitShowSelection3DVisibility()), + this , SLOT (on_listWidgetConstraints_emitShowSelection3DVisibility()) + ); connectionConstraintsChanged = sketchView->signalConstraintsChanged.connect( boost::bind(&SketcherGui::TaskSketcherConstrains::slotConstraintsChanged, this)); @@ -706,20 +706,32 @@ TaskSketcherConstrains::~TaskSketcherConstrains() connectionConstraintsChanged.disconnect(); } -void TaskSketcherConstrains::changeFilteredVisibility(bool show) +void TaskSketcherConstrains::changeFilteredVisibility(bool show, ActionTarget target) { assert(sketchView); const Sketcher::SketchObject * sketch = sketchView->getSketchObject(); bool doCommit = false; + auto selecteditems = ui->listWidgetConstraints->selectedItems(); + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Update constraint's virtual space")); for(int i = 0; i < ui->listWidgetConstraints->count(); ++i) { QListWidgetItem* item = ui->listWidgetConstraints->item(i); - if(!item->isHidden()) { // The item is shown in the filtered list + bool processItem = false; + + if(target == ActionTarget::All) { + processItem = !item->isHidden(); + } + else if(target == ActionTarget::Selected) { + if(std::find(selecteditems.begin(), selecteditems.end(), item) != selecteditems.end()) + processItem = true; + } + + if(processItem) { // The item is shown in the filtered list const ConstraintItem *it = dynamic_cast(item); if (!it) @@ -757,11 +769,22 @@ void TaskSketcherConstrains::on_showAllButton_clicked(bool) { changeFilteredVisibility(true); } + void TaskSketcherConstrains::on_hideAllButton_clicked(bool) { changeFilteredVisibility(false); } +void TaskSketcherConstrains::on_listWidgetConstraints_emitHideSelection3DVisibility() +{ + changeFilteredVisibility(false, ActionTarget::Selected); +} + +void TaskSketcherConstrains::on_listWidgetConstraints_emitShowSelection3DVisibility() +{ + changeFilteredVisibility(true, ActionTarget::Selected); +} + void TaskSketcherConstrains::onSelectionChanged(const Gui::SelectionChanges& msg) { std::string temp; diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h index e0f2fd6d5c..059d29a789 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.h @@ -53,6 +53,8 @@ Q_SIGNALS: void onUpdateDrivingStatus(QListWidgetItem *item, bool status); void onUpdateActiveStatus(QListWidgetItem *item, bool status); void emitCenterSelectedItems(); + void emitHideSelection3DVisibility(); + void emitShowSelection3DVisibility(); protected Q_SLOTS: void modifyCurrentItem(); @@ -98,6 +100,11 @@ class TaskSketcherConstrains : public Gui::TaskView::TaskBox, public Gui::Select InternalAlignment = 23 }; + enum class ActionTarget { + All, + Selected + }; + public: TaskSketcherConstrains(ViewProviderSketch *sketchView); ~TaskSketcherConstrains(); @@ -109,7 +116,7 @@ private: void slotConstraintsChanged(void); bool isConstraintFiltered(QListWidgetItem * item); void change3DViewVisibilityToTrackFilter(); - void changeFilteredVisibility(bool show); + void changeFilteredVisibility(bool show, ActionTarget target = ActionTarget::All); public Q_SLOTS: void on_comboBoxFilter_currentIndexChanged(int); @@ -123,6 +130,8 @@ public Q_SLOTS: void on_extendedInformation_stateChanged(int state); void on_showAllButton_clicked(bool); void on_hideAllButton_clicked(bool); + void on_listWidgetConstraints_emitShowSelection3DVisibility(); + void on_listWidgetConstraints_emitHideSelection3DVisibility(); protected: void changeEvent(QEvent *e); From 0ec51a959adac08ff748be7d8af9bfeccc99ba1a Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 22 Sep 2021 19:30:58 +0200 Subject: [PATCH 18/21] Gui: remove QUiLoader from Qt4All.h to make sure it is included from a single file --- src/Gui/Qt4All.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Gui/Qt4All.h b/src/Gui/Qt4All.h index 3dca95bd68..256dd249cc 100644 --- a/src/Gui/Qt4All.h +++ b/src/Gui/Qt4All.h @@ -157,9 +157,6 @@ // QtSvg #include #include -// QtUiTools -#include -#include #include "qmath.h" #include From 30848cb635db5b7e9b6a718db3f1d9ac9017c6d5 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 22 Sep 2021 19:49:39 +0200 Subject: [PATCH 19/21] Gui: extend PythonWrapper: * add methods to wrap/unwrap QDir * add method to load QtUiTools module --- src/Gui/WidgetFactory.cpp | 46 +++++++++++++++++++++++++++++++++++++++ src/Gui/WidgetFactory.h | 7 ++++++ 2 files changed, 53 insertions(+) diff --git a/src/Gui/WidgetFactory.cpp b/src/Gui/WidgetFactory.cpp index 4ffeeff5db..d55c7b7c7c 100644 --- a/src/Gui/WidgetFactory.cpp +++ b/src/Gui/WidgetFactory.cpp @@ -422,8 +422,39 @@ QIcon *PythonWrapper::toQIcon(PyObject *pyobj) return 0; } +Py::Object PythonWrapper::fromQDir(const QDir& dir) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + const char* typeName = typeid(dir).name(); + PyObject* pyobj = Shiboken::Object::newObject(reinterpret_cast(getPyTypeObjectForTypeName()), + const_cast(&dir), false, false, typeName); + if (pyobj) + return Py::asObject(pyobj); +#endif + throw Py::RuntimeError("Failed to wrap icon"); +} + +QDir* PythonWrapper::toQDir(PyObject* pyobj) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + PyTypeObject* type = getPyTypeObjectForTypeName(); + if (type) { + if (Shiboken::Object::checkType(pyobj)) { + SbkObject* sbkobject = reinterpret_cast(pyobj); + void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); + return reinterpret_cast(cppobject); + } + } +#else + Q_UNUSED(pyobj); +#endif + return nullptr; +} + Py::Object PythonWrapper::fromQObject(QObject* object, const char* className) { + if (!object) + return Py::None(); #if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) // Access shiboken/PySide via C++ // @@ -563,6 +594,21 @@ bool PythonWrapper::loadWidgetsModule() return true; } +bool PythonWrapper::loadUiToolsModule() +{ +#if defined (HAVE_SHIBOKEN2) && defined(HAVE_PYSIDE2) + // QtUiTools + static PyTypeObject** SbkPySide2_QtUiToolsTypes = nullptr; + if (!SbkPySide2_QtUiToolsTypes) { + Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtUiTools")); + if (requiredModule.isNull()) + return false; + SbkPySide2_QtUiToolsTypes = Shiboken::Module::getTypes(requiredModule); + } +#endif + return true; +} + void PythonWrapper::createChildrenNameAttributes(PyObject* root, QObject* object) { Q_FOREACH (QObject* child, object->children()) { diff --git a/src/Gui/WidgetFactory.h b/src/Gui/WidgetFactory.h index d1d1ecf6eb..def0235037 100644 --- a/src/Gui/WidgetFactory.h +++ b/src/Gui/WidgetFactory.h @@ -35,6 +35,10 @@ #include "PropertyPage.h" #include +QT_BEGIN_NAMESPACE +class QDir; +QT_END_NAMESPACE + namespace Gui { namespace Dialog{ class PreferencePage; @@ -47,6 +51,7 @@ public: bool loadCoreModule(); bool loadGuiModule(); bool loadWidgetsModule(); + bool loadUiToolsModule(); bool toCString(const Py::Object&, std::string&); QObject* toQObject(const Py::Object&); @@ -60,6 +65,8 @@ public: */ Py::Object fromQIcon(const QIcon*); QIcon *toQIcon(PyObject *pyobj); + Py::Object fromQDir(const QDir&); + QDir* toQDir(PyObject* pyobj); static void createChildrenNameAttributes(PyObject* root, QObject* object); static void setParent(PyObject* pyWdg, QObject* parent); }; From b4e69f1b2df2eb7e740b93d1d328368716a9d13d Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 22 Sep 2021 20:31:01 +0200 Subject: [PATCH 20/21] Gui: move Ui loader classes to their own source files --- src/Gui/Application.cpp | 1 + src/Gui/CMakeLists.txt | 2 + src/Gui/PropertyPage.cpp | 2 +- src/Gui/TaskView/TaskDialogPython.cpp | 1 + src/Gui/UiLoader.cpp | 315 ++++++++++++++++++++++++++ src/Gui/UiLoader.h | 90 ++++++++ src/Gui/WidgetFactory.cpp | 284 +---------------------- src/Gui/WidgetFactory.h | 58 ----- 8 files changed, 411 insertions(+), 342 deletions(-) create mode 100644 src/Gui/UiLoader.cpp create mode 100644 src/Gui/UiLoader.h diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index c93b2b6b03..6af9e09da9 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -69,6 +69,7 @@ #include "DocumentPy.h" #include "View.h" #include "View3DPy.h" +#include "UiLoader.h" #include "WidgetFactory.h" #include "Command.h" #include "Macro.h" diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index e682cc825b..56a99366b4 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -1032,6 +1032,7 @@ SET(Widget_CPP_SRCS QuantitySpinBox.cpp SpinBox.cpp Splashscreen.cpp + UiLoader.cpp WidgetFactory.cpp Widgets.cpp Window.cpp @@ -1048,6 +1049,7 @@ SET(Widget_HPP_SRCS QuantitySpinBox_p.h SpinBox.h Splashscreen.h + UiLoader.h WidgetFactory.h Widgets.h Window.h diff --git a/src/Gui/PropertyPage.cpp b/src/Gui/PropertyPage.cpp index f95379dc55..303b730516 100644 --- a/src/Gui/PropertyPage.cpp +++ b/src/Gui/PropertyPage.cpp @@ -25,7 +25,7 @@ #include "PropertyPage.h" #include "PrefWidgets.h" -#include "WidgetFactory.h" +#include "UiLoader.h" #include using namespace Gui::Dialog; diff --git a/src/Gui/TaskView/TaskDialogPython.cpp b/src/Gui/TaskView/TaskDialogPython.cpp index 82da806206..9bbbbb2ff7 100644 --- a/src/Gui/TaskView/TaskDialogPython.cpp +++ b/src/Gui/TaskView/TaskDialogPython.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include diff --git a/src/Gui/UiLoader.cpp b/src/Gui/UiLoader.cpp new file mode 100644 index 0000000000..b2e8d036a8 --- /dev/null +++ b/src/Gui/UiLoader.cpp @@ -0,0 +1,315 @@ +/*************************************************************************** + * Copyright (c) 2021 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +#endif + +#include "UiLoader.h" +#include "WidgetFactory.h" +#include + +using namespace Gui; + + +PySideUicModule::PySideUicModule() + : Py::ExtensionModule("PySideUic") +{ + add_varargs_method("loadUiType",&PySideUicModule::loadUiType, + "PySide lacks the \"loadUiType\" command, so we have to convert the ui file to py code in-memory first\n" + "and then execute it in a special frame to retrieve the form_class."); + add_varargs_method("loadUi",&PySideUicModule::loadUi, + "Addition of \"loadUi\" to PySide."); + initialize("PySideUic helper module"); // register with Python +} + +Py::Object PySideUicModule::loadUiType(const Py::Tuple& args) +{ + Base::PyGILStateLocker lock; + PyObject* main = PyImport_AddModule("__main__"); + PyObject* dict = PyModule_GetDict(main); + Py::Dict d(PyDict_Copy(dict), true); + Py::String uiFile(args.getItem(0)); + std::string file = uiFile.as_string(); + std::replace(file.begin(), file.end(), '\\', '/'); + + QString cmd; + QTextStream str(&cmd); + // https://github.com/albop/dolo/blob/master/bin/load_ui.py + str << "import pyside2uic\n" + << "from PySide2 import QtCore, QtGui, QtWidgets\n" + << "import xml.etree.ElementTree as xml\n" + << "try:\n" + << " from cStringIO import StringIO\n" + << "except Exception:\n" + << " from io import StringIO\n" + << "\n" + << "uiFile = \"" << file.c_str() << "\"\n" + << "parsed = xml.parse(uiFile)\n" + << "widget_class = parsed.find('widget').get('class')\n" + << "form_class = parsed.find('class').text\n" + << "with open(uiFile, 'r') as f:\n" + << " o = StringIO()\n" + << " frame = {}\n" + << " pyside2uic.compileUi(f, o, indent=0)\n" + << " pyc = compile(o.getvalue(), '', 'exec')\n" + << " exec(pyc, frame)\n" + << " #Fetch the base_class and form class based on their type in the xml from designer\n" + << " form_class = frame['Ui_%s'%form_class]\n" + << " base_class = eval('QtWidgets.%s'%widget_class)\n"; + + PyObject* result = PyRun_String((const char*)cmd.toLatin1(), Py_file_input, d.ptr(), d.ptr()); + if (result) { + Py_DECREF(result); + if (d.hasKey("form_class") && d.hasKey("base_class")) { + Py::Tuple t(2); + t.setItem(0, d.getItem("form_class")); + t.setItem(1, d.getItem("base_class")); + return t; + } + } + else { + throw Py::Exception(); + } + + return Py::None(); +} + +Py::Object PySideUicModule::loadUi(const Py::Tuple& args) +{ + Base::PyGILStateLocker lock; + PyObject* main = PyImport_AddModule("__main__"); + PyObject* dict = PyModule_GetDict(main); + Py::Dict d(PyDict_Copy(dict), true); + d.setItem("uiFile_", args[0]); + if (args.size() > 1) + d.setItem("base_", args[1]); + else + d.setItem("base_", Py::None()); + + QString cmd; + QTextStream str(&cmd); +#if 0 + // https://github.com/lunaryorn/snippets/blob/master/qt4/designer/pyside_dynamic.py + str << "from PySide import QtCore, QtGui, QtUiTools\n" + << "import FreeCADGui" + << "\n" + << "class UiLoader(QtUiTools.QUiLoader):\n" + << " def __init__(self, baseinstance):\n" + << " QtUiTools.QUiLoader.__init__(self, baseinstance)\n" + << " self.baseinstance = baseinstance\n" + << " self.ui = FreeCADGui.UiLoader()\n" + << "\n" + << " def createWidget(self, class_name, parent=None, name=''):\n" + << " if parent is None and self.baseinstance:\n" + << " return self.baseinstance\n" + << " else:\n" + << " widget = self.ui.createWidget(class_name, parent, name)\n" + << " if not widget:\n" + << " widget = QtUiTools.QUiLoader.createWidget(self, class_name, parent, name)\n" + << " if self.baseinstance:\n" + << " setattr(self.baseinstance, name, widget)\n" + << " return widget\n" + << "\n" + << "loader = UiLoader(globals()[\"base_\"])\n" + << "widget = loader.load(globals()[\"uiFile_\"])\n" + << "\n"; +#else + str << "from PySide2 import QtCore, QtGui, QtWidgets\n" + << "import FreeCADGui" + << "\n" + << "loader = FreeCADGui.UiLoader()\n" + << "widget = loader.load(globals()[\"uiFile_\"])\n" + << "\n"; +#endif + + PyObject* result = PyRun_String((const char*)cmd.toLatin1(), Py_file_input, d.ptr(), d.ptr()); + if (result) { + Py_DECREF(result); + if (d.hasKey("widget")) { + return d.getItem("widget"); + } + } + else { + throw Py::Exception(); + } + + return Py::None(); +} + +// ---------------------------------------------------- + +UiLoader::UiLoader(QObject* parent) + : QUiLoader(parent) +{ + // do not use the plugins for additional widgets as we don't need them and + // the application may crash under Linux (tested on Ubuntu 7.04 & 7.10). + clearPluginPaths(); + this->cw = availableWidgets(); +} + +UiLoader::~UiLoader() +{ +} + +QWidget* UiLoader::createWidget(const QString & className, QWidget * parent, + const QString& name) +{ + if (this->cw.contains(className)) + return QUiLoader::createWidget(className, parent, name); + QWidget* w = 0; + if (WidgetFactory().CanProduce((const char*)className.toLatin1())) + w = WidgetFactory().createWidget((const char*)className.toLatin1(), parent); + if (w) w->setObjectName(name); + return w; +} + +// ---------------------------------------------------- + +PyObject *UiLoaderPy::PyMake(struct _typeobject * /*type*/, PyObject * args, PyObject * /*kwds*/) +{ + if (!PyArg_ParseTuple(args, "")) + return 0; + return new UiLoaderPy(); +} + +void UiLoaderPy::init_type() +{ + behaviors().name("UiLoader"); + behaviors().doc("UiLoader to create widgets"); + behaviors().set_tp_new(PyMake); + // you must have overwritten the virtual functions + behaviors().supportRepr(); + behaviors().supportGetattr(); + behaviors().supportSetattr(); + add_varargs_method("load",&UiLoaderPy::load,"load(string, QWidget parent=None) -> QWidget\n" + "load(QIODevice, QWidget parent=None) -> QWidget"); + add_varargs_method("createWidget",&UiLoaderPy::createWidget,"createWidget()"); +} + +UiLoaderPy::UiLoaderPy() +{ +} + +UiLoaderPy::~UiLoaderPy() +{ +} + +Py::Object UiLoaderPy::repr() +{ + std::string s; + std::ostringstream s_out; + s_out << "Ui loader"; + return Py::String(s_out.str()); +} + +Py::Object UiLoaderPy::load(const Py::Tuple& args) +{ + Gui::PythonWrapper wrap; + if (wrap.loadCoreModule()) { + std::string fn; + QFile file; + QIODevice* device = 0; + QWidget* parent = 0; + if (wrap.toCString(args[0], fn)) { + file.setFileName(QString::fromUtf8(fn.c_str())); + if (!file.open(QFile::ReadOnly)) + throw Py::RuntimeError("Cannot open file"); + device = &file; + } + else if (args[0].isString()) { + fn = (std::string)Py::String(args[0]); + file.setFileName(QString::fromUtf8(fn.c_str())); + if (!file.open(QFile::ReadOnly)) + throw Py::RuntimeError("Cannot open file"); + device = &file; + } + else { + QObject* obj = wrap.toQObject(args[0]); + device = qobject_cast(obj); + } + + if (args.size() > 1) { + QObject* obj = wrap.toQObject(args[1]); + parent = qobject_cast(obj); + } + + if (device) { + QWidget* widget = loader.load(device, parent); + if (widget) { + wrap.loadGuiModule(); + wrap.loadWidgetsModule(); + + const char* typeName = wrap.getWrapperName(widget); + Py::Object pyWdg = wrap.fromQWidget(widget, typeName); + wrap.createChildrenNameAttributes(*pyWdg, widget); + wrap.setParent(*pyWdg, parent); + return pyWdg; + } + } + else { + throw Py::TypeError("string or QIODevice expected"); + } + } + return Py::None(); +} + +Py::Object UiLoaderPy::createWidget(const Py::Tuple& args) +{ + Gui::PythonWrapper wrap; + + // 1st argument + Py::String str(args[0]); + std::string className; + className = str.as_std_string("utf-8"); + // 2nd argument + QWidget* parent = 0; + if (wrap.loadCoreModule() && args.size() > 1) { + QObject* object = wrap.toQObject(args[1]); + if (object) + parent = qobject_cast(object); + } + + // 3rd argument + std::string objectName; + if (args.size() > 2) { + Py::String str(args[2]); + objectName = str.as_std_string("utf-8"); + } + + QWidget* widget = loader.createWidget(QString::fromLatin1(className.c_str()), parent, + QString::fromLatin1(objectName.c_str())); + if (!widget) { + std::string err = "No such widget class '"; + err += className; + err += "'"; + throw Py::RuntimeError(err); + } + wrap.loadGuiModule(); + wrap.loadWidgetsModule(); + + const char* typeName = wrap.getWrapperName(widget); + return wrap.fromQWidget(widget, typeName); +} diff --git a/src/Gui/UiLoader.h b/src/Gui/UiLoader.h new file mode 100644 index 0000000000..e4567c0191 --- /dev/null +++ b/src/Gui/UiLoader.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (c) 2021 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef GUI_UILOADER_H +#define GUI_UILOADER_H + +#include +#include + + +namespace Gui { + +class PySideUicModule : public Py::ExtensionModule +{ + +public: + PySideUicModule(); + virtual ~PySideUicModule() {} + +private: + Py::Object loadUiType(const Py::Tuple& args); + Py::Object loadUi(const Py::Tuple& args); +}; + +/** + * The UiLoader class provides the abitlity to use the widget factory + * framework of FreeCAD within the framework provided by Qt. This class + * extends QUiLoader by the creation of FreeCAD specific widgets. + * @author Werner Mayer + */ +class UiLoader : public QUiLoader +{ +public: + UiLoader(QObject* parent=0); + virtual ~UiLoader(); + + /** + * Creates a widget of the type \a className with the parent \a parent. + * For more details see the documentation to QWidgetFactory. + */ + QWidget* createWidget(const QString & className, QWidget * parent=0, + const QString& name = QString()); +private: + QStringList cw; +}; + +// -------------------------------------------------------------------- + +class UiLoaderPy : public Py::PythonExtension +{ +public: + static void init_type(void); // announce properties and methods + + UiLoaderPy(); + ~UiLoaderPy(); + + Py::Object repr(); + Py::Object createWidget(const Py::Tuple&); + Py::Object load(const Py::Tuple&); + +private: + static PyObject *PyMake(struct _typeobject *, PyObject *, PyObject *); + +private: + UiLoader loader; +}; + +} // namespace Gui + +#endif // GUI_UILOADER_H diff --git a/src/Gui/WidgetFactory.cpp b/src/Gui/WidgetFactory.cpp index d55c7b7c7c..f55ebf4e36 100644 --- a/src/Gui/WidgetFactory.cpp +++ b/src/Gui/WidgetFactory.cpp @@ -25,7 +25,6 @@ #ifndef _PreComp_ # include # include -# include #endif #include @@ -123,6 +122,7 @@ PyTypeObject** SbkPySide2_QtWidgetsTypes=nullptr; #include "WidgetFactory.h" +#include "UiLoader.h" #include "PrefWidgets.h" #include "PropertyPage.h" @@ -788,288 +788,6 @@ QWidget* WidgetFactoryInst::createPrefWidget(const char* sName, QWidget* parent, // ---------------------------------------------------- -PySideUicModule::PySideUicModule() - : Py::ExtensionModule("PySideUic") -{ - add_varargs_method("loadUiType",&PySideUicModule::loadUiType, - "PySide lacks the \"loadUiType\" command, so we have to convert the ui file to py code in-memory first\n" - "and then execute it in a special frame to retrieve the form_class."); - add_varargs_method("loadUi",&PySideUicModule::loadUi, - "Addition of \"loadUi\" to PySide."); - initialize("PySideUic helper module"); // register with Python -} - -Py::Object PySideUicModule::loadUiType(const Py::Tuple& args) -{ - Base::PyGILStateLocker lock; - PyObject* main = PyImport_AddModule("__main__"); - PyObject* dict = PyModule_GetDict(main); - Py::Dict d(PyDict_Copy(dict), true); - Py::String uiFile(args.getItem(0)); - std::string file = uiFile.as_string(); - std::replace(file.begin(), file.end(), '\\', '/'); - - QString cmd; - QTextStream str(&cmd); - // https://github.com/albop/dolo/blob/master/bin/load_ui.py - str << "import pyside2uic\n" - << "from PySide2 import QtCore, QtGui, QtWidgets\n" - << "import xml.etree.ElementTree as xml\n" - << "try:\n" - << " from cStringIO import StringIO\n" - << "except Exception:\n" - << " from io import StringIO\n" - << "\n" - << "uiFile = \"" << file.c_str() << "\"\n" - << "parsed = xml.parse(uiFile)\n" - << "widget_class = parsed.find('widget').get('class')\n" - << "form_class = parsed.find('class').text\n" - << "with open(uiFile, 'r') as f:\n" - << " o = StringIO()\n" - << " frame = {}\n" - << " pyside2uic.compileUi(f, o, indent=0)\n" - << " pyc = compile(o.getvalue(), '', 'exec')\n" - << " exec(pyc, frame)\n" - << " #Fetch the base_class and form class based on their type in the xml from designer\n" - << " form_class = frame['Ui_%s'%form_class]\n" - << " base_class = eval('QtWidgets.%s'%widget_class)\n"; - - PyObject* result = PyRun_String((const char*)cmd.toLatin1(), Py_file_input, d.ptr(), d.ptr()); - if (result) { - Py_DECREF(result); - if (d.hasKey("form_class") && d.hasKey("base_class")) { - Py::Tuple t(2); - t.setItem(0, d.getItem("form_class")); - t.setItem(1, d.getItem("base_class")); - return t; - } - } - else { - throw Py::Exception(); - } - - return Py::None(); -} - -Py::Object PySideUicModule::loadUi(const Py::Tuple& args) -{ - Base::PyGILStateLocker lock; - PyObject* main = PyImport_AddModule("__main__"); - PyObject* dict = PyModule_GetDict(main); - Py::Dict d(PyDict_Copy(dict), true); - d.setItem("uiFile_", args[0]); - if (args.size() > 1) - d.setItem("base_", args[1]); - else - d.setItem("base_", Py::None()); - - QString cmd; - QTextStream str(&cmd); -#if 0 - // https://github.com/lunaryorn/snippets/blob/master/qt4/designer/pyside_dynamic.py - str << "from PySide import QtCore, QtGui, QtUiTools\n" - << "import FreeCADGui" - << "\n" - << "class UiLoader(QtUiTools.QUiLoader):\n" - << " def __init__(self, baseinstance):\n" - << " QtUiTools.QUiLoader.__init__(self, baseinstance)\n" - << " self.baseinstance = baseinstance\n" - << " self.ui = FreeCADGui.UiLoader()\n" - << "\n" - << " def createWidget(self, class_name, parent=None, name=''):\n" - << " if parent is None and self.baseinstance:\n" - << " return self.baseinstance\n" - << " else:\n" - << " widget = self.ui.createWidget(class_name, parent, name)\n" - << " if not widget:\n" - << " widget = QtUiTools.QUiLoader.createWidget(self, class_name, parent, name)\n" - << " if self.baseinstance:\n" - << " setattr(self.baseinstance, name, widget)\n" - << " return widget\n" - << "\n" - << "loader = UiLoader(globals()[\"base_\"])\n" - << "widget = loader.load(globals()[\"uiFile_\"])\n" - << "\n"; -#else - str << "from PySide2 import QtCore, QtGui, QtWidgets\n" - << "import FreeCADGui" - << "\n" - << "loader = FreeCADGui.UiLoader()\n" - << "widget = loader.load(globals()[\"uiFile_\"])\n" - << "\n"; -#endif - - PyObject* result = PyRun_String((const char*)cmd.toLatin1(), Py_file_input, d.ptr(), d.ptr()); - if (result) { - Py_DECREF(result); - if (d.hasKey("widget")) { - return d.getItem("widget"); - } - } - else { - throw Py::Exception(); - } - - return Py::None(); -} - -// ---------------------------------------------------- - -UiLoader::UiLoader(QObject* parent) - : QUiLoader(parent) -{ - // do not use the plugins for additional widgets as we don't need them and - // the application may crash under Linux (tested on Ubuntu 7.04 & 7.10). - clearPluginPaths(); - this->cw = availableWidgets(); -} - -UiLoader::~UiLoader() -{ -} - -QWidget* UiLoader::createWidget(const QString & className, QWidget * parent, - const QString& name) -{ - if (this->cw.contains(className)) - return QUiLoader::createWidget(className, parent, name); - QWidget* w = 0; - if (WidgetFactory().CanProduce((const char*)className.toLatin1())) - w = WidgetFactory().createWidget((const char*)className.toLatin1(), parent); - if (w) w->setObjectName(name); - return w; -} - -// ---------------------------------------------------- - -PyObject *UiLoaderPy::PyMake(struct _typeobject * /*type*/, PyObject * args, PyObject * /*kwds*/) -{ - if (!PyArg_ParseTuple(args, "")) - return 0; - return new UiLoaderPy(); -} - -void UiLoaderPy::init_type() -{ - behaviors().name("UiLoader"); - behaviors().doc("UiLoader to create widgets"); - behaviors().set_tp_new(PyMake); - // you must have overwritten the virtual functions - behaviors().supportRepr(); - behaviors().supportGetattr(); - behaviors().supportSetattr(); - add_varargs_method("load",&UiLoaderPy::load,"load(string, QWidget parent=None) -> QWidget\n" - "load(QIODevice, QWidget parent=None) -> QWidget"); - add_varargs_method("createWidget",&UiLoaderPy::createWidget,"createWidget()"); -} - -UiLoaderPy::UiLoaderPy() -{ -} - -UiLoaderPy::~UiLoaderPy() -{ -} - -Py::Object UiLoaderPy::repr() -{ - std::string s; - std::ostringstream s_out; - s_out << "Ui loader"; - return Py::String(s_out.str()); -} - -Py::Object UiLoaderPy::load(const Py::Tuple& args) -{ - Gui::PythonWrapper wrap; - if (wrap.loadCoreModule()) { - std::string fn; - QFile file; - QIODevice* device = 0; - QWidget* parent = 0; - if (wrap.toCString(args[0], fn)) { - file.setFileName(QString::fromUtf8(fn.c_str())); - if (!file.open(QFile::ReadOnly)) - throw Py::RuntimeError("Cannot open file"); - device = &file; - } - else if (args[0].isString()) { - fn = (std::string)Py::String(args[0]); - file.setFileName(QString::fromUtf8(fn.c_str())); - if (!file.open(QFile::ReadOnly)) - throw Py::RuntimeError("Cannot open file"); - device = &file; - } - else { - QObject* obj = wrap.toQObject(args[0]); - device = qobject_cast(obj); - } - - if (args.size() > 1) { - QObject* obj = wrap.toQObject(args[1]); - parent = qobject_cast(obj); - } - - if (device) { - QWidget* widget = loader.load(device, parent); - if (widget) { - wrap.loadGuiModule(); - wrap.loadWidgetsModule(); - - const char* typeName = wrap.getWrapperName(widget); - Py::Object pyWdg = wrap.fromQWidget(widget, typeName); - wrap.createChildrenNameAttributes(*pyWdg, widget); - wrap.setParent(*pyWdg, parent); - return pyWdg; - } - } - else { - throw Py::TypeError("string or QIODevice expected"); - } - } - return Py::None(); -} - -Py::Object UiLoaderPy::createWidget(const Py::Tuple& args) -{ - Gui::PythonWrapper wrap; - - // 1st argument - Py::String str(args[0]); - std::string className; - className = str.as_std_string("utf-8"); - // 2nd argument - QWidget* parent = 0; - if (wrap.loadCoreModule() && args.size() > 1) { - QObject* object = wrap.toQObject(args[1]); - if (object) - parent = qobject_cast(object); - } - - // 3rd argument - std::string objectName; - if (args.size() > 2) { - Py::String str(args[2]); - objectName = str.as_std_string("utf-8"); - } - - QWidget* widget = loader.createWidget(QString::fromLatin1(className.c_str()), parent, - QString::fromLatin1(objectName.c_str())); - if (!widget) { - std::string err = "No such widget class '"; - err += className; - err += "'"; - throw Py::RuntimeError(err); - } - wrap.loadGuiModule(); - wrap.loadWidgetsModule(); - - const char* typeName = wrap.getWrapperName(widget); - return wrap.fromQWidget(widget, typeName); -} - -// ---------------------------------------------------- - WidgetFactorySupplier* WidgetFactorySupplier::_pcSingleton = 0L; WidgetFactorySupplier & WidgetFactorySupplier::instance() diff --git a/src/Gui/WidgetFactory.h b/src/Gui/WidgetFactory.h index def0235037..83804621bb 100644 --- a/src/Gui/WidgetFactory.h +++ b/src/Gui/WidgetFactory.h @@ -25,7 +25,6 @@ #define GUI_WIDGETFACTORY_H #include -#include #include #include @@ -71,18 +70,6 @@ public: static void setParent(PyObject* pyWdg, QObject* parent); }; -class PySideUicModule : public Py::ExtensionModule -{ - -public: - PySideUicModule(); - virtual ~PySideUicModule() {} - -private: - Py::Object loadUiType(const Py::Tuple& args); - Py::Object loadUi(const Py::Tuple& args); -}; - /** * The widget factory provides methods for the dynamic creation of widgets. * To create these widgets once they must be registered to the factory. @@ -114,51 +101,6 @@ inline WidgetFactoryInst& WidgetFactory() // -------------------------------------------------------------------- -/** - * The UiLoader class provides the abitlity to use the widget factory - * framework of FreeCAD within the framework provided by Qt. This class - * extends QUiLoader by the creation of FreeCAD specific widgets. - * @author Werner Mayer - */ -class UiLoader : public QUiLoader -{ -public: - UiLoader(QObject* parent=0); - virtual ~UiLoader(); - - /** - * Creates a widget of the type \a className with the parent \a parent. - * For more details see the documentation to QWidgetFactory. - */ - QWidget* createWidget(const QString & className, QWidget * parent=0, - const QString& name = QString()); -private: - QStringList cw; -}; - -// -------------------------------------------------------------------- - -class UiLoaderPy : public Py::PythonExtension -{ -public: - static void init_type(void); // announce properties and methods - - UiLoaderPy(); - ~UiLoaderPy(); - - Py::Object repr(); - Py::Object createWidget(const Py::Tuple&); - Py::Object load(const Py::Tuple&); - -private: - static PyObject *PyMake(struct _typeobject *, PyObject *, PyObject *); - -private: - UiLoader loader; -}; - -// -------------------------------------------------------------------- - /** * The WidgetProducer class is a value-based template class that provides * the ability to create widgets dynamically. From 00759f9c96786575f671a645ab19004a0a2972d1 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 22 Sep 2021 23:30:03 +0200 Subject: [PATCH 21/21] Gui: move PythonWrapper class to its own source file --- src/Gui/ApplicationPy.cpp | 1 + src/Gui/CMakeLists.txt | 2 + src/Gui/CommandPyImp.cpp | 2 +- src/Gui/ExpressionBindingPy.cpp | 2 +- src/Gui/PythonWrapper.cpp | 647 ++++++++++++++++++++++ src/Gui/PythonWrapper.h | 68 +++ src/Gui/TaskView/TaskDialogPython.cpp | 2 +- src/Gui/UiLoader.cpp | 1 + src/Gui/View3DPy.cpp | 2 +- src/Gui/ViewProviderPyImp.cpp | 2 +- src/Gui/ViewProviderPythonFeature.cpp | 2 +- src/Gui/WidgetFactory.cpp | 604 +------------------- src/Gui/WidgetFactory.h | 28 - src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp | 2 +- 14 files changed, 727 insertions(+), 638 deletions(-) create mode 100644 src/Gui/PythonWrapper.cpp create mode 100644 src/Gui/PythonWrapper.h diff --git a/src/Gui/ApplicationPy.cpp b/src/Gui/ApplicationPy.cpp index 61d636a63b..cf23554b0d 100644 --- a/src/Gui/ApplicationPy.cpp +++ b/src/Gui/ApplicationPy.cpp @@ -52,6 +52,7 @@ #include "SplitView3DInventor.h" #include "ViewProvider.h" #include "WaitCursor.h" +#include "PythonWrapper.h" #include "WidgetFactory.h" #include "Workbench.h" #include "WorkbenchManager.h" diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 56a99366b4..5cbdfe8b00 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -1032,6 +1032,7 @@ SET(Widget_CPP_SRCS QuantitySpinBox.cpp SpinBox.cpp Splashscreen.cpp + PythonWrapper.cpp UiLoader.cpp WidgetFactory.cpp Widgets.cpp @@ -1049,6 +1050,7 @@ SET(Widget_HPP_SRCS QuantitySpinBox_p.h SpinBox.h Splashscreen.h + PythonWrapper.h UiLoader.h WidgetFactory.h Widgets.h diff --git a/src/Gui/CommandPyImp.cpp b/src/Gui/CommandPyImp.cpp index b705ec8149..332434ce65 100644 --- a/src/Gui/CommandPyImp.cpp +++ b/src/Gui/CommandPyImp.cpp @@ -31,7 +31,7 @@ #include "MainWindow.h" #include "Selection.h" #include "Window.h" -#include "WidgetFactory.h" +#include "PythonWrapper.h" // inclusion of the generated files (generated out of AreaPy.xml) #include "CommandPy.h" diff --git a/src/Gui/ExpressionBindingPy.cpp b/src/Gui/ExpressionBindingPy.cpp index 6899a59177..811bc77df7 100644 --- a/src/Gui/ExpressionBindingPy.cpp +++ b/src/Gui/ExpressionBindingPy.cpp @@ -25,7 +25,7 @@ #endif #include "ExpressionBindingPy.h" #include "ExpressionBinding.h" -#include "WidgetFactory.h" +#include "PythonWrapper.h" #include "QuantitySpinBox.h" #include "InputField.h" #include diff --git a/src/Gui/PythonWrapper.cpp b/src/Gui/PythonWrapper.cpp new file mode 100644 index 0000000000..cb4e9ba0aa --- /dev/null +++ b/src/Gui/PythonWrapper.cpp @@ -0,0 +1,647 @@ +/*************************************************************************** + * Copyright (c) 2021 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +#endif +#include + +// Uncomment this block to remove PySide C++ support and switch to its Python interface +//#undef HAVE_SHIBOKEN +//#undef HAVE_PYSIDE +//#undef HAVE_SHIBOKEN2 +//#undef HAVE_PYSIDE2 + +#ifdef FC_OS_WIN32 +#undef max +#undef min +#ifdef _MSC_VER +#pragma warning( disable : 4099 ) +#pragma warning( disable : 4522 ) +#endif +#endif + +// class and struct used for SbkObject +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wmismatched-tags" +# pragma clang diagnostic ignored "-Wunused-parameter" +# if __clang_major__ > 3 +# pragma clang diagnostic ignored "-Wkeyword-macro" +# endif +#elif defined (__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-parameter" +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#ifdef HAVE_SHIBOKEN +# undef _POSIX_C_SOURCE +# undef _XOPEN_SOURCE +# include +# include +# include +# include +# include +# ifdef HAVE_PYSIDE +# include +# include +PyTypeObject** SbkPySide_QtCoreTypes=nullptr; +PyTypeObject** SbkPySide_QtGuiTypes=nullptr; +# endif +#endif + +#ifdef HAVE_SHIBOKEN2 +# define HAVE_SHIBOKEN +# undef _POSIX_C_SOURCE +# undef _XOPEN_SOURCE +# include +# include +# include +# include +# ifdef HAVE_PYSIDE2 +# define HAVE_PYSIDE + +// Since version 5.12 shiboken offers a method to get wrapper by class name (typeForTypeName) +// This helps to avoid to include the PySide2 headers since MSVC has a compiler bug when +// compiling together with std::bitset (https://bugreports.qt.io/browse/QTBUG-72073) + +// Do not use SHIBOKEN_MICRO_VERSION; it might contain a dot +# define SHIBOKEN_FULL_VERSION QT_VERSION_CHECK(SHIBOKEN_MAJOR_VERSION, SHIBOKEN_MINOR_VERSION, 0) +# if (SHIBOKEN_FULL_VERSION >= QT_VERSION_CHECK(5, 12, 0)) +# define HAVE_SHIBOKEN_TYPE_FOR_TYPENAME +# endif + +# ifndef HAVE_SHIBOKEN_TYPE_FOR_TYPENAME +# include +# include +# include +# endif +# include +PyTypeObject** SbkPySide2_QtCoreTypes=nullptr; +PyTypeObject** SbkPySide2_QtGuiTypes=nullptr; +PyTypeObject** SbkPySide2_QtWidgetsTypes=nullptr; +# endif // HAVE_PYSIDE2 +#endif // HAVE_SHIBOKEN2 + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined (__GNUC__) +# pragma GCC diagnostic pop +#endif + +#include +#include +#include + +#include "PythonWrapper.h" +#include "UiLoader.h" +#include "MetaTypes.h" + + +using namespace Gui; + +#if defined (HAVE_SHIBOKEN) + +/** + Example: + \code + ui = FreeCADGui.UiLoader() + w = ui.createWidget("Gui::InputField") + w.show() + w.property("quantity") + \endcode + */ + +PyObject* toPythonFuncQuantityTyped(Base::Quantity cpx) { + return new Base::QuantityPy(new Base::Quantity(cpx)); +} + +PyObject* toPythonFuncQuantity(const void* cpp) +{ + return toPythonFuncQuantityTyped(*reinterpret_cast(cpp)); +} + +void toCppPointerConvFuncQuantity(PyObject* pyobj,void* cpp) +{ + *((Base::Quantity*)cpp) = *static_cast(pyobj)->getQuantityPtr(); +} + +PythonToCppFunc toCppPointerCheckFuncQuantity(PyObject* obj) +{ + if (PyObject_TypeCheck(obj, &(Base::QuantityPy::Type))) + return toCppPointerConvFuncQuantity; + else + return 0; +} + +void BaseQuantity_PythonToCpp_QVariant(PyObject* pyIn, void* cppOut) +{ + Base::Quantity* q = static_cast(pyIn)->getQuantityPtr(); + *((QVariant*)cppOut) = QVariant::fromValue(*q); +} + +PythonToCppFunc isBaseQuantity_PythonToCpp_QVariantConvertible(PyObject* obj) +{ + if (PyObject_TypeCheck(obj, &(Base::QuantityPy::Type))) + return BaseQuantity_PythonToCpp_QVariant; + return 0; +} + +#if defined (HAVE_PYSIDE) +Base::Quantity convertWrapperToQuantity(const PySide::PyObjectWrapper &w) +{ + PyObject* pyIn = static_cast(w); + if (PyObject_TypeCheck(pyIn, &(Base::QuantityPy::Type))) { + return *static_cast(pyIn)->getQuantityPtr(); + } + + return Base::Quantity(std::numeric_limits::quiet_NaN()); +} +#endif + +void registerTypes() +{ + SbkConverter* convert = Shiboken::Conversions::createConverter(&Base::QuantityPy::Type, + toPythonFuncQuantity); + Shiboken::Conversions::setPythonToCppPointerFunctions(convert, + toCppPointerConvFuncQuantity, + toCppPointerCheckFuncQuantity); + Shiboken::Conversions::registerConverterName(convert, "Base::Quantity"); + + SbkConverter* qvariant_conv = Shiboken::Conversions::getConverter("QVariant"); + if (qvariant_conv) { + // The type QVariant already has a converter from PyBaseObject_Type which will + // come before our own converter. + Shiboken::Conversions::addPythonToCppValueConversion(qvariant_conv, + BaseQuantity_PythonToCpp_QVariant, + isBaseQuantity_PythonToCpp_QVariantConvertible); + } + +#if defined (HAVE_PYSIDE) + QMetaType::registerConverter(&convertWrapperToQuantity); +#endif +} +#endif + +// -------------------------------------------------------- + +namespace Gui { +template +Py::Object qt_wrapInstance(qttype object, const char* className, + const char* shiboken, const char* pyside, + const char* wrap) +{ + PyObject* module = PyImport_ImportModule(shiboken); + if (!module) { + std::string error = "Cannot load "; + error += shiboken; + error += " module"; + throw Py::Exception(PyExc_ImportError, error); + } + + Py::Module mainmod(module, true); + Py::Callable func = mainmod.getDict().getItem(wrap); + + Py::Tuple arguments(2); + arguments[0] = Py::asObject(PyLong_FromVoidPtr((void*)object)); + + module = PyImport_ImportModule(pyside); + if (!module) { + std::string error = "Cannot load "; + error += pyside; + error += " module"; + throw Py::Exception(PyExc_ImportError, error); + } + + Py::Module qtmod(module); + arguments[1] = qtmod.getDict().getItem(className); + return func.apply(arguments); +} + +const char* qt_identifyType(QObject* ptr, const char* pyside) +{ + PyObject* module = PyImport_ImportModule(pyside); + if (!module) { + std::string error = "Cannot load "; + error += pyside; + error += " module"; + throw Py::Exception(PyExc_ImportError, error); + } + + Py::Module qtmod(module); + const QMetaObject* metaObject = ptr->metaObject(); + while (metaObject) { + const char* className = metaObject->className(); + if (qtmod.getDict().hasKey(className)) + return className; + metaObject = metaObject->superClass(); + } + + return nullptr; +} + +void* qt_getCppPointer(const Py::Object& pyobject, const char* shiboken, const char* unwrap) +{ + // https://github.com/PySide/Shiboken/blob/master/shibokenmodule/typesystem_shiboken.xml + PyObject* module = PyImport_ImportModule(shiboken); + if (!module) { + std::string error = "Cannot load "; + error += shiboken; + error += " module"; + throw Py::Exception(PyExc_ImportError, error); + } + + Py::Module mainmod(module, true); + Py::Callable func = mainmod.getDict().getItem(unwrap); + + Py::Tuple arguments(1); + arguments[0] = pyobject; //PySide pointer + Py::Tuple result(func.apply(arguments)); + void* ptr = PyLong_AsVoidPtr(result[0].ptr()); + return ptr; +} + + +template +PyTypeObject *getPyTypeObjectForTypeName() +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) +#if defined (HAVE_SHIBOKEN_TYPE_FOR_TYPENAME) + SbkObjectType* sbkType = Shiboken::ObjectType::typeForTypeName(typeid(qttype).name()); + if (sbkType) + return &(sbkType->type); +#else + return Shiboken::SbkType(); +#endif +#endif + return nullptr; +} +} + +// -------------------------------------------------------- + +PythonWrapper::PythonWrapper() +{ +#if defined (HAVE_SHIBOKEN) + static bool init = false; + if (!init) { + init = true; + registerTypes(); + } +#endif +} + +bool PythonWrapper::toCString(const Py::Object& pyobject, std::string& str) +{ + if (PyUnicode_Check(pyobject.ptr())) { + PyObject* unicode = PyUnicode_AsUTF8String(pyobject.ptr()); + str = PyBytes_AsString(unicode); + Py_DECREF(unicode); + return true; + } + else if (PyBytes_Check(pyobject.ptr())) { + str = PyBytes_AsString(pyobject.ptr()); + return true; + } +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + if (Shiboken::String::check(pyobject.ptr())) { + const char* s = Shiboken::String::toCString(pyobject.ptr()); + if (s) str = s; + return true; + } +#endif + return false; +} + +QObject* PythonWrapper::toQObject(const Py::Object& pyobject) +{ + // http://pastebin.com/JByDAF5Z +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + PyTypeObject * type = getPyTypeObjectForTypeName(); + if (type) { + if (Shiboken::Object::checkType(pyobject.ptr())) { + SbkObject* sbkobject = reinterpret_cast(pyobject.ptr()); + void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); + return reinterpret_cast(cppobject); + } + } +#else + // Access shiboken2/PySide2 via Python + // + void* ptr = qt_getCppPointer(pyobject, "shiboken2", "getCppPointer"); + return reinterpret_cast(ptr); +#endif + +#if 0 // Unwrapping using sip/PyQt + void* ptr = qt_getCppPointer(pyobject, "sip", "unwrapinstance"); + return reinterpret_cast(ptr); +#endif + + return 0; +} + +QGraphicsItem* PythonWrapper::toQGraphicsItem(PyObject* pyPtr) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + PyTypeObject* type = getPyTypeObjectForTypeName(); + if (type) { + if (Shiboken::Object::checkType(pyPtr)) { + SbkObject* sbkobject = reinterpret_cast(pyPtr); + void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); + return reinterpret_cast(cppobject); + } + } +#else + // Access shiboken2/PySide2 via Python + // + void* ptr = qt_getCppPointer(Py::asObject(pyPtr), "shiboken2", "getCppPointer"); + return reinterpret_cast(ptr); +#endif + return nullptr; +} + +Py::Object PythonWrapper::fromQIcon(const QIcon* icon) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + const char* typeName = typeid(*const_cast(icon)).name(); + PyObject* pyobj = Shiboken::Object::newObject(reinterpret_cast(getPyTypeObjectForTypeName()), + const_cast(icon), true, false, typeName); + if (pyobj) + return Py::asObject(pyobj); +#else + // Access shiboken2/PySide2 via Python + // + return qt_wrapInstance(icon, "QIcon", "shiboken2", "PySide2.QtGui", "wrapInstance"); +#endif + throw Py::RuntimeError("Failed to wrap icon"); +} + +QIcon *PythonWrapper::toQIcon(PyObject *pyobj) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + PyTypeObject * type = getPyTypeObjectForTypeName(); + if(type) { + if (Shiboken::Object::checkType(pyobj)) { + SbkObject* sbkobject = reinterpret_cast(pyobj); + void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); + return reinterpret_cast(cppobject); + } + } +#else + Q_UNUSED(pyobj); +#endif + return 0; +} + +Py::Object PythonWrapper::fromQDir(const QDir& dir) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + const char* typeName = typeid(dir).name(); + PyObject* pyobj = Shiboken::Object::newObject(reinterpret_cast(getPyTypeObjectForTypeName()), + const_cast(&dir), false, false, typeName); + if (pyobj) + return Py::asObject(pyobj); +#endif + throw Py::RuntimeError("Failed to wrap icon"); +} + +QDir* PythonWrapper::toQDir(PyObject* pyobj) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + PyTypeObject* type = getPyTypeObjectForTypeName(); + if (type) { + if (Shiboken::Object::checkType(pyobj)) { + SbkObject* sbkobject = reinterpret_cast(pyobj); + void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); + return reinterpret_cast(cppobject); + } + } +#else + Q_UNUSED(pyobj); +#endif + return nullptr; +} + +Py::Object PythonWrapper::fromQObject(QObject* object, const char* className) +{ + if (!object) + return Py::None(); +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + // Access shiboken/PySide via C++ + // + PyTypeObject * type = getPyTypeObjectForTypeName(); + if (type) { + SbkObjectType* sbk_type = reinterpret_cast(type); + std::string typeName; + if (className) + typeName = className; + else + typeName = object->metaObject()->className(); + PyObject* pyobj = Shiboken::Object::newObject(sbk_type, object, false, false, typeName.c_str()); + return Py::asObject(pyobj); + } + throw Py::RuntimeError("Failed to wrap object"); +#else + // Access shiboken2/PySide2 via Python + // + return qt_wrapInstance(object, className, "shiboken2", "PySide2.QtCore", "wrapInstance"); +#endif +#if 0 // Unwrapping using sip/PyQt + Q_UNUSED(className); + return qt_wrapInstance(object, "QObject", "sip", "PyQt5.QtCore", "wrapinstance"); +#endif +} + +Py::Object PythonWrapper::fromQWidget(QWidget* widget, const char* className) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + // Access shiboken/PySide via C++ + // + PyTypeObject * type = getPyTypeObjectForTypeName(); + if (type) { + SbkObjectType* sbk_type = reinterpret_cast(type); + std::string typeName; + if (className) + typeName = className; + else + typeName = widget->metaObject()->className(); + PyObject* pyobj = Shiboken::Object::newObject(sbk_type, widget, false, false, typeName.c_str()); + return Py::asObject(pyobj); + } + throw Py::RuntimeError("Failed to wrap widget"); + +#else + // Access shiboken2/PySide2 via Python + // + return qt_wrapInstance(widget, className, "shiboken2", "PySide2.QtWidgets", "wrapInstance"); +#endif + +#if 0 // Unwrapping using sip/PyQt + Q_UNUSED(className); + return qt_wrapInstance(widget, "QWidget", "sip", "PyQt5.QtWidgets", "wrapinstance"); +#endif +} + +const char* PythonWrapper::getWrapperName(QObject* obj) const +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + const QMetaObject* meta = obj->metaObject(); + while (meta) { + const char* typeName = meta->className(); + PyTypeObject* exactType = Shiboken::Conversions::getPythonTypeObject(typeName); + if (exactType) + return typeName; + meta = meta->superClass(); + } +#else + QUiLoader ui; + QStringList names = ui.availableWidgets(); + const QMetaObject* meta = obj->metaObject(); + while (meta) { + const char* typeName = meta->className(); + if (names.indexOf(QLatin1String(typeName)) >= 0) + return typeName; + meta = meta->superClass(); + } +#endif + return "QObject"; +} + +bool PythonWrapper::loadCoreModule() +{ +#if defined (HAVE_SHIBOKEN2) && (HAVE_PYSIDE2) + // QtCore + if (!SbkPySide2_QtCoreTypes) { + Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtCore")); + if (requiredModule.isNull()) + return false; + SbkPySide2_QtCoreTypes = Shiboken::Module::getTypes(requiredModule); + } +#elif defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + // QtCore + if (!SbkPySide_QtCoreTypes) { + Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide.QtCore")); + if (requiredModule.isNull()) + return false; + SbkPySide_QtCoreTypes = Shiboken::Module::getTypes(requiredModule); + } +#endif + return true; +} + +bool PythonWrapper::loadGuiModule() +{ +#if defined (HAVE_SHIBOKEN2) && defined(HAVE_PYSIDE2) + // QtGui + if (!SbkPySide2_QtGuiTypes) { + Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtGui")); + if (requiredModule.isNull()) + return false; + SbkPySide2_QtGuiTypes = Shiboken::Module::getTypes(requiredModule); + } +#elif defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + // QtGui + if (!SbkPySide_QtGuiTypes) { + Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide.QtGui")); + if (requiredModule.isNull()) + return false; + SbkPySide_QtGuiTypes = Shiboken::Module::getTypes(requiredModule); + } +#endif + return true; +} + +bool PythonWrapper::loadWidgetsModule() +{ +#if defined (HAVE_SHIBOKEN2) && defined(HAVE_PYSIDE2) + // QtWidgets + if (!SbkPySide2_QtWidgetsTypes) { + Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtWidgets")); + if (requiredModule.isNull()) + return false; + SbkPySide2_QtWidgetsTypes = Shiboken::Module::getTypes(requiredModule); + } +#endif + return true; +} + +bool PythonWrapper::loadUiToolsModule() +{ +#if defined (HAVE_SHIBOKEN2) && defined(HAVE_PYSIDE2) + // QtUiTools + static PyTypeObject** SbkPySide2_QtUiToolsTypes = nullptr; + if (!SbkPySide2_QtUiToolsTypes) { + Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtUiTools")); + if (requiredModule.isNull()) + return false; + SbkPySide2_QtUiToolsTypes = Shiboken::Module::getTypes(requiredModule); + } +#endif + return true; +} + +void PythonWrapper::createChildrenNameAttributes(PyObject* root, QObject* object) +{ + Q_FOREACH (QObject* child, object->children()) { + const QByteArray name = child->objectName().toLocal8Bit(); + + if (!name.isEmpty() && !name.startsWith("_") && !name.startsWith("qt_")) { + bool hasAttr = PyObject_HasAttrString(root, name.constData()); + if (!hasAttr) { +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + Shiboken::AutoDecRef pyChild(Shiboken::Conversions::pointerToPython(reinterpret_cast(getPyTypeObjectForTypeName()), child)); + PyObject_SetAttrString(root, name.constData(), pyChild); +#else + const char* className = qt_identifyType(child, "PySide2.QtWidgets"); + if (!className) { + if (qobject_cast(child)) + className = "QWidget"; + else + className = "QObject"; + } + + Py::Object pyChild(qt_wrapInstance(child, className, "shiboken2", "PySide2.QtWidgets", "wrapInstance")); + PyObject_SetAttrString(root, name.constData(), pyChild.ptr()); +#endif + } + createChildrenNameAttributes(root, child); + } + createChildrenNameAttributes(root, child); + } +} + +void PythonWrapper::setParent(PyObject* pyWdg, QObject* parent) +{ +#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) + if (parent) { + Shiboken::AutoDecRef pyParent(Shiboken::Conversions::pointerToPython(reinterpret_cast(getPyTypeObjectForTypeName()), parent)); + Shiboken::Object::setParent(pyParent, pyWdg); + } +#else + Q_UNUSED(pyWdg); + Q_UNUSED(parent); +#endif +} diff --git a/src/Gui/PythonWrapper.h b/src/Gui/PythonWrapper.h new file mode 100644 index 0000000000..7b5a8929ab --- /dev/null +++ b/src/Gui/PythonWrapper.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * Copyright (c) 2021 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef GUI_PYTHONWRAPPER_H +#define GUI_PYTHONWRAPPER_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QDir; +QT_END_NAMESPACE + +namespace Gui { + +class GuiExport PythonWrapper +{ +public: + PythonWrapper(); + bool loadCoreModule(); + bool loadGuiModule(); + bool loadWidgetsModule(); + bool loadUiToolsModule(); + + bool toCString(const Py::Object&, std::string&); + QObject* toQObject(const Py::Object&); + QGraphicsItem* toQGraphicsItem(PyObject* ptr); + Py::Object fromQObject(QObject*, const char* className=0); + Py::Object fromQWidget(QWidget*, const char* className=0); + const char* getWrapperName(QObject*) const; + /*! + Create a Python wrapper for the icon. The icon must be created on the heap + and the Python wrapper takes ownership of it. + */ + Py::Object fromQIcon(const QIcon*); + QIcon *toQIcon(PyObject *pyobj); + Py::Object fromQDir(const QDir&); + QDir* toQDir(PyObject* pyobj); + static void createChildrenNameAttributes(PyObject* root, QObject* object); + static void setParent(PyObject* pyWdg, QObject* parent); +}; + +} // namespace Gui + +#endif // GUI_PYTHONWRAPPER_H diff --git a/src/Gui/TaskView/TaskDialogPython.cpp b/src/Gui/TaskView/TaskDialogPython.cpp index 9bbbbb2ff7..7415c85799 100644 --- a/src/Gui/TaskView/TaskDialogPython.cpp +++ b/src/Gui/TaskView/TaskDialogPython.cpp @@ -37,7 +37,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/Gui/UiLoader.cpp b/src/Gui/UiLoader.cpp index b2e8d036a8..b90f3c59db 100644 --- a/src/Gui/UiLoader.cpp +++ b/src/Gui/UiLoader.cpp @@ -28,6 +28,7 @@ #endif #include "UiLoader.h" +#include "PythonWrapper.h" #include "WidgetFactory.h" #include diff --git a/src/Gui/View3DPy.cpp b/src/Gui/View3DPy.cpp index 18cf5985ec..50e8325c3a 100644 --- a/src/Gui/View3DPy.cpp +++ b/src/Gui/View3DPy.cpp @@ -51,7 +51,7 @@ #include "View3DInventorViewer.h" #include "View3DViewerPy.h" #include "ActiveObjectList.h" -#include "WidgetFactory.h" +#include "PythonWrapper.h" #include diff --git a/src/Gui/ViewProviderPyImp.cpp b/src/Gui/ViewProviderPyImp.cpp index 506050f9fc..db048d382d 100644 --- a/src/Gui/ViewProviderPyImp.cpp +++ b/src/Gui/ViewProviderPyImp.cpp @@ -37,7 +37,7 @@ #include "SoFCDB.h" #include "ViewProvider.h" -#include "WidgetFactory.h" +#include "PythonWrapper.h" #include diff --git a/src/Gui/ViewProviderPythonFeature.cpp b/src/Gui/ViewProviderPythonFeature.cpp index 56c32885de..7b1c946844 100644 --- a/src/Gui/ViewProviderPythonFeature.cpp +++ b/src/Gui/ViewProviderPythonFeature.cpp @@ -57,7 +57,7 @@ #include "Application.h" #include "BitmapFactory.h" #include "Document.h" -#include "WidgetFactory.h" +#include "PythonWrapper.h" #include "View3DInventorViewer.h" #include #include diff --git a/src/Gui/WidgetFactory.cpp b/src/Gui/WidgetFactory.cpp index f55ebf4e36..70c30fdc3e 100644 --- a/src/Gui/WidgetFactory.cpp +++ b/src/Gui/WidgetFactory.cpp @@ -26,13 +26,6 @@ # include # include #endif -#include - -// Uncomment this block to remove PySide C++ support and switch to its Python interface -//#undef HAVE_SHIBOKEN -//#undef HAVE_PYSIDE -//#undef HAVE_SHIBOKEN2 -//#undef HAVE_PYSIDE2 #ifdef FC_OS_WIN32 #undef max @@ -43,617 +36,22 @@ #endif #endif -// class and struct used for SbkObject -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wmismatched-tags" -# pragma clang diagnostic ignored "-Wunused-parameter" -# if __clang_major__ > 3 -# pragma clang diagnostic ignored "-Wkeyword-macro" -# endif -#elif defined (__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunused-parameter" -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - -#ifdef HAVE_SHIBOKEN -# undef _POSIX_C_SOURCE -# undef _XOPEN_SOURCE -# include -# include -# include -# include -# include -# ifdef HAVE_PYSIDE -# include -# include -PyTypeObject** SbkPySide_QtCoreTypes=nullptr; -PyTypeObject** SbkPySide_QtGuiTypes=nullptr; -# endif -#endif - -#ifdef HAVE_SHIBOKEN2 -# define HAVE_SHIBOKEN -# undef _POSIX_C_SOURCE -# undef _XOPEN_SOURCE -# include -# include -# include -# include -# ifdef HAVE_PYSIDE2 -# define HAVE_PYSIDE - -// Since version 5.12 shiboken offers a method to get wrapper by class name (typeForTypeName) -// This helps to avoid to include the PySide2 headers since MSVC has a compiler bug when -// compiling together with std::bitset (https://bugreports.qt.io/browse/QTBUG-72073) - -// Do not use SHIBOKEN_MICRO_VERSION; it might contain a dot -# define SHIBOKEN_FULL_VERSION QT_VERSION_CHECK(SHIBOKEN_MAJOR_VERSION, SHIBOKEN_MINOR_VERSION, 0) -# if (SHIBOKEN_FULL_VERSION >= QT_VERSION_CHECK(5, 12, 0)) -# define HAVE_SHIBOKEN_TYPE_FOR_TYPENAME -# endif - -# ifndef HAVE_SHIBOKEN_TYPE_FOR_TYPENAME -# include -# include -# include -# endif -# include -PyTypeObject** SbkPySide2_QtCoreTypes=nullptr; -PyTypeObject** SbkPySide2_QtGuiTypes=nullptr; -PyTypeObject** SbkPySide2_QtWidgetsTypes=nullptr; -# endif // HAVE_PYSIDE2 -#endif // HAVE_SHIBOKEN2 - -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined (__GNUC__) -# pragma GCC diagnostic pop -#endif - #include #include #include #include #include -#include -#include #include "WidgetFactory.h" #include "UiLoader.h" +#include "PythonWrapper.h" #include "PrefWidgets.h" #include "PropertyPage.h" using namespace Gui; -#if defined (HAVE_SHIBOKEN) - -/** - Example: - \code - ui = FreeCADGui.UiLoader() - w = ui.createWidget("Gui::InputField") - w.show() - w.property("quantity") - \endcode - */ - -PyObject* toPythonFuncQuantityTyped(Base::Quantity cpx) { - return new Base::QuantityPy(new Base::Quantity(cpx)); -} - -PyObject* toPythonFuncQuantity(const void* cpp) -{ - return toPythonFuncQuantityTyped(*reinterpret_cast(cpp)); -} - -void toCppPointerConvFuncQuantity(PyObject* pyobj,void* cpp) -{ - *((Base::Quantity*)cpp) = *static_cast(pyobj)->getQuantityPtr(); -} - -PythonToCppFunc toCppPointerCheckFuncQuantity(PyObject* obj) -{ - if (PyObject_TypeCheck(obj, &(Base::QuantityPy::Type))) - return toCppPointerConvFuncQuantity; - else - return 0; -} - -void BaseQuantity_PythonToCpp_QVariant(PyObject* pyIn, void* cppOut) -{ - Base::Quantity* q = static_cast(pyIn)->getQuantityPtr(); - *((QVariant*)cppOut) = QVariant::fromValue(*q); -} - -PythonToCppFunc isBaseQuantity_PythonToCpp_QVariantConvertible(PyObject* obj) -{ - if (PyObject_TypeCheck(obj, &(Base::QuantityPy::Type))) - return BaseQuantity_PythonToCpp_QVariant; - return 0; -} - -#if defined (HAVE_PYSIDE) -Base::Quantity convertWrapperToQuantity(const PySide::PyObjectWrapper &w) -{ - PyObject* pyIn = static_cast(w); - if (PyObject_TypeCheck(pyIn, &(Base::QuantityPy::Type))) { - return *static_cast(pyIn)->getQuantityPtr(); - } - - return Base::Quantity(std::numeric_limits::quiet_NaN()); -} -#endif - -void registerTypes() -{ - SbkConverter* convert = Shiboken::Conversions::createConverter(&Base::QuantityPy::Type, - toPythonFuncQuantity); - Shiboken::Conversions::setPythonToCppPointerFunctions(convert, - toCppPointerConvFuncQuantity, - toCppPointerCheckFuncQuantity); - Shiboken::Conversions::registerConverterName(convert, "Base::Quantity"); - - SbkConverter* qvariant_conv = Shiboken::Conversions::getConverter("QVariant"); - if (qvariant_conv) { - // The type QVariant already has a converter from PyBaseObject_Type which will - // come before our own converter. - Shiboken::Conversions::addPythonToCppValueConversion(qvariant_conv, - BaseQuantity_PythonToCpp_QVariant, - isBaseQuantity_PythonToCpp_QVariantConvertible); - } - -#if defined (HAVE_PYSIDE) - QMetaType::registerConverter(&convertWrapperToQuantity); -#endif -} -#endif - -// -------------------------------------------------------- - -namespace Gui { -template -Py::Object qt_wrapInstance(qttype object, const char* className, - const char* shiboken, const char* pyside, - const char* wrap) -{ - PyObject* module = PyImport_ImportModule(shiboken); - if (!module) { - std::string error = "Cannot load "; - error += shiboken; - error += " module"; - throw Py::Exception(PyExc_ImportError, error); - } - - Py::Module mainmod(module, true); - Py::Callable func = mainmod.getDict().getItem(wrap); - - Py::Tuple arguments(2); - arguments[0] = Py::asObject(PyLong_FromVoidPtr((void*)object)); - - module = PyImport_ImportModule(pyside); - if (!module) { - std::string error = "Cannot load "; - error += pyside; - error += " module"; - throw Py::Exception(PyExc_ImportError, error); - } - - Py::Module qtmod(module); - arguments[1] = qtmod.getDict().getItem(className); - return func.apply(arguments); -} - -const char* qt_identifyType(QObject* ptr, const char* pyside) -{ - PyObject* module = PyImport_ImportModule(pyside); - if (!module) { - std::string error = "Cannot load "; - error += pyside; - error += " module"; - throw Py::Exception(PyExc_ImportError, error); - } - - Py::Module qtmod(module); - const QMetaObject* metaObject = ptr->metaObject(); - while (metaObject) { - const char* className = metaObject->className(); - if (qtmod.getDict().hasKey(className)) - return className; - metaObject = metaObject->superClass(); - } - - return nullptr; -} - -void* qt_getCppPointer(const Py::Object& pyobject, const char* shiboken, const char* unwrap) -{ - // https://github.com/PySide/Shiboken/blob/master/shibokenmodule/typesystem_shiboken.xml - PyObject* module = PyImport_ImportModule(shiboken); - if (!module) { - std::string error = "Cannot load "; - error += shiboken; - error += " module"; - throw Py::Exception(PyExc_ImportError, error); - } - - Py::Module mainmod(module, true); - Py::Callable func = mainmod.getDict().getItem(unwrap); - - Py::Tuple arguments(1); - arguments[0] = pyobject; //PySide pointer - Py::Tuple result(func.apply(arguments)); - void* ptr = PyLong_AsVoidPtr(result[0].ptr()); - return ptr; -} - - -template -PyTypeObject *getPyTypeObjectForTypeName() -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) -#if defined (HAVE_SHIBOKEN_TYPE_FOR_TYPENAME) - SbkObjectType* sbkType = Shiboken::ObjectType::typeForTypeName(typeid(qttype).name()); - if (sbkType) - return &(sbkType->type); -#else - return Shiboken::SbkType(); -#endif -#endif - return nullptr; -} -} - -// -------------------------------------------------------- - -PythonWrapper::PythonWrapper() -{ -#if defined (HAVE_SHIBOKEN) - static bool init = false; - if (!init) { - init = true; - registerTypes(); - } -#endif -} - -bool PythonWrapper::toCString(const Py::Object& pyobject, std::string& str) -{ - if (PyUnicode_Check(pyobject.ptr())) { - PyObject* unicode = PyUnicode_AsUTF8String(pyobject.ptr()); - str = PyBytes_AsString(unicode); - Py_DECREF(unicode); - return true; - } - else if (PyBytes_Check(pyobject.ptr())) { - str = PyBytes_AsString(pyobject.ptr()); - return true; - } -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - if (Shiboken::String::check(pyobject.ptr())) { - const char* s = Shiboken::String::toCString(pyobject.ptr()); - if (s) str = s; - return true; - } -#endif - return false; -} - -QObject* PythonWrapper::toQObject(const Py::Object& pyobject) -{ - // http://pastebin.com/JByDAF5Z -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - PyTypeObject * type = getPyTypeObjectForTypeName(); - if (type) { - if (Shiboken::Object::checkType(pyobject.ptr())) { - SbkObject* sbkobject = reinterpret_cast(pyobject.ptr()); - void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); - return reinterpret_cast(cppobject); - } - } -#else - // Access shiboken2/PySide2 via Python - // - void* ptr = qt_getCppPointer(pyobject, "shiboken2", "getCppPointer"); - return reinterpret_cast(ptr); -#endif - -#if 0 // Unwrapping using sip/PyQt - void* ptr = qt_getCppPointer(pyobject, "sip", "unwrapinstance"); - return reinterpret_cast(ptr); -#endif - - return 0; -} - -QGraphicsItem* PythonWrapper::toQGraphicsItem(PyObject* pyPtr) -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - PyTypeObject* type = getPyTypeObjectForTypeName(); - if (type) { - if (Shiboken::Object::checkType(pyPtr)) { - SbkObject* sbkobject = reinterpret_cast(pyPtr); - void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); - return reinterpret_cast(cppobject); - } - } -#else - // Access shiboken2/PySide2 via Python - // - void* ptr = qt_getCppPointer(Py::asObject(pyPtr), "shiboken2", "getCppPointer"); - return reinterpret_cast(ptr); -#endif - return nullptr; -} - -Py::Object PythonWrapper::fromQIcon(const QIcon* icon) -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - const char* typeName = typeid(*const_cast(icon)).name(); - PyObject* pyobj = Shiboken::Object::newObject(reinterpret_cast(getPyTypeObjectForTypeName()), - const_cast(icon), true, false, typeName); - if (pyobj) - return Py::asObject(pyobj); -#else - // Access shiboken2/PySide2 via Python - // - return qt_wrapInstance(icon, "QIcon", "shiboken2", "PySide2.QtGui", "wrapInstance"); -#endif - throw Py::RuntimeError("Failed to wrap icon"); -} - -QIcon *PythonWrapper::toQIcon(PyObject *pyobj) -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - PyTypeObject * type = getPyTypeObjectForTypeName(); - if(type) { - if (Shiboken::Object::checkType(pyobj)) { - SbkObject* sbkobject = reinterpret_cast(pyobj); - void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); - return reinterpret_cast(cppobject); - } - } -#else - Q_UNUSED(pyobj); -#endif - return 0; -} - -Py::Object PythonWrapper::fromQDir(const QDir& dir) -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - const char* typeName = typeid(dir).name(); - PyObject* pyobj = Shiboken::Object::newObject(reinterpret_cast(getPyTypeObjectForTypeName()), - const_cast(&dir), false, false, typeName); - if (pyobj) - return Py::asObject(pyobj); -#endif - throw Py::RuntimeError("Failed to wrap icon"); -} - -QDir* PythonWrapper::toQDir(PyObject* pyobj) -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - PyTypeObject* type = getPyTypeObjectForTypeName(); - if (type) { - if (Shiboken::Object::checkType(pyobj)) { - SbkObject* sbkobject = reinterpret_cast(pyobj); - void* cppobject = Shiboken::Object::cppPointer(sbkobject, type); - return reinterpret_cast(cppobject); - } - } -#else - Q_UNUSED(pyobj); -#endif - return nullptr; -} - -Py::Object PythonWrapper::fromQObject(QObject* object, const char* className) -{ - if (!object) - return Py::None(); -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - // Access shiboken/PySide via C++ - // - PyTypeObject * type = getPyTypeObjectForTypeName(); - if (type) { - SbkObjectType* sbk_type = reinterpret_cast(type); - std::string typeName; - if (className) - typeName = className; - else - typeName = object->metaObject()->className(); - PyObject* pyobj = Shiboken::Object::newObject(sbk_type, object, false, false, typeName.c_str()); - return Py::asObject(pyobj); - } - throw Py::RuntimeError("Failed to wrap object"); -#else - // Access shiboken2/PySide2 via Python - // - return qt_wrapInstance(object, className, "shiboken2", "PySide2.QtCore", "wrapInstance"); -#endif -#if 0 // Unwrapping using sip/PyQt - Q_UNUSED(className); - return qt_wrapInstance(object, "QObject", "sip", "PyQt5.QtCore", "wrapinstance"); -#endif -} - -Py::Object PythonWrapper::fromQWidget(QWidget* widget, const char* className) -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - // Access shiboken/PySide via C++ - // - PyTypeObject * type = getPyTypeObjectForTypeName(); - if (type) { - SbkObjectType* sbk_type = reinterpret_cast(type); - std::string typeName; - if (className) - typeName = className; - else - typeName = widget->metaObject()->className(); - PyObject* pyobj = Shiboken::Object::newObject(sbk_type, widget, false, false, typeName.c_str()); - return Py::asObject(pyobj); - } - throw Py::RuntimeError("Failed to wrap widget"); - -#else - // Access shiboken2/PySide2 via Python - // - return qt_wrapInstance(widget, className, "shiboken2", "PySide2.QtWidgets", "wrapInstance"); -#endif - -#if 0 // Unwrapping using sip/PyQt - Q_UNUSED(className); - return qt_wrapInstance(widget, "QWidget", "sip", "PyQt5.QtWidgets", "wrapinstance"); -#endif -} - -const char* PythonWrapper::getWrapperName(QObject* obj) const -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - const QMetaObject* meta = obj->metaObject(); - while (meta) { - const char* typeName = meta->className(); - PyTypeObject* exactType = Shiboken::Conversions::getPythonTypeObject(typeName); - if (exactType) - return typeName; - meta = meta->superClass(); - } -#else - QUiLoader ui; - QStringList names = ui.availableWidgets(); - const QMetaObject* meta = obj->metaObject(); - while (meta) { - const char* typeName = meta->className(); - if (names.indexOf(QLatin1String(typeName)) >= 0) - return typeName; - meta = meta->superClass(); - } -#endif - return "QObject"; -} - -bool PythonWrapper::loadCoreModule() -{ -#if defined (HAVE_SHIBOKEN2) && (HAVE_PYSIDE2) - // QtCore - if (!SbkPySide2_QtCoreTypes) { - Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtCore")); - if (requiredModule.isNull()) - return false; - SbkPySide2_QtCoreTypes = Shiboken::Module::getTypes(requiredModule); - } -#elif defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - // QtCore - if (!SbkPySide_QtCoreTypes) { - Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide.QtCore")); - if (requiredModule.isNull()) - return false; - SbkPySide_QtCoreTypes = Shiboken::Module::getTypes(requiredModule); - } -#endif - return true; -} - -bool PythonWrapper::loadGuiModule() -{ -#if defined (HAVE_SHIBOKEN2) && defined(HAVE_PYSIDE2) - // QtGui - if (!SbkPySide2_QtGuiTypes) { - Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtGui")); - if (requiredModule.isNull()) - return false; - SbkPySide2_QtGuiTypes = Shiboken::Module::getTypes(requiredModule); - } -#elif defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - // QtGui - if (!SbkPySide_QtGuiTypes) { - Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide.QtGui")); - if (requiredModule.isNull()) - return false; - SbkPySide_QtGuiTypes = Shiboken::Module::getTypes(requiredModule); - } -#endif - return true; -} - -bool PythonWrapper::loadWidgetsModule() -{ -#if defined (HAVE_SHIBOKEN2) && defined(HAVE_PYSIDE2) - // QtWidgets - if (!SbkPySide2_QtWidgetsTypes) { - Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtWidgets")); - if (requiredModule.isNull()) - return false; - SbkPySide2_QtWidgetsTypes = Shiboken::Module::getTypes(requiredModule); - } -#endif - return true; -} - -bool PythonWrapper::loadUiToolsModule() -{ -#if defined (HAVE_SHIBOKEN2) && defined(HAVE_PYSIDE2) - // QtUiTools - static PyTypeObject** SbkPySide2_QtUiToolsTypes = nullptr; - if (!SbkPySide2_QtUiToolsTypes) { - Shiboken::AutoDecRef requiredModule(Shiboken::Module::import("PySide2.QtUiTools")); - if (requiredModule.isNull()) - return false; - SbkPySide2_QtUiToolsTypes = Shiboken::Module::getTypes(requiredModule); - } -#endif - return true; -} - -void PythonWrapper::createChildrenNameAttributes(PyObject* root, QObject* object) -{ - Q_FOREACH (QObject* child, object->children()) { - const QByteArray name = child->objectName().toLocal8Bit(); - - if (!name.isEmpty() && !name.startsWith("_") && !name.startsWith("qt_")) { - bool hasAttr = PyObject_HasAttrString(root, name.constData()); - if (!hasAttr) { -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - Shiboken::AutoDecRef pyChild(Shiboken::Conversions::pointerToPython(reinterpret_cast(getPyTypeObjectForTypeName()), child)); - PyObject_SetAttrString(root, name.constData(), pyChild); -#else - const char* className = qt_identifyType(child, "PySide2.QtWidgets"); - if (!className) { - if (qobject_cast(child)) - className = "QWidget"; - else - className = "QObject"; - } - - Py::Object pyChild(qt_wrapInstance(child, className, "shiboken2", "PySide2.QtWidgets", "wrapInstance")); - PyObject_SetAttrString(root, name.constData(), pyChild.ptr()); -#endif - } - createChildrenNameAttributes(root, child); - } - createChildrenNameAttributes(root, child); - } -} - -void PythonWrapper::setParent(PyObject* pyWdg, QObject* parent) -{ -#if defined (HAVE_SHIBOKEN) && defined(HAVE_PYSIDE) - if (parent) { - Shiboken::AutoDecRef pyParent(Shiboken::Conversions::pointerToPython(reinterpret_cast(getPyTypeObjectForTypeName()), parent)); - Shiboken::Object::setParent(pyParent, pyWdg); - } -#else - Q_UNUSED(pyWdg); - Q_UNUSED(parent); -#endif -} - -// ---------------------------------------------------- - Gui::WidgetFactoryInst* Gui::WidgetFactoryInst::_pcSingleton = NULL; WidgetFactoryInst& WidgetFactoryInst::instance() diff --git a/src/Gui/WidgetFactory.h b/src/Gui/WidgetFactory.h index 83804621bb..db59c16535 100644 --- a/src/Gui/WidgetFactory.h +++ b/src/Gui/WidgetFactory.h @@ -25,7 +25,6 @@ #define GUI_WIDGETFACTORY_H #include -#include #include #include @@ -43,33 +42,6 @@ namespace Gui { class PreferencePage; } -class GuiExport PythonWrapper -{ -public: - PythonWrapper(); - bool loadCoreModule(); - bool loadGuiModule(); - bool loadWidgetsModule(); - bool loadUiToolsModule(); - - bool toCString(const Py::Object&, std::string&); - QObject* toQObject(const Py::Object&); - QGraphicsItem* toQGraphicsItem(PyObject* ptr); - Py::Object fromQObject(QObject*, const char* className=0); - Py::Object fromQWidget(QWidget*, const char* className=0); - const char* getWrapperName(QObject*) const; - /*! - Create a Python wrapper for the icon. The icon must be created on the heap - and the Python wrapper takes ownership of it. - */ - Py::Object fromQIcon(const QIcon*); - QIcon *toQIcon(PyObject *pyobj); - Py::Object fromQDir(const QDir&); - QDir* toQDir(PyObject* pyobj); - static void createChildrenNameAttributes(PyObject* root, QObject* object); - static void setParent(PyObject* pyWdg, QObject* parent); -}; - /** * The widget factory provides methods for the dynamic creation of widgets. * To create these widgets once they must be registered to the factory. diff --git a/src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp b/src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp index 14dc2882c3..93836655b0 100644 --- a/src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp +++ b/src/Mod/TechDraw/Gui/AppTechDrawGuiPy.cpp @@ -45,7 +45,7 @@ #include #include #include -#include //for PythonWrappers +#include #include #include