From abb50a4daa43abee9e6b263dd82e5e876c978c6a Mon Sep 17 00:00:00 2001 From: WandererFan Date: Mon, 23 Dec 2024 17:36:22 -0500 Subject: [PATCH] [TD]Long and link dim refs (fix #13375) (#18641) * [Meas]Changes for TD dimension refs for links * [TD]App changes for dim refs to links * [TD]Gui changes for dim refs to links * [TD]fix 2 lint messages * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/Mod/Measure/App/AppMeasurePy.cpp | 170 +++++++ src/Mod/Measure/App/CMakeLists.txt | 6 + src/Mod/Measure/App/Measurement.cpp | 38 +- src/Mod/Measure/App/ShapeFinder.cpp | 416 ++++++++++++++++++ src/Mod/Measure/App/ShapeFinder.h | 126 ++++++ src/Mod/Measure/App/SubnameHelper.cpp | 178 ++++++++ src/Mod/Measure/App/SubnameHelper.h | 57 +++ src/Mod/TechDraw/App/DimensionAutoCorrect.cpp | 29 +- src/Mod/TechDraw/App/DimensionReferences.cpp | 58 +-- src/Mod/TechDraw/App/DimensionReferences.h | 2 +- src/Mod/TechDraw/App/DrawViewDimension.cpp | 56 +-- src/Mod/TechDraw/App/DrawViewDimension.h | 6 +- src/Mod/TechDraw/App/ShapeExtractor.cpp | 32 +- src/Mod/TechDraw/App/ShapeExtractor.h | 14 +- src/Mod/TechDraw/Gui/DimensionValidators.cpp | 79 ++-- src/Mod/TechDraw/Gui/DimensionValidators.h | 23 +- 16 files changed, 1092 insertions(+), 198 deletions(-) create mode 100644 src/Mod/Measure/App/AppMeasurePy.cpp create mode 100644 src/Mod/Measure/App/ShapeFinder.cpp create mode 100644 src/Mod/Measure/App/ShapeFinder.h create mode 100644 src/Mod/Measure/App/SubnameHelper.cpp create mode 100644 src/Mod/Measure/App/SubnameHelper.h diff --git a/src/Mod/Measure/App/AppMeasurePy.cpp b/src/Mod/Measure/App/AppMeasurePy.cpp new file mode 100644 index 0000000000..292b5196dd --- /dev/null +++ b/src/Mod/Measure/App/AppMeasurePy.cpp @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 wandererfan * + * * + * 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 * + * . * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" +#ifndef _PreComp_ + +#endif +#include + +#include // clears "include what you use" lint message, but creates "included header not used" +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "Mod/Part/App/OCCError.h" + +#include "ShapeFinder.h" + + +namespace Measure +{ +// module level static C++ functions go here +} + +namespace Measure +{ +/** Copies a Python dictionary of Python strings to a C++ container. + * + * After the function call, the key-value pairs of the Python + * dictionary are copied into the target buffer as C++ pairs + * (pair). + * + * @param sourceRange is a Python dictionary (Py::Dict). Both, the + * keys and the values must be Python strings. + * + * @param targetIt refers to where the data should be inserted. Must + * be of concept output iterator. + */ +template +void copy(Py::Dict sourceRange, OutputIt targetIt) +{ + std::string key; + std::string value; + + for (const auto& keyPy : sourceRange.keys()) { + key = Py::String(keyPy); + value = Py::String(sourceRange[keyPy]); + *targetIt = {key, value}; + ++targetIt; + } +} + + +class Module: public Py::ExtensionModule +{ +public: + Module() + : Py::ExtensionModule("Measure") + { + add_varargs_method( + "getLocatedTopoShape", + &Module::getLocatedTopoShape, + "Part.TopoShape = Measure.getLocatedTopoShape(DocumentObject, longSubElement) Resolves " + "the net placement of DocumentObject and returns the object's shape/subshape with the " + "net placement applied. Link scaling operations along the path are also applied."); + initialize("This is a module for measuring"); // register with Python + } + ~Module() override + {} + +private: + Py::Object invoke_method_varargs(void* method_def, const Py::Tuple& args) override + { + try { + return Py::ExtensionModule::invoke_method_varargs(method_def, args); + } + catch (const Standard_Failure& e) { + std::string str; + Standard_CString msg = e.GetMessageString(); + str += typeid(e).name(); + str += " "; + if (msg) { + str += msg; + } + else { + str += "No OCCT Exception Message"; + } + Base::Console().Error("%s\n", str.c_str()); + throw Py::Exception(Part::PartExceptionOCCError, str); + } + catch (const Base::Exception& e) { + std::string str; + str += "FreeCAD exception thrown ("; + str += e.what(); + str += ")"; + e.ReportException(); + throw Py::RuntimeError(str); + } + catch (const std::exception& e) { + std::string str; + str += "C++ exception thrown ("; + str += e.what(); + str += ")"; + Base::Console().Error("%s\n", str.c_str()); + throw Py::RuntimeError(str); + } + } + + Py::Object getLocatedTopoShape(const Py::Tuple& args) + { + PyObject* pyRootObject {nullptr}; + PyObject* pyLeafSubName {nullptr}; + App::DocumentObject* rootObject {nullptr}; + std::string leafSub; + if (!PyArg_ParseTuple(args.ptr(), "OO", &pyRootObject, &pyLeafSubName)) { + throw Py::TypeError("expected (rootObject, subname"); + } + + if (PyObject_TypeCheck(pyRootObject, &(App::DocumentObjectPy::Type))) { + rootObject = static_cast(pyRootObject)->getDocumentObjectPtr(); + } + + if (PyUnicode_Check(pyLeafSubName)) { + leafSub = PyUnicode_AsUTF8(pyLeafSubName); + } + + if (!rootObject) { + return Py::None(); + } + + // this is on the stack + auto temp = ShapeFinder::getLocatedShape(*rootObject, leafSub); + // need new in here to make the twin object on the heap + auto topoShapePy = new Part::TopoShapePy(new Part::TopoShape(temp)); + return Py::asObject(topoShapePy); + } +}; + +} // namespace Measure diff --git a/src/Mod/Measure/App/CMakeLists.txt b/src/Mod/Measure/App/CMakeLists.txt index d36806f1f6..fe9d0df3f5 100644 --- a/src/Mod/Measure/App/CMakeLists.txt +++ b/src/Mod/Measure/App/CMakeLists.txt @@ -31,6 +31,7 @@ SET(MeasureModule_SRCS PreCompiled.cpp PreCompiled.h AppMeasure.cpp + AppMeasurePy.cpp # original service routines Measurement.cpp @@ -54,6 +55,11 @@ SET(MeasureModule_SRCS Preferences.cpp Preferences.h + + ShapeFinder.cpp + ShapeFinder.h + SubnameHelper.cpp + SubnameHelper.h ) SOURCE_GROUP("Module" FILES ${MeasureModule_SRCS}) diff --git a/src/Mod/Measure/App/Measurement.cpp b/src/Mod/Measure/App/Measurement.cpp index 5d4e6b0140..7965870851 100644 --- a/src/Mod/Measure/App/Measurement.cpp +++ b/src/Mod/Measure/App/Measurement.cpp @@ -48,6 +48,7 @@ #include "Measurement.h" #include "MeasurementPy.h" +#include "ShapeFinder.h" using namespace Measure; @@ -278,43 +279,12 @@ MeasureType Measurement::getType() return measureType; } -TopoDS_Shape Measurement::getShape(App::DocumentObject* rootObj, const char* subName) const +TopoDS_Shape Measurement::getShape(App::DocumentObject* obj, const char* subName) const { - std::vector names = Base::Tools::splitSubName(subName); - - if (names.empty()) { - TopoDS_Shape shape = Part::Feature::getShape(rootObj); - if (shape.IsNull()) { - throw Part::NullShapeException("null shape in measurement"); - } - return shape; - } - - try { - App::DocumentObject* obj = rootObj->getSubObject(subName); - - Part::TopoShape partShape = Part::Feature::getTopoShape(obj); - - partShape.setPlacement(App::GeoFeature::getGlobalPlacement(obj, rootObj, subName)); - - TopoDS_Shape shape = partShape.getSubShape(names.back().c_str()); - if (shape.IsNull()) { - throw Part::NullShapeException("null shape in measurement"); - } - return shape; - } - catch (const Base::Exception&) { - // re-throw original exception - throw; - } - catch (Standard_Failure& e) { - throw Base::CADKernelError(e.GetMessageString()); - } - catch (...) { - throw Base::RuntimeError("Measurement: Unknown error retrieving shape"); - } + return ShapeFinder::getLocatedShape(*obj, subName); } + // TODO:: add lengthX, lengthY (and lengthZ??) support // Methods for distances (edge length, two points, edge and a point double Measurement::length() const diff --git a/src/Mod/Measure/App/ShapeFinder.cpp b/src/Mod/Measure/App/ShapeFinder.cpp new file mode 100644 index 0000000000..3b2a62ec19 --- /dev/null +++ b/src/Mod/Measure/App/ShapeFinder.cpp @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 wandererfan * + * * + * 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 * + * . * + * * + ***************************************************************************/ + +//! 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. + + +#include "PreCompiled.h" +#ifndef _PreComp_ +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "ShapeFinder.h" + + +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); + 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& plmStack, + std::vector& 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.transformGeometry(scaler); + 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(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(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(). 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() || obj->isDerivedFrom() + || obj->isDerivedFrom()) { + return true; + } + + auto namedProperty = obj->getPropertyByName("LinkedObject"); + auto linkedObjectProperty = dynamic_cast(namedProperty); + if (linkedObjectProperty) { + return true; + } + + namedProperty = obj->getPropertyByName("ElementList"); + auto elementListProperty = dynamic_cast(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 +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 plmStack; + std::vector 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}; +} + + +//! trys 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 +ShapeFinder::getGlobalTransform(const App::DocumentObject* cursorObject) +{ + if (!cursorObject) { + return {}; + } + + Base::Placement netPlm; + Base::Matrix4D netScale = getScale(cursorObject); + + Base::Placement geoPlm; + auto geoCursor = dynamic_cast(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 +ShapeFinder::sumTransforms(const std::vector& plmStack, + const std::vector& 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(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(); +} diff --git a/src/Mod/Measure/App/ShapeFinder.h b/src/Mod/Measure/App/ShapeFinder.h new file mode 100644 index 0000000000..297dc9c089 --- /dev/null +++ b/src/Mod/Measure/App/ShapeFinder.h @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 wandererfan * + * * + * 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 * + * . * + * * + ***************************************************************************/ + +#ifndef MEASURE_SHAPEFINDER_H +#define MEASURE_SHAPEFINDER_H + +#include + +#include + +#include +#include +#include +#include + +#include + +#include "SubnameHelper.h" + +namespace Measure +{ + +//! a class to hold the result of resolving a selection into the actual target object +//! and traditional subElement name (Vertex1) + +class MeasureExport ResolveResult +{ +public: + ResolveResult(); + ResolveResult(const App::DocumentObject* realTarget, + const std::string& shortSubName, + const App::DocumentObject* targetParent); + + App::DocumentObject& getTarget() const; + std::string getShortSub() const; + App::DocumentObject& getTargetParent() const; + +private: + App::SubObjectT m_target; + App::DocumentObjectT m_targetParent; +}; + + +//! a class to obtain the located shape pointed at by a selection +class MeasureExport ShapeFinder: public SubnameHelper +{ +public: + static TopoDS_Shape getLocatedShape(const App::DocumentObject& rootObject, + const std::string& leafSub); + static Part::TopoShape getLocatedTopoShape(const App::DocumentObject& rootObject, + const std::string& leafSub); + + + static std::pair + getGlobalTransform(const App::DocumentObject& rootObject, const std::string& leafSub); + static std::pair + getGlobalTransform(const App::DocumentObject* cursorObject); + + static void crawlPlacementChain(std::vector& plmStack, + std::vector& scaleStack, + const App::DocumentObject& rootObj, + const std::string& leafSub); + + static ResolveResult resolveSelection(const App::DocumentObject& selectObj, + const std::string& selectLongSub); + + static Base::Placement getPlacement(const App::DocumentObject* root); + static Base::Matrix4D getScale(const App::DocumentObject* root); + + static bool isLinkLike(const App::DocumentObject* obj); + static std::string PlacementAsString(const Base::Placement& inPlacement); + static std::string LocationAsString(const TopLoc_Location& location); + + static TopoDS_Shape transformShape(TopoDS_Shape& inShape, + const Base::Placement& placement, + const Base::Matrix4D& scaler); + static TopoDS_Shape stripInfiniteShapes(const TopoDS_Shape& inShape); + static bool isShapeReallyNull(const TopoDS_Shape& shape); + + static std::pair + sumTransforms(const std::vector& plmStack, + const std::vector& scaleStack); + static App::DocumentObject* getLinkAttachParent(const App::DocumentObject* attachedObject); + static Base::Placement getAttachedPlacement(const App::DocumentObject* cursorObject); + + static std::string getFullPath(const App::DocumentObject* object); + static std::vector getGeometryRootObjects(const App::Document* doc); + static std::vector> + getGeometryPathsFromOutList(const App::DocumentObject* object); + + +private: + static bool ignoreModule(const std::string& moduleName); + static bool ignoreObject(const App::DocumentObject* object); + static bool ignoreLinkAttachedObject(const App::DocumentObject* object, + const App::DocumentObject* inlistObject); + static std::vector + tidyInList(const std::vector& inlist); + static std::vector + tidyInListAttachment(const App::DocumentObject* owner, + const std::vector& inlist); +}; + +} // namespace Measure + +#endif // MEASURE_SHAPEFINDER_H diff --git a/src/Mod/Measure/App/SubnameHelper.cpp b/src/Mod/Measure/App/SubnameHelper.cpp new file mode 100644 index 0000000000..b929e605b8 --- /dev/null +++ b/src/Mod/Measure/App/SubnameHelper.cpp @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 wandererfan * + * * + * 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 * + * . * + * * + ***************************************************************************/ + +//! a class to perform common operations on subelement names. + + +#include "PreCompiled.h" +#ifndef _PreComp_ +#endif + +#include + +#include + +#include "SubnameHelper.h" + + +using namespace Measure; + + +std::string SubnameHelper::pathToLongSub(std::list path) +{ + std::vector elementNames; + for (auto& item : path) { + auto name = item->getNameInDocument(); + if (!name) { + continue; + } + elementNames.emplace_back(name); + } + return namesToLongSub(elementNames); +} + + +//! construct dot separated long subelement name from a list of elements. the elements should be +//! in topological order. +std::string SubnameHelper::namesToLongSub(const std::vector& pathElementNames) +{ + std::string result; + for (auto& name : pathElementNames) { + result += (name + "."); + } + return result; +} + + +//! return the last term of a dot separated string - A.B.C returns C +std::string SubnameHelper::getLastTerm(const std::string& inString) +{ + auto result {inString}; + size_t lastDot = inString.rfind('.'); + if (lastDot != std::string::npos) { + result = result.substr(lastDot + 1); + } + return result; +} + +//! return the first term of a dot separated string - A.B.C returns A +std::string SubnameHelper::getFirstTerm(const std::string& inString) +{ + auto result {inString}; + size_t lastDot = inString.find('.'); + if (lastDot != std::string::npos) { + result = result.substr(0, lastDot); + } + return result; +} + +//! remove the first term of a dot separated string - A.B.C returns B.C +std::string SubnameHelper::pruneFirstTerm(const std::string& inString) +{ + auto result {inString}; + size_t lastDot = inString.find('.'); + if (lastDot != std::string::npos) { + result = result.substr(lastDot + 1); + } + return result; +} + +//! return a dot separated string without its last term - A.B.C returns A.B. +// A.B.C. returns A.B.C +std::string SubnameHelper::pruneLastTerm(const std::string& inString) +{ + auto result {inString}; + if (result.back() == '.') { + // remove the trailing dot + result = result.substr(0, result.length() - 1); + } + + size_t lastDotPos = result.rfind('.'); + if (lastDotPos != std::string::npos) { + result = result.substr(0, lastDotPos + 1); + } + else { + // no dot in string, remove everything! + result = ""; + } + + return result; +} + +//! remove that part of a long subelement name that refers to a geometric subshape. "myObj.Vertex1" +//! would return "myObj.", "myObj.mySubObj." would return itself unchanged. If there is no +//! geometric reference the original input is returned. +std::string SubnameHelper::removeGeometryTerm(const std::string& longSubname) +{ + auto lastTerm = getLastTerm(longSubname); + if (longSubname.empty() || longSubname.back() == '.') { + // not a geometric reference + return longSubname; // need a copy? + } + + // brute force check for geometry names in the last term + auto pos = lastTerm.find("Vertex"); + if (pos != std::string::npos) { + return pruneLastTerm(longSubname); + } + + pos = lastTerm.find("Edge"); + if (pos != std::string::npos) { + return pruneLastTerm(longSubname); + } + + pos = lastTerm.find("Face"); + if (pos != std::string::npos) { + return pruneLastTerm(longSubname); + } + + pos = lastTerm.find("Shell"); + if (pos != std::string::npos) { + return pruneLastTerm(longSubname); + } + + pos = lastTerm.find("Solid"); + if (pos != std::string::npos) { + return pruneLastTerm(longSubname); + } + + return longSubname; +} + + +//! remove the tnp information from a selection sub name returning a dot separated path +//! Array001.Array001_i0.Array_i1.;Vertex33;:H1116,V.Vertex33 to +//! Array001.Array001_i0.Array_i1.Vertex33 +std::string SubnameHelper::removeTnpInfo(const std::string& inString) +{ + constexpr char TNPDelimiter {';'}; + size_t firstDelimiter = inString.find(TNPDelimiter); + if (firstDelimiter == std::string::npos) { + // no delimiter in string + return inString; + } + auto geomName = getLastTerm(inString); + auto path = inString.substr(0, firstDelimiter); + auto result = path + geomName; + return result; +} diff --git a/src/Mod/Measure/App/SubnameHelper.h b/src/Mod/Measure/App/SubnameHelper.h new file mode 100644 index 0000000000..51417e9891 --- /dev/null +++ b/src/Mod/Measure/App/SubnameHelper.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 wandererfan * + * * + * 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 * + * . * + * * + ***************************************************************************/ + +#ifndef MEASURE_SUBNAMEMANIPULATOR_H +#define MEASURE_SUBNAMEMANIPULATOR_H + +#include + +#include + +#include +#include +#include +#include + +#include + +namespace Measure +{ + +//! a class to perform common operations on subelement names. +class MeasureExport SubnameHelper +{ +public: + static std::string getLastTerm(const std::string& inString); + static std::string getFirstTerm(const std::string& inString); + static std::string namesToLongSub(const std::vector& pathElementNames); + static std::string pruneLastTerm(const std::string& inString); + static std::string pruneFirstTerm(const std::string& inString); + static std::string removeGeometryTerm(const std::string& longSubname); + static std::string pathToLongSub(std::list path); + static std::string removeTnpInfo(const std::string& inString); +}; + +} // namespace Measure + +#endif // MEASURE_SUBNAMEMANIPULATOR_H diff --git a/src/Mod/TechDraw/App/DimensionAutoCorrect.cpp b/src/Mod/TechDraw/App/DimensionAutoCorrect.cpp index 51b2b0807c..a18edac90c 100644 --- a/src/Mod/TechDraw/App/DimensionAutoCorrect.cpp +++ b/src/Mod/TechDraw/App/DimensionAutoCorrect.cpp @@ -44,7 +44,7 @@ // // reference // replace(ref, newRef) // else: -// // auto correct phase 2 +// // 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 @@ -68,6 +68,7 @@ #include #include +#include #include "GeometryMatcher.h" #include "DimensionReferences.h" @@ -77,6 +78,7 @@ #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 @@ -173,17 +175,17 @@ bool DimensionAutoCorrect::autocorrectReferences(std::vector& referenceSta continue; } - // we did not find an exact match, so check for an similar match + // 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 an similar match + // we did find a similar match referenceState.at(iRef) = true; repairedRefs.push_back(fixedRef); iRef++; continue; } - // we did not find an similar match the geometry + // we did not find a similar match the geometry result = false; referenceState.at(iRef) = false; repairedRefs.push_back(fixedRef); @@ -289,7 +291,8 @@ bool DimensionAutoCorrect::findExactEdge2d(ReferenceEntry& refToFix, const Part: return true; } } - // no match + + // no match, return the input reference return false; } @@ -413,8 +416,16 @@ bool DimensionAutoCorrect::findSimilarEdge3d(ReferenceEntry& refToFix, bool DimensionAutoCorrect::isMatchingGeometry(const ReferenceEntry& ref, const Part::TopoShape& savedGeometry) const { - // Base::Console().Message("DAC::isMatchingGeometry()\n"); - Part::TopoShape temp = ref.asCanonicalTopoShape(); + 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; @@ -435,7 +446,7 @@ ReferenceEntry DimensionAutoCorrect::searchObjForVert(App::DocumentObject* obj, bool exact) const { (void)exact; - auto shape3d = Part::Feature::getShape(obj); + auto shape3d = ShapeFinder::getLocatedShape(*obj, ""); if (shape3d.IsNull()) { // how to handle this? return {}; @@ -443,7 +454,7 @@ ReferenceEntry DimensionAutoCorrect::searchObjForVert(App::DocumentObject* obj, auto vertsAll = getDimension()->getVertexes(shape3d); size_t iVert {1}; for (auto& vert : vertsAll) { - bool isSame = getMatcher()->compareGeometry(vert, refVertex); + bool isSame = getMatcher()->compareGeometry(refVertex, vert); if (isSame) { auto newSubname = std::string("Vertex") + std::to_string(iVert); return {obj, newSubname, getDimension()->getDocument()}; diff --git a/src/Mod/TechDraw/App/DimensionReferences.cpp b/src/Mod/TechDraw/App/DimensionReferences.cpp index 72d9ca9f17..dd350d1a70 100644 --- a/src/Mod/TechDraw/App/DimensionReferences.cpp +++ b/src/Mod/TechDraw/App/DimensionReferences.cpp @@ -33,7 +33,10 @@ #include #include #include +#include #include + +#include #include #include #include @@ -45,11 +48,12 @@ #include "CosmeticVertex.h" using namespace TechDraw; +using namespace Measure; using DU = DrawUtil; using SU = ShapeUtils; -ReferenceEntry::ReferenceEntry( App::DocumentObject* docObject, std::string subName, App::Document* document) +ReferenceEntry::ReferenceEntry( App::DocumentObject* docObject, const std::string& subName, App::Document* document) { setObject(docObject); setSubName(subName); @@ -66,7 +70,7 @@ ReferenceEntry::ReferenceEntry( App::DocumentObject* docObject, std::string subN ReferenceEntry::ReferenceEntry(const ReferenceEntry& other) { setObject(other.getObject()); - setSubName(other.getSubName()); + setSubName(other.getSubName(true)); setObjectName(other.getObjectName()); setDocument(other.getDocument()); } @@ -79,7 +83,7 @@ ReferenceEntry& ReferenceEntry::operator=(const ReferenceEntry& otherRef) return *this; } setObject(otherRef.getObject()); - setSubName(otherRef.getSubName()); + setSubName(otherRef.getSubName(true)); setObjectName(otherRef.getObjectName()); setDocument(otherRef.getDocument()); return *this; @@ -94,8 +98,6 @@ bool ReferenceEntry::operator==(const ReferenceEntry& otherRef) const TopoDS_Shape ReferenceEntry::getGeometry() const { - // Base::Console().Message("RE::getGeometry() - objectName: %s sub: **%s**\n", - // getObjectName(), getSubName()); // first, make sure the object has not been deleted! App::DocumentObject* obj = getDocument()->getObject(getObjectName().c_str()); if (!obj) { @@ -112,24 +114,13 @@ TopoDS_Shape ReferenceEntry::getGeometry() const } // 3d geometry - Part::TopoShape shape = Part::Feature::getTopoShape(getObject()); - auto geoFeat = getObject(); - if (geoFeat) { - shape.setPlacement(geoFeat->globalPlacement()); - } - - if (getSubName().empty()) { - return shape.getShape(); - } - // TODO: what happens if the subelement is no longer present? - return shape.getSubShape(getSubName().c_str()); + return ShapeFinder::getLocatedShape(*getObject(), getSubName(true)); } //! get a shape for this 2d reference TopoDS_Shape ReferenceEntry::getGeometry2d() const { - // Base::Console().Message("RE::getGeometry2d()\n"); std::string gType; try { auto dvp = getObject(); //NOLINT cppcoreguidelines-pro-type-static-cast-downcast @@ -160,7 +151,6 @@ TopoDS_Shape ReferenceEntry::getGeometry2d() const } catch (...) { Base::Console().Message("RE::getGeometry2d - no shape for dimension 2d reference - gType: **%s**\n", gType.c_str()); - return {}; } return {}; @@ -172,12 +162,8 @@ std::string ReferenceEntry::getSubName(bool longForm) const if (longForm) { return m_subName; } - std::string workingSubName(m_subName); - size_t lastDot = workingSubName.rfind('.'); - if (lastDot != std::string::npos) { - workingSubName = workingSubName.substr(lastDot + 1); - } - return workingSubName; + + return ShapeFinder::getLastTerm(m_subName); } @@ -198,10 +184,8 @@ App::DocumentObject* ReferenceEntry::getObject() const //! return the reference geometry as a Part::TopoShape. Part::TopoShape ReferenceEntry::asTopoShape() const { - // Base::Console().Message("RE::asTopoShape()\n"); TopoDS_Shape geom = getGeometry(); if (geom.IsNull()) { - // throw Base::RuntimeError("Dimension Reference has null geometry"); return {}; } if (geom.ShapeType() == TopAbs_VERTEX) { @@ -222,7 +206,6 @@ Part::TopoShape ReferenceEntry::asTopoShape() const //! returns unscaled, unrotated version of inShape. inShape is assumed to be a 2d shape, but this is not enforced. Part::TopoShape ReferenceEntry::asCanonicalTopoShape() const { - // Base::Console().Message("RE::asCanonicalTopoShape()\n"); if (is3d()) { return asTopoShape(); } @@ -240,7 +223,6 @@ Part::TopoShape ReferenceEntry::asCanonicalTopoShape() const //! operations. Part::TopoShape ReferenceEntry::asCanonicalTopoShape(const Part::TopoShape& inShape, const DrawViewPart& dvp) { - // Base::Console().Message("RE::(static)asCanonicalTopoShape()\n"); gp_Ax2 OXYZ; auto unscaledShape = SU::scaleShape(inShape.getShape(), 1.0 / dvp.getScale()); if (dvp.Rotation.getValue() != 0.0) { @@ -270,7 +252,6 @@ Part::TopoShape ReferenceEntry::asTopoShapeFace(const TopoDS_Face &face) std::string ReferenceEntry::geomType() const { - // Base::Console().Message("RE::geomType() - subName: **%s**\n", getSubName().c_str()); return DrawUtil::getGeomTypeFromName(getSubName()); } @@ -295,14 +276,21 @@ bool ReferenceEntry::isWholeObject() const //! true if this reference point to 3d model geometry bool ReferenceEntry::is3d() const { - if (!getObject()) { - // we should really fail here? - return false; - } - if (getObject()->isDerivedFrom(TechDraw::DrawViewPart::getClassTypeId())) { + if (getObject() && + getObject()->isDerivedFrom(TechDraw::DrawViewPart::getClassTypeId()) && + !getSubName().empty()) { + // this is a well formed 2d reference return false; } + if (getObject() && + getObject()->isDerivedFrom(TechDraw::DrawViewPart::getClassTypeId()) && + getSubName().empty()) { + // this is a broken 3d reference, so it should be treated as 3d + return true; + } + + // either we have no object or we have an object and it is a 3d object return true; } @@ -310,7 +298,6 @@ bool ReferenceEntry::is3d() const //! true if the target of this reference has a shape bool ReferenceEntry::hasGeometry() const { - // Base::Console().Message("RE::hasGeometry()\n"); if (!getObject()) { return false; } @@ -321,6 +308,7 @@ bool ReferenceEntry::hasGeometry() const } // 3d reference + // TODO: shouldn't this be ShapeFinder.getLocatedShape? auto shape = Part::Feature::getTopoShape(getObject()); auto subShape = shape.getSubShape(getSubName().c_str()); diff --git a/src/Mod/TechDraw/App/DimensionReferences.h b/src/Mod/TechDraw/App/DimensionReferences.h index 8816cd8f88..e974949c96 100644 --- a/src/Mod/TechDraw/App/DimensionReferences.h +++ b/src/Mod/TechDraw/App/DimensionReferences.h @@ -55,7 +55,7 @@ class TechDrawExport ReferenceEntry { public: ReferenceEntry() = default; - ReferenceEntry( App::DocumentObject* docObject, std::string subName, App::Document* document = nullptr); + ReferenceEntry( App::DocumentObject* docObject, const std::string& subName, App::Document* document = nullptr); ReferenceEntry(const ReferenceEntry& other); ~ReferenceEntry() = default; diff --git a/src/Mod/TechDraw/App/DrawViewDimension.cpp b/src/Mod/TechDraw/App/DrawViewDimension.cpp index 3264180c6c..ce421aab3b 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.cpp +++ b/src/Mod/TechDraw/App/DrawViewDimension.cpp @@ -645,7 +645,6 @@ QStringList DrawViewDimension::getPrefixSuffixSpec(const QString &fSpec) //! NOTE: this returns the Dimension value in internal units (ie mm)!!!! double DrawViewDimension::getDimValue() { - // Base::Console().Message("DVD::getDimValue()\n"); constexpr double CircleDegrees{360.0}; double result = 0.0; if (!has2DReferences() && !has3DReferences()) { @@ -703,7 +702,7 @@ double DrawViewDimension::getTrueDimValue() const result = measurement->radius(); } else if (Type.isValue("Diameter")) { - result = 2.0 * measurement->radius(); + result = 2 * measurement->radius(); } else if (Type.isValue("Angle") || Type.isValue("Angle3Pt")) { result = measurement->angle(); @@ -720,7 +719,6 @@ double DrawViewDimension::getTrueDimValue() const //! retrieve the dimension value for "projected" (2d) dimensions. The returned value is in internal units (mm). double DrawViewDimension::getProjectedDimValue() const { - // Base::Console().Message("DVD::getProjectedDimValue()\n"); double result = 0.0; double scale = getViewPart()->getScale(); @@ -736,7 +734,8 @@ double DrawViewDimension::getProjectedDimValue() const // then we should not move the points. // pts.invertY(); - pts.scale(1.0 / scale); + // unscale the points, map them to the broken view then rescale them to draw. + pts.scale(1 / scale); pts.first(dbv->mapPoint2dFromView(pts.first())); pts.second(dbv->mapPoint2dFromView(pts.second())); pts.invertY(); @@ -808,7 +807,6 @@ pointPair DrawViewDimension::getLinearPoints() const pointPair DrawViewDimension::getPointsOneEdge(ReferenceVector references) { - // Base::Console().Message("DVD::getPointsOneEdge()\n"); App::DocumentObject* refObject = references.front().getObject(); int iSubelement = DrawUtil::getIndexFromName(references.front().getSubName()); if (refObject->isDerivedFrom(TechDraw::DrawViewPart::getClassTypeId()) @@ -849,7 +847,6 @@ pointPair DrawViewDimension::getPointsOneEdge(ReferenceVector references) pointPair DrawViewDimension::getPointsTwoEdges(ReferenceVector references) { - // Base::Console().Message("DVD::getPointsTwoEdges() - %s\n", getNameInDocument()); App::DocumentObject* refObject = references.front().getObject(); int iSubelement0 = DrawUtil::getIndexFromName(references.at(0).getSubName()); int iSubelement1 = DrawUtil::getIndexFromName(references.at(1).getSubName()); @@ -882,7 +879,6 @@ pointPair DrawViewDimension::getPointsTwoEdges(ReferenceVector references) pointPair DrawViewDimension::getPointsTwoVerts(ReferenceVector references) { - // Base::Console().Message("DVD::getPointsTwoVerts() - %s\n", getNameInDocument()); App::DocumentObject* refObject = references.front().getObject(); int iSubelement0 = DrawUtil::getIndexFromName(references.at(0).getSubName()); int iSubelement1 = DrawUtil::getIndexFromName(references.at(1).getSubName()); @@ -920,7 +916,6 @@ pointPair DrawViewDimension::getPointsTwoVerts(ReferenceVector references) pointPair DrawViewDimension::getPointsEdgeVert(ReferenceVector references) { - // Base::Console().Message("DVD::getPointsEdgeVert() - %s\n", getNameInDocument()); App::DocumentObject* refObject = references.front().getObject(); int iSubelement0 = DrawUtil::getIndexFromName(references.at(0).getSubName()); int iSubelement1 = DrawUtil::getIndexFromName(references.at(1).getSubName()); @@ -978,7 +973,6 @@ pointPair DrawViewDimension::getPointsEdgeVert(ReferenceVector references) arcPoints DrawViewDimension::getArcParameters(ReferenceVector references) { - // Base::Console().Message("DVD::getArcParameters()\n"); App::DocumentObject* refObject = references.front().getObject(); int iSubelement = DrawUtil::getIndexFromName(references.front().getSubName()); if (refObject->isDerivedFrom(TechDraw::DrawViewPart::getClassTypeId()) @@ -1039,7 +1033,7 @@ arcPoints DrawViewDimension::arcPointsFromBaseGeom(TechDraw::BaseGeomPtr base) if (ellipse->closed()) { double r1 = ellipse->minor; double r2 = ellipse->major; - double rAvg = (r1 + r2) / 2.0; + double rAvg = (r1 + r2) / 2; pts.center = Base::Vector3d(ellipse->center.x, ellipse->center.y, 0.0); pts.radius = rAvg; pts.isArc = false; @@ -1052,7 +1046,7 @@ arcPoints DrawViewDimension::arcPointsFromBaseGeom(TechDraw::BaseGeomPtr base) TechDraw::AOEPtr aoe = std::static_pointer_cast(base); double r1 = aoe->minor; double r2 = aoe->major; - double rAvg = (r1 + r2) / 2.0; + double rAvg = (r1 + r2) / 2; pts.isArc = true; pts.center = Base::Vector3d(aoe->center.x, aoe->center.y, 0.0); pts.radius = rAvg; @@ -1113,10 +1107,12 @@ arcPoints DrawViewDimension::arcPointsFromEdge(TopoDS_Edge occEdge) arcPoints pts; pts.isArc = !BRep_Tool::IsClosed(occEdge); pts.arcCW = false; + + // get all the common information for circle, ellipse and bspline conversions BRepAdaptor_Curve adapt(occEdge); double pFirst = adapt.FirstParameter(); double pLast = adapt.LastParameter(); - double pMid = (pFirst + pLast) / 2.0; + double pMid = (pFirst + pLast) / 2; BRepLProp_CLProps props(adapt, pFirst, 0, Precision::Confusion()); pts.arcEnds.first(DrawUtil::toVector3d(props.Value())); props.SetParameter(pLast); @@ -1208,7 +1204,6 @@ arcPoints DrawViewDimension::arcPointsFromEdge(TopoDS_Edge occEdge) anglePoints DrawViewDimension::getAnglePointsTwoEdges(ReferenceVector references) { - // Base::Console().Message("DVD::getAnglePointsTwoEdges() - %s\n", getNameInDocument()); App::DocumentObject* refObject = references.front().getObject(); int iSubelement0 = DrawUtil::getIndexFromName(references.at(0).getSubName()); int iSubelement1 = DrawUtil::getIndexFromName(references.at(1).getSubName()); @@ -1343,7 +1338,6 @@ anglePoints DrawViewDimension::getAnglePointsTwoEdges(ReferenceVector references // somewhere? anglePoints DrawViewDimension::getAnglePointsThreeVerts(ReferenceVector references) { - // Base::Console().Message("DVD::getAnglePointsThreeVerts() - %s\n", getNameInDocument()); if (references.size() < 3) { throw Base::RuntimeError("Not enough references to make angle dimension"); } @@ -1478,7 +1472,6 @@ DrawViewPart* DrawViewDimension::getViewPart() const // subName) ReferenceVector DrawViewDimension::getEffectiveReferences() const { - // Base::Console().Message("DVD::getEffectiveReferences()\n"); const std::vector& objects3d = References3D.getValues(); const std::vector& subElements3d = References3D.getSubValues(); const std::vector& objects = References2D.getValues(); @@ -1549,7 +1542,7 @@ int DrawViewDimension::getRefType() const // decide what the reference configuration is by examining the names of the sub elements int DrawViewDimension::getRefTypeSubElements(const std::vector& subElements) { - int refType = invalidRef; + int refType{invalidRef}; int refEdges{0}; int refVertices{0}; int refFaces{0}; @@ -1591,7 +1584,6 @@ int DrawViewDimension::getRefTypeSubElements(const std::vector& sub //! validate 2D references - only checks if the target exists bool DrawViewDimension::checkReferences2D() const { - // Base::Console().Message("DVD::checkReferences2d() - %s\n", getNameInDocument()); const std::vector& objects = References2D.getValues(); if (objects.empty()) { return false; @@ -1650,8 +1642,6 @@ bool DrawViewDimension::hasBroken3dReferences() const void DrawViewDimension::updateSavedGeometry() { - // Base::Console().Message("DVD::updateSavedGeometry() - %s - savedGeometry: %d\n", - // getNameInDocument(), SavedGeometry.getValues().size()); ReferenceVector references = getEffectiveReferences(); if (references.empty()) { // no references to save @@ -1719,6 +1709,20 @@ std::vector DrawViewDimension::getVertexes(const TopoShape& inShape) return ret; } +//! returns the angle subtended by an arc from 3 points. +double DrawViewDimension::getArcAngle(Base::Vector3d center, Base::Vector3d startPoint, Base::Vector3d endPoint) +{ + auto leg0 = startPoint - center; + auto leg1 = endPoint - startPoint; + auto referenceDirection = leg0.Cross(leg1); + gp_Ax1 axis{DU::togp_Pnt(center), DU::togp_Vec(referenceDirection)}; + gp_Vec startVec = DrawUtil::togp_Vec(leg0); + gp_Vec endVec = DrawUtil::togp_Vec(leg1); + double angle = startVec.AngleWithRef(endVec, axis.Direction().XYZ()); + return angle; +} + + pointPair DrawViewDimension::closestPoints(TopoDS_Shape s1, TopoDS_Shape s2) const { pointPair result; @@ -1740,7 +1744,6 @@ pointPair DrawViewDimension::closestPoints(TopoDS_Shape s1, TopoDS_Shape s2) con // set the reference property from a reference vector void DrawViewDimension::setReferences2d(const ReferenceVector& refsAll) { - // Base::Console().Message("DVD::setReferences2d(%d)\n", refs.size()); std::vector objects; std::vector subNames; if (objects.size() != subNames.size()) { @@ -1759,7 +1762,6 @@ void DrawViewDimension::setReferences2d(const ReferenceVector& refsAll) // set the reference property from a reference vector void DrawViewDimension::setReferences3d(const ReferenceVector &refsAll) { - // Base::Console().Message("DVD::setReferences3d()\n"); if (refsAll.empty() && !References3D.getValues().empty()) { // clear the property of any old links References3D.setValue(nullptr, nullptr); @@ -1773,7 +1775,7 @@ void DrawViewDimension::setReferences3d(const ReferenceVector &refsAll) for (auto& ref : refsAll) { objects.push_back(ref.getObject()); - subNames.push_back(ref.getSubName()); + subNames.push_back(ref.getSubName(true)); // cache the referenced object m_3dObjectCache.insert(ref.getObject()->getNameInDocument()); // cache the parent object if available. Ideally, we would handle deletion @@ -1794,7 +1796,6 @@ void DrawViewDimension::setReferences3d(const ReferenceVector &refsAll) //! add Dimension 3D references to measurement void DrawViewDimension::setAll3DMeasurement() { - // Base::Console().Message("DVD::setAll3dMeasurement()\n"); measurement->clear(); const std::vector& Objs = References3D.getValues(); const std::vector& Subs = References3D.getSubValues(); @@ -1820,7 +1821,6 @@ void DrawViewDimension::setAll3DMeasurement() //! dimension. bool DrawViewDimension::validateReferenceForm() const { - // Base::Console().Message("DVD::validateReferenceForm()\n"); // we have either or both valid References3D and References2D ReferenceVector references = getEffectiveReferences(); if (references.empty()) { @@ -1925,8 +1925,8 @@ void DrawViewDimension::dumpRefs2D(const char* text) const Base::Console().Message("DUMP - %s\n", text); const std::vector& objects = References2D.getValues(); const std::vector& subElements = References2D.getSubValues(); - std::vector::const_iterator objIt = objects.begin(); - std::vector::const_iterator subIt = subElements.begin(); + auto objIt = objects.begin(); + auto subIt = subElements.begin(); int i = 0; for (; objIt != objects.end(); objIt++, subIt++, i++) { Base::Console().Message("DUMP - ref: %d object: %s subElement: %s\n", @@ -1936,6 +1936,7 @@ void DrawViewDimension::dumpRefs2D(const char* text) const } } +// TODO: this should go into DrawUtil or ShapeUtil or ?? double DrawViewDimension::dist2Segs(Base::Vector3d s1, Base::Vector3d e1, Base::Vector3d s2, @@ -2043,7 +2044,6 @@ pointPair DrawViewDimension::getArrowPositions() bool DrawViewDimension::has2DReferences() const { - // Base::Console().Message("DVD::has2DReferences() - %s\n",getNameInDocument()); const std::vector& objects = References2D.getValues(); const std::vector& subNames = References2D.getSubValues(); if (objects.empty()) { @@ -2120,6 +2120,8 @@ PyObject* DrawViewDimension::getPyObject() return Py::new_reference_to(PythonObject); } + +//! store the corners of this dimension's base view for use by phase 2 of the auto correct process. void DrawViewDimension::saveFeatureBox() { std::vector bbxCorners; diff --git a/src/Mod/TechDraw/App/DrawViewDimension.h b/src/Mod/TechDraw/App/DrawViewDimension.h index 5f3bd1ec28..c45ab4f0d1 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.h +++ b/src/Mod/TechDraw/App/DrawViewDimension.h @@ -204,9 +204,9 @@ public: return m_corrector; } - // these should probably be static as they don't use the dimension at all - std::vector getEdges(const Part::TopoShape& inShape); - std::vector getVertexes(const Part::TopoShape& inShape); + static std::vector getEdges(const Part::TopoShape& inShape); + static std::vector getVertexes(const Part::TopoShape& inShape); + static double getArcAngle(Base::Vector3d center, Base::Vector3d startPoint, Base::Vector3d endPoint); // autocorrect support methods void saveFeatureBox(); diff --git a/src/Mod/TechDraw/App/ShapeExtractor.cpp b/src/Mod/TechDraw/App/ShapeExtractor.cpp index e7b97426e3..24ecc02a22 100644 --- a/src/Mod/TechDraw/App/ShapeExtractor.cpp +++ b/src/Mod/TechDraw/App/ShapeExtractor.cpp @@ -47,6 +47,7 @@ #include #include #include +#include //#include #include "ShapeExtractor.h" @@ -55,6 +56,7 @@ #include "Preferences.h" using namespace TechDraw; +using namespace Measure; using DU = DrawUtil; using SU = ShapeUtils; @@ -166,7 +168,7 @@ TopoDS_Shape ShapeExtractor::getShapes(const std::vector l continue; } else if (s.ShapeType() < TopAbs_SOLID) { //clean up composite shapes - TopoDS_Shape cleanShape = stripInfiniteShapes(s); + TopoDS_Shape cleanShape = ShapeFinder::ShapeFinder::stripInfiniteShapes(s); if (!cleanShape.IsNull()) { builder.Add(comp, cleanShape); } @@ -226,7 +228,7 @@ std::vector ShapeExtractor::getXShapes(const App::Link* xLink) auto shape = Part::Feature::getShape(l); // TODO: getTopoShape() ? Part::TopoShape ts(shape); if (ts.isInfinite()) { - shape = stripInfiniteShapes(shape); + shape = ShapeFinder::stripInfiniteShapes(shape); } if (!checkShape(l, shape)) { continue; @@ -285,7 +287,7 @@ TopoDS_Shape ShapeExtractor::getShapeFromXLink(const App::Link* xLink) } Part::TopoShape ts(shape); if (ts.isInfinite()) { - shape = stripInfiniteShapes(shape); + shape = ShapeFinder::stripInfiniteShapes(shape); ts = Part::TopoShape(shape); } //ts might be garbage now, better check @@ -379,30 +381,6 @@ TopoDS_Shape ShapeExtractor::getShapesFused(const std::vector links, bool include2d = true); static std::vector getShapes2d(const std::vector links); static std::vector getXShapes(const App::Link* xLink); - static std::vector getShapesFromObject(const App::DocumentObject* docObj); static TopoDS_Shape getShapesFused(const std::vector links); static TopoDS_Shape getShapeFromXLink(const App::Link* xLink); + static std::vector getShapesFromXRoot(const App::DocumentObject *xLinkRoot); + static std::vector getShapesFromObject(const App::DocumentObject* docObj); static bool is2dObject(const App::DocumentObject* obj); static bool isEdgeType(const App::DocumentObject* obj); @@ -52,14 +53,19 @@ public: static bool isDraftPoint(const App::DocumentObject* obj); static bool isDatumPoint(const App::DocumentObject* obj); static bool isSketchObject(const App::DocumentObject* obj); + static bool isExplodedAssembly(const App::DocumentObject* obj); + static Base::Vector3d getLocation3dFromFeat(const App::DocumentObject *obj); - - static TopoDS_Shape stripInfiniteShapes(TopoDS_Shape inShape); - static TopoDS_Shape getLocatedShape(const App::DocumentObject* docObj); static bool checkShape(const App::DocumentObject* shapeObj, TopoDS_Shape shape); + static App::DocumentObject* getExplodedAssembly(std::vector& sourceShapes, + App::DocumentObject* link); + static void restoreExplodedAssembly(App::DocumentObject* link); + + static App::DocumentObject* getLinkedObject(const App::DocumentObject* root); + protected: private: diff --git a/src/Mod/TechDraw/Gui/DimensionValidators.cpp b/src/Mod/TechDraw/Gui/DimensionValidators.cpp index f2d9c82478..f477c2f6c9 100644 --- a/src/Mod/TechDraw/Gui/DimensionValidators.cpp +++ b/src/Mod/TechDraw/Gui/DimensionValidators.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,7 @@ using namespace TechDraw; +using namespace Measure; using DU = DrawUtil; TechDraw::DrawViewPart* TechDraw::getReferencesFromSelection(ReferenceVector& references2d, @@ -44,15 +46,21 @@ TechDraw::DrawViewPart* TechDraw::getReferencesFromSelection(ReferenceVector& re { TechDraw::DrawViewPart* dvp(nullptr); TechDraw::DrawViewDimension* dim(nullptr); - std::vector selectionAll = Gui::Selection().getSelectionEx(); + constexpr bool allowOnlySingle{false}; + std::vector selectionAll = + Gui::Selection().getSelectionEx("*", + App::DocumentObject::getClassTypeId(), + Gui::ResolveMode::NoResolve, + allowOnlySingle); + for (auto& selItem : selectionAll) { if (selItem.getObject()->isDerivedFrom(TechDraw::DrawViewDimension::getClassTypeId())) { //we are probably repairing a dimension, but we will check later - dim = static_cast(selItem.getObject()); + dim = static_cast(selItem.getObject()); //NOLINT cppcoreguidelines-pro-type-static-cast-downcast } else if (selItem.getObject()->isDerivedFrom(TechDraw::DrawViewPart::getClassTypeId())) { //this could be a 2d geometry selection or just a DrawViewPart for context in //a 3d selection - dvp = static_cast(selItem.getObject()); + dvp = static_cast(selItem.getObject()); //NOLINT cppcoreguidelines-pro-type-static-cast-downcast if (selItem.getSubNames().empty()) { //there are no subNames, so we think this is a 3d case, //and we only need to select the view. We set the reference @@ -62,38 +70,17 @@ TechDraw::DrawViewPart* TechDraw::getReferencesFromSelection(ReferenceVector& re continue; } for (auto& sub : selItem.getSubNames()) { - ReferenceEntry ref(dvp, sub); + // plain ordinary 2d view + geometry reference + + ReferenceEntry ref(dvp, ShapeFinder::getLastTerm(sub)); references2d.push_back(ref); } } else if (!selItem.getObject()->isDerivedFrom(TechDraw::DrawView::getClassTypeId())) { - //this is not a TechDraw object, so we check to see if it has 3d geometry - std::vector links; - links.push_back(selItem.getObject()); - if (!ShapeExtractor::getShapes(links).IsNull()) { - //this item has 3d geometry so we are interested - App::DocumentObject* obj3d = selItem.getObject(); - if (selItem.getSubNames().empty()) { - if (ShapeExtractor::isPointType(obj3d)) { - //a point object may not have a subName when selected, - //so we need to perform some special handling. - ReferenceEntry ref(obj3d, "Vertex1"); - references3d.push_back(ref); - continue; - } else { - //this is a whole object reference, probably for an extent dimension - ReferenceEntry ref(obj3d, std::string()); - references3d.push_back(ref); - continue; - } - } - //this is a regular reference in form obj+subelement - for (auto& sub3d : selItem.getSubNames()) { - ReferenceEntry ref(obj3d, sub3d); - references3d.push_back(ref); - } - } else { - Base::Console().Message("DV::getRefsFromSel - %s has no shape!\n", - selItem.getObject()->getNameInDocument()); + App::DocumentObject* obj3d = selItem.getObject(); + // this is a regular 3d reference in form obj + long subelement + for (auto& sub3d : selItem.getSubNames()) { + ReferenceEntry ref(obj3d, sub3d); + references3d.push_back(ref); } } } @@ -109,15 +96,15 @@ TechDraw::DrawViewPart* TechDraw::getReferencesFromSelection(ReferenceVector& re //! verify that the proposed references contains valid geometries from a 2d DrawViewPart. DimensionGeometryType TechDraw::validateDimSelection( - ReferenceVector references, //[(dvp*, std::string),...,(dvp*, std::string)] - StringVector acceptableGeometry,//"Edge", "Vertex", etc - std::vector minimumCounts, //how many of each geometry are needed for a good dimension - std::vector acceptableDimensionGeometrys)//isVertical, isHorizontal, ... + const ReferenceVector& references, //[(dvp*, std::string),...,(dvp*, std::string)] + const StringVector& acceptableGeometry,//"Edge", "Vertex", etc + const std::vector& minimumCounts, //how many of each geometry are needed for a good dimension + const std::vector& acceptableDimensionGeometrys)//isVertical, isHorizontal, ... { StringVector subNames; TechDraw::DrawViewPart* dvpSave(nullptr); for (auto& ref : references) { - auto* dvp = dynamic_cast(ref.getObject()); + auto dvp = dynamic_cast(ref.getObject()); if (dvp) { dvpSave = dvp; if (!ref.getSubName().empty()) { @@ -181,15 +168,15 @@ DimensionGeometryType TechDraw::validateDimSelection( //! verify that the proposed references contains valid geometries from non-TechDraw objects. DimensionGeometryType TechDraw::validateDimSelection3d( TechDraw::DrawViewPart* dvp, - ReferenceVector references, //[(dvp*, std::string),...,(dvp*, std::string)] - StringVector acceptableGeometry,//"Edge", "Vertex", etc - std::vector minimumCounts, //how many of each geometry are needed for a good dimension - std::vector acceptableDimensionGeometrys)//isVertical, isHorizontal, ... + const ReferenceVector& references, //[(dvp*, std::string),...,(dvp*, std::string)] + const StringVector& acceptableGeometry,//"Edge", "Vertex", etc + const std::vector& minimumCounts, //how many of each geometry are needed for a good dimension + const std::vector& acceptableDimensionGeometrys)//isVertical, isHorizontal, ... { StringVector subNames; for (auto& ref : references) { if (!ref.getSubName().empty()) { - subNames.push_back(ref.getSubName()); + subNames.push_back(ref.getSubName(true)); } } @@ -225,7 +212,7 @@ DimensionGeometryType TechDraw::validateDimSelection3d( bool TechDraw::validateSubnameList(StringVector subNames, GeometrySet acceptableGeometrySet) { for (auto& sub : subNames) { - std::string geometryType = DrawUtil::getGeomTypeFromName(sub); + std::string geometryType = DrawUtil::getGeomTypeFromName(ShapeFinder::getLastTerm(sub)); if (acceptableGeometrySet.count(geometryType) == 0) { //this geometry type is not allowed return false; @@ -240,7 +227,7 @@ bool TechDraw::checkGeometryOccurrences(StringVector subNames, GeomCountMap keye //how many of each geometry descriptor are input GeomCountMap foundCounts; for (auto& sub : subNames) { - std::string geometryType = DrawUtil::getGeomTypeFromName(sub); + std::string geometryType = DrawUtil::getGeomTypeFromName(ShapeFinder::getLastTerm(sub)); std::map::iterator it0(foundCounts.find(geometryType)); if (it0 == foundCounts.end()) { //first occurrence of this geometryType @@ -355,8 +342,8 @@ DimensionGeometryType TechDraw::getGeometryConfiguration3d(DrawViewPart* dvp, //fill the GeomCountMap with pairs made from corresponding items in acceptableGeometry //and minimumCounts -GeomCountMap TechDraw::loadRequiredCounts(StringVector& acceptableGeometry, - std::vector& minimumCounts) +GeomCountMap TechDraw::loadRequiredCounts(const StringVector& acceptableGeometry, + const std::vector& minimumCounts) { if (acceptableGeometry.size() != minimumCounts.size()) { throw Base::IndexError("acceptableGeometry and minimum counts have different sizes."); diff --git a/src/Mod/TechDraw/Gui/DimensionValidators.h b/src/Mod/TechDraw/Gui/DimensionValidators.h index 339f734556..d2865b7527 100644 --- a/src/Mod/TechDraw/Gui/DimensionValidators.h +++ b/src/Mod/TechDraw/Gui/DimensionValidators.h @@ -66,16 +66,15 @@ enum DimensionGeometryEnum { DrawViewPart* getReferencesFromSelection(ReferenceVector& references2d, ReferenceVector& references3d); -DimensionGeometryType validateDimSelection( - ReferenceVector references, - StringVector acceptableGeometry,//"Edge", "Vertex", etc - std::vector minimumCounts, //how many of each geometry are needed for a good dimension - std::vector acceptableDimensionGeometrys);//isVertical, isHorizontal, ... -DimensionGeometryType validateDimSelection3d( - DrawViewPart* dvp, ReferenceVector references, - StringVector acceptableGeometry,//"Edge", "Vertex", etc - std::vector minimumCounts, //how many of each geometry are needed for a good dimension - std::vector acceptableDimensionGeometrys);//isVertical, isHorizontal, ... +DimensionGeometryType validateDimSelection(const ReferenceVector& references, + const StringVector& acceptableGeometry,//"Edge", "Vertex", etc + const std::vector& minimumCounts, //how many of each geometry are needed for a good dimension + const std::vector& acceptableDimensionGeometrys);//isVertical, isHorizontal, ... +DimensionGeometryType validateDimSelection3d(DrawViewPart* dvp, + const ReferenceVector& references, + const StringVector& acceptableGeometry, //"Edge", "Vertex", etc + const std::vector& minimumCounts, //how many of each geometry are needed for a good dimension + const std::vector& acceptableDimensionGeometrys);//isVertical, isHorizontal, ... bool validateSubnameList(StringVector subNames, GeometrySet acceptableGeometrySet); @@ -83,8 +82,8 @@ DimensionGeometryType getGeometryConfiguration(ReferenceVector valid2dReferences DimensionGeometryType getGeometryConfiguration3d(DrawViewPart* dvp, ReferenceVector valid3dReferences); -GeomCountMap loadRequiredCounts(StringVector& acceptableGeometry, - std::vector& minimumCouts); +GeomCountMap loadRequiredCounts(const StringVector& acceptableGeometry, + const std::vector& minimumCouts); bool checkGeometryOccurrences(StringVector subNames, GeomCountMap keyedMinimumCounts); DimensionGeometryType isValidVertexes(ReferenceVector refs);