* [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>
This commit is contained in:
416
src/Mod/Measure/App/ShapeFinder.cpp
Normal file
416
src/Mod/Measure/App/ShapeFinder.cpp
Normal file
@@ -0,0 +1,416 @@
|
||||
// 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.
|
||||
|
||||
|
||||
#include "PreCompiled.h"
|
||||
#ifndef _PreComp_
|
||||
#endif
|
||||
|
||||
#include <boost_regex.hpp>
|
||||
|
||||
#include <BRep_Builder.hxx>
|
||||
#include <BRepTools.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopoDS_Iterator.hxx>
|
||||
#include <BRepBuilderAPI_Copy.hxx>
|
||||
#include <TopLoc_Location.hxx>
|
||||
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObjectGroup.h>
|
||||
#include <App/Link.h>
|
||||
#include <App/GeoFeature.h>
|
||||
#include <App/GeoFeatureGroupExtension.h>
|
||||
#include <App/Part.h>
|
||||
#include <Base/Tools.h>
|
||||
|
||||
#include <Mod/Part/App/PartFeature.h>
|
||||
#include <Mod/Part/App/AttachExtension.h>
|
||||
#include <Mod/Part/App/Attacher.h>
|
||||
|
||||
#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<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.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<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};
|
||||
}
|
||||
|
||||
|
||||
//! 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<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();
|
||||
}
|
||||
Reference in New Issue
Block a user