// SPDX-License-Identifier: LGPL-2-or-later /*************************************************************************** * Copyright (c) 2024 WandererFan * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ //! DrawBrokenView produces a view of the Source shapes after a portion of the shapes //! has been removed. //! DrawBrokenView processing is essentially the same as DrawViewPart, except that the //! Source shapes are cut and the cut pieces moved before the projection/hlr steps. //! //! Break points are defined by //! - a horizontal or vertical edge whose endpoints represent where the source //! shape should be cut, or, //! - a sketch containing 2 horizontal or vertical edges whose midpoints represent //! where the source shape should be cut. //! Terminology: //! - "break direction" is the direction pieces will need to be moved along to //! close a break. for edges, the break direction is parallel to the edge. //! for sketch based breaks, the break direction is perpendicular to the edges //! in the sketch. // ??? is option 1 actually working? Not used in practice? #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DrawGeomHatch.h" #include "DrawHatch.h" #include "DrawUtil.h" #include "DrawViewDetail.h" #include "GeometryObject.h" #include "ShapeExtractor.h" #include "ShapeUtils.h" #include "DrawBrokenView.h" #include "DrawBrokenViewPy.h" using namespace TechDraw; using DU = DrawUtil; using SU = ShapeUtils; //=========================================================================== // DrawBrokenView //=========================================================================== // NOLINTNEXTLINE const char *DrawBrokenView::BreakTypeEnums[] = { QT_TRANSLATE_NOOP("DrawBrokenView", "None"), QT_TRANSLATE_NOOP("DrawBrokenView", "ZigZag"), QT_TRANSLATE_NOOP("DrawBrokenView", "Simple"), nullptr}; PROPERTY_SOURCE(TechDraw::DrawBrokenView, TechDraw::DrawViewPart) DrawBrokenView::DrawBrokenView() { static const char* sgroup = "Broken view"; constexpr double DefaultGapSizeMm{10.0}; ADD_PROPERTY_TYPE(Breaks, (nullptr), sgroup, App::Prop_None, "Objects in the 3D view that define the start/end points and direction of breaks in this view."); Breaks.setScope(App::LinkScope::Global); Breaks.setAllowExternal(true); ADD_PROPERTY_TYPE(Gap, (DefaultGapSizeMm), sgroup, App::Prop_None, "The separation distance for breaks in this view (unscaled 3D length)."); } short DrawBrokenView::mustExecute() const { if (isRestoring()) { return TechDraw::DrawViewPart::mustExecute(); } if (Breaks.isTouched() || Gap.isTouched() ) { return 1; } return TechDraw::DrawViewPart::mustExecute(); } App::DocumentObjectExecReturn* DrawBrokenView::execute() { if (!keepUpdated()) { return App::DocumentObject::StdReturn; } if (waitingForResult()) { // don't start something new until the in-progress events complete return DrawView::execute(); // NOLINT } TopoDS_Shape shape = getSourceShape(); if (shape.IsNull()) { Base::Console().message("DBV::execute - %s - Source shape is Null.\n", getNameInDocument()); return DrawView::execute(); // NOLINT } BRepBuilderAPI_Copy BuilderCopy(shape); TopoDS_Shape safeShape = BuilderCopy.Shape(); m_unbrokenCenter = SU::findCentroidVec(safeShape, getProjectionCS()); TopoDS_Shape brokenShape = breakShape(safeShape); m_compressedShape = compressShape(brokenShape); partExec(m_compressedShape); return DrawView::execute(); // NOLINT } //! applies the breaks to the input shape. returns a compound of broken //! pieces moved so they are separated by a distance of Gap. TopoDS_Shape DrawBrokenView::breakShape(const TopoDS_Shape& shapeToBreak) const { auto breaksAll = Breaks.getValues(); TopoDS_Shape updatedShape = shapeToBreak; for (auto& item : breaksAll) { updatedShape = apply1Break(*item, updatedShape); } return updatedShape; } //! applies a single break to the input shape. returns a compound of the //! broken pieces. TopoDS_Shape DrawBrokenView::apply1Break(const App::DocumentObject& breakObj, const TopoDS_Shape& inShape) const { auto breakPoints = breakPointsFromObj(breakObj); if (breakPoints.first.IsEqual(breakPoints.second, EWTOLERANCE)) { Base::Console().message("DBV::apply1Break - break points are equal\n"); return inShape; } auto breakDirection = DU::closestBasisOriented(directionFromObj(breakObj)); breakDirection.Normalize(); // make a halfspace that is positioned at the first breakpoint and extends // in the direction of the second point Base::Vector3d moveDir0 = breakPoints.second - breakPoints.first; moveDir0.Normalize(); moveDir0 = DU::closestBasisOriented(moveDir0); auto halfSpace0 = makeHalfSpace(breakPoints.first, moveDir0, breakPoints.second); FCBRepAlgoAPI_Cut mkCut0(inShape, halfSpace0); if (!mkCut0.IsDone()) { Base::Console().message("DBV::apply1Break - cut0 failed\n"); } TopoDS_Shape cut0 = mkCut0.Shape(); // make a halfspace that is positioned at the second breakpoint and extends // in the direction of the first point Base::Vector3d moveDir1 = breakPoints.first - breakPoints.second; moveDir1.Normalize(); moveDir1 = DU::closestBasisOriented(moveDir1); auto halfSpace1 = makeHalfSpace(breakPoints.second, moveDir1, breakPoints.first); FCBRepAlgoAPI_Cut mkCut1(inShape, halfSpace1); if (!mkCut1.IsDone()) { Base::Console().message("DBV::apply1Break - cut1 failed\n"); } TopoDS_Shape cut1 = mkCut1.Shape(); BRep_Builder builder; TopoDS_Compound result; builder.MakeCompound(result); builder.Add(result, cut0); builder.Add(result, cut1); return result; } //! compress the broken shape at the breaks TopoDS_Shape DrawBrokenView::compressShape(const TopoDS_Shape& shapeToCompress) const { TopoDS_Shape compressed = compressHorizontal(shapeToCompress); return compressVertical(compressed); } //! move the broken pieces in the input shape "right" to close up the removed areas. //! note: breaks and pieces should not intersect by this point //! a break: BbbbbbbB //! a piece: PpppP move right by removed(B) //! a piece: PppppP no need to move TopoDS_Shape DrawBrokenView::compressHorizontal(const TopoDS_Shape& shapeToCompress)const { std::vector pieces = getPieces(shapeToCompress); std::vector breaksAll = Breaks.getValues(); Base::Vector3d moveDirection = DU::closestBasisOriented(Base::convertTo(getProjectionCS().XDirection())); bool descend = false; BreakList sortedBreaks = makeSortedBreakList(breaksAll, moveDirection, descend); PieceLimitList limits = getPieceLimits(pieces, moveDirection); // for each break, move all the pieces left of the break to the right by the removed amount // for the break for (auto& breakItem : sortedBreaks) { // check each break against all the pieces Base::Vector3d netBreakDisplace = moveDirection * (removedLengthFromObj(*breakItem.breakObj) - Gap.getValue()); size_t iPiece{0}; for (auto& pieceLimit : limits) { // check each piece against the current break if (moveThisPiece(pieceLimit, breakItem, moveDirection)) { // piece is to left of break, so needs to move right TopoDS_Shape temp = ShapeUtils::moveShape(pieces.at(iPiece), netBreakDisplace); pieces.at(iPiece) = temp; } iPiece++; } } // turn updated pieces into a compound BRep_Builder builder; TopoDS_Compound result; builder.MakeCompound(result); for (auto& pieceShape : pieces) { builder.Add(result, pieceShape); } return result; } //! move the broken pieces in the input shape "Up" to close up the removed areas. //! note: breaks and pieces should not intersect by this point TopoDS_Shape DrawBrokenView::compressVertical(const TopoDS_Shape& shapeToCompress)const { auto pieces = getPieces(shapeToCompress); auto breaksAll = Breaks.getValues(); // not sure about using closestBasis here. may prevent oblique breaks later. auto moveDirection = DU::closestBasisOriented(Base::convertTo(getProjectionCS().YDirection())); bool descend = false; auto sortedBreaks = makeSortedBreakList(breaksAll, moveDirection, descend); auto limits = getPieceLimits(pieces, moveDirection); // for each break, move all the pieces below the break up by the removed amount // for the break for (auto& breakItem : sortedBreaks) { // check each break against all the pieces Base::Vector3d netBreakDisplace = moveDirection * (removedLengthFromObj(*breakItem.breakObj) - Gap.getValue()); size_t iPiece{0}; for (auto& pieceLimit : limits) { // check each piece against the current break using a fuzzy equality if (moveThisPiece(pieceLimit, breakItem, moveDirection)) { // piece is below the break, move it up TopoDS_Shape temp = ShapeUtils::moveShape(pieces.at(iPiece), netBreakDisplace); pieces.at(iPiece) = temp; } iPiece++; } } // turn updated pieces into a compound BRep_Builder builder; TopoDS_Compound result; builder.MakeCompound(result); for (auto& pieceShape : pieces) { builder.Add(result, pieceShape); } return result; } //! returns a half space. The half space is defined by a plane created by (planePoint, //! plane normal) and a point inside the half space (pointInSpace). TopoDS_Shape DrawBrokenView::makeHalfSpace(const Base::Vector3d& planePoint, const Base::Vector3d& planeNormal, const Base::Vector3d& pointInSpace) const { auto origin = Base::convertTo(planePoint); auto axis = Base::convertTo(planeNormal); gp_Pln plane(origin, axis); BRepBuilderAPI_MakeFace mkFace(plane); BRepPrimAPI_MakeHalfSpace mkHalf(mkFace.Face(), Base::convertTo(pointInSpace)); return mkHalf.Solid(); } //! extract the break points from the break object. std::pair DrawBrokenView::breakPointsFromObj(const App::DocumentObject& breakObj) const { if (ShapeExtractor::isSketchObject(&breakObj)) { return breakPointsFromSketch(breakObj); } TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); if (!locShape.IsNull() && locShape.ShapeType() == TopAbs_EDGE) { return breakPointsFromEdge(breakObj); } return {Base::Vector3d(), Base::Vector3d()}; } //! extract the breakDirection from the break object. The break direction is //! perpendicular to the break lines. Base::Vector3d DrawBrokenView::directionFromObj(const App::DocumentObject& breakObj) const { std::pair ends = breakPointsFromObj(breakObj); Base::Vector3d direction = ends.second - ends.first; direction.Normalize(); // does this need to be oriented? return DU::closestBasis(direction); } //! extract the breakDirection from the break object in a form the gui will understand. Base::Vector3d DrawBrokenView::guiDirectionFromObj(const App::DocumentObject& breakObj) const { return projectPoint(directionFromObj(breakObj)); } //! calculate the length to be removed as specified by break object. double DrawBrokenView::removedLengthFromObj(const App::DocumentObject& breakObj) const { std::pair ends = breakPointsFromObj(breakObj); Base::Vector3d direction = ends.second - ends.first; return direction.Length(); } //! determine if a given object can be used as a break object bool DrawBrokenView::isBreakObject(const App::DocumentObject& breakObj) { if (ShapeExtractor::isSketchObject(&breakObj)) { return isBreakObjectSketch(breakObj); } TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); return (!locShape.IsNull() && locShape.ShapeType() == TopAbs_EDGE); } //! determine if a sketch object can be used as a break object //! to be a break object the sketch must contain 2 edges, both of which are //! horizontal or vertical bool DrawBrokenView::isBreakObjectSketch(const App::DocumentObject& breakObj) { TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); if (locShape.IsNull()) { return false; } // get the edges from the shape. std::vector sketchEdges; TopExp_Explorer expl(locShape, TopAbs_EDGE); for (; expl.More(); expl.Next()) { sketchEdges.push_back(TopoDS::Edge(expl.Current())); } // there should be 2 if (sketchEdges.size() != 2) { Base::Console().message("DBV::isBreakObjectSketch - wrong number of edges\n"); return false; } // they should both have the same orientation TopoDS_Edge first = sketchEdges.front(); TopoDS_Edge last = sketchEdges.back(); return SU::edgesAreParallel(first, last); } //! extract the break points from a sketch. The sketch is expected to contain //! 2 vertical or horizontal edges only. std::pair DrawBrokenView::breakPointsFromSketch(const App::DocumentObject& breakObj) const { TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); if (locShape.IsNull()) { return {Base::Vector3d(), Base::Vector3d()};; } // get the edges from the shape. // vertical or horizontal std::vector sketchEdges; TopExp_Explorer expl(locShape, TopAbs_EDGE); for (; expl.More(); expl.Next()) { sketchEdges.push_back(TopoDS::Edge(expl.Current())); } // there should be 2 if (sketchEdges.size() != 2) { return {Base::Vector3d(), Base::Vector3d()}; } // they should both have the same orientation TopoDS_Edge first = sketchEdges.front(); TopoDS_Edge last = sketchEdges.back(); if ((isVertical(first) && isVertical(last)) || (isHorizontal(first) && isHorizontal(last))) { // get points on each edge that are in line with the center of the sketch // along the break direction Bnd_Box edgeBox; edgeBox.SetGap(0.0); BRepBndLib::AddOptimal(first, edgeBox); BRepBndLib::AddOptimal(last, edgeBox); double xMin = 0, xMax = 0, yMin = 0, yMax = 0, zMin = 0, zMax = 0; // NOLINT edgeBox.Get(xMin, yMin, zMin, xMax, yMax, zMax); Base::Vector3d center( (xMin + xMax) / 2, (yMin + yMax) / 2, (zMin + zMax) / 2 ); auto ends0 = SU::getEdgeEnds(first); auto dir0 = ends0.second - ends0.first; dir0.Normalize(); // get a point on edge first on a perpendicular line through center auto break0 = center.Perpendicular(ends0.first, dir0); auto ends1 = SU::getEdgeEnds(last); auto dir1 = ends1.second - ends1.first; dir1.Normalize(); auto break1 = center.Perpendicular(ends1.first, dir1); return { break0, break1 }; } return {Base::Vector3d(), Base::Vector3d()}; } //! extract the break points from an edge. The edge should be vertical or horizontal, perpendicular to the desired //! break lines. std::pair DrawBrokenView::breakPointsFromEdge(const App::DocumentObject& breakObj) const { TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); if (locShape.IsNull() || locShape.ShapeType() != TopAbs_EDGE) { return {Base::Vector3d(), Base::Vector3d()}; } TopoDS_Edge edge = TopoDS::Edge(locShape); gp_Pnt start = BRep_Tool::Pnt(TopExp::FirstVertex(edge)); gp_Pnt end = BRep_Tool::Pnt(TopExp::LastVertex(edge)); return {Base::convertTo(start), Base::convertTo(end)}; } //! determine the rectangle to be occupied by the break lines. used by gui. std::pair DrawBrokenView::breakBoundsFromObj(const App::DocumentObject& breakObj) const { if (ShapeExtractor::isSketchObject(&breakObj)) { auto unscaled = breakBoundsFromSketch(breakObj); return scalePair(unscaled); } TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); if (!locShape.IsNull() && locShape.ShapeType() == TopAbs_EDGE) { auto unscaled = breakBoundsFromEdge(breakObj); return scalePair(unscaled); } return {Base::Vector3d(), Base::Vector3d()}; } //! extract the boundary of the break lines from a sketch (3d coords) and map to this //! broken view. used in making break lines. std::pair DrawBrokenView::breakBoundsFromSketch(const App::DocumentObject& breakObj) const { std::pair breakPoints = breakPointsFromObj(breakObj); Base::Vector3d center = (breakPoints.first + breakPoints.second) / 2; Base::Vector3d breakDir = directionFromObj(breakObj); breakDir.Normalize(); Base::Vector3d perpDir = makePerpendicular(breakDir); perpDir.Normalize(); // get the midpoint of the zigzags Base::Vector3d ptOnLine0 = center + breakDir * removedLengthFromObj(breakObj) / 2; Base::Vector3d ptOnLine1 = center - breakDir * removedLengthFromObj(breakObj) / 2; double lineLength = breaklineLength(breakObj); Base::Vector3d corner0 = ptOnLine0 - perpDir * lineLength / 2; Base::Vector3d corner1 = ptOnLine1 + perpDir * lineLength / 2; corner0 = mapPoint3dToView(corner0); corner1 = mapPoint3dToView(corner1); // these are unscaled, unrotated points return{corner0, corner1}; } //! extract the boundary of the break lines from an edge std::pair DrawBrokenView::breakBoundsFromEdge(const App::DocumentObject& breakObj) const { TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); if (locShape.IsNull() || locShape.ShapeType() != TopAbs_EDGE) { return {Base::Vector3d(), Base::Vector3d()}; } auto edge = projectEdge(TopoDS::Edge(locShape)); auto start = edge->getStartPoint(); auto end = edge->getEndPoint(); Base::Vector3d direction = end - start; double length = direction.Length(); direction.Normalize(); Base::Vector3d stdX{1.0, 0.0, 0.0}; Base::Vector3d stdY{0.0, 1.0, 0.0}; if (DU::fpCompare(fabs(direction.Dot(stdX)), 1.0, EWTOLERANCE) ) { double left = std::min(start.x, end.x); double right = std::max(start.x, end.x); // not wild about this for top/bottom double top = start.y + length; double bottom = start.y - length; Base::Vector3d topLeft{left, top, 0.0}; Base::Vector3d bottomRight{right, bottom, 0.0}; return{topLeft, bottomRight}; } if (!DU::fpCompare(fabs(direction.Dot(stdY)), 1.0, EWTOLERANCE) ) { Base::Console().message("DBV::breakBoundsFromEdge - direction is neither X nor Y\n"); // TODO: throw? return nonsense? } double left = start.x - length; double right = start.x + length; double bottom = std::min(start.y, end.y); double top = std::max(start.y, end.y); Base::Vector3d topLeft{left, top, 0.0}; Base::Vector3d bottomRight{right, bottom, 0.0}; return{topLeft, bottomRight}; } //! calculate the unscaled length of the breakline double DrawBrokenView::breaklineLength(const App::DocumentObject& breakObj) const { if (ShapeExtractor::isSketchObject(&breakObj)) { return breaklineLengthFromSketch(breakObj); } TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); if (!locShape.IsNull() && locShape.ShapeType() == TopAbs_EDGE) { return breaklineLengthFromEdge(breakObj); } return 0.0; } //! calculate the length of the breakline for a sketch based break double DrawBrokenView::breaklineLengthFromSketch(const App::DocumentObject& breakObj) const { TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); if (locShape.IsNull()) { return 0; } // get the edges from the sketch std::vector sketchEdges; TopExp_Explorer expl(locShape, TopAbs_EDGE); for (; expl.More(); expl.Next()) { sketchEdges.push_back(TopoDS::Edge(expl.Current())); } if (sketchEdges.size() < 2) { // need 2 edges Base::Console().message("DBV::breaklineLengthFromSketch - not enough edges\n"); } std::pair ends0 = SU::getEdgeEnds(sketchEdges.front()); ends0.first = projectPoint(ends0.first, false); ends0.second = projectPoint(ends0.second, false); std::pair ends1 = SU::getEdgeEnds(sketchEdges.back()); ends1.first = projectPoint(ends1.first, false); ends1.second = projectPoint(ends1.second, false); if (isVertical(ends0, true)) { // sketch line is vertical, so breakline is also vertical double yLow = std::min({ends0.first.y, ends0.second.y, ends1.first.y, ends1.second.y}); double yHigh = std::max({ends0.first.y, ends0.second.y, ends1.first.y, ends1.second.y}); return yHigh - yLow; } // sketch line is horizontal, so breakline is also horizontal double xLow = std::min({ends0.first.x, ends0.second.x, ends1.first.x, ends1.second.x}); double xHigh = std::max({ends0.first.x, ends0.second.x, ends1.first.x, ends1.second.x}); return xHigh - xLow; } //! calculate the length of the breakline for an edge based break double DrawBrokenView::breaklineLengthFromEdge(const App::DocumentObject& breakObj) const { TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); if (!locShape.IsNull() && locShape.ShapeType() != TopAbs_EDGE) { return 0.0; } // the breakline could be very long. do we need a max breakline length? auto edge = projectEdge(TopoDS::Edge(locShape)); auto start = edge->getStartPoint(); auto end = edge->getEndPoint(); return (end - start).Length(); } //! return true if the edge is vertical. bool DrawBrokenView::isVertical(const TopoDS_Edge& edge, const bool projected) const { Base::Vector3d stdY{0.0, 1.0, 0.0}; auto ends = SU::getEdgeEnds(edge); auto edgeDir = ends.second - ends.first; edgeDir.Normalize(); auto upDir = Base::convertTo(getProjectionCS().YDirection()); if (projected) { upDir = stdY; } upDir.Normalize(); // probably superfluous return (DU::fpCompare(std::fabs(upDir.Dot(edgeDir)), 1.0, EWTOLERANCE)); } //! return true if the input points are vertical bool DrawBrokenView::isVertical(std::pair inPoints, bool projected) const { Base::Vector3d stdY{0.0, 1.0, 0.0}; auto pointDir = inPoints.second - inPoints.first; pointDir.Normalize(); auto upDir = Base::convertTo(getProjectionCS().YDirection()); if (projected) { upDir = stdY; } upDir.Normalize(); // probably superfluous return (DU::fpCompare(std::fabs(upDir.Dot(pointDir)), 1.0, EWTOLERANCE)); } //! return true if the edge is horizontal bool DrawBrokenView::isHorizontal(const TopoDS_Edge& edge, bool projected) const { Base::Vector3d stdX{1.0, 0.0, 0.0}; auto ends = SU::getEdgeEnds(edge); auto edgeDir = ends.second - ends.first; edgeDir.Normalize(); auto sideDir = Base::convertTo(getProjectionCS().XDirection()); if (projected) { sideDir = stdX; } sideDir.Normalize(); // probably superfluous return (DU::fpCompare(std::fabs(sideDir.Dot(edgeDir)), 1.0, EWTOLERANCE)); } //! removes break objects from a list of document objects and returns the rest of the objects. //! used by TechDrawGui::Command std::vector DrawBrokenView::removeBreakObjects(std::vector breaks, std::vector shapes) { std::vector result; for (auto& shapeObj : shapes) { bool found = false; for (auto& breakObj : breaks) { if (breakObj == shapeObj) { found = true; break; } } if (!found) { result.push_back(shapeObj); } } return result; } std::vector DrawBrokenView::edgesFromCompound(const TopoDS_Shape& compound) { std::vector edgesOut; TopExp_Explorer expl(compound, TopAbs_EDGE); for (; expl.More(); expl.Next()) { edgesOut.push_back(TopoDS::Edge(expl.Current())); } return edgesOut; } //! find the limits of each piece's bounding box in cardinal direction (if we support oblique projection directions, then the //! piece will have to be transformed to align with OXYZ cardinal axes as in DrawViewPart::getSizeAlongVector) PieceLimitList DrawBrokenView::getPieceLimits(const std::vector& pieces, Base::Vector3d direction) { Base::Vector3d stdX{1.0, 0.0, 0.0}; Base::Vector3d stdY{0.0, 1.0, 0.0}; Base::Vector3d stdZ{0.0, 0.0, 1.0}; PieceLimitList limits; limits.reserve(pieces.size()); for (auto& item : pieces) { Bnd_Box pieceBox; pieceBox.SetGap(0.0); BRepBndLib::AddOptimal(item, pieceBox); double xMin = 0, xMax = 0, yMin = 0, yMax = 0, zMin = 0, zMax = 0; // NOLINT pieceBox.Get(xMin, yMin, zMin, xMax, yMax, zMax); if (DU::fpCompare(std::fabs(direction.Dot(stdX)), 1.0, EWTOLERANCE)) { PieceLimitEntry newEntry; // NOLINT newEntry.highLimit = xMax; newEntry.lowLimit = xMin; limits.push_back(newEntry); } else if (DU::fpCompare(std::fabs(direction.Dot(stdY)), 1.0, EWTOLERANCE)) { PieceLimitEntry newEntry; // NOLINT newEntry.highLimit = yMax; newEntry.lowLimit = yMin; limits.push_back(newEntry); } else { PieceLimitEntry newEntry; // NOLINT newEntry.highLimit = zMax; newEntry.lowLimit = zMin; limits.push_back(newEntry); } } return limits; } //! get the pieces of the broken shape. std::vector DrawBrokenView::getPieces(const TopoDS_Shape& brokenShape) { std::vector result = getPiecesByType(brokenShape, TopAbs_SOLID); std::vector temp = getPiecesByType(brokenShape, TopAbs_SHELL, TopAbs_SOLID); result.insert(result.end(), temp.begin(), temp.end()); temp = getPiecesByType(brokenShape, TopAbs_FACE, TopAbs_SHELL); result.insert(result.end(), temp.begin(), temp.end()); temp = getPiecesByType(brokenShape, TopAbs_WIRE, TopAbs_FACE); result.insert(result.end(), temp.begin(), temp.end()); temp = getPiecesByType(brokenShape, TopAbs_EDGE, TopAbs_WIRE); result.insert(result.end(), temp.begin(), temp.end()); return result; } //! retrieve the subelements of a shape that are of type desiredShapeType, but that do not //! belong to a shape of type avoidShapeType. std::vector DrawBrokenView::getPiecesByType(const TopoDS_Shape& shapeToSearch, TopAbs_ShapeEnum desiredShapeType, TopAbs_ShapeEnum avoidShapeType) { std::vector result; TopExp_Explorer expl(shapeToSearch, desiredShapeType, avoidShapeType); for (; expl.More(); expl.Next()) { result.push_back(expl.Current()); } return result; } //! sort the breaks that match direction by their minimum limit BreakList DrawBrokenView::makeSortedBreakList(const std::vector& breaks, const Base::Vector3d& direction, const bool descend) const { Base::Vector3d stdX{1.0, 0.0, 0.0}; Base::Vector3d stdY{0.0, 1.0, 0.0}; Base::Vector3d stdZ{0.0, 0.0, 1.0}; BreakList unsorted; for (auto& breakObj : breaks) { auto breakDirection = directionFromObj(*breakObj); if (DU::fpCompare(std::fabs(direction.Dot(breakDirection)), 1.0, EWTOLERANCE)) { // this break interests us BreakListEntry newEntry; // NOLINT newEntry.breakObj = breakObj; auto breakPoints = breakPointsFromObj(*breakObj); if (DU::fpCompare(std::fabs(direction.Dot(stdX)), 1.0, EWTOLERANCE )) { newEntry.lowLimit = std::min(breakPoints.first.x, breakPoints.second.x); newEntry.highLimit = std::max(breakPoints.first.x, breakPoints.second.x); } else if (DU::fpCompare(std::fabs(direction.Dot(stdY)), 1.0, EWTOLERANCE )) { newEntry.lowLimit = std::min(breakPoints.first.y, breakPoints.second.y); newEntry.highLimit = std::max(breakPoints.first.y, breakPoints.second.y); } else { // must be Z! newEntry.lowLimit = std::min(breakPoints.first.z, breakPoints.second.z); newEntry.highLimit = std::max(breakPoints.first.z, breakPoints.second.z); } newEntry.netRemoved = removedLengthFromObj(*breakObj) - Gap.getValue(); unsorted.push_back(newEntry); } } BreakList sorted = sortBreaks(unsorted, descend); return sorted; } //! find the compressed location of the breaks, and sort the result by lower limit BreakList DrawBrokenView::makeSortedBreakListCompressed(const std::vector& breaks, const Base::Vector3d& moveDirection, const bool descend) const { // sortedBreaks is in lowLimit order auto sortedBreaks = makeSortedBreakList(breaks, moveDirection, descend); BreakList result; size_t iBreak{0}; for (auto& breakObj : sortedBreaks) { BreakListEntry newEntry; // NOLINT double breakSum{0}; if (isDirectionReversed(moveDirection)) { // reversed X+ high low X- // our list is sorted ascending by low limit - each is further left than the previous // so we must reverse our list to get the correct shift values. std::reverse(sortedBreaks.begin(), sortedBreaks.end()); for (size_t iSum = iBreak + 1; iSum < sortedBreaks.size(); iSum++) { // shift right by the removed amount of all the breaks to the right of this break breakSum += sortedBreaks.at(iSum).netRemoved; } newEntry.breakObj = breakObj.breakObj; newEntry.lowLimit = breakObj.lowLimit - breakSum; // move to right newEntry.highLimit = newEntry.lowLimit + Gap.getValue(); newEntry.netRemoved = breakObj.netRemoved; result.push_back(newEntry); } else { // forward X- low high X+ for (size_t iSum = iBreak + 1; iSum < sortedBreaks.size(); iSum++) { // shift right by the removed amount of all the breaks to the right of this break breakSum += sortedBreaks.at(iSum).netRemoved; } newEntry.breakObj = breakObj.breakObj; newEntry.lowLimit = breakObj.lowLimit + breakObj.netRemoved + breakSum; newEntry.highLimit = newEntry.lowLimit + Gap.getValue(); newEntry.netRemoved = breakObj.netRemoved; result.push_back(newEntry); } iBreak++; } if (isDirectionReversed(moveDirection)) { std::reverse(sortedBreaks.begin(), sortedBreaks.end()); } return result; } BreakList DrawBrokenView::sortBreaks(BreakList& inList, bool descend) { BreakList sorted = inList; std::sort(sorted.begin(), sorted.end(), DrawBrokenView::breakLess); if (descend) { std::reverse(sorted.begin(), sorted.end()); } return sorted; } //! return true if entry0 "is less than" entry /*static*/bool DrawBrokenView::breakLess(const BreakListEntry& entry0, const BreakListEntry& entry1) { return (entry0.lowLimit < entry1.lowLimit); } //! transform a 3d point into its position within the broken view. used in creating //! dimensions. Base::Vector3d DrawBrokenView::mapPoint3dToView(Base::Vector3d point3d) const { Base::Vector3d result{point3d}; auto breaksAll = Breaks.getValues(); bool descend = false; auto moveXDirection = DU::closestBasisOriented(Base::convertTo(getProjectionCS().XDirection())); // get the breaks that move us in X auto sortedXBreaks = makeSortedBreakList(breaksAll, moveXDirection, descend); double xLimit = DU::coordinateForDirection(point3d, moveXDirection); double xShift = shiftAmountShrink(xLimit, moveXDirection, sortedXBreaks); Base::Vector3d xMove = moveXDirection * xShift; // move to the right (+X) auto moveYDirection = DU::closestBasisOriented(Base::convertTo(getProjectionCS().YDirection())); descend = false; // get the breaks that move us in Y auto sortedYBreaks = makeSortedBreakList(breaksAll, moveYDirection, descend); double yLimit = DU::coordinateForDirection(point3d, moveYDirection); double yShift = shiftAmountShrink(yLimit, moveYDirection, sortedYBreaks); Base::Vector3d yMove = moveYDirection * yShift; // move up (+Y) auto moved3d = point3d + xMove + yMove; result = moved3d - getCompressedCentroid(); result = projectPoint(result, false); return result; } //! transform a 2d point in the broken view into the equivalent point on the XY //! paper plane. used in creating dimensions from points on the broken view. Base::Vector3d DrawBrokenView::mapPoint2dFromView(Base::Vector3d point2d) const { Base::Vector3d stdX(1.0, 0.0, 0.0); Base::Vector3d stdY(0.0, 1.0, 0.0); // make pseudo 3d point from point2d gp_Ax3 OXYZ; gp_Ax3 projCS3(getProjectionCS(getCompressedCentroid())); gp_Trsf xTo3d; xTo3d.SetTransformation(projCS3, OXYZ); auto pseudo3d = Base::convertTo(Base::convertTo(point2d).Transformed(xTo3d)); // now shift down and left auto breaksAll = Breaks.getValues(); auto moveXDirection = DU::closestBasisOriented(Base::convertTo(getProjectionCS().XDirection())); // we are expanding, so the direction should be to the "left"/"down" which is the opposite of // our XDirection auto moveXReverser = isDirectionReversed(moveXDirection) ? 1.0 : -1.0; bool descend = false; // should be false so we move from lowest break to highest? auto sortedXBreaks = makeSortedBreakList(breaksAll, moveXDirection, descend); double xLimit = DU::coordinateForDirection(pseudo3d, moveXDirection); std::vector fullGaps; int partialGapIndex{-1}; auto compressedXBreaks = makeSortedBreakListCompressed(breaksAll, moveXDirection, descend); double partialGapPenetration = getExpandGaps(xLimit, compressedXBreaks, moveXDirection, fullGaps, partialGapIndex); double breakSum{0}; for (auto& index : fullGaps) { double breakSize = sortedXBreaks.at(index).netRemoved; breakSum += breakSize; } if (partialGapIndex >= 0) { double breakSize = sortedXBreaks.at(partialGapIndex).netRemoved; breakSum += breakSize * partialGapPenetration; } double xCoord2 = xLimit + breakSum * moveXReverser; auto moveYDirection = DU::closestBasisOriented(Base::convertTo(getProjectionCS().YDirection())); auto moveYReverser = isDirectionReversed(moveYDirection) ? 1.0 : -1.0; descend = false; auto sortedYBreaks = makeSortedBreakList(breaksAll, moveYDirection, descend); double yLimit = DU::coordinateForDirection(pseudo3d, moveYDirection); fullGaps.clear(); partialGapIndex = -1; auto compressedYBreaks = makeSortedBreakListCompressed(breaksAll, moveYDirection, descend); partialGapPenetration = getExpandGaps(yLimit, compressedYBreaks, moveYDirection, fullGaps, partialGapIndex); breakSum = 0; for (auto& index : fullGaps) { double breakSize = sortedYBreaks.at(index).netRemoved; breakSum += breakSize; } if (partialGapIndex >= 0) { double breakSize = sortedYBreaks.at(partialGapIndex).netRemoved; breakSum += breakSize * partialGapPenetration; } double yCoord2 = yLimit + breakSum * moveYReverser; Base::Vector3d movedResult{xCoord2, yCoord2, 0.0}; return movedResult; } //! returns the amount a coordinate needs to move to reflect the effect of the breaks to the right/above //! the input. used in mapping points to the broken view. double DrawBrokenView::shiftAmountShrink(double pointCoord, Base::Vector3d direction, const BreakList& sortedBreaks) const { double shift{0}; for (auto& breakItem : sortedBreaks) { if (isDirectionReversed(direction)) { if (pointCoord <= breakItem.lowLimit) { // h--------l -ve // p // point is right/above break // leave alone, this break doesn't affect us continue; } if (pointCoord > breakItem.highLimit || DU::fpCompare(pointCoord, breakItem.highLimit, EWTOLERANCE) ) { // h--------l -ve // p // point is left/below break shift += breakItem.netRemoved; continue; } // h--------l -ve // p // g-g // p' // break.start < value < break.end - point is in the break area // we move our point by a fraction of the Gap length double penetration = fabs(pointCoord - breakItem.highLimit); // (p - h) start(high) to point distance double removed = removedLengthFromObj(*breakItem.breakObj); // (h-l) full break size double factor = 1 - (penetration / removed); // fraction of break to right double toShift = pointCoord - (breakItem.lowLimit - factor * Gap.getValue()); shift += fabs(toShift); } else { if (pointCoord >= breakItem.highLimit) { // l--------h +ve // p // leave alone, this break doesn't affect us continue; } if (pointCoord < breakItem.lowLimit || DU::fpCompare(pointCoord, breakItem.lowLimit, EWTOLERANCE) ) { // l--------h +ve // p // move right/up by the removed area less the gap shift += removedLengthFromObj(*breakItem.breakObj) - Gap.getValue(); continue; } // l--------h +ve // p // g--g // p' // break.start < value < break.end - point is in the break area // we move our point by a fraction of the Gap length double penetration = fabs(pointCoord - breakItem.lowLimit); double removed = removedLengthFromObj(*breakItem.breakObj); double factor = 1 - (penetration / removed); double netRemoved = pointCoord - breakItem.highLimit - factor * Gap.getValue(); shift += fabs(netRemoved); } } return shift; } //! determine which gaps require pointCoord to move by a full gap and if there is a partial gap that must //! be included in the move operation. If there is a partial gap, the penetration factor is returned. //! penetration is measure right to left in the view. double DrawBrokenView::getExpandGaps (double pointCoord, const BreakList& compressedBreakList, Base::Vector3d moveDirection, std::vector& fullGaps, int& partialGapIndex) const { double partialPenetrationFactor{0}; // check pointCoord against compressed gaps size_t iBreak{0}; for (auto& gap : compressedBreakList) { if (isDirectionReversed(moveDirection)) { // reversed X+ is to the left if (pointCoord < gap.lowLimit) { // not interested iBreak++; continue; } if (pointCoord > gap.highLimit || DU::fpCompare(pointCoord, gap.highLimit, EWTOLERANCE) ) { // need to move by full length of associated break fullGaps.push_back(iBreak); iBreak++; continue; } // pointCoord is in gap // X+ high > pointCoord > low X- partialGapIndex = static_cast(iBreak); partialPenetrationFactor = (pointCoord - gap.lowLimit) / Gap.getValue(); iBreak++; } else { // forward +X is to the right if (pointCoord > gap.highLimit) { // not interested iBreak++; continue; } if (pointCoord < gap.lowLimit || DU::fpCompare(pointCoord, gap.lowLimit, EWTOLERANCE) ) { // need to move by full length of associated break fullGaps.push_back(iBreak); iBreak++; continue; } // pointCoord is in gap // low < pointCoord < highLimit X+ partialGapIndex = static_cast(iBreak); partialPenetrationFactor = (gap.highLimit - pointCoord) / Gap.getValue(); iBreak++; } } return partialPenetrationFactor; } Base::Vector3d DrawBrokenView::getCompressedCentroid() const { if (m_compressedShape.IsNull()) { return Base::Vector3d(0.0, 0.0, 0.0); } gp_Ax2 cs = getProjectionCS(); gp_Pnt gCenter = ShapeUtils::findCentroid(m_compressedShape, cs); return Base::convertTo(gCenter); } //! construct a perpendicular direction in the projection CS Base::Vector3d DrawBrokenView::makePerpendicular(Base::Vector3d inDir) const { auto gDir = Base::convertTo(inDir); gp_Pnt origin(0.0, 0.0, 0.0); auto dir = getProjectionCS().Direction(); gp_Ax1 axis(origin, dir); auto gRotated = gDir.Rotated(axis, std::numbers::pi/2); return Base::convertTo(gRotated); } //! true if this piece should be moved bool DrawBrokenView::moveThisPiece(PieceLimitEntry piece, BreakListEntry breakItem, Base::Vector3d moveDirection) const { if (isDirectionReversed(moveDirection)) { // -ve direction if (piece.lowLimit > breakItem.highLimit || DU::fpCompare(piece.lowLimit, breakItem.highLimit, EWTOLERANCE) ) { return true; } } else { // +ve direction if (piece.highLimit < breakItem.lowLimit || DU::fpCompare(piece.highLimit, breakItem.lowLimit, EWTOLERANCE) ) { return true; } } return false; } //! true if direction is the reversed of a cardinal direction. (1.0, 0.0, 0.0) //! returns false, (-1.0, 0.0, 0.0) returns true; bool DrawBrokenView::isDirectionReversed(Base::Vector3d direction) const { Base::Vector3d stdX{1.0, 0.0, 0.0}; Base::Vector3d stdY{0.0, 1.0, 0.0}; Base::Vector3d stdZ{0.0, 0.0, 1.0}; if (DU::fpCompare(std::fabs(direction.Dot(stdX)), 1.0, EWTOLERANCE)) { return DU::fpCompare(direction.Dot(stdX), -1.0, EWTOLERANCE); } if (DU::fpCompare(std::fabs(direction.Dot(stdY)), 1.0, EWTOLERANCE)) { return DU::fpCompare(direction.Dot(stdY), -1.0, EWTOLERANCE); } if (DU::fpCompare(std::fabs(direction.Dot(stdZ)), 1.0, EWTOLERANCE)) { return DU::fpCompare(direction.Dot(stdZ), -1.0, EWTOLERANCE); } return false; } void DrawBrokenView::printBreakList(const std::string& text, const BreakList& inBreaks) const { Base::Console().message("DBV - %s\n", text.c_str()); for (auto& entry : inBreaks) { Base::Console().message(" > label: %s > low: %.3f > high: %.3f > net: %.3f\n", entry.breakObj->Label.getValue(), entry.lowLimit, entry.highLimit, entry.netRemoved); } } std::pair DrawBrokenView::scalePair(std::pair inPair) const { std::pair result; result.first = inPair.first * getScale(); result.second = inPair.second * getScale(); return result; } PyObject *DrawBrokenView::getPyObject() { if (PythonObject.is(Py::_None())) { // ref counter is set to 1 PythonObject = Py::Object(new DrawBrokenViewPy(this),true); } return Py::new_reference_to(PythonObject); } namespace App { /// @cond DOXERR PROPERTY_SOURCE_TEMPLATE(TechDraw::DrawBrokenViewPython, TechDraw::DrawBrokenView) template<> const char* TechDraw::DrawBrokenViewPython::getViewProviderName() const { return "TechDrawGui::ViewProviderViewPart"; } /// @endcond // explicit template instantiation template class TechDrawExport FeaturePythonT; }// namespace App