[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>
This commit is contained in:
WandererFan
2024-12-23 17:36:22 -05:00
committed by GitHub
parent 0d128517de
commit abb50a4daa
16 changed files with 1092 additions and 198 deletions

View File

@@ -0,0 +1,170 @@
// 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/>. *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
#endif
#include <Mod/Measure/MeasureGlobal.h>
#include <algorithm> // clears "include what you use" lint message, but creates "included header not used"
#include <string>
#include <Python.h>
#include <App/DocumentObject.h>
#include <App/DocumentObjectPy.h>
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/Interpreter.h>
#include <Base/MatrixPy.h>
#include <Base/PlacementPy.h>
#include <Base/PyWrapParseTupleAndKeywords.h>
#include <Mod/Part/App/TopoShapePy.h>
#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<string, string>).
*
* @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<typename OutputIt>
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<Module>
{
public:
Module()
: Py::ExtensionModule<Module>("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<Module>::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<App::DocumentObjectPy*>(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

View File

@@ -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})

View File

@@ -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<std::string> 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

View 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();
}

View File

@@ -0,0 +1,126 @@
// 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/>. *
* *
***************************************************************************/
#ifndef MEASURE_SHAPEFINDER_H
#define MEASURE_SHAPEFINDER_H
#include <Mod/Measure/MeasureGlobal.h>
#include <TopoDS_Shape.hxx>
#include <App/DocumentObject.h>
#include <App/DocumentObserver.h>
#include <Base/Placement.h>
#include <Base/Matrix.h>
#include <Mod/Part/App/TopoShape.h>
#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<Base::Placement, Base::Matrix4D>
getGlobalTransform(const App::DocumentObject& rootObject, const std::string& leafSub);
static std::pair<Base::Placement, Base::Matrix4D>
getGlobalTransform(const App::DocumentObject* cursorObject);
static void crawlPlacementChain(std::vector<Base::Placement>& plmStack,
std::vector<Base::Matrix4D>& 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<Base::Placement, Base::Matrix4D>
sumTransforms(const std::vector<Base::Placement>& plmStack,
const std::vector<Base::Matrix4D>& 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<App::DocumentObject*> getGeometryRootObjects(const App::Document* doc);
static std::vector<std::list<App::DocumentObject*>>
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<App::DocumentObject*>
tidyInList(const std::vector<App::DocumentObject*>& inlist);
static std::vector<App::DocumentObject*>
tidyInListAttachment(const App::DocumentObject* owner,
const std::vector<App::DocumentObject*>& inlist);
};
} // namespace Measure
#endif // MEASURE_SHAPEFINDER_H

View File

@@ -0,0 +1,178 @@
// 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/>. *
* *
***************************************************************************/
//! a class to perform common operations on subelement names.
#include "PreCompiled.h"
#ifndef _PreComp_
#endif
#include <boost_regex.hpp>
#include <Base/Tools.h>
#include "SubnameHelper.h"
using namespace Measure;
std::string SubnameHelper::pathToLongSub(std::list<App::DocumentObject*> path)
{
std::vector<std::string> 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<std::string>& 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;
}

View File

@@ -0,0 +1,57 @@
// 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/>. *
* *
***************************************************************************/
#ifndef MEASURE_SUBNAMEMANIPULATOR_H
#define MEASURE_SUBNAMEMANIPULATOR_H
#include <Mod/Measure/MeasureGlobal.h>
#include <TopoDS_Shape.hxx>
#include <App/DocumentObject.h>
#include <App/DocumentObserver.h>
#include <Base/Placement.h>
#include <Base/Matrix.h>
#include <Mod/Part/App/TopoShape.h>
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<std::string>& 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<App::DocumentObject*> path);
static std::string removeTnpInfo(const std::string& inString);
};
} // namespace Measure
#endif // MEASURE_SUBNAMEMANIPULATOR_H

View File

@@ -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 <Base/Tools.h>
#include <Mod/Part/App/TopoShape.h>
#include <Mod/Measure/App/ShapeFinder.h>
#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<bool>& 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()};

View File

@@ -33,7 +33,10 @@
#include <App/GeoFeature.h>
#include <App/DocumentObject.h>
#include <App/Document.h>
#include <App/Link.h>
#include <Base/Console.h>
#include <Mod/Measure/App/ShapeFinder.h>
#include <Mod/Part/App/TopoShape.h>
#include <Mod/PartDesign/App/Body.h>
#include <Mod/PartDesign/App/Feature.h>
@@ -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<App::GeoFeature>();
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<TechDraw::DrawViewPart>(); //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());

View File

@@ -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;

View File

@@ -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<TechDraw::AOE>(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<App::DocumentObject*>& objects3d = References3D.getValues();
const std::vector<std::string>& subElements3d = References3D.getSubValues();
const std::vector<App::DocumentObject*>& 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<std::string>& 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<std::string>& 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<App::DocumentObject*>& 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<TopoShape> 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<App::DocumentObject*> objects;
std::vector<std::string> 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<App::DocumentObject*>& Objs = References3D.getValues();
const std::vector<std::string>& 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<App::DocumentObject*>& objects = References2D.getValues();
const std::vector<std::string>& subElements = References2D.getSubValues();
std::vector<App::DocumentObject*>::const_iterator objIt = objects.begin();
std::vector<std::string>::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<App::DocumentObject*>& objects = References2D.getValues();
const std::vector<std::string>& 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<Base::Vector3d> bbxCorners;

View File

@@ -204,9 +204,9 @@ public:
return m_corrector;
}
// these should probably be static as they don't use the dimension at all
std::vector<Part::TopoShape> getEdges(const Part::TopoShape& inShape);
std::vector<Part::TopoShape> getVertexes(const Part::TopoShape& inShape);
static std::vector<Part::TopoShape> getEdges(const Part::TopoShape& inShape);
static std::vector<Part::TopoShape> getVertexes(const Part::TopoShape& inShape);
static double getArcAngle(Base::Vector3d center, Base::Vector3d startPoint, Base::Vector3d endPoint);
// autocorrect support methods
void saveFeatureBox();

View File

@@ -47,6 +47,7 @@
#include <Mod/Part/App/PrimitiveFeature.h>
#include <Mod/Part/App/FeaturePartCircle.h>
#include <Mod/Part/App/TopoShapePy.h>
#include <Mod/Measure/App/ShapeFinder.h>
//#include <Mod/Sketcher/App/SketchObject.h>
#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<App::DocumentObject*> 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<TopoDS_Shape> 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<App::DocumentObjec
return baseShape;
}
//inShape is a compound
//The shapes of datum features (Axis, Plan and CS) are infinite
//Infinite shapes can not be projected, so they need to be removed.
TopoDS_Shape ShapeExtractor::stripInfiniteShapes(TopoDS_Shape inShape)
{
BRep_Builder builder;
TopoDS_Compound comp;
builder.MakeCompound(comp);
TopoDS_Iterator it(inShape);
for (; it.More(); it.Next()) {
TopoDS_Shape s = it.Value();
if (s.ShapeType() < TopAbs_SOLID) {
//look inside composite shapes
s = stripInfiniteShapes(s);
} else if (Part::TopoShape(s).isInfinite()) {
continue;
} else {
//simple shape
}
builder.Add(comp, s);
}
return TopoDS_Shape(std::move(comp));
}
bool ShapeExtractor::is2dObject(const App::DocumentObject* obj)
{

View File

@@ -42,9 +42,10 @@ public:
static TopoDS_Shape getShapes(const std::vector<App::DocumentObject*> links, bool include2d = true);
static std::vector<TopoDS_Shape> getShapes2d(const std::vector<App::DocumentObject*> links);
static std::vector<TopoDS_Shape> getXShapes(const App::Link* xLink);
static std::vector<TopoDS_Shape> getShapesFromObject(const App::DocumentObject* docObj);
static TopoDS_Shape getShapesFused(const std::vector<App::DocumentObject*> links);
static TopoDS_Shape getShapeFromXLink(const App::Link* xLink);
static std::vector<TopoDS_Shape> getShapesFromXRoot(const App::DocumentObject *xLinkRoot);
static std::vector<TopoDS_Shape> 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<TopoDS_Shape>& sourceShapes,
App::DocumentObject* link);
static void restoreExplodedAssembly(App::DocumentObject* link);
static App::DocumentObject* getLinkedObject(const App::DocumentObject* root);
protected:
private:

View File

@@ -30,6 +30,7 @@
#include <App/DocumentObject.h>
#include <Base/Console.h>
#include <Gui/Selection.h>
#include <Mod/Measure/App/ShapeFinder.h>
#include <Mod/TechDraw/App/DrawViewPart.h>
#include <Mod/TechDraw/App/ShapeExtractor.h>
@@ -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<Gui::SelectionObject> selectionAll = Gui::Selection().getSelectionEx();
constexpr bool allowOnlySingle{false};
std::vector<Gui::SelectionObject> 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<TechDraw::DrawViewDimension*>(selItem.getObject());
dim = static_cast<TechDraw::DrawViewDimension*>(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<TechDraw::DrawViewPart*>(selItem.getObject());
dvp = static_cast<TechDraw::DrawViewPart*>(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<App::DocumentObject*> 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<int> minimumCounts, //how many of each geometry are needed for a good dimension
std::vector<DimensionGeometryType> acceptableDimensionGeometrys)//isVertical, isHorizontal, ...
const ReferenceVector& references, //[(dvp*, std::string),...,(dvp*, std::string)]
const StringVector& acceptableGeometry,//"Edge", "Vertex", etc
const std::vector<int>& minimumCounts, //how many of each geometry are needed for a good dimension
const std::vector<DimensionGeometryType>& acceptableDimensionGeometrys)//isVertical, isHorizontal, ...
{
StringVector subNames;
TechDraw::DrawViewPart* dvpSave(nullptr);
for (auto& ref : references) {
auto* dvp = dynamic_cast<TechDraw::DrawViewPart*>(ref.getObject());
auto dvp = dynamic_cast<TechDraw::DrawViewPart*>(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<int> minimumCounts, //how many of each geometry are needed for a good dimension
std::vector<DimensionGeometryType> acceptableDimensionGeometrys)//isVertical, isHorizontal, ...
const ReferenceVector& references, //[(dvp*, std::string),...,(dvp*, std::string)]
const StringVector& acceptableGeometry,//"Edge", "Vertex", etc
const std::vector<int>& minimumCounts, //how many of each geometry are needed for a good dimension
const std::vector<DimensionGeometryType>& 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<std::string, int>::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<int>& minimumCounts)
GeomCountMap TechDraw::loadRequiredCounts(const StringVector& acceptableGeometry,
const std::vector<int>& minimumCounts)
{
if (acceptableGeometry.size() != minimumCounts.size()) {
throw Base::IndexError("acceptableGeometry and minimum counts have different sizes.");

View File

@@ -66,16 +66,15 @@ enum DimensionGeometryEnum {
DrawViewPart* getReferencesFromSelection(ReferenceVector& references2d,
ReferenceVector& references3d);
DimensionGeometryType validateDimSelection(
ReferenceVector references,
StringVector acceptableGeometry,//"Edge", "Vertex", etc
std::vector<int> minimumCounts, //how many of each geometry are needed for a good dimension
std::vector<DimensionGeometryType> acceptableDimensionGeometrys);//isVertical, isHorizontal, ...
DimensionGeometryType validateDimSelection3d(
DrawViewPart* dvp, ReferenceVector references,
StringVector acceptableGeometry,//"Edge", "Vertex", etc
std::vector<int> minimumCounts, //how many of each geometry are needed for a good dimension
std::vector<DimensionGeometryType> acceptableDimensionGeometrys);//isVertical, isHorizontal, ...
DimensionGeometryType validateDimSelection(const ReferenceVector& references,
const StringVector& acceptableGeometry,//"Edge", "Vertex", etc
const std::vector<int>& minimumCounts, //how many of each geometry are needed for a good dimension
const std::vector<DimensionGeometryType>& acceptableDimensionGeometrys);//isVertical, isHorizontal, ...
DimensionGeometryType validateDimSelection3d(DrawViewPart* dvp,
const ReferenceVector& references,
const StringVector& acceptableGeometry, //"Edge", "Vertex", etc
const std::vector<int>& minimumCounts, //how many of each geometry are needed for a good dimension
const std::vector<DimensionGeometryType>& 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<int>& minimumCouts);
GeomCountMap loadRequiredCounts(const StringVector& acceptableGeometry,
const std::vector<int>& minimumCouts);
bool checkGeometryOccurrences(StringVector subNames, GeomCountMap keyedMinimumCounts);
DimensionGeometryType isValidVertexes(ReferenceVector refs);