FreeCAD / src /Mod /TechDraw /App /DrawBrokenView.cpp
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
// SPDX-License-Identifier: LGPL-2-or-later
/***************************************************************************
* Copyright (c) 2024 WandererFan <wandererfan@gmail.com> *
* *
* 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 <BRepAdaptor_Curve.hxx>
#include <Mod/Part/App/FCBRepAlgoAPI_Cut.h>
#include <BRepBndLib.hxx>
#include <BRepBuilderAPI_Copy.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_Transform.hxx>
#include <BRepPrimAPI_MakePrism.hxx>
#include <BRepTools.hxx>
#include <BRep_Builder.hxx>
#include <Bnd_Box.hxx>
#include <GCPnts_AbscissaPoint.hxx>
#include <QtConcurrentRun>
#include <ShapeAnalysis.hxx>
#include <ShapeFix_Shape.hxx>
#include <TopExp.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Compound.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shape.hxx>
#include <gp_Ax2.hxx>
#include <gp_Ax3.hxx>
#include <gp_Dir.hxx>
#include <gp_Pln.hxx>
#include <gp_Pnt.hxx>
#include <App/Document.h>
#include <Base/BoundBox.h>
#include <Base/Console.h>
#include <Base/Converter.h>
#include <Base/FileInfo.h>
#include <Base/Parameter.h>
#include <Mod/Part/App/PartFeature.h>
#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<TopoDS_Shape> pieces = getPieces(shapeToCompress);
std::vector<App::DocumentObject*> breaksAll = Breaks.getValues();
Base::Vector3d moveDirection = DU::closestBasisOriented(Base::convertTo<Base::Vector3d>(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<Base::Vector3d>(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<gp_Pnt>(planePoint);
auto axis = Base::convertTo<gp_Dir>(planeNormal);
gp_Pln plane(origin, axis);
BRepBuilderAPI_MakeFace mkFace(plane);
BRepPrimAPI_MakeHalfSpace mkHalf(mkFace.Face(), Base::convertTo<gp_Pnt>(pointInSpace));
return mkHalf.Solid();
}
//! extract the break points from the break object.
std::pair<Base::Vector3d, Base::Vector3d> 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<Base::Vector3d, Base::Vector3d> 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<Base::Vector3d, Base::Vector3d> 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<TopoDS_Edge> 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<Base::Vector3d, Base::Vector3d> 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<TopoDS_Edge> 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<Base::Vector3d, Base::Vector3d> 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<Base::Vector3d>(start), Base::convertTo<Base::Vector3d>(end)};
}
//! determine the rectangle to be occupied by the break lines. used by gui.
std::pair<Base::Vector3d, Base::Vector3d> 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<Base::Vector3d, Base::Vector3d> DrawBrokenView::breakBoundsFromSketch(const App::DocumentObject& breakObj) const
{
std::pair<Base::Vector3d, Base::Vector3d> 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<Base::Vector3d, Base::Vector3d> 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<TopoDS_Edge> 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<Base::Vector3d, Base::Vector3d> ends0 = SU::getEdgeEnds(sketchEdges.front());
ends0.first = projectPoint(ends0.first, false);
ends0.second = projectPoint(ends0.second, false);
std::pair<Base::Vector3d, Base::Vector3d> 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<Base::Vector3d>(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<Base::Vector3d, Base::Vector3d> 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<Base::Vector3d>(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<Base::Vector3d>(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<App::DocumentObject*> DrawBrokenView::removeBreakObjects(std::vector<App::DocumentObject*> breaks, std::vector<App::DocumentObject*> shapes)
{
std::vector<App::DocumentObject*> 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<TopoDS_Edge> DrawBrokenView::edgesFromCompound(const TopoDS_Shape& compound)
{
std::vector<TopoDS_Edge> 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<TopoDS_Shape>& 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<TopoDS_Shape> DrawBrokenView::getPieces(const TopoDS_Shape& brokenShape)
{
std::vector<TopoDS_Shape> result = getPiecesByType(brokenShape, TopAbs_SOLID);
std::vector<TopoDS_Shape> 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<TopoDS_Shape> DrawBrokenView::getPiecesByType(const TopoDS_Shape& shapeToSearch,
TopAbs_ShapeEnum desiredShapeType,
TopAbs_ShapeEnum avoidShapeType)
{
std::vector<TopoDS_Shape> 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<App::DocumentObject*>& 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<App::DocumentObject*>& 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<Base::Vector3d>(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<Base::Vector3d>(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::Vector3d>(Base::convertTo<gp_Pnt>(point2d).Transformed(xTo3d));
// now shift down and left
auto breaksAll = Breaks.getValues();
auto moveXDirection = DU::closestBasisOriented(Base::convertTo<Base::Vector3d>(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<size_t> 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<Base::Vector3d>(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<size_t>& 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<int>(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<int>(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<Base::Vector3d>(gCenter);
}
//! construct a perpendicular direction in the projection CS
Base::Vector3d DrawBrokenView::makePerpendicular(Base::Vector3d inDir) const
{
auto gDir = Base::convertTo<gp_Dir>(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<Base::Vector3d>(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<Base::Vector3d, Base::Vector3d> DrawBrokenView::scalePair(std::pair<Base::Vector3d, Base::Vector3d> inPair) const
{
std::pair<Base::Vector3d, Base::Vector3d> 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<TechDraw::DrawBrokenView>;
}// namespace App