* [TD]fix broken view does not collapse * [TD]delint and clean up code * Update src/Mod/TechDraw/App/DrawBrokenView.cpp Co-authored-by: Benjamin Bræstrup Sayoc <benj5378@outlook.com> * Update src/Mod/TechDraw/App/DrawBrokenView.cpp Co-authored-by: Benjamin Bræstrup Sayoc <benj5378@outlook.com> * Update src/Mod/TechDraw/App/DrawBrokenView.h Co-authored-by: Benjamin Bræstrup Sayoc <benj5378@outlook.com> * Update src/Mod/TechDraw/App/DrawBrokenView.h Co-authored-by: Benjamin Bræstrup Sayoc <benj5378@outlook.com> * Update src/Mod/TechDraw/App/DrawBrokenView.cpp Co-authored-by: Benjamin Bræstrup Sayoc <benj5378@outlook.com> * [TD]fix merge errors --------- Co-authored-by: Benjamin Bræstrup Sayoc <benj5378@outlook.com>
1202 lines
46 KiB
C++
1202 lines
46 KiB
C++
// 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.
|
|
|
|
#include "PreCompiled.h" // NOLINT
|
|
|
|
#ifndef _PreComp_
|
|
#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>
|
|
#endif
|
|
|
|
#include <App/Document.h>
|
|
#include <Base/BoundBox.h>
|
|
#include <Base/Console.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 "Preferences.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
|
|
{
|
|
auto pieces = getPieces(shapeToCompress);
|
|
auto breaksAll = Breaks.getValues();
|
|
auto moveDirection = DU::closestBasisOriented(DU::toVector3d(getProjectionCS().XDirection()));
|
|
bool descend = false;
|
|
auto sortedBreaks = makeSortedBreakList(breaksAll, moveDirection, descend);
|
|
auto 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(DU::toVector3d(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 = DU::to<gp_Pnt>(planePoint);
|
|
auto axis = DU::to<gp_Dir>(planeNormal);
|
|
gp_Pln plane(origin, axis);
|
|
BRepBuilderAPI_MakeFace mkFace(plane);
|
|
BRepPrimAPI_MakeHalfSpace mkHalf(mkFace.Face(), DU::to<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 {DU::toVector3d(start), DU::toVector3d(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 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
|
|
{
|
|
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 = DU::toVector3d(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 = DU::toVector3d(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 = DU::toVector3d(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;
|
|
}
|
|
|
|
std::vector<TopoDS_Shape> DrawBrokenView::getPieces(const TopoDS_Shape& brokenShape)
|
|
{
|
|
std::vector<TopoDS_Shape> 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<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(DU::toVector3d(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(DU::toVector3d(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 = DU::toVector3d(DU::to<gp_Pnt>(point2d).Transformed(xTo3d));
|
|
|
|
// now shift down and left
|
|
auto breaksAll = Breaks.getValues();
|
|
|
|
auto moveXDirection = DU::closestBasisOriented(DU::toVector3d(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(DU::toVector3d(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 DU::toVector3d(gCenter);
|
|
}
|
|
|
|
//! construct a perpendicular direction in the projection CS
|
|
Base::Vector3d DrawBrokenView::makePerpendicular(Base::Vector3d inDir) const
|
|
{
|
|
auto gDir = DU::to<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, M_PI_2);
|
|
return DU::toVector3d(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
|