From 2ff1a2b6de535d9f08356b0f814904a687b6ee06 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Thu, 17 Oct 2024 15:27:11 -0400 Subject: [PATCH] [TD]fix area calculation --- src/Mod/TechDraw/App/DimensionGeometry.cpp | 8 +- src/Mod/TechDraw/App/DimensionGeometry.h | 11 +- src/Mod/TechDraw/App/DrawViewDimension.cpp | 79 +++++++++++-- src/Mod/TechDraw/App/DrawViewDimension.h | 5 + src/Mod/TechDraw/App/DrawViewDimensionPy.xml | 5 + .../TechDraw/App/DrawViewDimensionPyImp.cpp | 16 +++ src/Mod/TechDraw/App/Geometry.cpp | 110 +++++++++++++++++- src/Mod/TechDraw/App/Geometry.h | 8 ++ src/Mod/TechDraw/App/ShapeUtils.cpp | 29 +++++ src/Mod/TechDraw/App/ShapeUtils.h | 4 + 10 files changed, 261 insertions(+), 14 deletions(-) diff --git a/src/Mod/TechDraw/App/DimensionGeometry.cpp b/src/Mod/TechDraw/App/DimensionGeometry.cpp index fad87f1308..9483443e4c 100644 --- a/src/Mod/TechDraw/App/DimensionGeometry.cpp +++ b/src/Mod/TechDraw/App/DimensionGeometry.cpp @@ -365,6 +365,7 @@ void arcPoints::dump(const std::string& text) const areaPoint::areaPoint() : area(0.0), + actualArea(0.0), center(Base::Vector3d()) { } @@ -373,6 +374,7 @@ areaPoint& areaPoint::operator=(const areaPoint& ap) { area = ap.area; center = ap.center; + actualArea = ap.actualArea; return *this; } @@ -383,10 +385,14 @@ void areaPoint::move(const Base::Vector3d& offset) void areaPoint::project(const DrawViewPart* dvp) { - area = area * dvp->getScale(); center = dvp->projectPoint(center) * dvp->getScale(); } +void areaPoint::invertY() +{ + center = DU::invertY(center); +} + void areaPoint::dump(const std::string& text) const { Base::Console().Message("areaPoint - %s\n", text.c_str()); diff --git a/src/Mod/TechDraw/App/DimensionGeometry.h b/src/Mod/TechDraw/App/DimensionGeometry.h index 0a74b2e2eb..da9dc69da9 100644 --- a/src/Mod/TechDraw/App/DimensionGeometry.h +++ b/src/Mod/TechDraw/App/DimensionGeometry.h @@ -167,11 +167,16 @@ public: void move(const Base::Vector3d& offset); void project(const DrawViewPart* dvp); + void invertY(); void dump(const std::string& text) const; -//TODO: setters and getters - double area; - Base::Vector3d center; + double getFilledArea() const { return area; } + double getActualArea() const { return actualArea; } + Base::Vector3d getCenter() const { return center; } + + double area{0}; // this is the outer area without considering holes + double actualArea{0}; // this is the net area after holes are removed + Base::Vector3d center; // this is geometric center of the outer face }; } //end namespace TechDraw diff --git a/src/Mod/TechDraw/App/DrawViewDimension.cpp b/src/Mod/TechDraw/App/DrawViewDimension.cpp index 661f8ce817..05a9da9119 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.cpp +++ b/src/Mod/TechDraw/App/DrawViewDimension.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -224,6 +226,11 @@ DrawViewDimension::DrawViewDimension() UnderTolerance.setStatus(App::Property::ReadOnly, true); FormatSpecUnderTolerance.setStatus(App::Property::ReadOnly, true); + // legacy behaviour if this is false + ADD_PROPERTY_TYPE(UseActualArea, (true), "Area", App::Prop_Output, + "If true, area dimensions return the area of the face minus the areas of any enclosed faces. \ + If false, the area of the face's outer boundary is returned."); + measurement = new Measure::Measurement(); // TODO: should have better initial datumLabel position than (0, 0) in the DVP?? something // closer to the object being measured? @@ -277,6 +284,7 @@ void DrawViewDimension::resetArea() { m_areaPoint.center = Base::Vector3d(0, 0, 0); m_areaPoint.area = 0.0; + m_areaPoint.actualArea = 0.0; } void DrawViewDimension::onChanged(const App::Property* prop) @@ -686,7 +694,6 @@ double DrawViewDimension::getDimValue() //! retrieve the dimension value for "true" dimensions. The returned value is in internal units (mm). double DrawViewDimension::getTrueDimValue() const { - // Base::Console().Message("DVD::getTrueDimValue()\n"); double result = 0.0; if (Type.isValue("Distance") || Type.isValue("DistanceX") || Type.isValue("DistanceY")) { @@ -763,7 +770,17 @@ double DrawViewDimension::getProjectedDimValue() const result = legAngle; } else if (Type.isValue("Area")) { - result = m_areaPoint.area / scale / scale; + // 2d reference makes scaled values in areaPoint + // 3d reference makes actual values in areaPoint :p + double divisor{scale / scale}; + if (has3DReferences()) { + divisor = 1.0; + } + if (UseActualArea.getValue()) { + result = m_areaPoint.actualArea / divisor; + } else { + result = m_areaPoint.area / divisor; + } } return result; @@ -1383,27 +1400,71 @@ areaPoint DrawViewDimension::getAreaParameters(ReferenceVector references) ssMessage << getNameInDocument() << " can not find geometry for 2d reference (4)"; throw Base::RuntimeError(ssMessage.str()); } + auto dvp = static_cast(refObject); - pts.area = face->getArea(); - pts.center = face->getCenter(); + auto filteredFaces = GeometryUtils::findHolesInFace(dvp, references.front().getSubName()); + auto perforatedFace = GeometryUtils::makePerforatedFace(face, filteredFaces); + + // these areas are scaled because the source geometry is scaled, but it makes no sense to + // report a scaled area. + auto unscale = getViewPart()->getScale() * getViewPart()->getScale(); + pts.area = face->getArea() / unscale; // this will be the 2d area as projected onto the page? not really filled area? + pts.actualArea = getActualArea(perforatedFace) / unscale; + pts.center = getFaceCenter(perforatedFace); + pts.invertY(); // geometry class is over, back to -Y up/. } else { - // this is a 3d reference + // this is a 3d reference. perforations should be handled for us by OCC TopoDS_Shape geometry = references[0].getGeometry(); if (geometry.IsNull() || geometry.ShapeType() != TopAbs_FACE) { throw Base::RuntimeError("Geometry for dimension reference is null."); } const TopoDS_Face& face = TopoDS::Face(geometry); - GProp_GProps props; - BRepGProp::SurfaceProperties(face, props); - pts.area = props.Mass(); - pts.center = DrawUtil::toVector3d(props.CentreOfMass()); + // these areas are unscaled as the source is 3d geometry. + pts.area = getFilledArea(face); + pts.actualArea = getActualArea(face); + pts.center = getFaceCenter(face); + pts.move(getViewPart()->getCurrentCentroid()); + pts.project(getViewPart()); } return pts; } + +//! returns the center of mass of a face (density = k) +Base::Vector3d DrawViewDimension::getFaceCenter(const TopoDS_Face& face) +{ + GProp_GProps props; + BRepGProp::SurfaceProperties(face, props); + auto center = DrawUtil::toVector3d(props.CentreOfMass()); + return center; +} + + +//! returns the "net" area of a face (area of the face's outer boundary less the area of any holes) +double DrawViewDimension::getActualArea(const TopoDS_Face& face) +{ + GProp_GProps props; + BRepGProp::SurfaceProperties(face, props); + return props.Mass(); +} + + +//! returns the "gross" area of a face (area of the face's outer boundary) +double DrawViewDimension::getFilledArea(const TopoDS_Face& face) +{ + TopoDS_Wire outerwire = ShapeAnalysis::OuterWire(face); + if (outerwire.IsNull()) { + return 0.0; + } + + double area = ShapeAnalysis::ContourArea(outerwire); + return area; +} + + DrawViewPart* DrawViewDimension::getViewPart() const { if (References2D.getValues().empty()) { diff --git a/src/Mod/TechDraw/App/DrawViewDimension.h b/src/Mod/TechDraw/App/DrawViewDimension.h index 110d0238d8..5f3bd1ec28 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.h +++ b/src/Mod/TechDraw/App/DrawViewDimension.h @@ -92,6 +92,7 @@ public: Part::PropertyTopoShapeList SavedGeometry; App::PropertyVectorList BoxCorners; + App::PropertyBool UseActualArea; enum RefType { @@ -212,6 +213,10 @@ public: Base::BoundBox3d getSavedBox(); Base::BoundBox3d getFeatureBox(); + static double getActualArea(const TopoDS_Face& face); + static double getFilledArea(const TopoDS_Face& face); + static Base::Vector3d getFaceCenter(const TopoDS_Face& face); + protected: void handleChangedPropertyType(Base::XMLReader&, const char*, App::Property*) override; void Restore(Base::XMLReader& reader) override; diff --git a/src/Mod/TechDraw/App/DrawViewDimensionPy.xml b/src/Mod/TechDraw/App/DrawViewDimensionPy.xml index c6ac4074d3..62ca01526e 100644 --- a/src/Mod/TechDraw/App/DrawViewDimensionPy.xml +++ b/src/Mod/TechDraw/App/DrawViewDimensionPy.xml @@ -38,6 +38,11 @@ getAnglePoints() - returns list of points for angle Dimension + + + getAreaPoints() - returns list of values (center, filled area, actual area) for area Dimension. + + getArrowPositions() - returns list of locations or Dimension Arrowheads. Locations are in unscaled coordinates of parent View diff --git a/src/Mod/TechDraw/App/DrawViewDimensionPyImp.cpp b/src/Mod/TechDraw/App/DrawViewDimensionPyImp.cpp index 1890835cc6..8a18240c29 100644 --- a/src/Mod/TechDraw/App/DrawViewDimensionPyImp.cpp +++ b/src/Mod/TechDraw/App/DrawViewDimensionPyImp.cpp @@ -111,6 +111,22 @@ PyObject* DrawViewDimensionPy::getAnglePoints(PyObject* args) return Py::new_reference_to(ret); } + +PyObject* DrawViewDimensionPy::getAreaPoints(PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) { + return nullptr; + } + + DrawViewDimension* dvd = getDrawViewDimensionPtr(); + areaPoint pts = dvd->getAreaPoint(); + Py::List ret; + ret.append(Py::asObject(new Base::VectorPy(new Base::Vector3d(pts.getCenter())))); + ret.append(Py::asObject(PyFloat_FromDouble(pts.getFilledArea()))); + ret.append(Py::asObject(PyFloat_FromDouble(pts.getActualArea()))); + return Py::new_reference_to(ret); +} + PyObject* DrawViewDimensionPy::getArrowPositions(PyObject* args) { if (!PyArg_ParseTuple(args, "")) { diff --git a/src/Mod/TechDraw/App/Geometry.cpp b/src/Mod/TechDraw/App/Geometry.cpp index 9b93430047..0ecc3c4a92 100644 --- a/src/Mod/TechDraw/App/Geometry.cpp +++ b/src/Mod/TechDraw/App/Geometry.cpp @@ -82,9 +82,11 @@ #include #include +#include #include #include +#include "DrawViewPart.h" #include "Geometry.h" #include "ShapeUtils.h" #include "DrawUtil.h" @@ -145,10 +147,14 @@ void Wire::dump(std::string s) BRepTools::Write(toOccWire(), s.c_str()); //debug } +// note that the face returned is inverted in Y TopoDS_Face Face::toOccFace() const { + if (wires.empty()) { + return {}; + } + TopoDS_Face result; - //if (!wires.empty) { BRepBuilderAPI_MakeFace mkFace(wires.front()->toOccWire(), true); int limit = wires.size(); int iwire = 1; @@ -1734,3 +1740,105 @@ double GeometryUtils::edgeLength(TopoDS_Edge occEdge) } } +//! return a perforated shape/face (using Part::FaceMakerCheese) formed by creating holes in the input face. +TopoDS_Face GeometryUtils::makePerforatedFace(FacePtr bigCheese, const std::vector &holesAll) +{ + std::vector cheeseIngredients; + + // v0.0 brute force + + // Note: TD Faces are not perforated and should only ever have 1 wire. They are capable of + // having voids, but for now we will just take the first contour wire in all cases. + + if (bigCheese->wires.empty()) { + // run in circles. scream and shout. + return {}; + } + + auto flippedFace = ShapeUtils::fromQtAsFace(bigCheese->toOccFace()); + + if (holesAll.empty()) { + return flippedFace; + } + + auto outer = ShapeUtils::fromQtAsWire(bigCheese->wires.front()->toOccWire()); + cheeseIngredients.push_back(outer); + for (auto& hole : holesAll) { + if (hole->wires.empty()) { + continue; + } + auto holeR3 = ShapeUtils::fromQtAsWire(hole->wires.front()->toOccWire()); + cheeseIngredients.push_back(holeR3); + } + + TopoDS_Shape faceShape; + try { + faceShape = Part::FaceMakerCheese::makeFace(cheeseIngredients); + } + catch (const Standard_Failure &e) { + Base::Console().Warning("Area - could not make holes in face\n"); + return flippedFace; + } + + + // v0.0 just grab the first face + TopoDS_Face foundFace; + TopExp_Explorer expFaces(faceShape, TopAbs_FACE); + if (expFaces.More()) { + foundFace = TopoDS::Face(expFaces.Current()); + } + // TODO: sort out the compound => shape but !compound => face business in FaceMakerCheese here. + // first guess is it does not affect us? + + return foundFace; +} + + +//! find faces within the bounds of the input face +std::vector GeometryUtils::findHolesInFace(const DrawViewPart* dvp, const std::string& bigCheeseSubRef) +{ + if (!dvp || bigCheeseSubRef.empty()) { + return {}; + } + + std::vector holes; + auto bigCheeseIndex = DU::getIndexFromName(bigCheeseSubRef); + + // v0.0 brute force + auto facesAll = dvp->getFaceGeometry(); + if (facesAll.empty()) { + // tarfu + throw Base::RuntimeError("GU::findHolesInFace - no holes to find!!"); + } + + auto bigCheeseFace = facesAll.at(bigCheeseIndex); + auto bigCheeseOCCFace = bigCheeseFace->toOccFace(); + auto bigCheeseArea = bigCheeseFace->getArea(); + + int iFace{0}; + for (auto& face : facesAll) { + if (iFace == bigCheeseIndex) { + iFace++; + continue; + } + if (face->getArea() > bigCheeseArea) { + iFace++; + continue; + } + auto faceCenter = DU::togp_Pnt(face->getCenter()); + auto faceCenterVertex = BRepBuilderAPI_MakeVertex(faceCenter); + auto distance = DU::simpleMinDist(faceCenterVertex, bigCheeseOCCFace); + if (distance > EWTOLERANCE) { + // hole center not within outer contour. not the best test but cheese maker handles it + // for us? + // FaceMakerCheese does not support partial overlaps and just ignores them? + iFace++; + continue; + } + holes.push_back(face); + iFace++; + } + + return holes; +} + diff --git a/src/Mod/TechDraw/App/Geometry.h b/src/Mod/TechDraw/App/Geometry.h index a417a41b65..93909deadd 100644 --- a/src/Mod/TechDraw/App/Geometry.h +++ b/src/Mod/TechDraw/App/Geometry.h @@ -29,6 +29,7 @@ #include #include #include + #include #include @@ -42,6 +43,8 @@ class TopoShape; namespace TechDraw { +class DrawViewPart; + enum ExtractionType { //obs Plain, WithHidden, @@ -339,6 +342,7 @@ class TechDrawExport Wire void dump(std::string s); BaseGeomPtrVector geoms; }; +using WirePtr = std::shared_ptr; /// Simple Collection of geometric features based on BaseGeom inherited classes in order class TechDrawExport Face @@ -429,6 +433,7 @@ class TechDrawExport GeometryUtils {} }; + // TODO: prune unused methods /// Find an unused geom starts or ends at atPoint. /*! * returns index[1:geoms.size()), reversed [true, false] @@ -452,6 +457,9 @@ class TechDrawExport GeometryUtils static double edgeLength(TopoDS_Edge occEdge); + static TopoDS_Face makePerforatedFace(FacePtr bigCheese, const std::vector& holesAll); + static std::vector findHolesInFace(const DrawViewPart* dvp, const std::string& bigCheeseSubRef); + }; diff --git a/src/Mod/TechDraw/App/ShapeUtils.cpp b/src/Mod/TechDraw/App/ShapeUtils.cpp index 2ea0b837ee..1e134c8e93 100644 --- a/src/Mod/TechDraw/App/ShapeUtils.cpp +++ b/src/Mod/TechDraw/App/ShapeUtils.cpp @@ -222,6 +222,7 @@ Base::Vector3d ShapeUtils::findCentroidVec(const TopoDS_Shape& shape, const gp_A return Base::Vector3d(p.X(), p.Y(), p.Z()); } + //!scales & mirrors a shape about a center TopoDS_Shape ShapeUtils::mirrorShapeVec(const TopoDS_Shape& input, const Base::Vector3d& inputCenter, double scale) @@ -349,6 +350,34 @@ TopoDS_Shape ShapeUtils::fromQt(const TopoDS_Shape& inShape) return mkTrf.Shape(); } +//! specialization offromQt for Faces. should be templated? +TopoDS_Face ShapeUtils::fromQtAsFace(const TopoDS_Shape& inShape) +{ + auto flippedShape = ShapeUtils::fromQt(inShape); + + TopoDS_Face foundFace; + TopExp_Explorer expFaces(flippedShape, TopAbs_FACE); + if (expFaces.More()) { + foundFace = TopoDS::Face(expFaces.Current()); + } + + return foundFace; +} + +//! specialization offromQt for Wire. should be templated? +TopoDS_Wire ShapeUtils::fromQtAsWire(const TopoDS_Shape& inShape) +{ + auto flippedShape = ShapeUtils::fromQt(inShape); + + TopoDS_Wire foundWire; + TopExp_Explorer expWires(flippedShape, TopAbs_WIRE); + if (expWires.More()) { + foundWire = TopoDS::Wire(expWires.Current()); + } + + return foundWire; +} + //! transforms a shape defined in conventional coordinates coordinates into one defined by //! invertedY (Qt) coordinates TopoDS_Shape ShapeUtils::toQt(const TopoDS_Shape& inShape) diff --git a/src/Mod/TechDraw/App/ShapeUtils.h b/src/Mod/TechDraw/App/ShapeUtils.h index dab431248f..755da53d98 100644 --- a/src/Mod/TechDraw/App/ShapeUtils.h +++ b/src/Mod/TechDraw/App/ShapeUtils.h @@ -31,6 +31,8 @@ #include #include +#include +#include #include #include @@ -113,6 +115,8 @@ public: static TopoDS_Shape fromQt(const TopoDS_Shape& inShape); static TopoDS_Shape toQt(const TopoDS_Shape& inShape); + static TopoDS_Wire fromQtAsWire(const TopoDS_Shape& inShape); + static TopoDS_Face fromQtAsFace(const TopoDS_Shape& inShape); }; }