From cf31a3afdb9b15042b803a3b87f42acc9a884041 Mon Sep 17 00:00:00 2001 From: Syres916 <46537884+Syres916@users.noreply.github.com> Date: Sat, 16 Oct 2021 12:20:55 +0100 Subject: [PATCH 01/14] [Sketcher] Minor bugfix to display angle... ...constraint names as per recommendation, see discussion https://forum.freecadweb.org/viewtopic.php?f=3&t=62953 --- src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index 698dadd0a4..c3a89f5c12 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -3193,7 +3193,7 @@ QString ViewProviderSketch::getPresentationString(const Constraint *constraint) // Hide units if user has requested it, is being displayed in the base // units, and the schema being used has a clear base unit in the first // place. Otherwise, display units. - if( iHideUnits ) + if( iHideUnits && constraint->Type != Sketcher::Angle ) { // Only hide the default length unit. Right now there is not an easy way // to get that from the Unit system so we have to manually add it here. @@ -5766,7 +5766,7 @@ Restart: break; SoDatumLabel *asciiText = static_cast(sep->getChild(CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL)); - asciiText->string = SbString(Constr->getPresentationValue().getUserString().toUtf8().constData()); + asciiText->string = SbString( getPresentationString(Constr).toUtf8().constData() ); asciiText->datumtype = SoDatumLabel::ANGLE; asciiText->param1 = Constr->LabelDistance; asciiText->param2 = startangle; From 124d06b7f7966814b9816d2c3064602f80e281ea Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 20 Oct 2021 13:01:11 +0200 Subject: [PATCH 02/14] Mesh: extend API to access edges of a facet via Python --- src/Mod/Mesh/App/AppMesh.cpp | 2 + src/Mod/Mesh/App/CMakeLists.txt | 5 + src/Mod/Mesh/App/Core/Elements.cpp | 19 +++ src/Mod/Mesh/App/Core/Elements.h | 10 ++ src/Mod/Mesh/App/Core/tritritest.h | 2 +- src/Mod/Mesh/App/Edge.cpp | 74 +++++++++++ src/Mod/Mesh/App/Edge.h | 63 ++++++++++ src/Mod/Mesh/App/EdgePy.xml | 90 +++++++++++++ src/Mod/Mesh/App/EdgePyImp.cpp | 194 +++++++++++++++++++++++++++++ src/Mod/Mesh/App/Facet.cpp | 20 +++ src/Mod/Mesh/App/Facet.h | 3 +- src/Mod/Mesh/App/FacetPy.xml | 7 ++ src/Mod/Mesh/App/FacetPyImp.cpp | 13 +- src/Mod/Mesh/App/MeshTestsApp.py | 59 +++++++++ 14 files changed, 558 insertions(+), 3 deletions(-) create mode 100644 src/Mod/Mesh/App/Edge.cpp create mode 100644 src/Mod/Mesh/App/Edge.h create mode 100644 src/Mod/Mesh/App/EdgePy.xml create mode 100644 src/Mod/Mesh/App/EdgePyImp.cpp diff --git a/src/Mod/Mesh/App/AppMesh.cpp b/src/Mod/Mesh/App/AppMesh.cpp index e179e90de6..f398fa9408 100644 --- a/src/Mod/Mesh/App/AppMesh.cpp +++ b/src/Mod/Mesh/App/AppMesh.cpp @@ -33,6 +33,7 @@ #include "Mesh.h" #include "MeshPy.h" #include "MeshPointPy.h" +#include "EdgePy.h" #include "FacetPy.h" #include "MeshFeaturePy.h" #include "FeatureMeshImport.h" @@ -66,6 +67,7 @@ PyMOD_INIT_FUNC(Mesh) // add mesh elements Base::Interpreter().addType(&Mesh::MeshPointPy ::Type,meshModule,"MeshPoint"); + Base::Interpreter().addType(&Mesh::EdgePy ::Type,meshModule,"Edge"); Base::Interpreter().addType(&Mesh::FacetPy ::Type,meshModule,"Facet"); Base::Interpreter().addType(&Mesh::MeshPy ::Type,meshModule,"Mesh"); Base::Interpreter().addType(&Mesh::MeshFeaturePy::Type,meshModule,"Feature"); diff --git a/src/Mod/Mesh/App/CMakeLists.txt b/src/Mod/Mesh/App/CMakeLists.txt index c987d475f1..43351fdd9f 100644 --- a/src/Mod/Mesh/App/CMakeLists.txt +++ b/src/Mod/Mesh/App/CMakeLists.txt @@ -28,12 +28,14 @@ if (BUILD_QT5) ) endif() +generate_from_xml(EdgePy) generate_from_xml(FacetPy) generate_from_xml(MeshFeaturePy) generate_from_xml(MeshPointPy) generate_from_xml(MeshPy) SET(Mesh_XML_SRCS + EdgePy.xml FacetPy.xml MeshFeaturePy.xml MeshPointPy.xml @@ -331,6 +333,9 @@ SET(Mesh_SRCS Exporter.h Importer.cpp Importer.h + Edge.cpp + Edge.h + EdgePyImp.cpp Facet.cpp Facet.h FacetPyImp.cpp diff --git a/src/Mod/Mesh/App/Core/Elements.cpp b/src/Mod/Mesh/App/Core/Elements.cpp index 9366fa1415..4e8cad49d3 100644 --- a/src/Mod/Mesh/App/Core/Elements.cpp +++ b/src/Mod/Mesh/App/Core/Elements.cpp @@ -280,6 +280,25 @@ bool MeshGeomEdge::IntersectWithLine (const Base::Vector3f &rclPt, return dist2 + dist3 <= dist1 + eps; } +bool MeshGeomEdge::IsParallel(const MeshGeomEdge &edge) const +{ + Base::Vector3f r(_aclPoints[1] - _aclPoints[0]); + Base::Vector3f s(edge._aclPoints[1] - edge._aclPoints[0]); + Base::Vector3f n = r.Cross(s); + return n.IsNull(); +} + +bool MeshGeomEdge::IsCollinear(const MeshGeomEdge &edge) const +{ + if (IsParallel(edge)) { + Base::Vector3f r(_aclPoints[1] - _aclPoints[0]); + Base::Vector3f d = edge._aclPoints[0] - _aclPoints[0]; + return d.Cross(r).IsNull(); + } + + return false; +} + bool MeshGeomEdge::IntersectWithEdge (const MeshGeomEdge &edge, Base::Vector3f &res) const { const float eps = 1e-06f; diff --git a/src/Mod/Mesh/App/Core/Elements.h b/src/Mod/Mesh/App/Core/Elements.h index db9fa791ca..a0af29e49c 100644 --- a/src/Mod/Mesh/App/Core/Elements.h +++ b/src/Mod/Mesh/App/Core/Elements.h @@ -198,6 +198,16 @@ public: * Checks if the projection point of \a point lies on the edge. */ bool IsProjectionPointOf(const Base::Vector3f& point) const; + /** + * Checks if the two edges are parallel. + * \note Parallel edges could be collinear. + */ + bool IsParallel(const MeshGeomEdge &edge) const; + /** + * Checks if the two edges are collinear. + * \note Collinear edges always are parallel. + */ + bool IsCollinear(const MeshGeomEdge &edge) const; public: Base::Vector3f _aclPoints[2]; /**< Corner points */ diff --git a/src/Mod/Mesh/App/Core/tritritest.h b/src/Mod/Mesh/App/Core/tritritest.h index 35f34b7536..44d790391f 100644 --- a/src/Mod/Mesh/App/Core/tritritest.h +++ b/src/Mod/Mesh/App/Core/tritritest.h @@ -41,7 +41,7 @@ OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE */ -#include +#include #define FABS(x) ((float)fabs(x)) /* implement as is fastest on your machine */ diff --git a/src/Mod/Mesh/App/Edge.cpp b/src/Mod/Mesh/App/Edge.cpp new file mode 100644 index 0000000000..388b4a69b7 --- /dev/null +++ b/src/Mod/Mesh/App/Edge.cpp @@ -0,0 +1,74 @@ +/*************************************************************************** + * 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., 51 Franklin Street, * + * Fifth Floor, Boston, MA 02110-1301, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +#endif + +#include "Edge.h" +#include "Mesh.h" + +using namespace Mesh; + +Edge::Edge() + : Index(-1) + , Mesh(nullptr) +{ + for (int i=0; i<2; i++) { + PIndex[i] = MeshCore::POINT_INDEX_MAX; + NIndex[i] = MeshCore::FACET_INDEX_MAX; + } +} + +Edge::Edge(const Edge& e) + : MeshCore::MeshGeomEdge(e) + , Index(e.Index) + , Mesh(e.Mesh) +{ + for (int i=0; i<2; i++) { + PIndex[i] = e.PIndex[i]; + NIndex[i] = e.NIndex[i]; + } +} + +Edge::~Edge() +{ +} + +void Edge::operator = (const Edge& e) +{ + MeshCore::MeshGeomEdge::operator = (e); + Mesh = e.Mesh; + Index = e.Index; + for (int i=0; i<2; i++) { + PIndex[i] = e.PIndex[i]; + NIndex[i] = e.NIndex[i]; + } +} + +void Edge::unbound() +{ + Index = -1; + Mesh = nullptr; +} diff --git a/src/Mod/Mesh/App/Edge.h b/src/Mod/Mesh/App/Edge.h new file mode 100644 index 0000000000..0dda23e542 --- /dev/null +++ b/src/Mod/Mesh/App/Edge.h @@ -0,0 +1,63 @@ +/*************************************************************************** + * 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., 51 Franklin Street, * + * Fifth Floor, Boston, MA 02110-1301, USA * + * * + ***************************************************************************/ + + +#ifndef MESH_EDGE_H +#define MESH_EDGE_H + +#include +#include +#include + +#include + +namespace Mesh +{ +// forward declaration +class MeshObject; + +/** The Edge helper class + * The Edge class provides an interface for the EdgePy class for + * convenient access to the Mesh data structure. This class should not be used + * for programming algorithms in C++. Use Mesh Core classes instead! + */ +class MeshExport Edge : public MeshCore::MeshGeomEdge +{ +public: + Edge(); + Edge(const Edge& f); + ~Edge(); + + bool isBound() const {return Index != -1;} + void unbound(); + void operator = (const Edge& f); + + int Index; + MeshCore::PointIndex PIndex[2]; + MeshCore::FacetIndex NIndex[2]; + Base::Reference Mesh; +}; + +} // namespace Mesh + + +#endif // MESH_EDGE_H diff --git a/src/Mod/Mesh/App/EdgePy.xml b/src/Mod/Mesh/App/EdgePy.xml new file mode 100644 index 0000000000..4416925a1d --- /dev/null +++ b/src/Mod/Mesh/App/EdgePy.xml @@ -0,0 +1,90 @@ + + + + + + Edge in a Mesh + Edge in mesh +This is an edge of a facet in a MeshObject. You can get it by e.g. iterating over the facets of a +mesh and calling getEdge(index). + + + + + intersectWithEdge(Edge) -> list +Get a list of intersection points with another edge. + + + + + + isParallel(Edge) -> bool +Checks if the two edges are parallel. + + + + + + isCollinear(Edge) -> bool +Checks if the two edges are collinear. + + + + + + method unbound() +Cut the connection to a MeshObject. The edge becomes +free and is more or less a simple edge. +After calling unbound() no topological operation will +work! + + + + + + The index of this edge of the facet + + + + + + A list of points of the edge + + + + + + The index tuple of point vertices of the mesh this edge is built of + + + + + + The index tuple of neighbour facets of the mesh this edge is adjacent with + + + + + + The length of the edge + + + + + + Bound state of the edge + + + + + diff --git a/src/Mod/Mesh/App/EdgePyImp.cpp b/src/Mod/Mesh/App/EdgePyImp.cpp new file mode 100644 index 0000000000..701595297a --- /dev/null +++ b/src/Mod/Mesh/App/EdgePyImp.cpp @@ -0,0 +1,194 @@ +/*************************************************************************** + * 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., 51 Franklin Street, * + * Fifth Floor, Boston, MA 02110-1301, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#include "Mesh.h" +#include "Edge.h" +#include +#include + +#include +#include +#include + +using namespace Mesh; + +// returns a string which represent the object e.g. when printed in python +std::string EdgePy::representation() const +{ + EdgePy::PointerType ptr = getEdgePtr(); + std::stringstream str; + str << "Edge ("; + str << "(" << ptr->_aclPoints[0].x << ", " << ptr->_aclPoints[0].y << ", " << ptr->_aclPoints[0].z << ", Idx=" << ptr->PIndex[0] << "), "; + str << "(" << ptr->_aclPoints[1].x << ", " << ptr->_aclPoints[1].y << ", " << ptr->_aclPoints[1].z << ", Idx=" << ptr->PIndex[1] << "), "; + str << "Idx=" << ptr->Index << ", (" << ptr->NIndex[0] << ", " << ptr->NIndex[1] << ")"; + str << ")"; + + return str.str(); +} + +PyObject *EdgePy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper +{ + // create a new instance of EdgePy and the Twin object + return new EdgePy(new Edge); +} + +// constructor method +int EdgePy::PyInit(PyObject* args, PyObject* /*kwds*/) +{ + PyObject* pt1 = nullptr; + PyObject* pt2 = nullptr; + if (!PyArg_ParseTuple(args, "|O!O!", &Base::VectorPy::Type, &pt1 + , &Base::VectorPy::Type, &pt2)) + return -1; + + if (pt1) + getEdgePtr()->_aclPoints[0] = Base::convertTo(Py::Vector(pt1, false).toVector()); + if (pt2) + getEdgePtr()->_aclPoints[1] = Base::convertTo(Py::Vector(pt2, false).toVector()); + return 0; +} + +Py::Long EdgePy::getIndex() const +{ + return Py::Long((long) getEdgePtr()->Index); +} + +PyObject* EdgePy::intersectWithEdge(PyObject *args) +{ + PyObject* object; + if (!PyArg_ParseTuple(args, "O!", &EdgePy::Type, &object)) + return nullptr; + EdgePy *edge = static_cast(object); + EdgePy::PointerType edge_ptr = edge->getEdgePtr(); + EdgePy::PointerType this_ptr = this->getEdgePtr(); + Base::Vector3f p; + bool ok = this_ptr->IntersectWithEdge(*edge_ptr, p); + + try { + Py::List sct; + if (ok) { + Py::Tuple pt(3); + pt.setItem(0, Py::Float(p.x)); + pt.setItem(1, Py::Float(p.y)); + pt.setItem(2, Py::Float(p.z)); + sct.append(pt); + } + return Py::new_reference_to(sct); + } + catch (const Py::Exception&) { + return nullptr; + } +} + +PyObject* EdgePy::isParallel(PyObject *args) +{ + PyObject* object; + if (!PyArg_ParseTuple(args, "O!", &EdgePy::Type, &object)) + return nullptr; + EdgePy *edge = static_cast(object); + EdgePy::PointerType edge_ptr = edge->getEdgePtr(); + EdgePy::PointerType this_ptr = this->getEdgePtr(); + bool ok = this_ptr->IsParallel(*edge_ptr); + return Py::new_reference_to(Py::Boolean(ok)); +} + +PyObject* EdgePy::isCollinear(PyObject *args) +{ + PyObject* object; + if (!PyArg_ParseTuple(args, "O!", &EdgePy::Type, &object)) + return nullptr; + EdgePy *edge = static_cast(object); + EdgePy::PointerType edge_ptr = edge->getEdgePtr(); + EdgePy::PointerType this_ptr = this->getEdgePtr(); + bool ok = this_ptr->IsCollinear(*edge_ptr); + return Py::new_reference_to(Py::Boolean(ok)); +} + +PyObject* EdgePy::unbound(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + getEdgePtr()->unbound(); + Py_Return; +} + +Py::Boolean EdgePy::getBound() const +{ + return Py::Boolean(getEdgePtr()->isBound()); +} + +Py::List EdgePy::getPoints() const +{ + EdgePy::PointerType edge = this->getEdgePtr(); + + Py::List pts; + for (int i=0; i<2; i++) { + Py::Tuple pt(3); + pt.setItem(0, Py::Float(edge->_aclPoints[i].x)); + pt.setItem(1, Py::Float(edge->_aclPoints[i].y)); + pt.setItem(2, Py::Float(edge->_aclPoints[i].z)); + pts.append(pt); + } + + return pts; +} + +Py::Tuple EdgePy::getPointIndices() const +{ + EdgePy::PointerType edge = this->getEdgePtr(); + + Py::Tuple idxTuple(2); + for (int i=0; i<2; i++) { + idxTuple.setItem(i, Py::Long(edge->PIndex[i])); + } + return idxTuple; +} + +Py::Tuple EdgePy::getNeighbourIndices() const +{ + EdgePy::PointerType edge = this->getEdgePtr(); + + Py::Tuple idxTuple(2); + for (int i=0; i<2; i++) { + idxTuple.setItem(i, Py::Long(edge->NIndex[i])); + } + return idxTuple; +} + +Py::Float EdgePy::getLength() const +{ + EdgePy::PointerType edge = this->getEdgePtr(); + return Py::Float(Base::Distance(edge->_aclPoints[0], edge->_aclPoints[1])); +} + +PyObject *EdgePy::getCustomAttributes(const char* /*attr*/) const +{ + return nullptr; +} + +int EdgePy::setCustomAttributes(const char* /*attr*/, PyObject * /*obj*/) +{ + return 0; +} diff --git a/src/Mod/Mesh/App/Facet.cpp b/src/Mod/Mesh/App/Facet.cpp index 73ff133527..01119c1a63 100644 --- a/src/Mod/Mesh/App/Facet.cpp +++ b/src/Mod/Mesh/App/Facet.cpp @@ -69,3 +69,23 @@ void Facet::operator = (const Facet& f) NIndex[i] = f.NIndex[i]; } } + +Edge Facet::getEdge(int index) const +{ + index = index % 3; + Edge edge; + // geometric coordinates + edge._aclPoints[0] = this->_aclPoints[index]; + edge._aclPoints[1] = this->_aclPoints[(index + 1) % 3]; + + // indices + edge.Index = index; + edge.PIndex[0] = this->PIndex[index]; + edge.PIndex[1] = this->PIndex[(index + 1) % 3]; + edge.NIndex[0] = this->Index; + edge.NIndex[1] = this->NIndex[index]; + edge._bBorder = (this->NIndex[index] == MeshCore::FACET_INDEX_MAX); + + edge.Mesh = this->Mesh; + return edge; +} diff --git a/src/Mod/Mesh/App/Facet.h b/src/Mod/Mesh/App/Facet.h index 9fb8224605..893670fe76 100644 --- a/src/Mod/Mesh/App/Facet.h +++ b/src/Mod/Mesh/App/Facet.h @@ -28,7 +28,7 @@ #include #include -#include "Core/Elements.h" +#include namespace Mesh { @@ -49,6 +49,7 @@ public: bool isBound() const {return Index != MeshCore::FACET_INDEX_MAX;} void operator = (const Facet& f); + Edge getEdge(int) const; MeshCore::FacetIndex Index; MeshCore::PointIndex PIndex[3]; diff --git a/src/Mod/Mesh/App/FacetPy.xml b/src/Mod/Mesh/App/FacetPy.xml index c20f970447..1b1295816e 100644 --- a/src/Mod/Mesh/App/FacetPy.xml +++ b/src/Mod/Mesh/App/FacetPy.xml @@ -56,6 +56,13 @@ The two angles are given in radian. + + + getEdge(int) -> Edge +Returns the edge of the facet. + + + The index of this facet in the MeshObject diff --git a/src/Mod/Mesh/App/FacetPyImp.cpp b/src/Mod/Mesh/App/FacetPyImp.cpp index 4610b17e95..899e7a4828 100644 --- a/src/Mod/Mesh/App/FacetPyImp.cpp +++ b/src/Mod/Mesh/App/FacetPyImp.cpp @@ -27,6 +27,7 @@ #include "Facet.h" #include #include +#include #include #include @@ -78,6 +79,16 @@ PyObject* FacetPy::unbound(PyObject *args) Py_Return; } +PyObject* FacetPy::getEdge(PyObject *args) +{ + int index; + if (!PyArg_ParseTuple(args, "i", &index)) + return nullptr; + + Edge edge = getFacetPtr()->getEdge(index); + return new EdgePy(new Edge(edge)); +} + Py::Long FacetPy::getIndex() const { return Py::Long((long) getFacetPtr()->Index); @@ -85,7 +96,7 @@ Py::Long FacetPy::getIndex() const Py::Boolean FacetPy::getBound() const { - return Py::Boolean(getFacetPtr()->Index != UINT_MAX); + return Py::Boolean(getFacetPtr()->isBound()); } Py::Object FacetPy::getNormal() const diff --git a/src/Mod/Mesh/App/MeshTestsApp.py b/src/Mod/Mesh/App/MeshTestsApp.py index 01712ef519..d467b1e016 100644 --- a/src/Mod/Mesh/App/MeshTestsApp.py +++ b/src/Mod/Mesh/App/MeshTestsApp.py @@ -252,6 +252,65 @@ class MeshGeoTestCases(unittest.TestCase): res = f1.intersect(f2) self.assertTrue(len(res) == 2) + def testIntersectionOfIntersectingEdges(self): + self.planarMesh.append( [0.,10.,10.] ) + self.planarMesh.append( [10.,0.,10.] ) + self.planarMesh.append( [10.,10.,10.] ) + self.planarMesh.append( [6.,8.,10.] ) + self.planarMesh.append( [16.,8.,10.] ) + self.planarMesh.append( [6.,18.,10.] ) + planarMeshObject = Mesh.Mesh(self.planarMesh) + + edge1 = planarMeshObject.Facets[0].getEdge(2) + edge2 = planarMeshObject.Facets[1].getEdge(2) + res = edge1.intersectWithEdge(edge2) + self.assertTrue(len(res) == 1) + self.assertEqual(res[0][0], 6.0) + self.assertEqual(res[0][1], 10.0) + self.assertEqual(res[0][2], 10.0) + + def testIntersectionOfParallelEdges(self): + self.planarMesh.append( [0.,10.,10.] ) + self.planarMesh.append( [10.,0.,10.] ) + self.planarMesh.append( [10.,10.,10.] ) + self.planarMesh.append( [6.,8.,10.] ) + self.planarMesh.append( [16.,8.,10.] ) + self.planarMesh.append( [6.,18.,10.] ) + planarMeshObject = Mesh.Mesh(self.planarMesh) + + edge1 = planarMeshObject.Facets[0].getEdge(2) + edge2 = planarMeshObject.Facets[1].getEdge(0) + res = edge1.intersectWithEdge(edge2) + self.assertTrue(len(res) == 0) + + def testIntersectionOfCollinearEdges(self): + self.planarMesh.append( [0.,0.,0.] ) + self.planarMesh.append( [6.,0.,0.] ) + self.planarMesh.append( [3.,4.,0.] ) + self.planarMesh.append( [7.,0.,0.] ) + self.planarMesh.append( [13.,0.,0.] ) + self.planarMesh.append( [10.,4.,0.] ) + planarMeshObject = Mesh.Mesh(self.planarMesh) + + edge1 = planarMeshObject.Facets[0].getEdge(0) + edge2 = planarMeshObject.Facets[1].getEdge(0) + res = edge1.intersectWithEdge(edge2) + self.assertTrue(len(res) == 0) + + def testIntersectionOfWarpedEdges(self): + self.planarMesh.append( [0.,0.,0.] ) + self.planarMesh.append( [6.,0.,0.] ) + self.planarMesh.append( [3.,4.,0.] ) + self.planarMesh.append( [2.,2.,1.] ) + self.planarMesh.append( [8.,2.,1.] ) + self.planarMesh.append( [5.,6.,1.] ) + planarMeshObject = Mesh.Mesh(self.planarMesh) + + edge1 = planarMeshObject.Facets[0].getEdge(1) + edge2 = planarMeshObject.Facets[1].getEdge(0) + res = edge1.intersectWithEdge(edge2) + self.assertTrue(len(res) == 0) + def testSelfIntersection(self): s = b"""solid Simple facet normal 0.0e0 0.0e0 1.0e1 From 54ea57259650b26a72618de138ea05955fa3f00c Mon Sep 17 00:00:00 2001 From: Roy-043 <70520633+Roy-043@users.noreply.github.com> Date: Wed, 20 Oct 2021 13:25:18 +0200 Subject: [PATCH 03/14] Draft: Fix TeighaFileConverter tooltip The TeighaFileConverter preference is now used for 3 DWG converters. The tooltip needs to reflect that. --- src/Mod/Draft/Resources/ui/preferences-dwg.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Draft/Resources/ui/preferences-dwg.ui b/src/Mod/Draft/Resources/ui/preferences-dwg.ui index 4d97ab91cc..b52ea78448 100644 --- a/src/Mod/Draft/Resources/ui/preferences-dwg.ui +++ b/src/Mod/Draft/Resources/ui/preferences-dwg.ui @@ -82,7 +82,7 @@ - The path to your ODA (formerly Teigha) File Converter executable + The path to your DWG file converter executable TeighaFileConverter From 4031e851206c0a468c6fdc2c24c8bc012d82f7f6 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 20 Oct 2021 08:06:05 -0500 Subject: [PATCH 04/14] [Sketcher] Workaround for Elements theme issue After merging the change to the Constraint status label in 1acb74072, the TaskSketcherElements would sometimes be reduced in size to just a line or two, if multiple task views were expanded and a stylesheet was enabled. This commit introduces a minimum size to the TSE to prevent that from occurring. --- src/Mod/Sketcher/Gui/TaskSketcherElements.ui | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.ui b/src/Mod/Sketcher/Gui/TaskSketcherElements.ui index 777e9ce69e..d2321f6930 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.ui +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.ui @@ -10,6 +10,12 @@ 401 + + + 0 + 400 + + Form From 508d113e2187c39f99d69c3e4694008a570a3a13 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 20 Oct 2021 19:42:39 +0200 Subject: [PATCH 05/14] Mesh: improve MeshGeomFacet::IntersectWithFacet --- src/Mod/Mesh/App/Core/Elements.cpp | 74 ++++++++++++++++++------------ src/Mod/Mesh/App/Core/Elements.h | 4 ++ src/Mod/Mesh/App/MeshTestsApp.py | 53 +++++++++++++++++++++ 3 files changed, 101 insertions(+), 30 deletions(-) diff --git a/src/Mod/Mesh/App/Core/Elements.cpp b/src/Mod/Mesh/App/Core/Elements.cpp index 4e8cad49d3..bd434698ce 100644 --- a/src/Mod/Mesh/App/Core/Elements.cpp +++ b/src/Mod/Mesh/App/Core/Elements.cpp @@ -1036,6 +1036,15 @@ void MeshGeomFacet::SubSample (float fStep, std::vector &rclPoin rclPoints.insert(rclPoints.end(), clPoints.begin(), clPoints.end()); } +bool MeshGeomFacet::IsCoplanar(const MeshGeomFacet &facet) const +{ + const float eps = 1e-06f; + const float unit = 0.9995f; + float mult = fabs(this->GetNormal() * facet.GetNormal()); + float dist = fabs(DistancePlaneToPoint(facet._aclPoints[0])); + return (mult >= unit) && (dist <= eps); +} + /** * Fast Triangle-Triangle Intersection Test by Tomas Moeller * http://www.acm.org/jgt/papers/Moller97/tritri.html @@ -1068,6 +1077,39 @@ int MeshGeomFacet::IntersectWithFacet (const MeshGeomFacet& rclFacet, Base::Vector3f& rclPt0, Base::Vector3f& rclPt1) const { + // Note: tri_tri_intersect_with_isection() does not return line of + // intersection when triangles are coplanar. See tritritest.h:18 and 658. + if (IsCoplanar(rclFacet)) { + // Since tri_tri_intersect_with_isection may return garbage values try to get + // sensible values with edge/edge intersections + std::vector intersections; + for (short i=0; i<3; i++) { + MeshGeomEdge edge1 = GetEdge(i); + for (short j=0; j<3; j++) { + MeshGeomEdge edge2 = rclFacet.GetEdge(j); + Base::Vector3f point; + if (edge1.IntersectWithEdge(edge2, point)) { + intersections.push_back(point); + } + } + } + + // If triangles overlap there can be more than two intersection points + // In that case use any two of them. + if (intersections.size() >= 2) { + rclPt0 = intersections[0]; + rclPt1 = intersections[1]; + return 2; + } + else if (intersections.size() == 1) { + rclPt0 = intersections[0]; + rclPt1 = intersections[0]; + return 1; + } + + return 0; + } + float V[3][3], U[3][3]; int coplanar = 0; float isectpt1[3], isectpt2[3]; @@ -1089,45 +1131,17 @@ int MeshGeomFacet::IntersectWithFacet (const MeshGeomFacet& rclFacet, rclPt0.x = isectpt1[0]; rclPt0.y = isectpt1[1]; rclPt0.z = isectpt1[2]; rclPt1.x = isectpt2[0]; rclPt1.y = isectpt2[1]; rclPt1.z = isectpt2[2]; - // Note: tri_tri_intersect_with_isection() does not return line of - // intersection when triangles are coplanar. See tritritest.h:18 and 658. - if (coplanar) { - // Since tri_tri_intersect_with_isection may return garbage values try to get - // sensible values with edge/edge intersections - std::vector intersections; - for (short i=0; i<3; i++) { - MeshGeomEdge edge1 = GetEdge(i); - for (short j=0; j<3; j++) { - MeshGeomEdge edge2 = rclFacet.GetEdge(j); - Base::Vector3f point; - if (edge1.IntersectWithEdge(edge2, point)) { - intersections.push_back(point); - } - } - } - - // If triangles overlap there can be more than two intersection points - // In that case use any two of them. - if (intersections.size() >= 2) { - rclPt0 = intersections[0]; - rclPt1 = intersections[1]; - } - else if (intersections.size() == 1) { - rclPt0 = intersections[0]; - rclPt1 = intersections[0]; - } - return 2; - } - // With extremely acute-angled triangles it may happen that the algorithm // claims an intersection but the intersection points are far outside the // model. So, a plausibility check is to verify that the intersection points // are inside the bounding boxes of both triangles. Base::BoundBox3f box1 = this->GetBoundBox(); + box1.Enlarge(0.001f); if (!box1.IsInBox(rclPt0) || !box1.IsInBox(rclPt1)) return 0; Base::BoundBox3f box2 = rclFacet.GetBoundBox(); + box2.Enlarge(0.001f); if (!box2.IsInBox(rclPt0) || !box2.IsInBox(rclPt1)) return 0; diff --git a/src/Mod/Mesh/App/Core/Elements.h b/src/Mod/Mesh/App/Core/Elements.h index a0af29e49c..513c4adacb 100644 --- a/src/Mod/Mesh/App/Core/Elements.h +++ b/src/Mod/Mesh/App/Core/Elements.h @@ -546,6 +546,10 @@ public: /** Apply a transformation on the triangle. */ void Transform(const Base::Matrix4D&); + /** + * Checks if the two triangles are coplanar. + */ + bool IsCoplanar(const MeshGeomFacet &facet) const; protected: Base::Vector3f _clNormal; /**< Normal of the facet. */ diff --git a/src/Mod/Mesh/App/MeshTestsApp.py b/src/Mod/Mesh/App/MeshTestsApp.py index d467b1e016..4c32fc6570 100644 --- a/src/Mod/Mesh/App/MeshTestsApp.py +++ b/src/Mod/Mesh/App/MeshTestsApp.py @@ -226,6 +226,59 @@ class MeshGeoTestCases(unittest.TestCase): res=f1.intersect(f2) self.assertTrue(len(res) == 0) + def testIntersectionOfTransformedMesh(self): + self.planarMesh.append( [0.0,10.0,10.0] ) + self.planarMesh.append( [10.0,0.0,10.0] ) + self.planarMesh.append( [10.0,10.0,10.0] ) + self.planarMesh.append( [6.0,8.0,10.0] ) + self.planarMesh.append( [16.0,8.0,10.0] ) + self.planarMesh.append( [6.0,18.0,10.0] ) + planarMeshObject = Mesh.Mesh(self.planarMesh) + + mat = Base.Matrix() + mat.rotateX(1.0) + mat.rotateY(1.0) + mat.rotateZ(1.0) + planarMeshObject.transformGeometry(mat) + + f1 = planarMeshObject.Facets[0] + f2 = planarMeshObject.Facets[1] + res=f1.intersect(f2) + self.assertEqual(len(res), 2) + + def testIntersectionOfParallelTriangles(self): + self.planarMesh.append( [0.0,10.0,10.0] ) + self.planarMesh.append( [10.0,0.0,10.0] ) + self.planarMesh.append( [10.0,10.0,10.0] ) + self.planarMesh.append( [6.0,8.0,10.1] ) + self.planarMesh.append( [16.0,8.0,10.1] ) + self.planarMesh.append( [6.0,18.0,10.1] ) + planarMeshObject = Mesh.Mesh(self.planarMesh) + + mat = Base.Matrix() + mat.rotateX(1.0) + mat.rotateY(1.0) + mat.rotateZ(1.0) + planarMeshObject.transformGeometry(mat) + + f1 = planarMeshObject.Facets[0] + f2 = planarMeshObject.Facets[1] + res=f1.intersect(f2) + self.assertTrue(len(res) == 0) + + def testIntersectionOnEdge(self): + self.planarMesh.append( [5.0, -1.9371663331985474, 0.49737977981567383] ) + self.planarMesh.append( [4.0, -1.9371663331985474, 0.49737977981567383] ) + self.planarMesh.append( [5.0, -1.9842294454574585, 0.25066646933555603] ) + self.planarMesh.append( [4.6488823890686035, -1.7827962636947632, 0.4577442705631256] ) + self.planarMesh.append( [4.524135112762451, -2.0620131492614746, 0.5294350385665894] ) + self.planarMesh.append( [4.6488823890686035, -1.8261089324951172, 0.23069120943546295] ) + planarMeshObject = Mesh.Mesh(self.planarMesh) + f1 = planarMeshObject.Facets[0] + f2 = planarMeshObject.Facets[1] + res = f1.intersect(f2) + self.assertEqual(len(res), 2) + def testIntersectionCoplanar(self): self.planarMesh.append( [0.,10.,10.] ) self.planarMesh.append( [10.,0.,10.] ) From cac50da55ba893450914afe7ada5c44571b007b5 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 20 Oct 2021 21:57:51 +0200 Subject: [PATCH 06/14] Gui: move handling of failed document saving to a function to reduce code duplication --- src/Gui/Document.cpp | 90 +++++++++++++++++++++++--------------------- src/Gui/Document.h | 2 + 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/src/Gui/Document.cpp b/src/Gui/Document.cpp index c4fdf1a943..589deecede 100644 --- a/src/Gui/Document.cpp +++ b/src/Gui/Document.cpp @@ -405,13 +405,16 @@ bool Document::setEdit(Gui::ViewProvider* p, int ModNum, const char *subname) d->_editViewProviderParent = vp; d->_editSubElement.clear(); d->_editSubname.clear(); - if(subname) { + + if (subname) { const char *element = Data::ComplexGeoData::findElementName(subname); - if(element) { + if (element) { d->_editSubname = std::string(subname,element-subname); d->_editSubElement = element; - }else + } + else { d->_editSubname = subname; + } } auto sobjs = obj->getSubObjectList(subname); @@ -1096,6 +1099,31 @@ static bool checkCanonicalPath(const std::map &docs) return ret == QMessageBox::Yes; } +bool Document::askIfSavingFailed(const QString& error) +{ + int ret = QMessageBox::question( + getMainWindow(), + QObject::tr("Could not save document"), + QObject::tr("There was an issue trying to save the file. " + "This may be because some of the parent folders do not exist, " + "or you do not have sufficient permissions, " + "or for other reasons. Error details:\n\n\"%1\"\n\n" + "Would you like to save the file with a different name?") + .arg(error), + QMessageBox::Yes, QMessageBox::No); + + if (ret == QMessageBox::No) { + // TODO: Understand what exactly is supposed to be returned here + getMainWindow()->showMessage(QObject::tr("Saving aborted"), 2000); + return false; + } + else if (ret == QMessageBox::Yes) { + return saveAs(); + } + + return false; +} + /// Save the document bool Document::save(void) { @@ -1105,7 +1133,7 @@ bool Document::save(void) std::map dmap; try { docs = getDocument()->getDependentDocuments(); - for(auto it=docs.begin(); it!=docs.end();) { + for (auto it=docs.begin(); it!=docs.end();) { App::Document *doc = *it; if (doc == getDocument()) { dmap[doc] = doc->mustExecute(); @@ -1123,18 +1151,21 @@ bool Document::save(void) dmap[doc] = doc->mustExecute(); ++it; } - }catch(const Base::RuntimeError &e) { + } + catch (const Base::RuntimeError &e) { FC_ERR(e.what()); docs = {getDocument()}; dmap.clear(); dmap[getDocument()] = getDocument()->mustExecute(); } - if(docs.size()>1) { + + if (docs.size()>1) { int ret = QMessageBox::question(getMainWindow(), QObject::tr("Save dependent files"), QObject::tr("The file contains external dependencies. " "Do you want to save the dependent files, too?"), QMessageBox::Yes,QMessageBox::No); + if (ret != QMessageBox::Yes) { docs = {getDocument()}; dmap.clear(); @@ -1147,35 +1178,22 @@ bool Document::save(void) Gui::WaitCursor wc; // save all documents - for(auto doc : docs) { + for (auto doc : docs) { // Changed 'mustExecute' status may be triggered by saving external document - if(!dmap[doc] && doc->mustExecute()) { + if (!dmap[doc] && doc->mustExecute()) { App::AutoTransaction trans("Recompute"); Command::doCommand(Command::Doc,"App.getDocument(\"%s\").recompute()",doc->getName()); } + Command::doCommand(Command::Doc,"App.getDocument(\"%s\").save()",doc->getName()); auto gdoc = Application::Instance->getDocument(doc); - if(gdoc) gdoc->setModified(false); + if (gdoc) + gdoc->setModified(false); } } catch (const Base::FileException& e) { - int ret = QMessageBox::question( - getMainWindow(), - QObject::tr("Could not save document"), - QObject::tr("There was an issue trying to save the file. " - "This may be because some of the parent folders do not exist, " - "or you do not have sufficient permissions, " - "or for other reasons. Error details:\n\n\"%1\"\n\n" - "Would you like to save the file with a different name?") - .arg(QString::fromUtf8(e.what())), - QMessageBox::Yes, QMessageBox::No); - if (ret == QMessageBox::No) { - // TODO: Understand what exactly is supposed to be returned here - getMainWindow()->showMessage(QObject::tr("Saving aborted"), 2000); - return false; - } else if (ret == QMessageBox::Yes) { - return saveAs(); - } + e.ReportException(); + return askIfSavingFailed(QString::fromUtf8(e.what())); } catch (const Base::Exception& e) { QMessageBox::critical(getMainWindow(), QObject::tr("Saving document failed"), @@ -1198,6 +1216,7 @@ bool Document::saveAs(void) QString fn = FileDialog::getSaveFileName(getMainWindow(), QObject::tr("Save %1 Document").arg(exe), QString::fromUtf8(getDocument()->FileName.getValue()), QString::fromLatin1("%1 %2 (*.FCStd)").arg(exe).arg(QObject::tr("Document"))); + if (!fn.isEmpty()) { QFileInfo fi; fi.setFile(fn); @@ -1217,23 +1236,8 @@ bool Document::saveAs(void) getMainWindow()->appendRecentFile(fi.filePath()); } catch (const Base::FileException& e) { - int ret = QMessageBox::question( - getMainWindow(), - QObject::tr("Could not save document"), - QObject::tr("There was an issue trying to save the file. " - "This may be because some of the parent folders do not exist, " - "or you do not have sufficient permissions, " - "or for other reasons. Error details:\n\n\"%1\"\n\n" - "Would you like to save the file with a different name?") - .arg(QString::fromUtf8(e.what())), - QMessageBox::Yes, QMessageBox::No); - if (ret == QMessageBox::No) { - // TODO: Understand what exactly is supposed to be returned here - getMainWindow()->showMessage(QObject::tr("Saving aborted"), 2000); - return false; - } else if (ret == QMessageBox::Yes) { - return saveAs(); - } + e.ReportException(); + return askIfSavingFailed(QString::fromUtf8(e.what())); } catch (const Base::Exception& e) { QMessageBox::critical(getMainWindow(), QObject::tr("Saving document failed"), diff --git a/src/Gui/Document.h b/src/Gui/Document.h index b24887aaf8..a7884670ed 100644 --- a/src/Gui/Document.h +++ b/src/Gui/Document.h @@ -306,6 +306,8 @@ private: /// Check other documents for the same transaction ID bool checkTransactionID(bool undo, int iSteps); + /// Ask for user interaction if saving has failed + bool askIfSavingFailed(const QString&); struct DocumentP* d; static int _iDocCount; From 370a120f75254711f70fefca52ee243935a3abf0 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 20 Oct 2021 22:21:52 +0200 Subject: [PATCH 07/14] Gui: [skip ci] improve whitespaces --- src/Gui/View3DPy.cpp | 102 ++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 49 deletions(-) diff --git a/src/Gui/View3DPy.cpp b/src/Gui/View3DPy.cpp index 50e8325c3a..2ca90e415f 100644 --- a/src/Gui/View3DPy.cpp +++ b/src/Gui/View3DPy.cpp @@ -243,7 +243,7 @@ PyObject *View3DInventorPy::method_varargs_ext_handler(PyObject *_self_and_name_ catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } } @@ -307,7 +307,7 @@ Py::Object View3DInventorPy::message(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } return Py::None(); @@ -328,7 +328,7 @@ Py::Object View3DInventorPy::fitAll(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } return Py::None(); @@ -474,7 +474,7 @@ Py::Object View3DInventorPy::viewBottom(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -495,7 +495,7 @@ Py::Object View3DInventorPy::viewFront(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -516,7 +516,7 @@ Py::Object View3DInventorPy::viewLeft(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -537,7 +537,7 @@ Py::Object View3DInventorPy::viewRear(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -558,7 +558,7 @@ Py::Object View3DInventorPy::viewRight(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -579,7 +579,7 @@ Py::Object View3DInventorPy::viewTop(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -600,7 +600,7 @@ Py::Object View3DInventorPy::viewIsometric(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -621,7 +621,7 @@ Py::Object View3DInventorPy::viewDimetric(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -642,7 +642,7 @@ Py::Object View3DInventorPy::viewTrimetric(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -733,7 +733,7 @@ Py::Object View3DInventorPy::viewDefaultOrientation(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -759,7 +759,7 @@ Py::Object View3DInventorPy::viewRotateLeft(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -785,7 +785,7 @@ Py::Object View3DInventorPy::viewRotateRight(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -806,7 +806,7 @@ Py::Object View3DInventorPy::zoomIn(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -827,7 +827,7 @@ Py::Object View3DInventorPy::zoomOut(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -1073,7 +1073,7 @@ Py::Object View3DInventorPy::getCamera(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } } @@ -1092,7 +1092,7 @@ Py::Object View3DInventorPy::getViewDirection(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } } @@ -1126,7 +1126,7 @@ Py::Object View3DInventorPy::setViewDirection(const Py::Tuple& args) catch (const std::exception& e) { throw Py::RuntimeError(e.what()); } - catch(...) { + catch (...) { throw Py::RuntimeError("Unknown C++ exception"); } @@ -1415,23 +1415,23 @@ Py::Object View3DInventorPy::getObjectInfo(const Py::Tuple& args) Gui::Document* doc = _view->getViewer()->getDocument(); ViewProvider *vp = doc ? doc->getViewProviderByPathFromHead(Point->getPath()) : _view->getViewer()->getViewProviderByPath(Point->getPath()); - if(vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { - if(!vp->isSelectable()) + if (vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { + if (!vp->isSelectable()) return ret; ViewProviderDocumentObject* vpd = static_cast(vp); - if(vp->useNewSelectionModel()) { + if (vp->useNewSelectionModel()) { std::string subname; - if(!vp->getElementPicked(Point,subname)) + if (!vp->getElementPicked(Point,subname)) return ret; auto obj = vpd->getObject(); - if(!obj) + if (!obj) return ret; - if(subname.size()) { + if (subname.size()) { std::pair elementName; auto sobj = App::GeoFeature::resolveElement(obj,subname.c_str(),elementName); - if(!sobj) + if (!sobj) return ret; - if(sobj!=obj) { + if (sobj != obj) { dict.setItem("ParentObject",Py::Object(obj->getPyObject(),true)); dict.setItem("SubName",Py::String(subname)); obj = sobj; @@ -1443,7 +1443,8 @@ Py::Object View3DInventorPy::getObjectInfo(const Py::Tuple& args) dict.setItem("Object", Py::String(obj->getNameInDocument())); dict.setItem("Component",Py::String(subname)); - } else { + } + else { dict.setItem("Document", Py::String(vpd->getObject()->getDocument()->getName())); dict.setItem("Object", @@ -1530,19 +1531,19 @@ Py::Object View3DInventorPy::getObjectsInfo(const Py::Tuple& args) if(!vp->isSelectable()) continue; ViewProviderDocumentObject* vpd = static_cast(vp); - if(vp->useNewSelectionModel()) { + if (vp->useNewSelectionModel()) { std::string subname; - if(!vp->getElementPicked(point,subname)) + if (!vp->getElementPicked(point,subname)) continue; auto obj = vpd->getObject(); - if(!obj) + if (!obj) continue; - if(subname.size()) { + if (subname.size()) { std::pair elementName; auto sobj = App::GeoFeature::resolveElement(obj,subname.c_str(),elementName); - if(!sobj) + if (!sobj) continue; - if(sobj!=obj) { + if (sobj != obj) { dict.setItem("ParentObject",Py::Object(obj->getPyObject(),true)); dict.setItem("SubName",Py::String(subname)); obj = sobj; @@ -1554,7 +1555,8 @@ Py::Object View3DInventorPy::getObjectsInfo(const Py::Tuple& args) dict.setItem("Object", Py::String(obj->getNameInDocument())); dict.setItem("Component",Py::String(subname)); - } else { + } + else { dict.setItem("Document", Py::String(vpd->getObject()->getDocument()->getName())); dict.setItem("Object", @@ -2492,21 +2494,23 @@ Py::Object View3DInventorPy::removeDraggerCallback(const Py::Tuple& args) Py::Object View3DInventorPy::setActiveObject(const Py::Tuple& args) { - PyObject* docObject = Py_None; - char* name; + PyObject* docObject = Py_None; + char* name; char *subname = 0; if (!PyArg_ParseTuple(args.ptr(), "s|Os", &name, &docObject, &subname)) - throw Py::Exception(); + throw Py::Exception(); - if (docObject == Py_None) - _view->setActiveObject(0, name); - else{ - if(!PyObject_TypeCheck(docObject, &App::DocumentObjectPy::Type)) + if (docObject == Py_None) { + _view->setActiveObject(0, name); + } + else { + if (!PyObject_TypeCheck(docObject, &App::DocumentObjectPy::Type)) throw Py::TypeError("Expect the second argument to be a document object or None"); - App::DocumentObject* obj = static_cast(docObject)->getDocumentObjectPtr(); - _view->setActiveObject(obj, name, subname); - } - return Py::None(); + App::DocumentObject* obj = static_cast(docObject)->getDocumentObjectPtr(); + _view->setActiveObject(obj, name, subname); + } + + return Py::None(); } Py::Object View3DInventorPy::getActiveObject(const Py::Tuple& args) @@ -2519,10 +2523,10 @@ Py::Object View3DInventorPy::getActiveObject(const Py::Tuple& args) App::DocumentObject *parent = 0; std::string subname; App::DocumentObject* obj = _view->getActiveObject(name,&parent,&subname); - if(!obj) + if (!obj) return Py::None(); - if(PyObject_IsTrue(resolve)) + if (PyObject_IsTrue(resolve)) return Py::asObject(obj->getPyObject()); return Py::TupleN( From 503a2ff6cdbc43140b8d6887d9fd0a6598db40bc Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 20 Oct 2021 22:33:50 +0200 Subject: [PATCH 08/14] Gui: [skip ci] improve whitespaces --- src/Gui/ActiveObjectList.cpp | 94 ++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 41 deletions(-) diff --git a/src/Gui/ActiveObjectList.cpp b/src/Gui/ActiveObjectList.cpp index aff4d1b06b..2d7bf2184d 100644 --- a/src/Gui/ActiveObjectList.cpp +++ b/src/Gui/ActiveObjectList.cpp @@ -44,93 +44,107 @@ App::DocumentObject *ActiveObjectList::getObject(const ObjectInfo &info, bool re App::DocumentObject **parent, std::string *subname) const { - if(parent) *parent = info.obj; - if(subname) *subname = info.subname; + if (parent) + *parent = info.obj; + if (subname) + *subname = info.subname; auto obj = info.obj; - if(!obj || !obj->getNameInDocument()) - return 0; - if(info.subname.size()) { + if (!obj || !obj->getNameInDocument()) + return nullptr; + if (!info.subname.empty()) { obj = obj->getSubObject(info.subname.c_str()); - if(!obj) - return 0; + if (!obj) + return nullptr; } - return resolve?obj->getLinkedObject(true):obj; + + return resolve ? obj->getLinkedObject(true) : obj; } void ActiveObjectList::setHighlight(const ObjectInfo &info, HighlightMode mode, bool enable) { - auto obj = getObject(info,false); - if(!obj) return; + auto obj = getObject(info, false); + if (!obj) + return; auto vp = dynamic_cast(Application::Instance->getViewProvider(obj)); - if(!vp) return; + if (!vp) + return; if (TreeParams::Instance()->TreeActiveAutoExpand()) { vp->getDocument()->signalExpandObject(*vp, enable ? TreeItemMode::ExpandPath : TreeItemMode::CollapseItem, info.obj, info.subname.c_str()); } - vp->getDocument()->signalHighlightObject(*vp, mode,enable,info.obj,info.subname.c_str()); + vp->getDocument()->signalHighlightObject(*vp, mode, enable, info.obj, info.subname.c_str()); } Gui::ActiveObjectList::ObjectInfo Gui::ActiveObjectList::getObjectInfo(App::DocumentObject *obj, const char *subname) const { ObjectInfo info; - info.obj = 0; - if(!obj || !obj->getNameInDocument()) + info.obj = nullptr; + if (!obj || !obj->getNameInDocument()) return info; - if(subname) { + + if (subname) { info.obj = obj; info.subname = subname; - }else{ + } + else { // If the input object is not from this document, it must be brought in // by some link type object of this document. We only accept the object // if we can find such object in the current selection. auto sels = Gui::Selection().getSelection(_Doc->getDocument()->getName(),false); - for(auto &sel : sels) { - if(sel.pObject == obj || sel.pObject->getLinkedObject(true)==obj) { + for (auto &sel : sels) { + if (sel.pObject == obj || sel.pObject->getLinkedObject(true)==obj) { info.obj = sel.pObject; break; } - for(auto dot=strchr(sel.SubName,'.');dot;dot=strchr(dot+1,'.')) { + + for (auto dot=strchr(sel.SubName,'.');dot;dot=strchr(dot+1,'.')) { std::string subname(sel.SubName,dot-sel.SubName+1); auto sobj = sel.pObject->getSubObject(subname.c_str()); - if(!sobj) break; - if(sobj == obj || sobj->getLinkedObject(true) == obj) { + if (!sobj) + break; + if (sobj == obj || sobj->getLinkedObject(true) == obj) { info.obj = sel.pObject; info.subname = subname; break; } } - if(info.obj) break; + + if (info.obj) + break; } - if(!info.obj && obj->getDocument()==_Doc->getDocument()) + + if (!info.obj && obj->getDocument()==_Doc->getDocument()) info.obj = obj; } return info; } bool Gui::ActiveObjectList::hasObject(App::DocumentObject *obj, - const char *name, const char *subname) const + const char *name, const char *subname) const { auto it = _ObjectMap.find(name); - if(it==_ObjectMap.end()) + if (it == _ObjectMap.end()) return false; - auto info = getObjectInfo(obj,subname); - return info.obj==it->second.obj && info.subname==it->second.subname; + auto info = getObjectInfo(obj, subname); + return info.obj == it->second.obj && info.subname == it->second.subname; } void Gui::ActiveObjectList::setObject(App::DocumentObject* obj, const char* name, - const char *subname, const Gui::HighlightMode& mode) + const char *subname, const Gui::HighlightMode& mode) { auto it = _ObjectMap.find(name); - if(it!=_ObjectMap.end()) { - setHighlight(it->second,mode,false); + if (it!=_ObjectMap.end()) { + setHighlight(it->second, mode, false); _ObjectMap.erase(it); } - if(!obj) return; + + if (!obj) + return; auto info = getObjectInfo(obj,subname); - if(!info.obj) { + if (!info.obj) { FC_ERR("Cannot set active object " << obj->getFullName() << '.' << (subname?subname:"") << " in document '" << _Doc->getDocument()->getName() @@ -139,7 +153,7 @@ void Gui::ActiveObjectList::setObject(App::DocumentObject* obj, const char* name } _ObjectMap[name] = info; - setHighlight(info,mode,true); + setHighlight(info, mode, true); } bool Gui::ActiveObjectList::hasObject(const char*name)const @@ -149,13 +163,11 @@ bool Gui::ActiveObjectList::hasObject(const char*name)const void ActiveObjectList::objectDeleted(const ViewProviderDocumentObject &vp) { - //maybe boost::bimap or boost::multi_index - for (auto it = _ObjectMap.begin(); it != _ObjectMap.end(); ++it) - { - if (it->second.obj == vp.getObject()) - { - _ObjectMap.erase(it); - return; + //maybe boost::bimap or boost::multi_index + for (auto it = _ObjectMap.begin(); it != _ObjectMap.end(); ++it) { + if (it->second.obj == vp.getObject()) { + _ObjectMap.erase(it); + return; + } } - } } From 397729e33ec67bcd044362f02e6ba4bcb399240f Mon Sep 17 00:00:00 2001 From: luz paz Date: Wed, 20 Oct 2021 18:18:33 -0400 Subject: [PATCH 09/14] Fix various typos Found via `codespell -q 3 -L aci,ake,aline,alle,alledges,alocation,als,ang,anid,apoints,ba,beginn,behaviour,bloaded,bottome,byteorder,calculater,cancelled,cancelling,cas,cascade,centimetre,childs,colour,colours,commen,connexion,currenty,dof,doubleclick,dum,eiter,elemente,ende,feld,finde,findf,freez,hist,iff,indicies,initialisation,initialise,initialised,initialises,initialisiert,inout,ist,kilometre,lod,mantatory,methode,metres,millimetre,modell,nd,noe,normale,normaly,nto,numer,oder,ontop,orgin,orginx,orginy,ot,pard,parms,pres,programm,que,recurrance,rougly,seperator,serie,sinc,strack,substraction,te,thist,thru,tread,uint,unter,vertexes,wallthickness,whitespaces -S ./.git,*.po,*.ts,./ChangeLog.txt,./src/3rdParty,./src/Mod/Assembly/App/opendcm,./src/CXX,./src/zipios++,./src/Base/swig*,./src/Mod/Robot/App/kdl_cp,./src/Mod/Import/App/SCL,./src/WindowsInstaller,./src/Doc/FreeCAD.uml,./build/doc/SourceDocu` --- src/Mod/Draft/Resources/ui/preferences-dwg.ui | 2 +- src/Mod/Draft/draftgeoutils/wires.py | 2 +- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 2 +- src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp | 2 +- src/Mod/Spreadsheet/App/SheetPy.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Mod/Draft/Resources/ui/preferences-dwg.ui b/src/Mod/Draft/Resources/ui/preferences-dwg.ui index b52ea78448..2f6e1572fd 100644 --- a/src/Mod/Draft/Resources/ui/preferences-dwg.ui +++ b/src/Mod/Draft/Resources/ui/preferences-dwg.ui @@ -38,7 +38,7 @@ - This is the method FreeCAD will use to convert DWG files to DXF. If "Automatic" is chosen, FreeCAD will try to find one of the following convertors in the same order as they are shown here. If FreeCAD is unable to find any, you might need to choose a specific convertor and indicate its path here under. Choose the "dwg2dxf" utility if using LibreDWG, "ODAFileConverter" if using the ODA file converter, or the "dwg2dwg" utility if using the pro version of QCAD. + This is the method FreeCAD will use to convert DWG files to DXF. If "Automatic" is chosen, FreeCAD will try to find one of the following converters in the same order as they are shown here. If FreeCAD is unable to find any, you might need to choose a specific converter and indicate its path here under. Choose the "dwg2dxf" utility if using LibreDWG, "ODAFileConverter" if using the ODA file converter, or the "dwg2dwg" utility if using the pro version of QCAD. DWGConversion diff --git a/src/Mod/Draft/draftgeoutils/wires.py b/src/Mod/Draft/draftgeoutils/wires.py index fabc5c2601..683f87b037 100644 --- a/src/Mod/Draft/draftgeoutils/wires.py +++ b/src/Mod/Draft/draftgeoutils/wires.py @@ -354,7 +354,7 @@ def removeInterVertices(wire): def cleanProjection(shape, tessellate=True, seglength=0.05): - """Return a compound of edges, optionally tesselate ellipses, splines + """Return a compound of edges, optionally tessellate ellipses, splines and bezcurves. The function was formerly used to workaround bugs in the projection diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 716cdba5ea..f6a4f0b57b 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -7137,7 +7137,7 @@ bool CmdSketcherConstrainSnellsLaw::isActive(void) DEF_STD_CMD_A(CmdSketcherConstrainInternalAlignment) // NOTE: This command is deprecated. Nobody seriously uses today manual creation of an internal alignment constraint -// The only reason this code remains is the extremelly unlikely scenario that some user macro may rely on it. +// The only reason this code remains is the extremely unlikely scenario that some user macro may rely on it. CmdSketcherConstrainInternalAlignment::CmdSketcherConstrainInternalAlignment() :Command("Sketcher_ConstrainInternalAlignment") { diff --git a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp index 7e3d84711e..5dda156b78 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherConstrains.cpp @@ -1002,7 +1002,7 @@ void TaskSketcherConstrains::onSelectionChanged(const Gui::SelectionChanges& msg if(geoid != Sketcher::Constraint::GeoUndef && pointpos == Sketcher::none){ // It is not possible to update on single addition/removal of a geometric element, // as one removal may imply removing a constraint that should be added by a different element - // that is still selected. The necessary checks outweight a full rebuild of the filter. + // that is still selected. The necessary checks outweigh a full rebuild of the filter. updateAssociatedConstraintsFilter(); } } diff --git a/src/Mod/Spreadsheet/App/SheetPy.xml b/src/Mod/Spreadsheet/App/SheetPy.xml index a2c4bf6330..d968809ea7 100644 --- a/src/Mod/Spreadsheet/App/SheetPy.xml +++ b/src/Mod/Spreadsheet/App/SheetPy.xml @@ -176,7 +176,7 @@ recomputeCells(from, to=None) Manually recompute cells in the given range with the given order without -following depedency order. +following dependency order. From cd1bd7114120e1f6295f80e41618964711c5177d Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 21 Oct 2021 15:50:07 +0200 Subject: [PATCH 10/14] App: [skip ci] add get() method to WeakPtrT class --- src/App/DocumentObserver.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/App/DocumentObserver.h b/src/App/DocumentObserver.h index c92cb3155b..c32c780a01 100644 --- a/src/App/DocumentObserver.h +++ b/src/App/DocumentObserver.h @@ -387,6 +387,11 @@ public: bool operator!= (const WeakPtrT& p) const { return ptr != p.ptr; } + /*! Get a pointer to the object or 0 if it doesn't exist any more. */ + T* get() const noexcept + { + return ptr.get(); + } private: // disable From c906775aded2809737141d6cf5357ad392e1f1e5 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 21 Oct 2021 15:50:18 +0200 Subject: [PATCH 11/14] Gui: [skip ci] add get() method to WeakPtrT class --- src/Gui/DocumentObserver.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Gui/DocumentObserver.h b/src/Gui/DocumentObserver.h index 6608fa9df7..63a20df9b4 100644 --- a/src/Gui/DocumentObserver.h +++ b/src/Gui/DocumentObserver.h @@ -274,6 +274,11 @@ public: bool operator!= (const WeakPtrT& p) const { return ptr != p.ptr; } + /*! Get a pointer to the object or 0 if it doesn't exist any more. */ + T* get() const noexcept + { + return ptr.get(); + } private: // disable From d0a75e0a8415e519608c48d4985c3d911e805748 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 21 Oct 2021 16:22:11 +0200 Subject: [PATCH 12/14] Sketcher: fix segmentation fault when using sketch validation dialog after document has been closed Therefore replace the raw pointer of SketchObject with the template class WeakPtrT. This class will be notified as soon as its handled object will be deleted. --- .../Sketcher/Gui/TaskSketcherValidation.cpp | 50 +++++++++++++++++-- src/Mod/Sketcher/Gui/TaskSketcherValidation.h | 3 +- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp b/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp index afd57cf1ba..11b9eb0cd2 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherValidation.cpp @@ -107,6 +107,9 @@ void SketcherValidation::changeEvent(QEvent *e) void SketcherValidation::on_findButton_clicked() { + if (sketch.expired()) + return; + double prec = Precision::Confusion(); bool ok; double conv; @@ -150,6 +153,9 @@ void SketcherValidation::on_findButton_clicked() void SketcherValidation::on_fixButton_clicked() { + if (sketch.expired()) + return; + // undo command open App::Document* doc = sketch->getDocument(); doc->openTransaction("add coincident constraint"); @@ -167,6 +173,9 @@ void SketcherValidation::on_fixButton_clicked() void SketcherValidation::on_highlightButton_clicked() { + if (sketch.expired()) + return; + std::vector points; points = sketchAnalyser.getOpenVertices(); @@ -178,6 +187,9 @@ void SketcherValidation::on_highlightButton_clicked() void SketcherValidation::on_findConstraint_clicked() { + if (sketch.expired()) + return; + if (sketch->evaluateConstraints()) { QMessageBox::information(this, tr("No invalid constraints"), tr("No invalid constraints found")); @@ -192,12 +204,18 @@ void SketcherValidation::on_findConstraint_clicked() void SketcherValidation::on_fixConstraint_clicked() { + if (sketch.expired()) + return; + sketch->validateConstraints(); ui->fixConstraint->setEnabled(false); } void SketcherValidation::on_findReversed_clicked() { + if (sketch.expired()) + return; + std::vector points; const std::vector& geom = sketch->getExternalGeometry(); for (std::size_t i=0; igetDocument(); doc->openTransaction("Sketch porting"); @@ -255,6 +276,9 @@ void SketcherValidation::on_swapReversed_clicked() void SketcherValidation::on_orientLockEnable_clicked() { + if (sketch.expired()) + return; + App::Document* doc = sketch->getDocument(); doc->openTransaction("Constraint orientation lock"); @@ -269,6 +293,9 @@ void SketcherValidation::on_orientLockEnable_clicked() void SketcherValidation::on_orientLockDisable_clicked() { + if (sketch.expired()) + return; + App::Document* doc = sketch->getDocument(); doc->openTransaction("Constraint orientation unlock"); @@ -284,6 +311,9 @@ void SketcherValidation::on_orientLockDisable_clicked() void SketcherValidation::on_delConstrExtr_clicked() { + if (sketch.expired()) + return; + int reply; reply = QMessageBox::question(this, tr("Delete constraints to external geom."), @@ -337,21 +367,28 @@ void SketcherValidation::showPoints(const std::vector& pts) } coords->point.finishEditing(); - Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(sketch); - vp->getRoot()->addChild(coincidenceRoot); + if (!sketch.expired()) { + Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(sketch.get()); + vp->getRoot()->addChild(coincidenceRoot); + } } void SketcherValidation::hidePoints() { if (coincidenceRoot) { - Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(sketch); - vp->getRoot()->removeChild(coincidenceRoot); - coincidenceRoot = 0; + if (!sketch.expired()) { + Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(sketch.get()); + vp->getRoot()->removeChild(coincidenceRoot); + } + coincidenceRoot = nullptr; } } void SketcherValidation::on_findDegenerated_clicked() { + if (sketch.expired()) + return; + double prec = Precision::Confusion(); int count = sketchAnalyser.detectDegeneratedGeometries(prec); @@ -369,6 +406,9 @@ void SketcherValidation::on_findDegenerated_clicked() void SketcherValidation::on_fixDegenerated_clicked() { + if (sketch.expired()) + return; + // undo command open App::Document* doc = sketch->getDocument(); doc->openTransaction("Remove degenerated geometry"); diff --git a/src/Mod/Sketcher/Gui/TaskSketcherValidation.h b/src/Mod/Sketcher/Gui/TaskSketcherValidation.h index 127a561e65..c787587385 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherValidation.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherValidation.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -67,7 +68,7 @@ private: private: std::unique_ptr ui; - Sketcher::SketchObject* sketch; + App::WeakPtrT sketch; Sketcher::SketchAnalysis sketchAnalyser; SoGroup* coincidenceRoot; }; From 167e11596ab8399583768b88cf83322385c47440 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 21 Oct 2021 18:23:03 +0200 Subject: [PATCH 13/14] Surface: [skip ci] only try to build surface if at least two boundary curves are used --- src/Mod/Surface/App/FeatureFilling.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Mod/Surface/App/FeatureFilling.cpp b/src/Mod/Surface/App/FeatureFilling.cpp index 2b3b9a4a20..d0b17c2362 100644 --- a/src/Mod/Surface/App/FeatureFilling.cpp +++ b/src/Mod/Surface/App/FeatureFilling.cpp @@ -298,6 +298,7 @@ App::DocumentObjectExecReturn *Filling::execute(void) } // Add the constraints of border curves/faces (bound) + int numBoundaries = BoundaryEdges.getSize(); addConstraints(builder, BoundaryEdges, BoundaryFaces, BoundaryOrder, Standard_True); // Add additional edge constraints if available (unbound) @@ -316,7 +317,8 @@ App::DocumentObjectExecReturn *Filling::execute(void) } //Build the face - builder.Build(); + if (numBoundaries > 1) + builder.Build(); if (!builder.IsDone()) { Standard_Failure::Raise("Failed to create a face from constraints"); } @@ -327,7 +329,6 @@ App::DocumentObjectExecReturn *Filling::execute(void) return App::DocumentObject::StdReturn; } catch (Standard_Failure& e) { - return new App::DocumentObjectExecReturn(e.GetMessageString()); } } From ebb9f4723fad4e753f933033a89477927695191d Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 21 Oct 2021 21:45:52 +0200 Subject: [PATCH 14/14] Gui: code-refactoring of document recovery handling to reduce code duplication --- src/Gui/Application.cpp | 76 +------------------ src/Gui/DocumentRecovery.cpp | 143 +++++++++++++++++++++++++++-------- src/Gui/DocumentRecovery.h | 19 +++++ 3 files changed, 132 insertions(+), 106 deletions(-) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 99500fa9c4..fa233c9ce9 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -2416,80 +2416,8 @@ void Application::setStyleSheet(const QString& qssFile, bool tiledBackground) void Application::checkForPreviousCrashes() { - QDir tmp = QString::fromUtf8(App::Application::getTempPath().c_str()); - tmp.setNameFilters(QStringList() << QString::fromLatin1("*.lock")); - tmp.setFilter(QDir::Files); - - QList restoreDocFiles; - QString exeName = QString::fromLatin1(App::GetApplication().getExecutableName()); - QList locks = tmp.entryInfoList(); - for (QList::iterator it = locks.begin(); it != locks.end(); ++it) { - QString bn = it->baseName(); - // ignore the lock file for this instance - QString pid = QString::number(QCoreApplication::applicationPid()); - if (bn.startsWith(exeName) && bn.indexOf(pid) < 0) { - QString fn = it->absoluteFilePath(); - boost::interprocess::file_lock flock((const char*)fn.toLocal8Bit()); - if (flock.try_lock()) { - // OK, this file is a leftover from a previous crash - QString crashed_pid = bn.mid(exeName.length()+1); - // search for transient directories with this PID - QString filter; - QTextStream str(&filter); - str << exeName << "_Doc_*_" << crashed_pid; - tmp.setNameFilters(QStringList() << filter); - tmp.setFilter(QDir::Dirs); - QList dirs = tmp.entryInfoList(); - if (dirs.isEmpty()) { - // delete the lock file immediately if no transient directories are related - tmp.remove(fn); - } - else { - int countDeletedDocs = 0; - QString recovery_files = QString::fromLatin1("fc_recovery_files"); - for (QList::iterator it = dirs.begin(); it != dirs.end(); ++it) { - QDir doc_dir(it->absoluteFilePath()); - doc_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries); - uint entries = doc_dir.entryList().count(); - if (entries == 0) { - // in this case we can delete the transient directory because - // we cannot do anything - if (tmp.rmdir(it->filePath())) - countDeletedDocs++; - } - // search for the existence of a recovery file - else if (doc_dir.exists(QLatin1String("fc_recovery_file.xml"))) { - // store the transient directory in case it's not empty - restoreDocFiles << *it; - } - // search for the 'fc_recovery_files' sub-directory and check that it's the only entry - else if (entries == 1 && doc_dir.exists(recovery_files)) { - // if the sub-directory is empty delete the transient directory - QDir rec_dir(doc_dir.absoluteFilePath(recovery_files)); - rec_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries); - if (rec_dir.entryList().isEmpty()) { - doc_dir.rmdir(recovery_files); - if (tmp.rmdir(it->filePath())) - countDeletedDocs++; - } - } - } - - // all directories corresponding to the lock file have been deleted - // so delete the lock file, too - if (countDeletedDocs == dirs.size()) { - tmp.remove(fn); - } - } - } - } - } - - if (!restoreDocFiles.isEmpty()) { - Gui::Dialog::DocumentRecovery dlg(restoreDocFiles, Gui::getMainWindow()); - if (dlg.foundDocuments()) - dlg.exec(); - } + Gui::Dialog::DocumentRecoveryFinder finder; + finder.checkForPreviousCrashes(); } App::Document *Application::reopen(App::Document *doc) { diff --git a/src/Gui/DocumentRecovery.cpp b/src/Gui/DocumentRecovery.cpp index 44e7f74116..4f2d60006e 100644 --- a/src/Gui/DocumentRecovery.cpp +++ b/src/Gui/DocumentRecovery.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #include #include @@ -68,6 +69,7 @@ FC_LOG_LEVEL_INIT("Gui",true,true) using namespace Gui; using namespace Gui::Dialog; +namespace sp = std::placeholders; // taken from the script doctools.py std::string DocumentRecovery::doctools = @@ -553,41 +555,20 @@ void DocumentRecovery::on_buttonCleanup_clicked() d_ptr->ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); d_ptr->ui.buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true); - QDir tmp = QString::fromUtf8(App::Application::getTempPath().c_str()); - tmp.setNameFilters(QStringList() << QString::fromLatin1("*.lock")); - tmp.setFilter(QDir::Files); + DocumentRecoveryHandler handler; + handler.checkForPreviousCrashes(std::bind(&DocumentRecovery::cleanup, this, sp::_1, sp::_2, sp::_3)); + QMessageBox::information(this, tr("Finished"), tr("Transient directories deleted.")); +} - QString exeName = QString::fromLatin1(App::GetApplication().getExecutableName()); - QList locks = tmp.entryInfoList(); - for (QList::iterator it = locks.begin(); it != locks.end(); ++it) { - QString bn = it->baseName(); - // ignore the lock file for this instance - QString pid = QString::number(QCoreApplication::applicationPid()); - if (bn.startsWith(exeName) && bn.indexOf(pid) < 0) { - QString fn = it->absoluteFilePath(); - boost::interprocess::file_lock flock((const char*)fn.toLocal8Bit()); - if (flock.try_lock()) { - // OK, this file is a leftover from a previous crash - QString crashed_pid = bn.mid(exeName.length()+1); - // search for transient directories with this PID - QString filter; - QTextStream str(&filter); - str << exeName << "_Doc_*_" << crashed_pid; - tmp.setNameFilters(QStringList() << filter); - tmp.setFilter(QDir::Dirs); - QList dirs = tmp.entryInfoList(); - if (!dirs.isEmpty()) { - for (QList::iterator jt = dirs.begin(); jt != dirs.end(); ++jt) { - clearDirectory(*jt); - tmp.rmdir(jt->fileName()); - } - } - tmp.remove(it->fileName()); - } +void DocumentRecovery::cleanup(QDir& tmp, const QList& dirs, const QString& lockFile) +{ + if (!dirs.isEmpty()) { + for (QList::const_iterator jt = dirs.cbegin(); jt != dirs.cend(); ++jt) { + clearDirectory(*jt); + tmp.rmdir(jt->fileName()); } } - - QMessageBox::information(this, tr("Finished"), tr("Transient directories deleted.")); + tmp.remove(lockFile); } void DocumentRecovery::clearDirectory(const QFileInfo& dir) @@ -613,4 +594,102 @@ void DocumentRecovery::clearDirectory(const QFileInfo& dir) } } +// ---------------------------------------------------------------------------- + +void DocumentRecoveryFinder::checkForPreviousCrashes() +{ + DocumentRecoveryHandler handler; + handler.checkForPreviousCrashes(std::bind(&DocumentRecoveryFinder::checkDocumentDirs, this, sp::_1, sp::_2, sp::_3)); + + showRecoveryDialogIfNeeded(); +} + +void DocumentRecoveryFinder::checkDocumentDirs(QDir& tmp, const QList& dirs, const QString& fn) +{ + if (dirs.isEmpty()) { + // delete the lock file immediately if no transient directories are related + tmp.remove(fn); + } + else { + int countDeletedDocs = 0; + QString recovery_files = QString::fromLatin1("fc_recovery_files"); + for (QList::const_iterator it = dirs.cbegin(); it != dirs.cend(); ++it) { + QDir doc_dir(it->absoluteFilePath()); + doc_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries); + uint entries = doc_dir.entryList().count(); + if (entries == 0) { + // in this case we can delete the transient directory because + // we cannot do anything + if (tmp.rmdir(it->filePath())) + countDeletedDocs++; + } + // search for the existence of a recovery file + else if (doc_dir.exists(QLatin1String("fc_recovery_file.xml"))) { + // store the transient directory in case it's not empty + restoreDocFiles << *it; + } + // search for the 'fc_recovery_files' sub-directory and check that it's the only entry + else if (entries == 1 && doc_dir.exists(recovery_files)) { + // if the sub-directory is empty delete the transient directory + QDir rec_dir(doc_dir.absoluteFilePath(recovery_files)); + rec_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries); + if (rec_dir.entryList().isEmpty()) { + doc_dir.rmdir(recovery_files); + if (tmp.rmdir(it->filePath())) + countDeletedDocs++; + } + } + } + + // all directories corresponding to the lock file have been deleted + // so delete the lock file, too + if (countDeletedDocs == dirs.size()) { + tmp.remove(fn); + } + } +} + +void DocumentRecoveryFinder::showRecoveryDialogIfNeeded() +{ + if (!restoreDocFiles.isEmpty()) { + Gui::Dialog::DocumentRecovery dlg(restoreDocFiles, Gui::getMainWindow()); + if (dlg.foundDocuments()) + dlg.exec(); + } +} + +// ---------------------------------------------------------------------------- + +void DocumentRecoveryHandler::checkForPreviousCrashes(const std::function&, const QString&)> & callableFunc) const +{ + QDir tmp = QString::fromUtf8(App::Application::getTempPath().c_str()); + tmp.setNameFilters(QStringList() << QString::fromLatin1("*.lock")); + tmp.setFilter(QDir::Files); + + QString exeName = QString::fromLatin1(App::GetApplication().getExecutableName()); + QList locks = tmp.entryInfoList(); + for (QList::iterator it = locks.begin(); it != locks.end(); ++it) { + QString bn = it->baseName(); + // ignore the lock file for this instance + QString pid = QString::number(QCoreApplication::applicationPid()); + if (bn.startsWith(exeName) && bn.indexOf(pid) < 0) { + QString fn = it->absoluteFilePath(); + boost::interprocess::file_lock flock((const char*)fn.toLocal8Bit()); + if (flock.try_lock()) { + // OK, this file is a leftover from a previous crash + QString crashed_pid = bn.mid(exeName.length()+1); + // search for transient directories with this PID + QString filter; + QTextStream str(&filter); + str << exeName << "_Doc_*_" << crashed_pid; + tmp.setNameFilters(QStringList() << filter); + tmp.setFilter(QDir::Dirs); + QList dirs = tmp.entryInfoList(); + + callableFunc(tmp, dirs, it->fileName()); + } + } + } +} + #include "moc_DocumentRecovery.cpp" diff --git a/src/Gui/DocumentRecovery.h b/src/Gui/DocumentRecovery.h index 966f551173..4b47306fe5 100644 --- a/src/Gui/DocumentRecovery.h +++ b/src/Gui/DocumentRecovery.h @@ -29,6 +29,7 @@ #include #include #include +#include namespace Gui { namespace Dialog { @@ -53,6 +54,7 @@ protected: void contextMenuEvent(QContextMenuEvent*); QString createProjectFile(const QString&); void clearDirectory(const QFileInfo&); + void cleanup(QDir&, const QList&, const QString&); protected Q_SLOTS: void on_buttonCleanup_clicked(); @@ -65,6 +67,23 @@ private: Q_DECLARE_PRIVATE(DocumentRecovery) }; +class DocumentRecoveryFinder { +public: + void checkForPreviousCrashes(); + +private: + void checkDocumentDirs(QDir&, const QList&, const QString&); + void showRecoveryDialogIfNeeded(); + +private: + QList restoreDocFiles; +}; + +class DocumentRecoveryHandler { +public: + void checkForPreviousCrashes(const std::function&, const QString&)> & callableFunc) const; +}; + } //namespace Dialog } //namespace Gui