Download src/Mod/Measure/App/ShapeFinder.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 15.2 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Measure/App/ShapeFinder.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Measure/App/ShapeFinder.cpp
-
curl -L -o ShapeFinder.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Measure/App/ShapeFinder.cpp
15.2 kB
| // SPDX-License-Identifier: LGPL-2.1-or-later | |
| /**************************************************************************** | |
| * * | |
| * Copyright (c) 2024 wandererfan <wandererfan@gmail.com> * | |
| * * | |
| * This file is part of FreeCAD. * | |
| * * | |
| * FreeCAD is free software: you can redistribute it and/or modify it * | |
| * under the terms of the GNU Lesser General Public License as * | |
| * published by the Free Software Foundation, either version 2.1 of the * | |
| * License, or (at your option) any later version. * | |
| * * | |
| * FreeCAD 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 * | |
| * Lesser General Public License for more details. * | |
| * * | |
| * You should have received a copy of the GNU Lesser General Public * | |
| * License along with FreeCAD. If not, see * | |
| * <https://www.gnu.org/licenses/>. * | |
| * * | |
| ***************************************************************************/ | |
| //! ShapeFinder is a class to obtain the located shape pointed at by a DocumentObject and a | |
| //! "new-style" long subelement name. It hides the complexities of obtaining the correct object | |
| //! and its placement. | |
| using namespace Measure; | |
| //! ResolveResult is a class to hold the result of resolving a selection into the actual target | |
| //! object and traditional subElement name (Vertex1). | |
| ResolveResult::ResolveResult( | |
| const App::DocumentObject* realTarget, | |
| const std::string& shortSubName, | |
| const App::DocumentObject* targetParent | |
| ) | |
| : m_target(App::SubObjectT(realTarget, shortSubName.c_str())) | |
| , m_targetParent(App::DocumentObjectT(targetParent)) | |
| {} | |
| App::DocumentObject& ResolveResult::getTarget() const | |
| { | |
| return *(m_target.getObject()); | |
| } | |
| std::string ResolveResult::getShortSub() const | |
| { | |
| return m_target.getSubName(); | |
| } | |
| App::DocumentObject& ResolveResult::getTargetParent() const | |
| { | |
| return *(m_targetParent.getObject()); | |
| } | |
| //! returns the actual target object and subname pointed to by selectObj and selectLongSub (which | |
| //! is likely a result from getSelection or getSelectionEx) | |
| ResolveResult ShapeFinder::resolveSelection( | |
| const App::DocumentObject& selectObj, | |
| const std::string& selectLongSub | |
| ) | |
| { | |
| App::DocumentObject* targetParent {nullptr}; | |
| std::string childName {}; | |
| const char* subElement {nullptr}; | |
| App::DocumentObject* realTarget | |
| = selectObj.resolve(selectLongSub.c_str(), &targetParent, &childName, &subElement); | |
| auto shortSub = getLastTerm(selectLongSub); | |
| return {realTarget, shortSub, targetParent}; | |
| } | |
| //! returns the shape of rootObject+leafSub. Any transforms from objects in the path from rootObject | |
| //! to leafSub are applied to the shape. | |
| //! leafSub is typically obtained from Selection as it provides the appropriate longSubname. The | |
| //! leaf sub string can also be constructed by walking the tree. | |
| // TODO: to truly locate the shape, we need to consider attachments - see | |
| // ShapeExtractor::getShapesFromXRoot() | |
| // and ShapeFinder::getLinkAttachParent() | |
| TopoDS_Shape ShapeFinder::getLocatedShape(const App::DocumentObject& rootObject, const std::string& leafSub) | |
| { | |
| auto resolved = resolveSelection(rootObject, leafSub); | |
| auto target = &resolved.getTarget(); | |
| auto shortSub = resolved.getShortSub(); | |
| if (!target) { | |
| return {}; | |
| } | |
| TopoDS_Shape shape = Part::Feature::getShape( | |
| target, | |
| Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform | |
| ); | |
| if (isShapeReallyNull(shape)) { | |
| return {}; | |
| } | |
| auto cleanSub = removeTnpInfo(leafSub); | |
| auto transform = getGlobalTransform(rootObject, cleanSub); | |
| shape = transformShape(shape, transform.first, transform.second); | |
| Part::TopoShape tShape {shape}; | |
| if (!shortSub.empty()) { | |
| return tShape.getSubTopoShape(shortSub.c_str()).getShape(); | |
| } | |
| return tShape.getShape(); | |
| } | |
| //! convenient version of previous method | |
| Part::TopoShape ShapeFinder::getLocatedTopoShape( | |
| const App::DocumentObject& rootObject, | |
| const std::string& leafSub | |
| ) | |
| { | |
| return {getLocatedShape(rootObject, leafSub)}; | |
| } | |
| //! traverse the tree from leafSub up to rootObject, obtaining placements along the way. Note that | |
| //! the placements will need to be applied in the reverse order (ie top down) of what is delivered | |
| //! in plm stack. leafSub is a dot separated longSubName which DOES NOT include rootObject. the | |
| //! result does not include rootObject's transform. | |
| void ShapeFinder::crawlPlacementChain( | |
| std::vector<Base::Placement>& plmStack, | |
| std::vector<Base::Matrix4D>& scaleStack, | |
| const App::DocumentObject& rootObject, | |
| const std::string& leafSub | |
| ) | |
| { | |
| auto currentSub = leafSub; | |
| std::string previousSub {}; | |
| while (!currentSub.empty() && currentSub != previousSub) { | |
| auto resolved = resolveSelection(rootObject, currentSub); | |
| auto target = &resolved.getTarget(); | |
| if (!target) { | |
| return; | |
| } | |
| auto currentPlacement = getPlacement(target); | |
| auto currentScale = getScale(target); | |
| if (!currentPlacement.isIdentity() || !currentScale.isUnity()) { | |
| plmStack.push_back(currentPlacement); | |
| scaleStack.push_back(currentScale); | |
| } | |
| previousSub = currentSub; | |
| currentSub = pruneLastTerm(currentSub); | |
| } | |
| } | |
| //! return inShape with placement and scaler applied. If inShape contains any infinite subshapes | |
| //! (such as Datum planes), the infinite shapes will not be included in the result. | |
| TopoDS_Shape ShapeFinder::transformShape( | |
| TopoDS_Shape& inShape, | |
| const Base::Placement& placement, | |
| const Base::Matrix4D& scaler | |
| ) | |
| { | |
| if (isShapeReallyNull(inShape)) { | |
| return {}; | |
| } | |
| // we modify the parameter shape here. we don't claim to be const, but may be better to copy | |
| // the shape? | |
| Part::TopoShape tshape {inShape}; | |
| if (tshape.isInfinite()) { | |
| inShape = stripInfiniteShapes(inShape); | |
| } | |
| // copying the shape prevents "non-orthogonal GTrsf" errors in some versions | |
| // of OCC. Something to do with triangulation of shape?? | |
| // it may be that incremental mesh would work here too. | |
| BRepBuilderAPI_Copy copier(inShape); | |
| tshape = Part::TopoShape(copier.Shape()); | |
| if (tshape.isNull()) { | |
| return {}; | |
| } | |
| tshape.transformShape(scaler, true, true); | |
| tshape.setPlacement(placement); | |
| return tshape.getShape(); | |
| } | |
| //! this getter should work for any object, not just links | |
| Base::Placement ShapeFinder::getPlacement(const App::DocumentObject* root) | |
| { | |
| auto namedProperty = root->getPropertyByName("Placement"); | |
| auto placementProperty = dynamic_cast<App::PropertyPlacement*>(namedProperty); | |
| if (namedProperty && placementProperty) { | |
| return placementProperty->getValue(); | |
| } | |
| return {}; | |
| } | |
| //! get root's scale property. If root is not a Link related object, then the identity matrrix will | |
| //! be returned. | |
| Base::Matrix4D ShapeFinder::getScale(const App::DocumentObject* root) | |
| { | |
| if (!isLinkLike(root)) { | |
| return {}; | |
| } | |
| Base::Matrix4D linkScale; | |
| auto namedProperty = root->getPropertyByName("ScaleVector"); | |
| auto scaleVectorProperty = dynamic_cast<App::PropertyVector*>(namedProperty); | |
| if (scaleVectorProperty) { | |
| linkScale.scale(scaleVectorProperty->getValue()); | |
| } | |
| return linkScale; | |
| } | |
| //! there isn't convenient common ancestor for the members of the Link family. We use | |
| //! isLinkLike(obj) instead of obj->isDerivedFrom<ConvenientCommonAncestor>(). Some links have | |
| //! proxy objects and will not be detected by isDerivedFrom(). | |
| bool ShapeFinder::isLinkLike(const App::DocumentObject* obj) | |
| { | |
| if (!obj) { | |
| return false; | |
| } | |
| if (obj->isDerivedFrom<App::Link>() || obj->isDerivedFrom<App::LinkElement>() | |
| || obj->isDerivedFrom<App::LinkGroup>()) { | |
| return true; | |
| } | |
| auto namedProperty = obj->getPropertyByName("LinkedObject"); | |
| auto linkedObjectProperty = dynamic_cast<App::PropertyLink*>(namedProperty); | |
| if (linkedObjectProperty) { | |
| return true; | |
| } | |
| namedProperty = obj->getPropertyByName("ElementList"); | |
| auto elementListProperty = dynamic_cast<App::PropertyLinkList*>(namedProperty); | |
| return elementListProperty != nullptr; | |
| } | |
| //! Infinite shapes can not be projected, so they need to be removed. inShape is usually a compound. | |
| //! Datum features (Axis, Plane and CS) are examples of infinite shapes. | |
| TopoDS_Shape ShapeFinder::stripInfiniteShapes(const TopoDS_Shape& inShape) | |
| { | |
| BRep_Builder builder; | |
| TopoDS_Compound comp; | |
| builder.MakeCompound(comp); | |
| TopoDS_Iterator it(inShape); | |
| for (; it.More(); it.Next()) { | |
| TopoDS_Shape shape = it.Value(); | |
| if (shape.ShapeType() < TopAbs_SOLID) { | |
| // look inside composite shapes | |
| shape = stripInfiniteShapes(shape); | |
| } | |
| else if (Part::TopoShape(shape).isInfinite()) { | |
| continue; | |
| } | |
| // simple shape & finite | |
| builder.Add(comp, shape); | |
| } | |
| return {std::move(comp)}; | |
| } | |
| //! check for shape is null or shape has no subshapes(vertex/edge/face/etc) | |
| //! this handles the case of an empty compound which is not IsNull, but has no | |
| //! content. | |
| // Note: the same code exists in TechDraw::ShapeUtils | |
| bool ShapeFinder::isShapeReallyNull(const TopoDS_Shape& shape) | |
| { | |
| // if the shape is null or it has no subshapes, then it is really null | |
| return shape.IsNull() || !TopoDS_Iterator(shape).More(); | |
| } | |
| //! Returns the net transformation of a path from rootObject to leafSub. rootObject's transform | |
| //! is included in the result. | |
| std::pair<Base::Placement, Base::Matrix4D> ShapeFinder::getGlobalTransform( | |
| const App::DocumentObject& rootObject, | |
| const std::string& leafSub | |
| ) | |
| { | |
| // we prune the last term if it is a vertex, edge or face | |
| std::string newSub = removeGeometryTerm(leafSub); | |
| std::vector<Base::Placement> plmStack; | |
| std::vector<Base::Matrix4D> scaleStack; | |
| // get transforms below rootObject | |
| // Note: root object is provided by the caller and may or may not be a top level object | |
| crawlPlacementChain(plmStack, scaleStack, rootObject, newSub); | |
| auto pathTransform = sumTransforms(plmStack, scaleStack); | |
| // apply the placements in reverse order - top to bottom | |
| // should this be rootObject's local transform? | |
| auto rootTransform = getGlobalTransform(&rootObject); | |
| auto netPlm = rootTransform.first * pathTransform.first; | |
| auto netScale = rootTransform.second * pathTransform.second; | |
| return {netPlm, netScale}; | |
| } | |
| //! tries to get the global position and scale for a object with no information about the | |
| //! path through the tree from a root to cursor object. | |
| std::pair<Base::Placement, Base::Matrix4D> ShapeFinder::getGlobalTransform( | |
| const App::DocumentObject* cursorObject | |
| ) | |
| { | |
| if (!cursorObject) { | |
| return {}; | |
| } | |
| Base::Placement netPlm; | |
| Base::Matrix4D netScale = getScale(cursorObject); | |
| Base::Placement geoPlm; | |
| auto geoCursor = dynamic_cast<const App::GeoFeature*>(cursorObject); | |
| if (!isLinkLike(cursorObject) && geoCursor) { | |
| netPlm = geoCursor->globalPlacement(); | |
| return {netPlm, netScale}; | |
| } | |
| netPlm = getPlacement(cursorObject); | |
| return {netPlm, netScale}; | |
| } | |
| //! combine a series of placement & scale transforms. The input stacks are expected in leaf to root | |
| //! order, but the result is in the expected root to leaf order. | |
| std::pair<Base::Placement, Base::Matrix4D> ShapeFinder::sumTransforms( | |
| const std::vector<Base::Placement>& plmStack, | |
| const std::vector<Base::Matrix4D>& scaleStack | |
| ) | |
| { | |
| Base::Placement netPlm; | |
| Base::Matrix4D netScale; | |
| auto itRevPlm = plmStack.rbegin(); | |
| for (; itRevPlm != plmStack.rend(); itRevPlm++) { | |
| netPlm *= *itRevPlm; | |
| } | |
| auto itRevScale = scaleStack.rbegin(); | |
| for (; itRevScale != scaleStack.rend(); itRevScale++) { | |
| netScale *= *itRevScale; | |
| } | |
| return {netPlm, netScale}; | |
| } | |
| //! get the parent to which attachObject is attached via Links (not regular Part::Attacher | |
| //! attachment) | |
| App::DocumentObject* ShapeFinder::getLinkAttachParent(const App::DocumentObject* attachedObject) | |
| { | |
| auto namedProperty = attachedObject->getPropertyByName("a1AttParent"); | |
| auto attachProperty = dynamic_cast<App::PropertyLink*>(namedProperty); | |
| if (namedProperty && attachProperty) { | |
| return attachProperty->getValue(); | |
| } | |
| return {}; | |
| } | |
| //! debugging routine that returns a string representation of a placement. | |
| // TODO: this should be in Base::Placement? | |
| std::string ShapeFinder::PlacementAsString(const Base::Placement& inPlacement) | |
| { | |
| auto position = inPlacement.getPosition(); | |
| auto rotation = inPlacement.getRotation(); | |
| Base::Vector3d axis; | |
| double angle {0.0}; | |
| rotation.getValue(axis, angle); | |
| std::stringstream ss; | |
| ss << "pos: (" << position.x << ", " << position.y << ", " << position.z << ") axis: (" | |
| << axis.x << ", " << axis.y << ", " << axis.z << ") angle: " << Base::toDegrees(angle); | |
| return ss.str(); | |
| } | |
| //! debug routine. return readable form of TopLoc_Location from OCC | |
| std::string ShapeFinder::LocationAsString(const TopLoc_Location& location) | |
| { | |
| auto position = Base::Vector3d { | |
| location.Transformation().TranslationPart().X(), | |
| location.Transformation().TranslationPart().Y(), | |
| location.Transformation().TranslationPart().Z() | |
| }; | |
| gp_XYZ axisDir; | |
| double angle {0}; | |
| auto isRotation = location.Transformation().GetRotation(axisDir, angle); | |
| Base::Vector3d axis {axisDir.X(), axisDir.Y(), axisDir.Z()}; | |
| std::stringstream ss; | |
| ss << "isRotation: " << isRotation << " pos: (" << position.x << ", " << position.y << ", " | |
| << position.z << ") axis: (" << axisDir.X() << ", " << axisDir.Y() << ", " << axisDir.Z() | |
| << ") angle: " << Base::toDegrees(angle); | |
| return ss.str(); | |
| } | |