From 43fc04309a0779f7ea730533f79720cee7d6e638 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Thu, 14 Mar 2024 21:04:23 -0400 Subject: [PATCH] [TD]implement BrokenView --- src/Mod/TechDraw/App/AppTechDraw.cpp | 3 + src/Mod/TechDraw/App/CMakeLists.txt | 13 +- src/Mod/TechDraw/App/DimensionGeometry.cpp | 9 + src/Mod/TechDraw/App/DimensionGeometry.h | 1 + src/Mod/TechDraw/App/DrawBrokenView.cpp | 1098 +++++++++++++++++ src/Mod/TechDraw/App/DrawBrokenView.h | 145 +++ src/Mod/TechDraw/App/DrawBrokenViewPy.xml | 33 + src/Mod/TechDraw/App/DrawBrokenViewPyImp.cpp | 93 ++ src/Mod/TechDraw/App/DrawUtil.cpp | 58 + src/Mod/TechDraw/App/DrawUtil.h | 1 + src/Mod/TechDraw/App/DrawViewDimension.cpp | 15 + src/Mod/TechDraw/App/DrawViewPart.cpp | 2 +- src/Mod/TechDraw/App/DrawViewPart.h | 2 +- src/Mod/TechDraw/App/DrawViewPartPy.xml | 8 +- src/Mod/TechDraw/App/DrawViewPartPyImp.cpp | 12 + src/Mod/TechDraw/App/ShapeExtractor.cpp | 38 +- src/Mod/TechDraw/App/ShapeExtractor.h | 13 +- src/Mod/TechDraw/App/ShapeUtils.cpp | 20 +- src/Mod/TechDraw/App/ShapeUtils.h | 2 + src/Mod/TechDraw/Gui/CMakeLists.txt | 2 + src/Mod/TechDraw/Gui/Command.cpp | 180 +++ src/Mod/TechDraw/Gui/PreferencesGui.cpp | 15 + src/Mod/TechDraw/Gui/PreferencesGui.h | 10 + src/Mod/TechDraw/Gui/QGIBreakLine.cpp | 213 ++++ src/Mod/TechDraw/Gui/QGIBreakLine.h | 84 ++ src/Mod/TechDraw/Gui/QGIUserTypes.h | 1 + src/Mod/TechDraw/Gui/QGIViewPart.cpp | 50 + src/Mod/TechDraw/Gui/QGIViewPart.h | 7 + src/Mod/TechDraw/Gui/Resources/TechDraw.qrc | 1 + .../icons/actions/TechDraw_BrokenView.svg | 650 ++++++++++ src/Mod/TechDraw/Gui/Workbench.cpp | 2 + 31 files changed, 2749 insertions(+), 32 deletions(-) create mode 100644 src/Mod/TechDraw/App/DrawBrokenView.cpp create mode 100644 src/Mod/TechDraw/App/DrawBrokenView.h create mode 100644 src/Mod/TechDraw/App/DrawBrokenViewPy.xml create mode 100644 src/Mod/TechDraw/App/DrawBrokenViewPyImp.cpp create mode 100644 src/Mod/TechDraw/Gui/QGIBreakLine.cpp create mode 100644 src/Mod/TechDraw/Gui/QGIBreakLine.h create mode 100644 src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_BrokenView.svg diff --git a/src/Mod/TechDraw/App/AppTechDraw.cpp b/src/Mod/TechDraw/App/AppTechDraw.cpp index 7fdbbaf34f..0d1ec2284e 100644 --- a/src/Mod/TechDraw/App/AppTechDraw.cpp +++ b/src/Mod/TechDraw/App/AppTechDraw.cpp @@ -61,6 +61,7 @@ #include "PropertyCosmeticEdgeList.h" #include "PropertyCosmeticVertexList.h" #include "PropertyGeomFormatList.h" +#include "DrawBrokenViewPy.h" @@ -116,6 +117,7 @@ PyMOD_INIT_FUNC(TechDraw) TechDraw::DrawTile ::init(); TechDraw::DrawTileWeld ::init(); TechDraw::DrawWeldSymbol ::init(); + TechDraw::DrawBrokenView ::init(); TechDraw::PropertyGeomFormatList::init(); TechDraw::GeomFormat ::init(); @@ -145,6 +147,7 @@ PyMOD_INIT_FUNC(TechDraw) TechDraw::DrawTilePython ::init(); TechDraw::DrawTileWeldPython ::init(); TechDraw::DrawWeldSymbolPython::init(); + TechDraw::DrawBrokenViewPython::init(); PyMOD_Return(mod); } diff --git a/src/Mod/TechDraw/App/CMakeLists.txt b/src/Mod/TechDraw/App/CMakeLists.txt index 7f2736df87..fb7194c3f7 100644 --- a/src/Mod/TechDraw/App/CMakeLists.txt +++ b/src/Mod/TechDraw/App/CMakeLists.txt @@ -62,6 +62,7 @@ generate_from_xml(DrawTilePy) generate_from_xml(DrawTileWeldPy) generate_from_xml(DrawWeldSymbolPy) generate_from_xml(CosmeticExtensionPy) +generate_from_xml(DrawBrokenViewPy) SET(Draw_SRCS DrawPage.cpp @@ -104,10 +105,10 @@ SET(Draw_SRCS DimensionReferences.h DimensionFormatter.cpp DimensionFormatter.h - GeometryMatcher.cpp - GeometryMatcher.h DimensionAutoCorrect.cpp DimensionAutoCorrect.h + GeometryMatcher.cpp + GeometryMatcher.h DrawViewBalloon.cpp DrawViewBalloon.h DrawViewSection.cpp @@ -138,7 +139,9 @@ SET(Draw_SRCS DrawWeldSymbol.h FeatureProjection.cpp FeatureProjection.h - ) + DrawBrokenView.cpp + DrawBrokenView.h +) SET(TechDraw_SRCS AppTechDraw.cpp @@ -253,7 +256,9 @@ SET(Python_SRCS DrawWeldSymbolPyImp.cpp CosmeticExtensionPy.xml CosmeticExtensionPyImp.cpp - ) + DrawBrokenViewPy.xml + DrawBrokenViewPyImp.cpp +) SOURCE_GROUP("Mod" FILES ${TechDraw_SRCS}) SOURCE_GROUP("Features" FILES ${Draw_SRCS}) diff --git a/src/Mod/TechDraw/App/DimensionGeometry.cpp b/src/Mod/TechDraw/App/DimensionGeometry.cpp index 2355ed622f..becf3e3897 100644 --- a/src/Mod/TechDraw/App/DimensionGeometry.cpp +++ b/src/Mod/TechDraw/App/DimensionGeometry.cpp @@ -60,6 +60,15 @@ void pointPair::move(const Base::Vector3d& offset) m_overrideSecond = m_overrideSecond - offset; } +//move the points by factor +void pointPair::scale(double factor) +{ + m_first = m_first * factor; + m_second = m_second * factor; + m_overrideFirst = m_overrideFirst * factor; + m_overrideSecond = m_overrideSecond * factor; +} + // project the points onto the dvp's paper plane. void pointPair::project(const DrawViewPart* dvp) { diff --git a/src/Mod/TechDraw/App/DimensionGeometry.h b/src/Mod/TechDraw/App/DimensionGeometry.h index 5fab391df7..f21f85f305 100644 --- a/src/Mod/TechDraw/App/DimensionGeometry.h +++ b/src/Mod/TechDraw/App/DimensionGeometry.h @@ -76,6 +76,7 @@ public: void project(const DrawViewPart* dvp); void mapToPage(const DrawViewPart* dvp); void invertY(); + void scale(double factor); void dump(const std::string& text) const; private: diff --git a/src/Mod/TechDraw/App/DrawBrokenView.cpp b/src/Mod/TechDraw/App/DrawBrokenView.cpp new file mode 100644 index 0000000000..b385ff9a9f --- /dev/null +++ b/src/Mod/TechDraw/App/DrawBrokenView.cpp @@ -0,0 +1,1098 @@ +// SPDX-License-Identifier: LGPL-2.0-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. + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#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 +#endif + +#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 "Preferences.h" + +#include "DrawBrokenView.h" +#include "DrawBrokenViewPy.h" + +using namespace TechDraw; +using DU = DrawUtil; +using SU = ShapeUtils; + +//=========================================================================== +// DrawBrokenView +//=========================================================================== + +PROPERTY_SOURCE(TechDraw::DrawBrokenView, TechDraw::DrawViewPart) + +DrawBrokenView::DrawBrokenView() +{ + static const char* sgroup = "Broken View"; + + 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, + (10.0), + sgroup, + App::Prop_None, + "The separation distance for breaks in this view (unscaled 3d length)."); +} + +DrawBrokenView::~DrawBrokenView() +{ +} + +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() +{ + // Base::Console().Message("DBV::execute() - %s\n", getNameInDocument()); + if (!keepUpdated()) { + return App::DocumentObject::StdReturn; + } + + if (waitingForResult()) { + // don't start something new until the in-progress events complete + return DrawView::execute(); + } + + TopoDS_Shape shape = getSourceShape(); + if (shape.IsNull()) { + Base::Console().Message("DBV::execute - %s - Source shape is Null.\n", getNameInDocument()); + return DrawView::execute(); + } + + BRepBuilderAPI_Copy BuilderCopy(shape); + TopoDS_Shape safeShape = BuilderCopy.Shape(); + + m_unbrokenCenter = SU::findCentroidVec(safeShape, getProjectionCS()); + + TopoDS_Shape brokenShape = breakShape(safeShape); + m_compressedShape = compressShape(brokenShape); + // BRepTools::Write(brokenShape, "DBVbroken.brep"); //debug + // BRepTools::Write(m_compressedShape, "DBVcompressed.brep"); + + partExec(m_compressedShape); + + return DrawView::execute(); +} + + +//! 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 +{ + // Base::Console().Message("DBV::breakShape()\n"); + 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 +{ + // Base::Console().Message("DBV::apply1Break()\n"); + 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); + BRepAlgoAPI_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); + BRepAlgoAPI_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 +{ + // Base::Console().Message("DBV::compressShape()\n"); + TopoDS_Shape result; + TopoDS_Shape compressed = compressHorizontal(shapeToCompress); + result = compressVertical(compressed); + + return result; +} + +//! 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 no need to move +//! a piece: PppppP move right by removed(B) +TopoDS_Shape DrawBrokenView::compressHorizontal(const TopoDS_Shape& shapeToCompress)const +{ + // Base::Console().Message("DBV::compressHorizontal()\n"); + auto pieces = getPieces(shapeToCompress); + auto breaksAll = Breaks.getValues(); + // ?? not sure about using closestBasis here. + auto moveDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().XDirection())); + bool descend = false; + auto sortedBreaks = makeSortedBreakList(breaksAll, moveDirection, descend); + auto limits = getPieceUpperLimits(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& pieceHighLimit : limits) { + // check each piece against the current break + // We have a problem with low digits here. The cut operations and later + // bounding box creation may generate pieceHighLimits that are slightly + // off. We know that the pieces were cut by a break, so we use a fuzzy + // comparison. + if (pieceHighLimit < breakItem.lowLimit || + DU::fpCompare(pieceHighLimit, breakItem.lowLimit, Precision::Confusion()) ) { + // 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 +{ + // Base::Console().Message("DBV::compressVertical()\n"); + auto pieces = getPieces(shapeToCompress); + auto breaksAll = Breaks.getValues(); + // not sure about using closestBasis here. may prevent oblique breaks later. + auto moveDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().YDirection())); + bool descend = false; + auto sortedBreaks = makeSortedBreakList(breaksAll, moveDirection, descend); + auto limits = getPieceUpperLimits(pieces, moveDirection); + // for each break, move all the pieces above the break down 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& pieceHighLimit : limits) { + // check each piece against the current break using a fuzzy equality + if (pieceHighLimit < breakItem.lowLimit || + DU::fpCompare(pieceHighLimit, breakItem.lowLimit, Precision::Confusion()) ) { + // 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(Base::Vector3d planePoint, Base::Vector3d planeNormal, Base::Vector3d pointInSpace) const +{ + gp_Pnt origin = DU::togp_Pnt(planePoint); + gp_Dir axis = DU::togp_Dir(planeNormal); + gp_Pln plane(origin, axis); + BRepBuilderAPI_MakeFace mkFace(plane); + TopoDS_Face face = mkFace.Face(); + BRepPrimAPI_MakeHalfSpace mkHalf(face, DU::togp_Pnt(pointInSpace)); + + return mkHalf.Solid(); +} + + +//! extract the break points from the break object. +std::pair DrawBrokenView::breakPointsFromObj(const App::DocumentObject& breakObj) const +{ + Base::Console().Message("DBV::breakPointsFromObj()\n"); + if (ShapeExtractor::isSketchObject(&breakObj)) { + return breakPointsFromSketch(breakObj); + } + + TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); + if (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(); + return DU::closestBasis(direction); +} + + +//! 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); + if (locShape.ShapeType() == TopAbs_EDGE) { + // TODO: add check for vertical or horizontal? + return true; + } + return false; +} + +//! 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) +{ + // Base::Console().Message("DBV::isBreakObjectSketch()\n"); + TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); + + // 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 +{ + // Base::Console().Message("DBV::breakPointsFromSketch()\n"); + TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); + + // 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(); + Base::Console().Message("DBV::breakPointsFromSketch - isvertical first: %d last: %d\n", isVertical(first), isVertical(last)); + if ((isVertical(first) && isVertical(last)) || + (isHorizontal(first) && isHorizontal(last))) { + auto ends0 = SU::getEdgeEnds(first); + // trouble here if the break points are wildly out of line? + // std::pair makeCardinal(p0, p1) to force horiz or vert? + auto break0 = (ends0.first + ends0.second) / 2.0; + auto ends1 = SU::getEdgeEnds(last); + auto break1 = (ends1.first + ends1.second) / 2.0; + 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.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 {DU::toVector3d(start), DU::toVector3d(end)}; +} + + +//! determine the rectangle to be occupied by the break lines. used by gui. +std::pair DrawBrokenView::breakBoundsFromObj(const App::DocumentObject& breakObj) const +{ + // Base::Console().Message("DBV::breakBoundsFromObj()\n"); + if (ShapeExtractor::isSketchObject(&breakObj)) { + auto unscaled = breakBoundsFromSketch(breakObj); + return scalePair(unscaled); + } + + TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); + if (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 +{ + Base::Console().Message("DBV::breakBoundsFromSketch()\n"); + std::pair breakPoints = breakPointsFromObj(breakObj); + Base::Vector3d anchor = (breakPoints.first + breakPoints.second) / 2.0; + Base::Vector3d breakDir = directionFromObj(breakObj); + breakDir.Normalize(); + Base::Vector3d lineDir = makePerpendicular(breakDir); + lineDir.Normalize(); + Base::Console().Message("DBV::breakBoundsFromSketch - breakDir: %s lineDir: %s\n", + DU::formatVector(breakDir).c_str(), + DU::formatVector(lineDir).c_str()); + + // is this right? or do we need to project the points first? Should be alright + // if the break points are not skewed? + double removed = (breakPoints.first - breakPoints.second).Length(); + Base::Console().Message("DBV::breakBoundsfromSketch - removed: %.3f\n", removed); + + // get the midpoint of the zigzags + Base::Vector3d ptOnLine0 = anchor + breakDir * removed / 2.0; + Base::Vector3d ptOnLine1 = anchor - breakDir * removed / 2.0; + double lineLength = breaklineLength(breakObj); + Base::Console().Message("DBV::breakBoundsFromSketch - pol0: %s pol1: %s lineLength: %.3f\n", + DU::formatVector(ptOnLine0).c_str(), + DU::formatVector(ptOnLine1).c_str(), lineLength); + + Base::Console().Message("DBV::breakBoundsFromSketch - lineDir: %s\n", DU::formatVector(lineDir).c_str()); + + Base::Vector3d corner0 = ptOnLine0 - lineDir * lineLength / 2.0; + Base::Vector3d corner1 = ptOnLine1 + lineDir * lineLength / 2.0; + Base::Console().Message("DBV::breakBoundsFromSketch - before map c0: %s c1: %s\n", + DU::formatVector(corner0).c_str(), + DU::formatVector(corner1).c_str()); + + corner0 = mapPoint3dToView(corner0); + corner1 = mapPoint3dToView(corner1); + // these are unscaled, unrotated points + Base::Console().Message("DBV::breakBoundsFromSketch - returns c0: %s c1: %s\n", + DU::formatVector(corner0).c_str(), + DU::formatVector(corner1).c_str()); + return{corner0, corner1}; +} + +//! extract the boundary of the break lines from an edge +std::pair DrawBrokenView::breakBoundsFromEdge(const App::DocumentObject& breakObj) const +{ + Base::Console().Message("DBV::breakBoundsFromEdge()\n"); + TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); + if (locShape.ShapeType() != TopAbs_EDGE) { + return {Base::Vector3d(), Base::Vector3d()}; + } + auto oldEdge = TopoDS::Edge(locShape); + gp_Pnt gStart = BRep_Tool::Pnt(TopExp::FirstVertex(oldEdge)); + Base::Vector3d oldStart = DU::toVector3d(gStart); + gp_Pnt gEnd = BRep_Tool::Pnt(TopExp::LastVertex(oldEdge)); + Base::Vector3d oldEnd = DU::toVector3d(gEnd); + Base::Console().Message("DBV::breakBoundsFromEdge - OLD start: %s end: %s\n", + DU::formatVector(oldStart).c_str(), + DU::formatVector(oldEnd).c_str()); + + auto edge = projectEdge(TopoDS::Edge(locShape)); + auto start = edge->getStartPoint(); + auto end = edge->getEndPoint(); + Base::Console().Message("DBV::breakBoundsFromEdge - start: %s end: %s\n", + DU::formatVector(start).c_str(), + DU::formatVector(end).c_str()); + 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 not X or 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 +{ + Base::Console().Message("DBV::breaklineLength()\n"); + if (ShapeExtractor::isSketchObject(&breakObj)) { + return breaklineLengthFromSketch(breakObj); + } + + TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); + if (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 +{ + Base::Console().Message("DBV::breaklineLengthFromSketch()\n"); + TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); + + // 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); + Base::Console().Message("DBV::brealineLengthFromSketch - ends0.first: %s second: %s\n", + DU::formatVector(ends0.first).c_str(), + DU::formatVector(ends0.second).c_str()); + + std::pair ends1 = SU::getEdgeEnds(sketchEdges.back()); + ends1.first = projectPoint(ends1.first, false); + ends1.second = projectPoint(ends1.second, false); + Base::Console().Message("DBV::brealineLengthFromSketch - ends1.first: %s second: %s\n", + DU::formatVector(ends1.first).c_str(), + DU::formatVector(ends1.second).c_str()); + Base::Console().Message("DBV::brealineLengthFromSketch - ends0 vertical: %d\n", isVertical(ends0, true)); + 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}); + Base::Console().Message("DBV::breaklineLengthFromSketch - vertical returns: %.3f\n", yHigh - yLow); + 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}); + Base::Console().Message("DBV::breaklineLengthFromSketch - horiz returns: %.3f\n", xHigh - xLow); + return xHigh - xLow; +} + +//! calculate the length of the breakline for an edge based break +double DrawBrokenView::breaklineLengthFromEdge(const App::DocumentObject& breakObj) const +{ + // Base::Console().Message("DBV::breaklineLengthFromEdge()\n"); + TopoDS_Shape locShape = ShapeExtractor::getLocatedShape(&breakObj); + if (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(TopoDS_Edge edge, bool projected) const +{ + Base::Console().Message("DBV::isVertical(edge, %d)\n", projected); + Base::Vector3d stdY{0.0, 1.0, 0.0}; + auto ends = SU::getEdgeEnds(edge); + auto edgeDir = ends.second - ends.first; + edgeDir.Normalize(); + + auto upDir = DU::toVector3d(getProjectionCS().YDirection()); + if (projected) { + upDir = stdY; + } + upDir.Normalize(); // probably superfluous + + + if (DU::fpCompare(std::fabs(upDir.Dot(edgeDir)), 1.0, EWTOLERANCE)) { + return true; + } + + return false; +} + +//! return true if the input points are vertical +bool DrawBrokenView::isVertical(std::pair inPoints, bool projected) const +{ + Base::Console().Message("DBV::isVertical(%s, %s, %d)\n", + DU::formatVector(inPoints.first).c_str(), + DU::formatVector(inPoints.second).c_str(), projected); + Base::Vector3d stdY{0.0, 1.0, 0.0}; + auto pointDir = inPoints.second - inPoints.first; + pointDir.Normalize(); + + auto upDir = DU::toVector3d(getProjectionCS().YDirection()); + if (projected) { + upDir = stdY; + } + upDir.Normalize(); // probably superfluous + if (DU::fpCompare(std::fabs(upDir.Dot(pointDir)), 1.0, EWTOLERANCE)) { + return true; + } + + return false; +} + +//! return true if the edge is horizontal +bool DrawBrokenView::isHorizontal(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 = DU::toVector3d(getProjectionCS().XDirection()); + if (projected) { + sideDir = stdX; + } + sideDir.Normalize(); // probably superfluous + + if (DU::fpCompare(std::fabs(sideDir.Dot(edgeDir)), 1.0, EWTOLERANCE)) { + return true; + } + + return false; +} + +//! 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) +{ + // Base::Console().Message("DBV::removeBreakObjects() - breaks: %d shapes in: %d\n", breaks.size(), shapes.size()); + 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(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 upper limits of each piece's bounding box in 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) +std::vector DrawBrokenView::getPieceUpperLimits(const std::vector& pieces, Base::Vector3d direction) +{ + // Base::Console().Message("DBV::getPieceUpperLimits(%s)\n", DU::formatVector(direction).c_str()); + 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}; + std::vector 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; + pieceBox.Get(xMin, yMin, zMin, xMax, yMax, zMax); + // this is a bit crude. will only work in well behaved cases + if (DU::fpCompare(std::fabs(direction.Dot(stdX)), 1.0, EWTOLERANCE)) { + limits.push_back(xMax); + } else if (DU::fpCompare(std::fabs(direction.Dot(stdY)), 1.0, EWTOLERANCE)) { + limits.push_back(yMax); + } else { + limits.push_back(zMax); + } + } + + return limits; +} + +std::vector DrawBrokenView::getPieces(TopoDS_Shape brokenShape) +{ + std::vector result; + + // ?? is it reasonable to expect that we only want the solids? do we need to + // pick based on ShapeType <= TopAbs_SHELL? to get shells, compounds etc? + TopExp_Explorer expl(brokenShape, TopAbs_SOLID); + for (; expl.More(); expl.Next()) { + const TopoDS_Solid& solid = TopoDS::Solid(expl.Current()); + result.push_back(solid); + } + + return result; +} + +//! sort the breaks that match direction by their minimum limit +BreakList DrawBrokenView::makeSortedBreakList(const std::vector& breaks, Base::Vector3d direction, 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 (breakDirection.IsEqual(direction, EWTOLERANCE)) { + // this break interests us + BreakListEntry newEntry; + 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, Base::Vector3d moveDirection, bool descend) const +{ + auto sortedBreaks = makeSortedBreakList(breaks, moveDirection, descend); + BreakList result; + size_t iBreak{0}; + for (auto& breakObj : sortedBreaks) { + BreakListEntry newEntry; + double breakSum{0}; + 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++; + } + return result; +} + + +BreakList DrawBrokenView::sortBreaks(BreakList& inList, bool descend) +{ + // Base::Console().Message("DBV::sortBreaks(%d, %d)\n", inList.size(), 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) +{ + if (entry0.lowLimit < entry1.lowLimit) { + return true; + } + return false; +} + +//! transform a 3d point into its position within the broken view. used in creating +//! dimensions. +Base::Vector3d DrawBrokenView::mapPoint3dToView(Base::Vector3d point3d) const +{ + Base::Console().Message("DBV::mapPoint3dToView(%s)\n", DU::formatVector(point3d).c_str()); + Base::Vector3d stdX(1.0, 0.0, 0.0); + Base::Vector3d stdY(0.0, 1.0, 0.0); + Base::Vector3d result{point3d}; + + // if the input point has been projected, then we have to use stdX and stdY instead + // of XDirection and YDirection. + Base::Vector3d point2d = projectPoint(point3d, false); // don't invert + + Base::Console().Message("DBV::mapPoint3dToView - point2d: %s\n", DU::formatVector(point2d).c_str()); + auto breaksAll = Breaks.getValues(); + bool descend = false; + auto moveXDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().XDirection())); + + // get the breaks that move us in X + auto sortedXBreaks = makeSortedBreakList(breaksAll, moveXDirection, descend); + double xLimit = point2d.x; + double xShift = shiftAmountShrink(xLimit, sortedXBreaks); + Base::Vector3d xMove = stdX * xShift; // move to the right (+X) + + auto moveYDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().YDirection())); + descend = false; + // get the breaks that move us in Y + auto sortedYBreaks = makeSortedBreakList(breaksAll, moveYDirection, descend); + double yLimit = point2d.y; + double yShift = shiftAmountShrink(yLimit, sortedYBreaks); + Base::Vector3d yMove = stdY * yShift; // move up (+Y) + Base::Console().Message("DBV::mapPoint3dToView - xBreaks: %d yBreaks: %d\n", sortedXBreaks.size(), sortedYBreaks.size()); + Base::Console().Message("DBV::mapPoint3dToView - xmove: %s ymove: %s\n", + DU::formatVector(xMove).c_str(), + DU::formatVector(yMove).c_str()); + + point2d = point2d + xMove + yMove; + + Base::Vector3d compressedCoM = projectPoint(getCompressedCentroid(), false); + result = point2d - compressedCoM; + 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::Console().Message("DBV::mapPoint2dFromView(%s)\n", DU::formatVector(point2d).c_str()); + Base::Vector3d stdX(1.0, 0.0, 0.0); + Base::Vector3d stdY(0.0, 1.0, 0.0); + + // convert point2d in view to pseudo-3d view coords + Base::Vector3d projectedCoM = projectPoint(getCompressedCentroid(), false); + Base::Vector3d result = projectedCoM + point2d; + // now shift down and left + auto breaksAll = Breaks.getValues(); + bool descend = false; // should be false so we move from lowest break to highest? + auto moveXDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().XDirection())); + // get the breaks that moved us in X + auto sortedXBreaks = makeSortedBreakListCompressed(breaksAll, moveXDirection, descend); + double xLimit = result.x; + double xShift = shiftAmountExpand(xLimit, sortedXBreaks); + Base::Vector3d xMove = stdX * xShift * -1.0; // move to the left (-X) + + auto moveYDirection = DU::closestBasis(DU::toVector3d(getProjectionCS().YDirection())); + descend = false; + // get the breaks that moved us in Y + auto sortedYBreaks = makeSortedBreakListCompressed(breaksAll, moveYDirection, descend); + double yLimit = result.y; + double yShift = shiftAmountExpand(yLimit, sortedYBreaks); + Base::Vector3d yMove = stdY * yShift * -1.0; // move down (-Y) + + result = result + xMove + yMove; + return result; +} + + +//! 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, const BreakList& sortedBreaks) const +{ + // Base::Console().Message("DBV::shiftAmountShrink(%.3f, %d)\n", pointCoord, sortedBreaks.size()); + double shift{0}; + for (auto& breakItem : sortedBreaks) { + if (pointCoord >= breakItem.highLimit) { + // leave alone, this break doesn't affect us + continue; + } + + if (pointCoord < breakItem.lowLimit || + DU::fpCompare(pointCoord, breakItem.lowLimit, Precision::Confusion()) ) { + // move right/up by the removed area less the gap + shift += removedLengthFromObj(*breakItem.breakObj) - Gap.getValue(); + continue; + } + + // break.start < value < break.end - point is in the break area + // we move our point by a fraction of the Gap length + double penetration = pointCoord - breakItem.lowLimit; + double removed = removedLengthFromObj(*breakItem.breakObj); + double factor = 1 - (penetration / removed); + double netRemoved = breakItem.highLimit - factor * Gap.getValue(); + shift += netRemoved - pointCoord; + } + + return shift; +} + + +//! returns the amount a compressed coordinate needs to be shifted to reverse the effect of breaking +//! the source shapes +double DrawBrokenView::shiftAmountExpand(double pointCoord, const BreakList& sortedBreaks) const +{ + // Base::Console().Message("DBV::shiftAmountExpand(%.3f, %d)\n", pointCoord, sortedBreaks.size()); + double shift{0}; + size_t iBreak{0}; + for (auto& breakItem : sortedBreaks) { + Base::Console().Message("DBV::shiftAmountExpand - break: %d low: %.3f high: %.3f\n", iBreak, + breakItem.lowLimit, breakItem.highLimit); + if (pointCoord >= breakItem.highLimit) { + // leave alone, this break doesn't affect us + iBreak++; + continue; + } + + if (pointCoord < breakItem.lowLimit || + DU::fpCompare(pointCoord, breakItem.lowLimit, Precision::Confusion()) ) { + // move by the whole removed area + shift += breakItem.netRemoved; + continue; + } + + // break.start < value < break.end - point is in the break area + // we move our point by the break's net removed * the penetration factor + double gapPenetration = pointCoord - breakItem.lowLimit; + double removed = removedLengthFromObj(*breakItem.breakObj); + double factor = 1 - gapPenetration / Gap.getValue(); + double shiftAmount = factor * (removed - Gap.getValue()); + shift += shiftAmount; + iBreak++; + } + + return shift; +} + + +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 DU::toVector3d(gCenter); +} + +//! construct a perpendicular direction in the projection CS +Base::Vector3d DrawBrokenView::makePerpendicular(Base::Vector3d inDir) const +{ + gp_Dir gDir = DU::togp_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, M_PI_2); + return DU::toVector3d(gRotated); +} + +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(void) +{ + 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 diff --git a/src/Mod/TechDraw/App/DrawBrokenView.h b/src/Mod/TechDraw/App/DrawBrokenView.h new file mode 100644 index 0000000000..bbd336793c --- /dev/null +++ b/src/Mod/TechDraw/App/DrawBrokenView.h @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: LGPL-2.0-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. + +#ifndef DRAWBROKENVIEW_H_ +#define DRAWBROKENVIEW_H_ + +#include + +#include + +#include + +#include "DrawViewPart.h" + +namespace TechDraw +{ + +struct BreakListEntry { + App::DocumentObject* breakObj; + double lowLimit; // the value to use for shifting shapes (the low end of the break) + double highLimit; // the other end of the break (the high end) + double netRemoved; // the removed amount of the break, less the gap size + // TODO: can the gap size change during the lifetime of BreakListEntry? if + // so, we need to save the gap size @ creation time? +}; + +using BreakList = std::vector; + +class TechDrawExport DrawBrokenView: public TechDraw::DrawViewPart +{ + PROPERTY_HEADER_WITH_OVERRIDE(TechDraw::DrawBrokenView); + +public: + DrawBrokenView(); + ~DrawBrokenView() override; + + App::PropertyLinkList Breaks; + App::PropertyLength Gap; + + App::DocumentObjectExecReturn* execute() override; + short mustExecute() const override; + PyObject *getPyObject(void) override; +// void onChanged(const App::Property* prop) override; + const char* getViewProviderName() const override + { + return "TechDrawGui::ViewProviderViewPart"; + } + + std::pair + breakPointsFromObj(const App::DocumentObject& breakObj) const; + std::pair + breakBoundsFromObj(const App::DocumentObject& breakObj) const; + Base::Vector3d directionFromObj(const App::DocumentObject& breakObj) const; + + static bool isBreakObject(const App::DocumentObject& breakObj); + static bool isBreakObjectSketch(const App::DocumentObject& breakObj); + static std::vector removeBreakObjects(std::vector breaks, std::vector shapes); + static std::vector edgesFromCompound(TopoDS_Shape compound); + + Base::Vector3d mapPoint3dToView(Base::Vector3d point3d) const; + Base::Vector3d mapPoint2dFromView(Base::Vector3d point2d) const; + + Base::Vector3d getCompressedCentroid() const; + double breaklineLength(const App::DocumentObject& breakObj) const; + + + +private: + TopoDS_Shape breakShape(const TopoDS_Shape& shapeToBreak) const; + TopoDS_Shape compressShape(const TopoDS_Shape& shapeToCompress) const; + TopoDS_Shape apply1Break(const App::DocumentObject& breakObj, const TopoDS_Shape& inShape) const; + TopoDS_Shape makeHalfSpace(Base::Vector3d point, Base::Vector3d direction, Base::Vector3d pointInSpace) const; + std::pair + breakPointsFromSketch(const App::DocumentObject& breakObj) const; + std::pair + breakPointsFromEdge(const App::DocumentObject& breakObj) const; + std::pair + breakBoundsFromSketch(const App::DocumentObject& breakObj) const; + std::pair + breakBoundsFromEdge(const App::DocumentObject& breakObj) const; + double removedLengthFromObj(const App::DocumentObject& breakObj) const; + double breaklineLengthFromSketch(const App::DocumentObject& breakObj) const; + double breaklineLengthFromEdge(const App::DocumentObject& breakObj) const; + + + bool isVertical(TopoDS_Edge edge, bool projected = false) const; + bool isVertical(std::pair, bool projected = false) const; + bool isHorizontal(TopoDS_Edge edge, bool projected = false) const; + + TopoDS_Shape compressHorizontal(const TopoDS_Shape& inShape) const; + TopoDS_Shape compressVertical(const TopoDS_Shape& inShape) const; + + static std::vector getPieceUpperLimits(const std::vector& pieces, Base::Vector3d direction); + + BreakList makeSortedBreakList(const std::vector& breaks, Base::Vector3d direction, bool descend = false) const; + BreakList makeSortedBreakListCompressed(const std::vector& breaks, Base::Vector3d moveDirection, bool descend = false) const; + static std::vector getPieces(TopoDS_Shape brokenShape); + static BreakList sortBreaks(BreakList& inList, bool descend = false); + static bool breakLess(const BreakListEntry& entry0, const BreakListEntry& entry1); + +// double pointToLimit(const Base::Vector3d& inPoint, const Base::Vector3d& direction) const; + double shiftAmountShrink(double pointCoord, const BreakList& sortedBreaks) const; + double shiftAmountExpand(double pointCoord, const BreakList& sortedBreaks) const; + + void printBreakList(const std::string& text, const BreakList& inBreaks) const; + + std::pair + scalePair(std::pair inPair) const; + Base::Vector3d makePerpendicular(Base::Vector3d inDir) const; + + Base::Vector3d m_unbrokenCenter; + TopoDS_Shape m_compressedShape; + +}; + +using DrawBrokenViewPython = App::FeaturePythonT; + +}//namespace TechDraw + +#endif + diff --git a/src/Mod/TechDraw/App/DrawBrokenViewPy.xml b/src/Mod/TechDraw/App/DrawBrokenViewPy.xml new file mode 100644 index 0000000000..3bed6978b9 --- /dev/null +++ b/src/Mod/TechDraw/App/DrawBrokenViewPy.xml @@ -0,0 +1,33 @@ + + + + + + Feature for creating and manipulating Technical Drawing broken views + + + + point2d = mapPoint3dToView(point3d) - returns the position of the 3d point within the broken view. + + + + + point2d = mapPoint2dFromView(point3d) - returns the position of the 2d point within an unbroken view. + + + + + point3d = getCompressedCenter() - returns the geometric center of the source shapes after break cuts and gap compression. + + + + + diff --git a/src/Mod/TechDraw/App/DrawBrokenViewPyImp.cpp b/src/Mod/TechDraw/App/DrawBrokenViewPyImp.cpp new file mode 100644 index 0000000000..ffadb48bef --- /dev/null +++ b/src/Mod/TechDraw/App/DrawBrokenViewPyImp.cpp @@ -0,0 +1,93 @@ +/*************************************************************************** + * 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 * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#include +#include +#include + +#include "DrawBrokenView.h" +#include "DrawViewPart.h" +// inclusion of the generated files +#include +#include +#include + + +using namespace TechDraw; + +// returns a string which represents the object e.g. when printed in python +std::string DrawBrokenViewPy::representation() const +{ + return std::string(""); +} + +PyObject* DrawBrokenViewPy::mapPoint3dToView(PyObject *args) +{ + PyObject* pPoint3d = nullptr; + if (!PyArg_ParseTuple(args, "O!", &(Base::VectorPy::Type), &pPoint3d)) { + return nullptr; + } + + DrawBrokenView* dvp = getDrawBrokenViewPtr(); + Base::Vector3d point3d = static_cast(pPoint3d)->value(); + Base::Vector3d point2d = dvp->mapPoint3dToView(point3d); + + return new Base::VectorPy(new Base::Vector3d(point2d)); +} + +PyObject* DrawBrokenViewPy::mapPoint2dFromView(PyObject *args) +{ + PyObject* pPoint2d = nullptr; + if (!PyArg_ParseTuple(args, "O!", &(Base::VectorPy::Type), &pPoint2d)) { + return nullptr; + } + + DrawBrokenView* dvp = getDrawBrokenViewPtr(); + Base::Vector3d pointIn = static_cast(pPoint2d)->value(); + Base::Vector3d pointOut = dvp->mapPoint2dFromView(pointIn); + + return new Base::VectorPy(new Base::Vector3d(pointOut)); +} + +PyObject* DrawBrokenViewPy::getCompressedCenter(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) { + return nullptr; + } + + DrawBrokenView* dvp = getDrawBrokenViewPtr(); + Base::Vector3d pointOut = dvp->getCompressedCentroid(); + return new Base::VectorPy(new Base::Vector3d(pointOut)); +} + + +PyObject *DrawBrokenViewPy::getCustomAttributes(const char* /*attr*/) const +{ + return nullptr; +} + +int DrawBrokenViewPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} diff --git a/src/Mod/TechDraw/App/DrawUtil.cpp b/src/Mod/TechDraw/App/DrawUtil.cpp index bfbb9648e4..79f1a4b0b7 100644 --- a/src/Mod/TechDraw/App/DrawUtil.cpp +++ b/src/Mod/TechDraw/App/DrawUtil.cpp @@ -642,6 +642,7 @@ gp_Vec DrawUtil::closestBasis(gp_Vec inVec) return gp_Vec(togp_Dir(closestBasis(toVector3d(inVec)))); } +//! returns stdX, stdY or stdZ. Base::Vector3d DrawUtil::closestBasis(Base::Vector3d v) { Base::Vector3d result(0.0, -1, 0); @@ -698,6 +699,63 @@ Base::Vector3d DrawUtil::closestBasis(Base::Vector3d v) return Base::Vector3d(1.0, 0.0, 0.0); } +//! returns +/- stdX, stdY or stdZ. +Base::Vector3d DrawUtil::closestBasisOriented(Base::Vector3d v) +{ + Base::Vector3d result(0.0, -1, 0); + 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); + Base::Vector3d stdXr(-1.0, 0.0, 0.0); + Base::Vector3d stdYr(0.0, -1.0, 0.0); + Base::Vector3d stdZr(0.0, 0.0, -1.0); + + //first check if already a basis + if (v.Dot(stdX) == 1.0 || v.Dot(stdY) == 1.0 || v.Dot(stdZ) == 1.0) { + return v; + } + if (v.Dot(stdX) == -1.0 || v.Dot(stdY) == -1.0 || v.Dot(stdZ) == -1.0) { + return v; + } + + //not a basis. find smallest angle with a basis. + double angleX, angleY, angleZ, angleXr, angleYr, angleZr, angleMin; + angleX = stdX.GetAngle(v); + angleY = stdY.GetAngle(v); + angleZ = stdZ.GetAngle(v); + angleXr = stdXr.GetAngle(v); + angleYr = stdYr.GetAngle(v); + angleZr = stdZr.GetAngle(v); + + angleMin = std::min({angleX, angleY, angleZ, angleXr, angleYr, angleZr}); + if (angleX == angleMin) { + return Base::Vector3d(1.0, 0.0, 0.0); + } + + if (angleY == angleMin) { + return Base::Vector3d(0.0, 1.0, 0.0); + } + + if (angleZ == angleMin) { + return Base::Vector3d(0.0, 0.0, 1.0); + } + + if (angleXr == angleMin) { + return Base::Vector3d(-1.0, 0.0, 0.0); + } + + if (angleYr == angleMin) { + return Base::Vector3d(0.0, -1.0, 0.0); + } + + if (angleZr == angleMin) { + return Base::Vector3d(0.0, 0.0, -1.0); + } + + //should not get to here + return Base::Vector3d(1.0, 0.0, 0.0); +} + Base::Vector3d DrawUtil::closestBasis(Base::Vector3d vDir, gp_Ax2 coordSys) { gp_Dir gDir(vDir.x, vDir.y, vDir.z); diff --git a/src/Mod/TechDraw/App/DrawUtil.h b/src/Mod/TechDraw/App/DrawUtil.h index 114dff11dc..1f4580efce 100644 --- a/src/Mod/TechDraw/App/DrawUtil.h +++ b/src/Mod/TechDraw/App/DrawUtil.h @@ -150,6 +150,7 @@ public: static gp_Vec closestBasis(gp_Vec inVec); static Base::Vector3d closestBasis(Base::Vector3d vDir, gp_Ax2 coordSys); static Base::Vector3d closestBasis(gp_Dir gDir, gp_Ax2 coordSys); + static Base::Vector3d closestBasisOriented(Base::Vector3d v); static double getWidthInDirection(gp_Dir direction, TopoDS_Shape& shape); static gp_Vec maskDirection(gp_Vec inVec, gp_Dir directionToMask); diff --git a/src/Mod/TechDraw/App/DrawViewDimension.cpp b/src/Mod/TechDraw/App/DrawViewDimension.cpp index 1b4d83be62..790ab7dda9 100644 --- a/src/Mod/TechDraw/App/DrawViewDimension.cpp +++ b/src/Mod/TechDraw/App/DrawViewDimension.cpp @@ -74,6 +74,7 @@ #include "GeometryMatcher.h" #include "Preferences.h" #include "DimensionAutoCorrect.h" +#include "DrawBrokenView.h" using namespace TechDraw; using namespace Part; @@ -648,7 +649,21 @@ double DrawViewDimension::getDimValue() return result; } if (Type.isValue("Distance") || Type.isValue("DistanceX") || Type.isValue("DistanceY")) { + // linear points are inverted? +Y down? scaled! pointPair pts = getLinearPoints(); + auto dbv = dynamic_cast(getViewPart()); + if (dbv) { + // pts are inverted Y, so we need to un-invert them before mapping + // pts are scaled, so we need to unscale them for mapPoint2dFromView + // then rescale them for the distance calculation below + // centers are right side up + pts.invertY(); + pts.scale(1.0 / getViewPart()->getScale()); + pts.first(dbv->mapPoint2dFromView(pts.first())); + pts.second(dbv->mapPoint2dFromView(pts.second())); + pts.invertY(); + pts.scale(getViewPart()->getScale()); + } Base::Vector3d dimVec = pts.first() - pts.second(); if (Type.isValue("Distance")) { result = dimVec.Length() / getViewPart()->getScale(); diff --git a/src/Mod/TechDraw/App/DrawViewPart.cpp b/src/Mod/TechDraw/App/DrawViewPart.cpp index a8aec4423a..14cc5ecc87 100644 --- a/src/Mod/TechDraw/App/DrawViewPart.cpp +++ b/src/Mod/TechDraw/App/DrawViewPart.cpp @@ -317,7 +317,7 @@ GeometryObjectPtr DrawViewPart::makeGeometryForShape(TopoDS_Shape& shape) } //! Modify a shape by centering, scaling and rotating and return the centered (but not rotated) shape -TopoDS_Shape DrawViewPart::centerScaleRotate(DrawViewPart* dvp, TopoDS_Shape& inOutShape, +TopoDS_Shape DrawViewPart::centerScaleRotate(const DrawViewPart *dvp, TopoDS_Shape& inOutShape, Base::Vector3d centroid) { // Base::Console().Message("DVP::centerScaleRotate() - %s\n", dvp->getNameInDocument()); diff --git a/src/Mod/TechDraw/App/DrawViewPart.h b/src/Mod/TechDraw/App/DrawViewPart.h index d4ab35e1df..896261665b 100644 --- a/src/Mod/TechDraw/App/DrawViewPart.h +++ b/src/Mod/TechDraw/App/DrawViewPart.h @@ -116,7 +116,7 @@ public: const char* getViewProviderName() const override { return "TechDrawGui::ViewProviderViewPart"; } PyObject* getPyObject() override; - static TopoDS_Shape centerScaleRotate(DrawViewPart* dvp, TopoDS_Shape& inOutShape, + static TopoDS_Shape centerScaleRotate(const DrawViewPart* dvp, TopoDS_Shape& inOutShape, Base::Vector3d centroid); std::vector getHatches() const; diff --git a/src/Mod/TechDraw/App/DrawViewPartPy.xml b/src/Mod/TechDraw/App/DrawViewPartPy.xml index 7b916213f8..99e8871dcc 100644 --- a/src/Mod/TechDraw/App/DrawViewPartPy.xml +++ b/src/Mod/TechDraw/App/DrawViewPartPy.xml @@ -165,8 +165,12 @@ result. - - + + + point3d = getGeometricCenter() - returns the geometric center of the source shapes. + + + requestPaint(). Redraw the graphic for this View. diff --git a/src/Mod/TechDraw/App/DrawViewPartPyImp.cpp b/src/Mod/TechDraw/App/DrawViewPartPyImp.cpp index 455e5d8c57..25c2a3b99c 100644 --- a/src/Mod/TechDraw/App/DrawViewPartPyImp.cpp +++ b/src/Mod/TechDraw/App/DrawViewPartPyImp.cpp @@ -110,6 +110,18 @@ PyObject* DrawViewPartPy::requestPaint(PyObject *args) Py_Return; } +PyObject* DrawViewPartPy::getGeometricCenter(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) { + return nullptr; + } + + DrawViewPart* dvp = getDrawViewPartPtr(); + Base::Vector3d pointOut = dvp->getCurrentCentroid(); + return new Base::VectorPy(new Base::Vector3d(pointOut)); +} + + // remove all cosmetics PyObject* DrawViewPartPy::clearCosmeticVertices(PyObject *args) { diff --git a/src/Mod/TechDraw/App/ShapeExtractor.cpp b/src/Mod/TechDraw/App/ShapeExtractor.cpp index a1c4cdbf77..3994b75d3c 100644 --- a/src/Mod/TechDraw/App/ShapeExtractor.cpp +++ b/src/Mod/TechDraw/App/ShapeExtractor.cpp @@ -355,16 +355,9 @@ TopoDS_Shape ShapeExtractor::stripInfiniteShapes(TopoDS_Shape inShape) return TopoDS_Shape(std::move(comp)); } -bool ShapeExtractor::is2dObject(App::DocumentObject* obj) +bool ShapeExtractor::is2dObject(const App::DocumentObject* obj) { -// TODO:: the check for an object being a sketch should be done as in the commented -// if statement below. To do this, we need to include Mod/Sketcher/SketchObject.h, -// but that makes TechDraw dependent on Eigen libraries which we don't use. As a -// workaround we will inspect the object's class name. -// if (obj->isDerivedFrom(Sketcher::SketchObject::getClassTypeId())) { - std::string objTypeName = obj->getTypeId().getName(); - std::string sketcherToken("Sketcher"); - if (objTypeName.find(sketcherToken) != std::string::npos) { + if (isSketchObject(obj)) { return true; } @@ -375,7 +368,7 @@ bool ShapeExtractor::is2dObject(App::DocumentObject* obj) } // just these for now -bool ShapeExtractor::isEdgeType(App::DocumentObject* obj) +bool ShapeExtractor::isEdgeType(const App::DocumentObject* obj) { bool result = false; Base::Type t = obj->getTypeId(); @@ -391,7 +384,7 @@ bool ShapeExtractor::isEdgeType(App::DocumentObject* obj) return result; } -bool ShapeExtractor::isPointType(App::DocumentObject* obj) +bool ShapeExtractor::isPointType(const App::DocumentObject* obj) { // Base::Console().Message("SE::isPointType(%s)\n", obj->getNameInDocument()); if (obj) { @@ -407,7 +400,7 @@ bool ShapeExtractor::isPointType(App::DocumentObject* obj) return false; } -bool ShapeExtractor::isDraftPoint(App::DocumentObject* obj) +bool ShapeExtractor::isDraftPoint(const App::DocumentObject* obj) { // Base::Console().Message("SE::isDraftPoint()\n"); //if the docObj doesn't have a Proxy property, it definitely isn't a Draft point @@ -422,7 +415,7 @@ bool ShapeExtractor::isDraftPoint(App::DocumentObject* obj) return false; } -bool ShapeExtractor::isDatumPoint(App::DocumentObject* obj) +bool ShapeExtractor::isDatumPoint(const App::DocumentObject* obj) { std::string objTypeName = obj->getTypeId().getName(); std::string pointToken("Point"); @@ -434,7 +427,7 @@ bool ShapeExtractor::isDatumPoint(App::DocumentObject* obj) //! get the location of a point object -Base::Vector3d ShapeExtractor::getLocation3dFromFeat(App::DocumentObject* obj) +Base::Vector3d ShapeExtractor::getLocation3dFromFeat(const App::DocumentObject* obj) { // Base::Console().Message("SE::getLocation3dFromFeat()\n"); if (!isPointType(obj)) { @@ -444,7 +437,7 @@ Base::Vector3d ShapeExtractor::getLocation3dFromFeat(App::DocumentObject* obj) // //Draft Points are not necc. Part::PartFeature?? // //if Draft option "use part primitives" is not set are Draft points still PartFeature? - Part::Feature* pf = dynamic_cast(obj); + const Part::Feature* pf = dynamic_cast(obj); if (pf) { Part::TopoShape pts = pf->Shape.getShape(); pts.setPlacement(pf->globalPlacement()); @@ -471,3 +464,18 @@ TopoDS_Shape ShapeExtractor::getLocatedShape(const App::DocumentObject* docObj) return shape.getShape(); } +bool ShapeExtractor::isSketchObject(const App::DocumentObject* obj) +{ +// TODO:: the check for an object being a sketch should be done as in the commented +// if statement below. To do this, we need to include Mod/Sketcher/SketchObject.h, +// but that makes TechDraw dependent on Eigen libraries which we don't use. As a +// workaround we will inspect the object's class name. +// if (obj->isDerivedFrom(Sketcher::SketchObject::getClassTypeId())) { + std::string objTypeName = obj->getTypeId().getName(); + std::string sketcherToken("Sketcher"); + if (objTypeName.find(sketcherToken) != std::string::npos) { + return true; + } + return false; +} + diff --git a/src/Mod/TechDraw/App/ShapeExtractor.h b/src/Mod/TechDraw/App/ShapeExtractor.h index 34e1898b96..972d37a33b 100644 --- a/src/Mod/TechDraw/App/ShapeExtractor.h +++ b/src/Mod/TechDraw/App/ShapeExtractor.h @@ -46,12 +46,13 @@ public: static TopoDS_Shape getShapesFused(const std::vector links); static TopoDS_Shape getShapeFromXLink(const App::Link* xLink); - static bool is2dObject(App::DocumentObject* obj); - static bool isEdgeType(App::DocumentObject* obj); - static bool isPointType(App::DocumentObject* obj); - static bool isDraftPoint(App::DocumentObject* obj); - static bool isDatumPoint(App::DocumentObject* obj); - static Base::Vector3d getLocation3dFromFeat(App::DocumentObject *obj); + static bool is2dObject(const App::DocumentObject* obj); + static bool isEdgeType(const App::DocumentObject* obj); + static bool isPointType(const App::DocumentObject* obj); + static bool isDraftPoint(const App::DocumentObject* obj); + static bool isDatumPoint(const App::DocumentObject* obj); + static bool isSketchObject(const App::DocumentObject* obj); + static Base::Vector3d getLocation3dFromFeat(const App::DocumentObject *obj); static TopoDS_Shape stripInfiniteShapes(TopoDS_Shape inShape); diff --git a/src/Mod/TechDraw/App/ShapeUtils.cpp b/src/Mod/TechDraw/App/ShapeUtils.cpp index 65f769c5b5..c0e8d085e1 100644 --- a/src/Mod/TechDraw/App/ShapeUtils.cpp +++ b/src/Mod/TechDraw/App/ShapeUtils.cpp @@ -63,9 +63,6 @@ #include #endif// #ifndef _PreComp_ -#include -#include - #include #include "DrawUtil.h" @@ -345,3 +342,20 @@ bool ShapeUtils::isShapeReallyNull(TopoDS_Shape shape) return shape.IsNull() || !TopoDS_Iterator(shape).More(); } +bool ShapeUtils::edgesAreParallel(TopoDS_Edge edge0, TopoDS_Edge edge1) +{ + std::pair ends0 = getEdgeEnds(edge0); + Base::Vector3d vec0 = ends0.second - ends0.first; + vec0.Normalize(); + std::pair ends1 = getEdgeEnds(edge1); + Base::Vector3d vec1 = ends1.second - ends1.first; + vec1.Normalize(); + double dot = fabs(vec0.Dot(vec1)); + if (DU::fpCompare(dot, 1.0, EWTOLERANCE)) { + // parallel vectors + return true; + } + return false; + +} + diff --git a/src/Mod/TechDraw/App/ShapeUtils.h b/src/Mod/TechDraw/App/ShapeUtils.h index dee7af58d2..e5bf7bca63 100644 --- a/src/Mod/TechDraw/App/ShapeUtils.h +++ b/src/Mod/TechDraw/App/ShapeUtils.h @@ -108,6 +108,8 @@ public: static std::pair getEdgeEnds(TopoDS_Edge edge); static bool isShapeReallyNull(TopoDS_Shape shape); + + static bool edgesAreParallel(TopoDS_Edge edge0, TopoDS_Edge edge1); }; } diff --git a/src/Mod/TechDraw/Gui/CMakeLists.txt b/src/Mod/TechDraw/Gui/CMakeLists.txt index c199ab9bea..fbd3c490c6 100644 --- a/src/Mod/TechDraw/Gui/CMakeLists.txt +++ b/src/Mod/TechDraw/Gui/CMakeLists.txt @@ -335,6 +335,8 @@ SET(TechDrawGuiView_SRCS QGIGhostHighlight.h PathBuilder.cpp PathBuilder.h + QGIBreakLine.cpp + QGIBreakLine.h ) SET(TechDrawGuiNav_SRCS diff --git a/src/Mod/TechDraw/Gui/Command.cpp b/src/Mod/TechDraw/Gui/Command.cpp index dea656de90..b814960a2c 100644 --- a/src/Mod/TechDraw/Gui/Command.cpp +++ b/src/Mod/TechDraw/Gui/Command.cpp @@ -61,6 +61,7 @@ #include #include #include +#include #include "DrawGuiUtil.h" #include "MDIViewPage.h" @@ -79,6 +80,12 @@ void execSimpleSection(Gui::Command* cmd); void execComplexSection(Gui::Command* cmd); +void getSelectedShapes(Gui::Command* cmd, + std::vector& shapes, + std::vector& xShapes, + App::DocumentObject* faceObj, + std::string& faceName); + class Vertex; using namespace TechDrawGui; @@ -419,6 +426,111 @@ void CmdTechDrawView::activated(int iMsg) bool CmdTechDrawView::isActive() { return DrawGuiUtil::needPage(this); } +//=========================================================================== +// TechDraw_BrokenView +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawBrokenView) + +CmdTechDrawBrokenView::CmdTechDrawBrokenView() + : Command("TechDraw_BrokenView") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Broken View"); + sToolTipText = QT_TR_NOOP("Insert Broken View"); + sWhatsThis = "TechDraw_BrokenView"; + sStatusTip = sToolTipText; + sPixmap = "actions/TechDraw_BrokenView"; +} + +void CmdTechDrawBrokenView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + // get the shape objects from the selection + std::vector shapes; + std::vector xShapes; + App::DocumentObject* faceObj = nullptr; + std::string faceName; + getSelectedShapes(this, shapes, xShapes, faceObj, faceName); + + // pick the Break objects out of the selected pile + std::vector selection = getSelection().getSelectionEx( + nullptr, App::DocumentObject::getClassTypeId(), Gui::ResolveMode::NoResolve); + std::vector breakObjects; + for (auto& selObj : selection) { + auto temp = selObj.getObject(); + if (DrawBrokenView::isBreakObject(*temp)) { + breakObjects.push_back(selObj.getObject()); + } + } + if (breakObjects.empty()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("No Break objects found in this selection")); + return; + } + + // remove Break objects from shape pile + shapes = DrawBrokenView::removeBreakObjects(breakObjects, shapes); + xShapes = DrawBrokenView::removeBreakObjects(breakObjects, xShapes); + if (shapes.empty() && + xShapes.empty()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("No Shapes, Groups or Links in this selection")); + return; + } + + Gui::WaitCursor wc; + openCommand(QT_TRANSLATE_NOOP("Command", "Create broken view")); + getDocument()->setStatus(App::Document::Status::SkipRecompute, true); + std::string FeatName = getUniqueObjectName("BrokenView"); + doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawBrokenView','%s')", FeatName.c_str()); + doCommand(Doc, "App.activeDocument().%s.addView(App.activeDocument().%s)", PageName.c_str(), FeatName.c_str()); + + App::DocumentObject* docObj = getDocument()->getObject(FeatName.c_str()); + TechDraw::DrawBrokenView* dbv = dynamic_cast(docObj); + if (!dbv) { + throw Base::TypeError("CmdTechDrawBrokenView DBV not found\n"); + } + dbv->Source.setValues(shapes); + dbv->XSource.setValues(xShapes); + dbv->Breaks.setValues(breakObjects); + + //set projection direction from selected Face + std::pair dirs; + if (faceName.size()) { + dirs = DrawGuiUtil::getProjDirFromFace(faceObj, faceName); + } + else { + dirs = DrawGuiUtil::get3DDirAndRot(); + } + + Base::Vector3d projDir = dirs.first; + doCommand(Doc, "App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), projDir.x, projDir.y, projDir.z); + doCommand(Doc, "App.activeDocument().%s.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), dirs.second.x, dirs.second.y, dirs.second.z); + getDocument()->setStatus(App::Document::Status::SkipRecompute, true); + + commitCommand(); + +// Gui::Control().showDialog(new TaskDlgBrokenView(dbv)); + + dbv->recomputeFeature(); +} + +bool CmdTechDrawBrokenView::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + + //=========================================================================== // TechDraw_ActiveView //=========================================================================== @@ -1680,4 +1792,72 @@ void CreateTechDrawCommands() rcCmdMgr.addCommand(new CmdTechDrawSpreadsheetView()); rcCmdMgr.addCommand(new CmdTechDrawBalloon()); rcCmdMgr.addCommand(new CmdTechDrawProjectShape()); + rcCmdMgr.addCommand(new CmdTechDrawBrokenView()); + +} + +//**************************************** + + +//! extract the selected shapes and xShapes and determine if a face has been +//! selected to define the projection direction +void getSelectedShapes(Gui::Command* cmd, + std::vector& shapes, + std::vector& xShapes, + App::DocumentObject* faceObj, + std::string& faceName) +{ + Gui::ResolveMode resolve = Gui::ResolveMode::OldStyleElement;//mystery + bool single = false; //mystery + auto selection = cmd->getSelection().getSelectionEx(nullptr, App::DocumentObject::getClassTypeId(), + resolve, single); + for (auto& sel : selection) { + bool is_linked = false; + auto obj = sel.getObject(); + if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId())) { + continue; + } + if (obj->isDerivedFrom(App::LinkElement::getClassTypeId()) + || obj->isDerivedFrom(App::LinkGroup::getClassTypeId()) + || obj->isDerivedFrom(App::Link::getClassTypeId())) { + is_linked = true; + } + // If parent of the obj is a link to another document, we possibly need to treat non-link obj as linked, too + // 1st, is obj in another document? + if (obj->getDocument() != cmd->getDocument()) { + std::set parents = obj->getInListEx(true); + for (auto& parent : parents) { + // Only consider parents in the current document, i.e. possible links in this View's document + if (parent->getDocument() != cmd->getDocument()) { + continue; + } + // 2nd, do we really have a link to obj? + if (parent->isDerivedFrom(App::LinkElement::getClassTypeId()) + || parent->isDerivedFrom(App::LinkGroup::getClassTypeId()) + || parent->isDerivedFrom(App::Link::getClassTypeId())) { + // We have a link chain from this document to obj, and obj is in another document -> it is an XLink target + is_linked = true; + } + } + } + if (is_linked) { + xShapes.push_back(obj); + continue; + } + //not a Link and not null. assume to be drawable. Undrawables will be + // skipped later. + shapes.push_back(obj); + if (faceObj) { + continue; + } + //don't know if this works for an XLink + for (auto& sub : sel.getSubNames()) { + if (TechDraw::DrawUtil::getGeomTypeFromName(sub) == "Face") { + faceName = sub; + // + faceObj = obj; + break; + } + } + } } diff --git a/src/Mod/TechDraw/Gui/PreferencesGui.cpp b/src/Mod/TechDraw/Gui/PreferencesGui.cpp index b149c00f0b..c4cfbcd26a 100644 --- a/src/Mod/TechDraw/Gui/PreferencesGui.cpp +++ b/src/Mod/TechDraw/Gui/PreferencesGui.cpp @@ -96,6 +96,21 @@ QColor PreferencesGui::sectionLineQColor() return fcColor.asValue(); } +App::Color PreferencesGui::breaklineColor() +{ + App::Color fcColor; + fcColor.setPackedValue(Preferences::getPreferenceGroup("Decorations")->GetUnsigned("BreaklineColor", 0x000000FF)); + return fcColor; +} + +QColor PreferencesGui::breaklineQColor() +{ +//if the App::Color version has already lightened the color, we don't want to do it again + App::Color fcColor; + fcColor.setPackedValue(Preferences::getPreferenceGroup("Decorations")->GetUnsigned("BreaklineColor", 0x000000FF)); + return fcColor.asValue(); +} + App::Color PreferencesGui::centerColor() { return App::Color((uint32_t) Preferences::getPreferenceGroup("Decorations")->GetUnsigned("CenterColor", 0x000000FF)); diff --git a/src/Mod/TechDraw/Gui/PreferencesGui.h b/src/Mod/TechDraw/Gui/PreferencesGui.h index eee71019f7..9a690636d0 100644 --- a/src/Mod/TechDraw/Gui/PreferencesGui.h +++ b/src/Mod/TechDraw/Gui/PreferencesGui.h @@ -27,6 +27,14 @@ #include +class QColor; +class QString; + +namespace App +{ +class Color; +} + class QFont; class QString; @@ -57,6 +65,8 @@ static App::Color dimColor(); static QColor dimQColor(); static App::Color pageColor(); static QColor pageQColor(); +static App::Color breaklineColor(); +static QColor breaklineQColor(); static int dimArrowStyle(); static double dimArrowSize(); diff --git a/src/Mod/TechDraw/Gui/QGIBreakLine.cpp b/src/Mod/TechDraw/Gui/QGIBreakLine.cpp new file mode 100644 index 0000000000..8d12ab2ef3 --- /dev/null +++ b/src/Mod/TechDraw/Gui/QGIBreakLine.cpp @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: LGPL-2.0-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 * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +#endif + +#include +#include +#include +#include + +#include +#include + +#include "QGIBreakLine.h" +#include "PreferencesGui.h" + +using namespace TechDrawGui; +using namespace TechDraw; + +using DU = DrawUtil; + +constexpr double zigzagWidth{30.0}; +constexpr double segments{8}; + +QGIBreakLine::QGIBreakLine() +{ + setFlag(QGraphicsItem::ItemIsSelectable, false); + setFlag(QGraphicsItem::ItemIsMovable, false); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); + + m_background = new QGraphicsRectItem(); + addToGroup(m_background); + m_line0 = new QGraphicsPathItem(); + addToGroup(m_line0); + m_line1 = new QGraphicsPathItem(); + addToGroup(m_line1); + + + setColor(PreferencesGui::sectionLineQColor()); + + // setFill(Qt::NoBrush); + setFill(Qt::SolidPattern); +} + +void QGIBreakLine::draw() +{ + // Base::Console().Message("QGIBL::draw()\n"); + Base::Vector3d horizontal{1.0, 0.0, 0.0}; + prepareGeometryChange(); + if (m_direction.IsEqual(horizontal, EWTOLERANCE)) { + // m_direction connects the two cut points. The zigzags have + // to be perpendicular to m_direction + // 2x vertical zigzag from upper to lower + Base::Vector3d start = Base::Vector3d(m_left, m_bottom, 0.0); + m_line0->setPath(makeVerticalZigZag(start)); + + start = Base::Vector3d(m_right - zigzagWidth, m_bottom, 0.0); + m_line1->setPath(makeVerticalZigZag(start)); + } else { + // m_top is lower than m_bottom due to Qt Y+ down coords + // the higher break line + // 2x horizontal zigszg from left to right + Base::Vector3d start = Base::Vector3d(m_left, m_bottom, 0.0); + m_line0->setPath(makeHorizontalZigZag(start)); + + // the lower break line + start = Base::Vector3d(m_left, m_top - zigzagWidth, 0.0); + m_line1->setPath(makeHorizontalZigZag(start)); + } + + QRectF backgroundRect(m_left, m_bottom, std::fabs(m_right - m_left), std::fabs(m_top - m_bottom)); + m_background->setRect(backgroundRect); + + m_line0->show(); + m_line1->show(); + m_background->show(); + update(); +} + +// start needs to be Rez'd and +Y up +QPainterPath QGIBreakLine::makeHorizontalZigZag(Base::Vector3d start) const +{ + // Base::Console().Message("QGIBL::makeHorizontalZigZag(%s)\n", DU::formatVector(start).c_str()); + QPainterPath pPath; + double step = (m_right - m_left) / segments; + Base::Vector3d xOffset = Base::Vector3d(step, 0.0, 0.0); // 1/2 wave length + Base::Vector3d yOffset = Base::Vector3d(0.0, zigzagWidth, 0.0); // amplitude + + pPath.moveTo(DU::toQPointF(start)); + Base::Vector3d current = start; + int iSegment = 0; + double flipflop = 1.0; + for (; iSegment < segments; iSegment++) { + current = current + xOffset; + current = current + yOffset * flipflop; + pPath.lineTo(DU::toQPointF(current)); + flipflop *= -1.0; + } + return pPath; +} + +QPainterPath QGIBreakLine::makeVerticalZigZag(Base::Vector3d start) const +{ + // Base::Console().Message("QGIBL::makeVerticalZigZag(%s)\n", DU::formatVector(start).c_str()); + QPainterPath pPath; + double step = (m_top - m_bottom) / segments; + Base::Vector3d xOffset = Base::Vector3d(zigzagWidth, 0.0, 0.0); // amplitude + Base::Vector3d yOffset = Base::Vector3d(0.0, step, 0.0); // 1/2 wave length + + pPath.moveTo(DU::toQPointF(start)); + Base::Vector3d current = start; + int iSegment = 0; + double flipflop = 1.0; + for (; iSegment < segments; iSegment++) { + current = current + xOffset * flipflop; + current = current + yOffset; + pPath.lineTo(DU::toQPointF(current)); + flipflop *= -1.0; + } + return pPath; +} + +void QGIBreakLine::setBounds(double left, double top, double right, double bottom) +{ + // Base::Console().Message("QGIBL::setBounds(%.3f, %.3f, %.3f, %.3f\n", left, top, right, bottom); + m_left = left; + m_right = right; + m_top = top; + m_bottom = bottom; +} + +void QGIBreakLine::setBounds(Base::Vector3d topLeft, Base::Vector3d bottomRight) +{ + double left = std::min(topLeft.x, bottomRight.x); + double right = std::max(topLeft.x, bottomRight.x); + double bottom = std::min(topLeft.y, bottomRight.y); + double top = std::max(topLeft.y, bottomRight.y); + + setBounds(left, top, right, bottom); +} + +void QGIBreakLine::setDirection(Base::Vector3d dir) +{ + m_direction = dir; +} + + +void QGIBreakLine::setBreakColor(QColor c) +{ + setColor(c); +} + +void QGIBreakLine::paint ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) { + QStyleOptionGraphicsItem myOption(*option); + myOption.state &= ~QStyle::State_Selected; + + setTools(); + + // painter->setPen(Qt::blue); + // painter->drawRect(boundingRect()); //good for debugging + + QGIDecoration::paint (painter, &myOption, widget); +} + +void QGIBreakLine::setTools() +{ + m_pen.setWidthF(m_width); + m_pen.setColor(m_colCurrent); + m_brush.setStyle(m_brushCurrent); + m_brush.setColor(PreferencesGui::pageQColor()); + + m_line0->setPen(m_pen); + m_line0->setBrush(Qt::NoBrush); + m_line1->setPen(m_pen); + m_line1->setBrush(Qt::NoBrush); + + m_background->setBrush(m_brush); + m_background->setPen(Qt::NoPen); +} + + +void QGIBreakLine::setLinePen(QPen isoPen) +{ + m_pen = isoPen; +} + diff --git a/src/Mod/TechDraw/Gui/QGIBreakLine.h b/src/Mod/TechDraw/Gui/QGIBreakLine.h new file mode 100644 index 0000000000..12c225234f --- /dev/null +++ b/src/Mod/TechDraw/Gui/QGIBreakLine.h @@ -0,0 +1,84 @@ +/*************************************************************************** + * 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 * + * * + ***************************************************************************/ + +#ifndef TECHDRAWGUI_QGIBREAKLINE_H +#define TECHDRAWGUI_QGIBREAKLINE_H + +#include + +#include +#include +#include +#include + +#include +#include + +#include "QGCustomText.h" +#include "QGIDecoration.h" + + +namespace TechDrawGui +{ + +class TechDrawGuiExport QGIBreakLine : public QGIDecoration +{ +public: + explicit QGIBreakLine(); + ~QGIBreakLine() override = default; + + enum {Type = QGraphicsItem::UserType + 250}; + int type() const override { return Type;} + + void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = nullptr ) override; + + void setBounds(double left, double top, double right, double bottom); + void setBounds(Base::Vector3d topLeft, Base::Vector3d bottomRight); + void setDirection(Base::Vector3d dir); // horizontal(1,0,0) vertical(0,1,0); + void draw() override; + + void setLinePen(QPen isoPen); + void setBreakColor(QColor c); + +protected: + +private: + QPainterPath makeHorizontalZigZag(Base::Vector3d start) const; + QPainterPath makeVerticalZigZag(Base::Vector3d start) const; + void setTools(); + + QGraphicsPathItem* m_line0; + QGraphicsPathItem* m_line1; + QGraphicsRectItem* m_background; + + Base::Vector3d m_direction; + + double m_top; + double m_bottom; + double m_left; + double m_right; +}; + +} + +#endif // TECHDRAWGUI_QGIBREAKLINE_H + diff --git a/src/Mod/TechDraw/Gui/QGIUserTypes.h b/src/Mod/TechDraw/Gui/QGIUserTypes.h index df54412148..34cdc480b1 100644 --- a/src/Mod/TechDraw/Gui/QGIUserTypes.h +++ b/src/Mod/TechDraw/Gui/QGIUserTypes.h @@ -47,6 +47,7 @@ QGIMatting: 205 QGTracker: 210 QGILeaderLine: 232 QGIRichAnno: 233 +QGIBreakLine: 250 QGMText: 300 QGEPath: 301 QGMarker: 302 diff --git a/src/Mod/TechDraw/Gui/QGIViewPart.cpp b/src/Mod/TechDraw/Gui/QGIViewPart.cpp index 349ee5992a..33c9cf8b74 100644 --- a/src/Mod/TechDraw/Gui/QGIViewPart.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewPart.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include "DrawGuiUtil.h" #include "MDIViewPage.h" @@ -64,6 +65,7 @@ #include "ViewProviderViewPart.h" #include "ZVALUE.h" #include "PathBuilder.h" +#include "QGIBreakLine.h" using namespace TechDraw; using namespace TechDrawGui; @@ -240,6 +242,7 @@ void QGIViewPart::draw() drawViewPart(); drawAllHighlights(); + drawBreakLines(); drawMatting(); //this is old C/L drawCenterLines(true);//have to draw centerlines after border to get size correct. @@ -1032,6 +1035,48 @@ void QGIViewPart::drawMatting() mat->show(); } + +//! if this is a broken view, draw the break lines. +void QGIViewPart::drawBreakLines() +{ + // Base::Console().Message("QGIVP::drawBreakLines()\n"); + + auto dbv = dynamic_cast(getViewObject()); + if (!dbv) { + return; + } + + auto vp = static_cast(getViewProvider(getViewObject())); + if (!vp) { + return; + } + + auto breaks = dbv->Breaks.getValues(); + for (auto& breakObj : breaks) { + QGIBreakLine* breakLine = new QGIBreakLine(); + addToGroup(breakLine); + + Base::Vector3d direction = dbv->directionFromObj(*breakObj); + direction.Normalize(); + breakLine->setDirection(direction); + std::pair bounds = dbv->breakBoundsFromObj(*breakObj); + // the bounds are in 3d form, so we need to invert & rez them + Base::Vector3d topLeft = Rez::guiX(DU::invertY(bounds.first)); + Base::Vector3d bottomRight = Rez::guiX(DU::invertY(bounds.second)); + breakLine->setBounds(topLeft, bottomRight); + breakLine->setPos(0.0, 0.0); + breakLine->setLinePen( + m_dashedLineGenerator->getLinePen(1, vp->HiddenWidth.getValue())); + breakLine->setWidth(Rez::guiX(vp->HiddenWidth.getValue())); + breakLine->setZValue(ZVALUE::SECTIONLINE); + App::Color color = prefBreaklineColor(); + breakLine->setBreakColor(color.asValue()); + breakLine->setRotation(-dbv->Rotation.getValue()); + breakLine->draw(); + } +} + + void QGIViewPart::toggleCache(bool state) { QList items = childItems(); @@ -1158,6 +1203,11 @@ bool QGIViewPart::prefPrintCenters() return printCenters; } +App::Color QGIViewPart::prefBreaklineColor() +{ + return Preferences::getAccessibleColor(PreferencesGui::breaklineColor()); +} + QGraphicsItem *QGIViewPart::getQGISubItemByName(const std::string &subName) const { int scanType = 0; diff --git a/src/Mod/TechDraw/Gui/QGIViewPart.h b/src/Mod/TechDraw/Gui/QGIViewPart.h index b4054370d1..bfb7098faf 100644 --- a/src/Mod/TechDraw/Gui/QGIViewPart.h +++ b/src/Mod/TechDraw/Gui/QGIViewPart.h @@ -33,6 +33,11 @@ #include "QGIView.h" +class QColor; + +namespace App { +class Color; +} namespace TechDraw { class DrawViewPart; @@ -86,6 +91,7 @@ public: virtual void drawAllHighlights(); virtual void drawHighlight(TechDraw::DrawViewDetail* viewDetail, bool b); virtual void drawMatting(); + virtual void drawBreakLines(); bool showSection; void draw() override; @@ -132,6 +138,7 @@ protected: void removeDecorations(); bool prefFaceEdges(); bool prefPrintCenters(); + App::Color prefBreaklineColor(); bool formatGeomFromCosmetic(std::string cTag, QGIEdge* item); bool formatGeomFromCenterLine(std::string cTag, QGIEdge* item); diff --git a/src/Mod/TechDraw/Gui/Resources/TechDraw.qrc b/src/Mod/TechDraw/Gui/Resources/TechDraw.qrc index bf5926dd53..df640134ff 100644 --- a/src/Mod/TechDraw/Gui/Resources/TechDraw.qrc +++ b/src/Mod/TechDraw/Gui/Resources/TechDraw.qrc @@ -57,6 +57,7 @@ icons/actions/TechDraw_SurfaceFinishSymbols.svg icons/actions/TechDraw_ComplexSection.svg icons/actions/TechDraw_CosmeticCircle.svg + icons/actions/TechDraw_BrokenView.svg icons/arrow-ccw.svg icons/arrow-cw.svg icons/arrow-down.svg diff --git a/src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_BrokenView.svg b/src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_BrokenView.svg new file mode 100644 index 0000000000..e5c3b7d28c --- /dev/null +++ b/src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_BrokenView.svg @@ -0,0 +1,650 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + [agryson] Alexander Gryson + + + http://agryson.net + + techdraw-view + 2016-01-14 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/TechDraw/Gui/Resources/icons/actions/techdraw-view.svg + + + FreeCAD LGPL2+ + + + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Gui/Workbench.cpp b/src/Mod/TechDraw/Gui/Workbench.cpp index 21f42ff24d..c966052650 100644 --- a/src/Mod/TechDraw/Gui/Workbench.cpp +++ b/src/Mod/TechDraw/Gui/Workbench.cpp @@ -212,6 +212,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const Gui::MenuItem* views = new Gui::MenuItem; views->setCommand("TechDraw Views"); *views << "TechDraw_View"; + *views << "TechDraw_BrokenView"; *views << "TechDraw_SectionView"; *views << "TechDraw_ComplexSection"; *views << "TechDraw_DetailView"; @@ -291,6 +292,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const Gui::ToolBarItem* views = new Gui::ToolBarItem(root); views->setCommand("TechDraw Views"); *views << "TechDraw_View"; + *views << "TechDraw_BrokenView"; *views << "TechDraw_ActiveView"; *views << "TechDraw_ProjectionGroup"; *views << "TechDraw_SectionGroup";