// SPDX-License-Identifier: LGPL-2.0-or-later /*************************************************************************** * Copyright (c) 2023 WandererFan * * * * 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 * * * ***************************************************************************/ // a class to validate and correct dimension references // the dimension reference auto correct algo: // // when a dimension is created, the shape of each reference is saved, so in addition // to the dimensionedObject + subElement reference, we also keep a Part::TopoShape copy // of the reference. // // when we later use that dimension, we check its references as follows: // for each reference: // // auto correct phase 1 // if ref.currentGeometry == ref.savedGeometry: // // the reference points to the same geometry as before, so we // // so we consider this to be correct. same geometry, same index case. // continue // else: // // search all the source shapes for a subelement with the exact same // // geometry. same geometry, different index case. // newRef = searchForExactSameGeometry(ref) // geometry matcher // if newRef: // // substitute the reference we just found in place of the old // // reference // replace(ref, newRef) // else: // // auto correct phase 2 - to be implemented // // we don't have any geometry that is identical to our saved geometry. // // finding a match now becomes guess work. we have to find the most // // similar geometry (with at least some level of same-ness) and use // // that to rebuild our reference. // // we do not have a good algo for searchForMostSimilarGeometry() yet. // newRef = searchForMostSimilarGeometry(ref) // geometry guesser // if newRef: // replace(ref, newRef) // else: // //we can't fix this #include #include #include #include #include #include #include "GeometryMatcher.h" #include "DimensionReferences.h" #include "DimensionGeometry.h" #include "DimensionAutoCorrect.h" #include "DrawUtil.h" #include "Preferences.h" using namespace TechDraw; using namespace Measure; using DU = DrawUtil; //! true if references point to valid geometry and the valid geometry matches the //! corresponding saved geometry. this method does not correct anything, it just //! verifies if the references point to the same geometry as when the reference //! was created. bool DimensionAutoCorrect::referencesHaveValidGeometry(std::vector& referenceState) const { // Base::Console().message("DAC::referencesHaveValidGeometry()\n"); ReferenceVector refsAll = getDimension()->getEffectiveReferences(); const std::vector savedGeometry = getDimension()->SavedGeometry.getValues(); if (savedGeometry.empty() || savedGeometry.size() != refsAll.size()) { // this must be an old document without savedGeometry property. We can not // validate the references in this case so we hope they are valid. referenceState = std::vector(refsAll.size(), true); return true; } bool result {true}; size_t iRef {0}; for (auto& entry : refsAll) { if (entry.hasGeometry()) { // entry points to something, is it the correct geom? if (isMatchingGeometry(entry, savedGeometry.at(iRef))) { referenceState.emplace_back(true); } else { result = false; referenceState.emplace_back(false); } } else { result = false; referenceState.emplace_back(false); } iRef++; } return result; } //! try to correct references that point to non-existent geometry or when the saved //! geometry does not match the current geometry the reference points to. //! referenceState is the output of a previous use of referencesHaveValidGeometry. bool DimensionAutoCorrect::autocorrectReferences(std::vector& referenceState, ReferenceVector& repairedRefs) const { // Base::Console().message("DAC::autocorrectReferences()\n"); if (!Preferences::autoCorrectDimRefs()) { return false; } bool result {true}; ReferenceVector refsAll = getDimension()->getEffectiveReferences(); const std::vector savedGeometry = getDimension()->SavedGeometry.getValues(); if (savedGeometry.empty() || savedGeometry.size() != refsAll.size()) { // this must be an old document without savedGeometry property. We can not // validate the references in this case so we have to assume the references are // correct. return true; } size_t iRef {0}; for (const auto& state : referenceState) { if (state) { // ref points to valid geometry that matches saved geometry referenceState.at(iRef) = true; repairedRefs.push_back(refsAll.at(iRef)); iRef++; continue; } const Part::TopoShape& temp = savedGeometry.at(iRef); if (temp.isNull()) { result = false; referenceState.at(iRef) = false; repairedRefs.push_back(refsAll.at(iRef)); iRef++; // we could exit here instead of checking all the refs? continue; } // this ref does not point to valid geometry or // the geometry it points to does not match the saved geometry ReferenceEntry fixedRef = refsAll.at(iRef); // first, look for an exact match to the saved geometry bool success = fix1GeomExact(fixedRef, savedGeometry.at(iRef).getShape()); if (success) { // we did find a match referenceState.at(iRef) = true; repairedRefs.push_back(fixedRef); iRef++; continue; } // we did not find an exact match, so check for a similar match success = fix1GeomSimilar(fixedRef, savedGeometry.at(iRef).getShape()); if (success) { // we did find a similar match referenceState.at(iRef) = true; repairedRefs.push_back(fixedRef); iRef++; continue; } // we did not find a similar match the geometry result = false; referenceState.at(iRef) = false; repairedRefs.push_back(fixedRef); iRef++; // we could exit here } return result; } //! fix a single reference with an exact match to geomToMatch bool DimensionAutoCorrect::fix1GeomExact(ReferenceEntry& refToFix, const TopoDS_Shape &geomToMatch) const { // Base::Console().message("DAC::fix1GeomExact()\n"); ReferenceEntry fixedRef = refToFix; Part::TopoShape topoShapeToMatch(geomToMatch); bool success {false}; if (refToFix.is3d()) { if (!refToFix.getObject() && m_3dObjectCache.empty()) { return false; } if (geomToMatch.ShapeType() == TopAbs_VERTEX) { success = findExactVertex3d(refToFix, topoShapeToMatch); } else { success = findExactEdge3d(refToFix, topoShapeToMatch); } } else { if (geomToMatch.ShapeType() == TopAbs_VERTEX) { success = findExactVertex2d(refToFix, topoShapeToMatch); } else { success = findExactEdge2d(refToFix, topoShapeToMatch); } } return success; } //! fix a single reference with an Similar match to geomToMatch bool DimensionAutoCorrect::fix1GeomSimilar(ReferenceEntry& refToFix, const TopoDS_Shape &geomToMatch) const { // Base::Console().message("DAC::fix1GeomSimilar()\n"); Part::TopoShape topoShapeToMatch(geomToMatch); bool success {false}; if (refToFix.is3d()) { if (!refToFix.getObject() && m_3dObjectCache.empty()) { // can't fix this. nothing to compare. return false; } if (geomToMatch.ShapeType() == TopAbs_VERTEX) { success = findSimilarVertex3d(refToFix, topoShapeToMatch); } else { success = findSimilarEdge3d(refToFix, topoShapeToMatch); } } else { if (geomToMatch.ShapeType() == TopAbs_VERTEX) { success = findSimilarVertex2d(refToFix, topoShapeToMatch); } else { success = findSimilarEdge2d(refToFix, topoShapeToMatch); } } return success; } //! search the view for a 2d vertex that is the same as the saved reference geometry //! and return a reference pointing to the matching vertex bool DimensionAutoCorrect::findExactVertex2d(ReferenceEntry& refToFix, const Part::TopoShape& refGeom) const { // Base::Console().message("DAC::findExactVertex2d()\n"); getMatcher()->setPointTolerance(EWTOLERANCE); auto refObj = refToFix.getObject(); auto refDvp = dynamic_cast(refObj); if (refDvp) { ReferenceEntry fixedRef = searchViewForVert(refDvp, refGeom); if (fixedRef.getObject()) { refToFix = fixedRef; return true; } } // no match return false; } //! search the view for a 2d edge that is the same as the saved reference geometry (from DVD::SavedGeometry) //! and return a reference pointing to the matching edge. bool DimensionAutoCorrect::findExactEdge2d(ReferenceEntry& refToFix, const Part::TopoShape& refGeom) const { // Base::Console().message("DAC::findExactEdge2d()\n"); auto refObj = refToFix.getObject(); auto refDvp = dynamic_cast(refObj); if (refDvp) { ReferenceEntry fixedRef = searchViewForExactEdge(refDvp, refGeom); if (fixedRef.getObject()) { refToFix = fixedRef; return true; } } // no match, return the input reference return false; } //! search the model for a 3d vertex that is the same as the saved reference geometry //! and return a reference pointing to the matching vertex bool DimensionAutoCorrect::findExactVertex3d(ReferenceEntry& refToFix, const Part::TopoShape& refGeom) const { // Base::Console().message("DAC::findExactVertex3d()\n"); getMatcher()->setPointTolerance(EWTOLERANCE); // try the referenced object auto refObj = refToFix.getObject(); if (refObj) { ReferenceEntry fixedRef = searchObjForVert(refObj, refGeom); if (fixedRef.getObject()) { refToFix = fixedRef; return true; } } // not match in refObj (or no refObj!) for (auto& objectName : m_3dObjectCache) { auto object3d = getDimension()->getDocument()->getObject(objectName.c_str()); ReferenceEntry fixedRef = searchObjForVert(object3d, refGeom); if (fixedRef.getObject()) { refToFix = fixedRef; return true; } } return false; } //! search the model for a 3d edge that is the same as the saved reference geometry //! and return a reference pointing to the matching edge. bool DimensionAutoCorrect::findExactEdge3d(ReferenceEntry& refToFix, const Part::TopoShape& refGeom) const { // Base::Console().message("DAC::findExactEdge3d() - cache: %d\n", m_3dObjectCache.size()); // first, try to find a match in the referenced object auto refObj = refToFix.getObject(); if (refObj) { ReferenceEntry fixedRef = searchObjForEdge(refObj, refGeom); if (fixedRef.getObject()) { refToFix = fixedRef; return true; } } // no match in refObj, so now search the cached objects for (auto& objectName : m_3dObjectCache) { auto object3d = getDimension()->getDocument()->getObject(objectName.c_str()); auto shape3d = Part::Feature::getShape(object3d, Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform); auto edgesAll = getDimension()->getEdges(shape3d); size_t iEdge {1}; for (auto& edge : edgesAll) { if (getMatcher()->compareGeometry(edge, refGeom)) { // found a match! refToFix.setObjectName(objectName); refToFix.setObject(object3d); refToFix.setSubName(std::string("Edge") + std::to_string(iEdge)); return true; } iEdge++; } } return false; } //! search the view for a vertex that is within a tolerance of the saved reference geometry //! and return a reference pointing to the matching vertex bool DimensionAutoCorrect::findSimilarVertex2d(ReferenceEntry& refToFix, const Part::TopoShape& refGeom) const { // Base::Console().message("DAC::findSimilarVertex2d()\n"); (void)refToFix; (void)refGeom; // Base::Console().message("DAC::findSimilarVertex2d is not implemented yet\n"); return false; } //! search the view for a 2d edge that is similar to the saved reference geometry //! and return a reference pointing to the similar edge. bool DimensionAutoCorrect::findSimilarEdge2d(ReferenceEntry& refToFix, const Part::TopoShape& refGeom) const { // Base::Console().message("DAC::findSimilarEdge2d()\n"); (void)refToFix; (void)refGeom; // Base::Console().message("DAC::findSimilarEdge2d is not implemented yet\n"); return false; } //! search the referenced 3d object and the object cache for a vertex that is within //! a tolerance of the saved reference geometry and return a reference pointing //! to the matching vertex bool DimensionAutoCorrect::findSimilarVertex3d(ReferenceEntry& refToFix, const Part::TopoShape& refGeom) const { // Base::Console().message("DAC::findSimilarVertex3d()\n"); (void)refToFix; (void)refGeom; // Base::Console().message("DAC::findSimilarVertex3d is not implemented yet\n"); return false; } //! search the referenced 3d object and the object cache for an edge that is //! similar to the saved reference geometry and return a reference pointing //! to the similar edge bool DimensionAutoCorrect::findSimilarEdge3d(ReferenceEntry& refToFix, const Part::TopoShape& refGeom) const { // Base::Console().message("DAC::findSimilarEdge3d(%s)\n", refToFix.getObjectName().c_str()); (void)refToFix; (void)refGeom; // Base::Console().message("DAC::findSimilarEdge3d is not implemented yet\n"); return false; } //! compare the geometry pointed to by a reference to the corresponding saved geometry bool DimensionAutoCorrect::isMatchingGeometry(const ReferenceEntry& ref, const Part::TopoShape& savedGeometry) const { Part::TopoShape temp; if (ref.is3d()) { auto shape3d = ShapeFinder::getLocatedShape(*ref.getObject(), ref.getSubName(true)); temp = Part::TopoShape(shape3d); } else { auto shape2d = ref.getGeometry(); temp = Part::TopoShape(shape2d); } if (temp.isNull()) { // this shouldn't happen as we already know that this ref points to valid geometry return false; } if (getMatcher()->compareGeometry(temp, savedGeometry)) { // reference still points to the same geometry return true; } return false; } //! search obj (3d object with a shape) for a match to refVertex. This is always //! an exact match for phase 1 (GeometryMatcher), but in phase 2 (GeometryGuesser) //! a similar match will be allowed. ReferenceEntry DimensionAutoCorrect::searchObjForVert(App::DocumentObject* obj, const Part::TopoShape& refVertex, bool exact) const { (void)exact; auto shape3d = ShapeFinder::getLocatedShape(*obj, ""); if (shape3d.IsNull()) { // how to handle this? return {}; } auto vertsAll = getDimension()->getVertexes(shape3d); size_t iVert {1}; for (auto& vert : vertsAll) { bool isSame = getMatcher()->compareGeometry(refVertex, vert); if (isSame) { auto newSubname = std::string("Vertex") + std::to_string(iVert); return {obj, newSubname, getDimension()->getDocument()}; } iVert++; } return {}; } //! search View (2d part display) for a match to refVertex. This can be an //! exact or Similar match depending on the setting of exact. ReferenceEntry DimensionAutoCorrect::searchViewForVert(DrawViewPart* obj, const Part::TopoShape& refVertex, bool exact) const { // Base::Console().message("DAC::searchViewForVert()\n"); (void)exact; std::vector gVertexAll = getDimension()->getViewPart()->getVertexGeometry(); getMatcher()->setPointTolerance(EWTOLERANCE); int iVertex = 0; for (auto& vert : gVertexAll) { // vert is in display form - scaled and rotated Part::TopoShape temp = ReferenceEntry::asCanonicalTopoShape(vert->asTopoShape(), *obj); bool isSame = getMatcher()->compareGeometry(temp, refVertex); if (isSame) { auto newSubname = std::string("Vertex") + std::to_string(iVertex); return {obj, newSubname, getDimension()->getDocument()}; } iVertex++; } return {}; } //! search View (2d part display) for an exact match to refEdge. ReferenceEntry DimensionAutoCorrect::searchViewForExactEdge(DrawViewPart* obj, const Part::TopoShape& refEdge) const { // Base::Console().message("DAC::searchViewForExactEdge()\n"); auto gEdgeAll = getDimension()->getViewPart()->getEdgeGeometry(); int iEdge {0}; for (auto& edge : gEdgeAll) { // edge is scaled and rotated. we need it in the same scale/rotate state as // the reference edge in order to match. Part::TopoShape temp = ReferenceEntry::asCanonicalTopoShape(edge->asTopoShape(), *obj); bool isSame = getMatcher()->compareGeometry(refEdge, temp); if (isSame) { auto newSubname = std::string("Edge") + std::to_string(iEdge); return {obj, newSubname, getDimension()->getDocument()}; } iEdge++; } return {}; } //! search View (2d part display) for an edge that is similar to refEdge ReferenceEntry DimensionAutoCorrect::searchViewForSimilarEdge(DrawViewPart* obj, const Part::TopoShape& refEdge) const { // Base::Console().message("DAC::searchViewForSimilarEdge()\n"); (void)obj; (void)refEdge; Base::Console().message("DAC::searchViewForSimilarEdge is not implemented yet\n"); return {}; } //! search model for for a 3d edge that is a match to refEdge //! note that only the exact match is implemented in phase 1 ReferenceEntry DimensionAutoCorrect::searchObjForEdge(App::DocumentObject* obj, const Part::TopoShape& refEdge, bool exact) const { // Base::Console().message("DAC::searchObjForEdge(%s)\n", obj->Label.getValue()); (void)exact; auto shape3d = Part::Feature::getShape(obj, Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform); if (shape3d.IsNull()) { // how to handle this? return {}; } auto edgesAll = getDimension()->getEdges(shape3d); size_t iEdge {1}; for (auto& edge : edgesAll) { bool isSame = getMatcher()->compareGeometry(edge, refEdge); if (isSame) { auto newSubname = std::string("Edge") + std::to_string(iEdge); return {obj, newSubname, getDimension()->getDocument()}; } iEdge++; } return {}; } //! rebuild 3d references from saved geometry. returns true is all references //! have been repaired bool DimensionAutoCorrect::fixBrokenReferences(ReferenceVector& fixedReferences) const { // Base::Console().message("DAC::fixBrokenReferences()\n"); bool success {true}; const std::vector savedGeometry = getDimension()->SavedGeometry.getValues(); int iGeom {0}; for (auto& geom : savedGeometry) { if (fixedReferences.at(iGeom).hasGeometry()) { iGeom++; continue; } // const TopoDS_Shape& geomShape = geom.getShape(); for (auto& objectName : m_3dObjectCache) { auto object3d = getDimension()->getDocument()->getObject(objectName.c_str()); if (!object3d) { // cached object has been deleted continue; } // TODO: do we need to check for Similar matches here too? ReferenceEntry newRef; if (geomShape.ShapeType() == TopAbs_VERTEX) { newRef = searchObjForVert(object3d, geomShape); fixedReferences.at(iGeom) = newRef; } else { newRef = searchObjForEdge(object3d, geomShape); fixedReferences.at(iGeom) = newRef; } fixedReferences.at(iGeom) = newRef; if (!newRef.getObject()) { success = false; } } } return success; } GeometryMatcher* DimensionAutoCorrect::getMatcher() const { return getDimension()->getMatcher(); }