diff --git a/src/Gui/MDIView.cpp b/src/Gui/MDIView.cpp index b803b3287f..27f312237f 100644 --- a/src/Gui/MDIView.cpp +++ b/src/Gui/MDIView.cpp @@ -147,11 +147,13 @@ void MDIView::onRelabel(Gui::Document *pDoc) // Either with dirty flag ... QRegularExpression rx(QLatin1String("(\\s\\:\\s\\d+\\[\\*\\])$")); QRegularExpressionMatch match; - int pos = cap.lastIndexOf(rx, -1, &match); + //int pos = + cap.lastIndexOf(rx, -1, &match); if (!match.hasMatch()) { // ... or not rx.setPattern(QLatin1String("(\\s\\:\\s\\d+)$")); - pos = cap.lastIndexOf(rx, -1, &match); + //pos = + cap.lastIndexOf(rx, -1, &match); } if (match.hasMatch()) { cap = QString::fromUtf8(pDoc->getDocument()->Label.getValue()); diff --git a/src/Mod/TechDraw/App/AppTechDraw.cpp b/src/Mod/TechDraw/App/AppTechDraw.cpp index 6e216ef70a..ada92519c6 100644 --- a/src/Mod/TechDraw/App/AppTechDraw.cpp +++ b/src/Mod/TechDraw/App/AppTechDraw.cpp @@ -24,8 +24,9 @@ #include #include -#include "Cosmetic.h" #include "CosmeticExtension.h" +#include "Cosmetic.h" +#include "DrawComplexSection.h" #include "DrawGeomHatch.h" #include "DrawHatch.h" #include "DrawLeaderLine.h" @@ -37,7 +38,6 @@ #include "DrawSVGTemplate.h" #include "DrawTile.h" #include "DrawTileWeld.h" -#include "DrawView.h" #include "DrawViewAnnotation.h" #include "DrawViewArch.h" #include "DrawViewBalloon.h" @@ -47,8 +47,8 @@ #include "DrawViewDimension.h" #include "DrawViewDimExtent.h" #include "DrawViewDraft.h" +#include "DrawView.h" #include "DrawViewImage.h" -#include "DrawViewMulti.h" #include "DrawViewPart.h" #include "DrawViewSection.h" #include "DrawViewSpreadsheet.h" @@ -62,6 +62,7 @@ #include "PropertyGeomFormatList.h" + namespace TechDraw { extern PyObject* initModule(); } @@ -90,7 +91,7 @@ PyMOD_INIT_FUNC(TechDraw) TechDraw::DrawViewSpreadsheet ::init(); TechDraw::DrawViewSection ::init(); - TechDraw::DrawViewMulti ::init(); + TechDraw::DrawComplexSection ::init(); TechDraw::DrawViewDimension ::init(); TechDraw::DrawViewDimExtent ::init(); TechDraw::LandmarkDimension ::init(); @@ -134,7 +135,8 @@ PyMOD_INIT_FUNC(TechDraw) TechDraw::DrawPagePython ::init(); TechDraw::DrawViewPython ::init(); TechDraw::DrawViewPartPython ::init(); - TechDraw::DrawViewMultiPython ::init(); + TechDraw::DrawViewSectionPython::init(); + TechDraw::DrawComplexSectionPython ::init(); TechDraw::DrawTemplatePython ::init(); TechDraw::DrawViewSymbolPython::init(); TechDraw::DrawLeaderLinePython::init(); diff --git a/src/Mod/TechDraw/App/CMakeLists.txt b/src/Mod/TechDraw/App/CMakeLists.txt index adc4cd507a..8b54b0e165 100644 --- a/src/Mod/TechDraw/App/CMakeLists.txt +++ b/src/Mod/TechDraw/App/CMakeLists.txt @@ -75,6 +75,8 @@ generate_from_xml(CosmeticExtensionPy) SET(Draw_SRCS DrawPage.cpp DrawPage.h + DrawComplexSection.cpp + DrawComplexSection.h DrawView.cpp DrawView.h DrawViewPart.cpp diff --git a/src/Mod/TechDraw/App/DrawComplexSection.cpp b/src/Mod/TechDraw/App/DrawComplexSection.cpp new file mode 100644 index 0000000000..802ae65c35 --- /dev/null +++ b/src/Mod/TechDraw/App/DrawComplexSection.cpp @@ -0,0 +1,1179 @@ +/*************************************************************************** + * Copyright (c) 2022 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 * + * * + ***************************************************************************/ +//DrawComplexSection processing overview +//for Strategy = Offset, DCS is much the same as DVS +//for Strategy = Aligned, there are many differences + +//execute +// sectionExec(getShapeToCut()*) + +//sectionExec +// makeSectionCut(baseShape) + +//makeSectionCut (separate thread) +// note that the section cut is not required for Aligned strategy, +// but it is useful for debugging +// m_cuttingTool = makeCuttingTool* (DVSTool.brep) +// m_cutPieces = (baseShape - m_cuttingTool) (DVSCutPieces.brep) + +//onSectionCutFinished +// m_preparedShape = prepareShape(m_cutPieces)* - centered, scaled, rotated +// geometryObject = DVP::buildGeometryObject(m_preparedShape) (HLR) + +//postHlrTasks +// faceIntersections = findSectionPlaneIntersections +// m_sectionTopoDSFaces = alignSectionFaces(faceIntersections) +// m_tdSectionFaces = makeTDSectionFaces(m_sectionTopoDSFaces) + +//* for Aligned, we use a different ShapeToCut, as the standard one will +// cause many coincident face problems later +//* the cutting tool is built up from the profile, instead of the simple plane in DVS +//* for Aligned, preparing the shape is much different than Offset or DVS +// - most of the work is done in makeAlignedPieces +// - for each segment of the profile, make a cutting tool, then get the boolean +// intersection of the tool and the shape to cut +// - align and distribute the intersections along an "effective" section plane +// which is a flattened version of the profile + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#define _USE_MATH_DEFINES +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "DrawComplexSection.h" +#include "DrawUtil.h" +#include "GeometryObject.h" + +using namespace TechDraw; +using namespace std; +using DU = DrawUtil; + +//class to store geometry of points where the section line changes direction +ChangePoint::ChangePoint(QPointF location, QPointF preDirection, QPointF postDirection) +{ + m_location = location; + m_preDirection = preDirection; + m_postDirection = postDirection; +} + +ChangePoint::ChangePoint(gp_Pnt location, gp_Dir preDirection, gp_Dir postDirection) +{ + m_location.setX(location.X()); + m_location.setY(location.Y()); + m_preDirection.setX(preDirection.X()); + m_preDirection.setY(preDirection.Y()); + m_postDirection.setX(postDirection.X()); + m_postDirection.setY(postDirection.Y()); +} + +void ChangePoint::scale(double scaleFactor) +{ + m_location = m_location * scaleFactor; + m_preDirection = m_preDirection * scaleFactor; + m_postDirection = m_postDirection * scaleFactor; +} + +//=========================================================================== +// DrawComplexSection +//=========================================================================== + +PROPERTY_SOURCE(TechDraw::DrawComplexSection, TechDraw::DrawViewSection) + +const char *DrawComplexSection::ProjectionStrategyEnums[] = {"Offset", "Aligned", "NoParallel", + nullptr}; + +DrawComplexSection::DrawComplexSection() + : m_toolFaceShape(TopoDS_Shape()), m_profileWire(TopoDS_Wire()) +{ + static const char *fgroup = "Cutting Tool"; + + ADD_PROPERTY_TYPE(CuttingToolWireObject, (nullptr), fgroup, App::Prop_None, + "A sketch that describes the cutting tool"); + CuttingToolWireObject.setScope(App::LinkScope::Global); + ProjectionStrategy.setEnums(ProjectionStrategyEnums); + ADD_PROPERTY_TYPE(ProjectionStrategy, ((long)0), fgroup, App::Prop_None, + "Make a single cut, or use the profile in pieces"); +} + +TopoDS_Shape DrawComplexSection::getShapeToCut() +{ + // Base::Console().Message("DCS::getShapeToCut()\n"); + App::DocumentObject *base = BaseView.getValue(); + TopoDS_Shape shapeToCut; + if (base && base == this) { + shapeToCut = getSourceShape(); + if (FuseBeforeCut.getValue()) { + shapeToCut = getSourceShapeFused(); + } + return shapeToCut; + } + if (!base + || !base->getTypeId().isDerivedFrom( + TechDraw::DrawViewPart::getClassTypeId())) {//is second clause necessary? + //Complex section is based on 3d objects, need to get our own shapes since we can't ask a dvp + shapeToCut = getSourceShape(); + if (FuseBeforeCut.getValue()) { + shapeToCut = getSourceShapeFused(); + } + return shapeToCut; + } + //complex section is based on a DVP, so get the shape the normal way + return DrawViewSection::getShapeToCut(); +} + +void DrawComplexSection::makeSectionCut(TopoDS_Shape &baseShape) +{ + // Base::Console().Message("DCS::makeSectionCut() - %s\n", getNameInDocument(), baseShape.IsNull()); + if (ProjectionStrategy.getValue() == 0) { + //Offset. Use regular section behaviour + DrawViewSection::makeSectionCut(baseShape); + return; + } + //Aligned strategy + if (debugSection()) { + //only useful for debugging with Aligned strategy + DrawViewSection::makeSectionCut(baseShape); + } + return; +} + +TopoDS_Shape DrawComplexSection::makeCuttingTool(double dMax) +{ + // Base::Console().Message("DCS::makeCuttingTool()\n"); + App::DocumentObject *toolObj = CuttingToolWireObject.getValue(); + if (!isProfileObject(toolObj)) { + return TopoDS_Shape(); + } + TopoDS_Wire profileWire = makeProfileWire(toolObj); + m_profileWire = profileWire; + BRepBuilderAPI_Copy BuilderCopy(profileWire); + m_profileWire = TopoDS::Wire(BuilderCopy.Shape()); + if (debugSection()) { + //the nose to tail version of the profile + BRepTools::Write(m_profileWire, "DCSProfileWire.brep");//debug + } + + gp_Ax2 sectionCS = getSectionCS(); + gp_Dir gClosestBasis;//direction perpendicular to profile & section normal + bool isPositionOK = validateProfilePosition(profileWire, sectionCS, gClosestBasis); + if (!isPositionOK) { + //profile is not in a good position. Result might not be right. + Base::Console().Warning("DCS::makeCuttingTool - %s - profile is outside shape box\n", + getNameInDocument()); + } + + //move the profile wire to one side of the shape + gp_Trsf mov; + mov.SetTranslation(gp_Vec(gClosestBasis) * (-dMax)); + TopLoc_Location loc(mov); + profileWire.Move(loc); + + gp_Vec extrudeDir(0.0, 0.0, 1.0);//arbitrary default + if (BRep_Tool::IsClosed(profileWire)) { + // Wire is closed, so make a face from it and extrude "vertically" + BRepBuilderAPI_MakeFace mkFace(profileWire); + TopoDS_Face toolFace = mkFace.Face(); + if (toolFace.IsNull()) { + return TopoDS_Shape(); + } + gp_Dir gpNormal = getFaceNormal(toolFace); + extrudeDir = 2.0 * dMax * gpNormal; + return BRepPrimAPI_MakePrism(toolFace, extrudeDir).Shape(); + } + + //if the wire is open we need to make a "face" from the wire by extruding it + //in the direction of gClosestBasis , then extrude the face in the direction of the section normal + m_toolFaceShape = extrudeWireToFace(profileWire, gClosestBasis, 2.0 * dMax); + if (debugSection()) { + BRepTools::Write(m_toolFaceShape, "DCSToolFaceShape.brep");//debug + } + extrudeDir = dMax * sectionCS.Direction(); + TopoDS_Shape roughTool = BRepPrimAPI_MakePrism(m_toolFaceShape, extrudeDir).Shape(); + if (roughTool.ShapeType() == TopAbs_COMPSOLID) { + //Composite Solids do not cut well if they contain "solids" with no volume. This + //happens if the profile has segments parallel to the extrude direction. + //We need to disassemble it and only keep the real solids. + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + TopExp_Explorer expSolids(roughTool, TopAbs_SOLID); + for (; expSolids.More(); expSolids.Next()) { + TopoDS_Solid solid = TopoDS::Solid(expSolids.Current()); + GProp_GProps gprops; + BRepGProp::VolumeProperties(solid, gprops); + double volume = gprops.Mass(); + if (volume > EWTOLERANCE) { + builder.Add(comp, solid); + } + } + return comp; + } + + return BRepPrimAPI_MakePrism(m_toolFaceShape, extrudeDir).Shape(); +} + +TopoDS_Shape DrawComplexSection::getShapeToPrepare() const +{ + if (ProjectionStrategy.getValue() == 0) { + //Offset. Use regular section behaviour + return DrawViewSection::getShapeToPrepare(); + } + //Aligned strategy + return m_saveShape;//the original input shape +} + +//get the shape ready for projection and cut surface finding +TopoDS_Shape DrawComplexSection::prepareShape(const TopoDS_Shape &cutShape, double shapeSize) +{ + // Base::Console().Message("DCS::prepareShape() - strat: %d\n", ProjectionStrategy.getValue()); + if (ProjectionStrategy.getValue() == 0) { + //Offset. Use regular section behaviour + return DrawViewSection::prepareShape(cutShape, shapeSize); + } + + //"Aligned" projection (Aligned Section) + TopoDS_Shape alignedResult = makeAlignedPieces(cutShape, m_toolFaceShape, shapeSize); + + if (alignedResult.IsNull()) { + return TopoDS_Shape(); + } + + return scaleShape(alignedResult, getScale()); +} + +//for Aligned strategy, cut the rawShape by each segment of the tool +//TODO: this process should replace the "makeSectionCut" from DVS +TopoDS_Shape DrawComplexSection::makeAlignedPieces(const TopoDS_Shape &rawShape, + const TopoDS_Shape &toolFaceShape, + double extrudeDistance) +{ + // Base::Console().Message("DCS::makeAlignedPieces()\n"); + std::vector pieces; + std::vector pieceXSize;//size in sectionCS.XDirection (width) + std::vector pieceYSize;//size in sectionCS.Direction (depth) + std::vector pieceZSize;//size in sectionCS.YDirection (height) + std::vector pieceDirections; + //make a "real" CS from the section projection CS + gp_Ax3 alignedCS(gp_Pnt(0.0, 0.0, 0.0), + getSectionCS().YDirection(), //up and down + getSectionCS().XDirection());//left to right + gp_Ax3 stdCS; //OXYZ + gp_Vec gProjectionUnit = gp_Vec(getSectionCS().Direction()); + + //get a vector that describes the profile's orientation in paper space. + gp_Vec gProfileVec = projectProfileWire(m_profileWire, gp_Ax3(getSectionCS())); + if (fabs(gProfileVec.Dot(getProjectionCS().Direction()) == 1.0)) { + Base::Console().Error( + "DCS::makeAlignedPieces - %s - profile is parallel to SectionNormal\n", + getNameInDocument()); + throw Base::RuntimeError("Profile orientation error"); + } + + //convert the profileVector with OXYZ. + gp_Trsf xProfileOXYZ; + gp_Ax3 OXYZ; + xProfileOXYZ.SetTransformation(OXYZ, gp_Ax3(getSectionCS())); + gp_Vec profileVecOXYZ = gProfileVec.Transformed(xProfileOXYZ); + + bool isVertical = true; + if (fabs(profileVecOXYZ.Dot(gp::OY().Direction().XYZ())) != 1.0) { + //profile is not parallel with stdY (paper space Up). + //this test is not good enough for "vertical-ish" diagonal profiles + isVertical = false; + } + + double leftToRight = 1.0;//profile vector points to right, so we move to right + if (profileVecOXYZ.Dot(gp_Vec(gp::OX().Direction().XYZ())) < 0.0) { + //profileVec does not point towards stdX (right in paper space) + leftToRight = -1.0; + } + double topToBottom = 1.0;//profile vector points to top, so we move to top + if (profileVecOXYZ.Dot(gp_Vec(gp::OY().Direction().XYZ())) < 0.0) { + //profileVec does not point towards stdY (up in paper space) + topToBottom = -1.0; + } + + gp_Vec rotateAxis = getSectionCS().Direction().Crossed(gProfileVec); + + + //make a tool for each segment of the toolFaceShape and intersect it with the + //raw shape + TopExp_Explorer expFaces(toolFaceShape, TopAbs_FACE); + for (int iPiece = 0; expFaces.More(); expFaces.Next(), iPiece++) { + TopoDS_Face face = TopoDS::Face(expFaces.Current()); + gp_Vec segmentNormal = gp_Vec(getFaceNormal(face)); + if (!showSegment(gp_Dir(segmentNormal))) { + //skip this segment of the profile + continue; + } + //we only want to reverse the segment normal if it is not perpendicular to section normal + if (segmentNormal.Dot(-gProjectionUnit) != 0.0 + && segmentNormal.Angle(gProjectionUnit) <= M_PI_2) { + segmentNormal.Reverse(); + } + + gp_Vec extrudeDir = segmentNormal * extrudeDistance; + BRepPrimAPI_MakePrism mkPrism(face, extrudeDir); + TopoDS_Shape segmentTool = mkPrism.Shape(); + TopoDS_Shape intersect = shapeShapeIntersect(segmentTool, rawShape); + + gp_Pnt pieceCentroid = findCentroid(intersect); + double faceAngle = + gp_Vec(getSectionCS().Direction().Reversed()).AngleWithRef(segmentNormal, rotateAxis); + + //move intersection shape to the origin + gp_Trsf xPieceCenter; + xPieceCenter.SetTranslation(gp_Vec(findCentroid(intersect).XYZ()) * -1.0); + BRepBuilderAPI_Transform mkTransXLate(intersect, xPieceCenter, true); + TopoDS_Shape pieceCentered = mkTransXLate.Shape(); + + //rotate the intersection so interesting face is aligned with paper plane + pieceCentroid = findCentroid(pieceCentered); + gp_Ax1 faceAxis(gp_Pnt(0.0, 0.0, 0.0), rotateAxis); + gp_Ax3 pieceCS;//XYZ tipped so face is aligned with sectionCS + pieceCS.Rotate(faceAxis, faceAngle); + gp_Trsf xPieceRotate; + xPieceRotate.SetTransformation(stdCS, pieceCS); + BRepBuilderAPI_Transform mkTransRotate(pieceCentered, xPieceRotate, true); + TopoDS_Shape pieceRotated = mkTransRotate.Shape(); + + //align a copy of the piece with OXYZ so we can use bounding box to get + //width, depth, height of the piece. We copy the piece so the tranformation + //does not affect the original. + BRepBuilderAPI_Copy BuilderPieceCopy(pieceRotated); + TopoDS_Shape copyPieceRotatedShape = BuilderPieceCopy.Shape(); + gp_Trsf xPieceAlign; + xPieceAlign.SetTransformation(stdCS, alignedCS); + BRepBuilderAPI_Transform mkTransAlign(copyPieceRotatedShape, xPieceAlign); + TopoDS_Shape pieceAligned = mkTransAlign.Shape(); + + Bnd_Box shapeBox; + shapeBox.SetGap(0.0); + BRepBndLib::AddOptimal(pieceAligned, shapeBox); + double xMin = 0, xMax = 0, yMin = 0, yMax = 0, zMin = 0, zMax = 0; + shapeBox.Get(xMin, yMin, zMin, xMax, yMax, zMax); + double pieceWidth(xMax - xMin); + double pieceDepth(yMax - yMin); + double pieceHeight(zMax - zMin); + pieceXSize.push_back(pieceWidth); + pieceYSize.push_back(pieceDepth); + pieceZSize.push_back(pieceHeight); + + //now we need to move the piece so that the interesting face is coincident + //with the paper plane + gp_Vec depthVector(gp::OY().Direction().XYZ() * pieceDepth / 2.0);//move "back" + //only aligned to paper plane + gp_Vec netDisplacement = -1.0 * gp_Vec(findCentroid(pieceAligned).XYZ()) + depthVector; + //if we are going to space along X, we need to bring the pieces into alignment + //with the XY plane. + gp_Vec xyDisplacement = isVertical ? gp_Vec(0.0, 0.0, 0.0) : gp_Vec(gp::OZ().Direction()); + xyDisplacement = xyDisplacement * topToBottom * pieceHeight / 2.0; + netDisplacement = netDisplacement + xyDisplacement; + + gp_Trsf xPieceDisplace; + xPieceDisplace.SetTranslation(netDisplacement); + BRepBuilderAPI_Transform mkTransDisplace(pieceAligned, xPieceDisplace, true); + TopoDS_Shape pieceDisplaced = mkTransDisplace.Shape(); + //piece is now centered on X, aligned with XZ plane (which will be the effective + //cutting plane) + pieces.push_back(pieceDisplaced); + } + + if (pieces.empty()) { + return TopoDS_Compound(); + } + + int pieceCount = pieces.size(); + if (pieceCount < 2) { + //no need to space out the pieces + return TopoDS::Compound(pieces.front()); + } + + //space the pieces "horizontally" (stdX) or "vertically" (stdZ) + double movementReverser = isVertical ? topToBottom : leftToRight; + //TODO: non-cardinal profiles! + gp_Vec movementAxis = isVertical ? gp_Vec(gp::OZ().Direction()) : gp_Vec(gp::OX().Direction()); + gp_Vec gMovementVector = movementAxis * movementReverser; + + int stopAt = pieces.size(); + double distanceToMove = 0.0; + for (int iPiece = 0; iPiece < stopAt; iPiece++) { + double pieceSize = pieceXSize.at(iPiece); + if (isVertical) { + pieceSize = pieceZSize.at(iPiece); + } + double myDistanceToMove = distanceToMove + pieceSize / 2.0; + gp_Trsf xPieceDistribute; + xPieceDistribute.SetTranslation(gMovementVector * myDistanceToMove); + BRepBuilderAPI_Transform mkTransDistribute(pieces.at(iPiece), xPieceDistribute, true); + pieces.at(iPiece) = mkTransDistribute.Shape(); + distanceToMove += pieceSize; + } + + //make a compound of the aligned pieces + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + for (auto &piece : pieces) { builder.Add(comp, piece); } + + //center the compound along SectionCS XDirection + Base::Vector3d centerVector = DU::toVector3d(gMovementVector) * distanceToMove / -2.0; + TopoDS_Shape centeredCompound = moveShape(comp, centerVector); + if (debugSection()) { + BRepTools::Write(centeredCompound, "DCSfinCenteredCompound.brep");//debug + } + //realign with SectionCS + gp_Trsf xPieceAlign; + xPieceAlign.SetTransformation(alignedCS, stdCS); + BRepBuilderAPI_Transform mkTransAlign(centeredCompound, xPieceAlign); + TopoDS_Shape alignedCompound = mkTransAlign.Shape(); + + if (debugSection()) { + BRepTools::Write(alignedCompound, "DCSAlignedResult.brep");//debug + } + + return alignedCompound; +} + +//! tries to find the intersection faces of the cut shape and the cutting tool. +//! Aligned complex sections need to intersect the final cut shape (which in this +//! case is a compound of individual cuts) with the "effective" (flattened) section plane. +//! Profiles containing curves need special handling. +TopoDS_Compound +DrawComplexSection::findSectionPlaneIntersections(const TopoDS_Shape &shapeToIntersect) +{ + // Base::Console().Message("DCS::findSectionPlaneIntersections() - %s\n", getNameInDocument()); + if (shapeToIntersect.IsNull()) { + // this shouldn't happen + Base::Console().Warning("DCS::findSectionPlaneInter - %s - cut shape is Null\n", + getNameInDocument()); + return TopoDS_Compound(); + } + if (ProjectionStrategy.getValue() == 0) {//Offset + return singleToolIntersections(shapeToIntersect); + } + + return piecewiseToolIntersections(shapeToIntersect); +} + +//Intersect cutShape with each segment of the cutting tool +TopoDS_Compound DrawComplexSection::singleToolIntersections(const TopoDS_Shape &cutShape) +{ + // Base::Console().Message("DCS::singleToolInterSections()\n"); + App::DocumentObject *toolObj = CuttingToolWireObject.getValue(); + if (!isLinearProfile(toolObj)) { + //TODO: special handling here + // Base::Console().Message("DCS::singleToolIntersection - profile has curves\n"); + } + + BRep_Builder builder; + TopoDS_Compound result; + builder.MakeCompound(result); + + if (debugSection()) { + BRepTools::Write(cutShape, "DCSOffsetCutShape.brep"); //debug + BRepTools::Write(m_toolFaceShape, "DCSOffsetCuttingToolFace.brep");//debug + } + + if (m_toolFaceShape.IsNull()) { + return result; + } + + TopExp_Explorer expFaces(cutShape, TopAbs_FACE); + for (; expFaces.More(); expFaces.Next()) { + TopoDS_Face face = TopoDS::Face(expFaces.Current()); + if (!boxesIntersect(face, m_toolFaceShape)) { + continue; + } + std::vector commonFaces = faceShapeIntersect(face, m_toolFaceShape); + for (auto &cFace : commonFaces) { builder.Add(result, cFace); } + } + return result; +} + +//Intersect cutShape with the effective (flattened) cutting plane to generate cut surface faces +TopoDS_Compound DrawComplexSection::piecewiseToolIntersections(const TopoDS_Shape &cutShape) +{ + // Base::Console().Message("DCS::piecewiseToolIntersections()\n"); + BRep_Builder builder; + TopoDS_Compound result; + builder.MakeCompound(result); + + App::DocumentObject *toolObj = CuttingToolWireObject.getValue(); + if (!isLinearProfile(toolObj)) { + //TODO: special handling here + // Base::Console().Message("DCS::pieceWiseToolIntersection - profile has curves\n"); + } + + gp_Pln effectivePlane = getSectionPlane(); + //piecewise result can be much wider than the shape itself, so we use an + //infinite face. + BRepBuilderAPI_MakeFace mkFace(effectivePlane, -Precision::Infinite(), Precision::Infinite(), + -Precision::Infinite(), Precision::Infinite()); + TopoDS_Face cuttingFace = mkFace.Face(); + + TopExp_Explorer expFaces(cutShape, TopAbs_FACE); + for (; expFaces.More(); expFaces.Next()) { + TopoDS_Face face = TopoDS::Face(expFaces.Current()); + if (!boxesIntersect(face, cuttingFace)) { + continue; + } + std::vector commonFaces = faceShapeIntersect(face, cuttingFace); + for (auto &cFace : commonFaces) { builder.Add(result, cFace); } + } + if (debugSection()) { + BRepTools::Write(cuttingFace, "DCSAlignedCuttingFace.brep"); //debug + BRepTools::Write(cutShape, "DCSAlignedCutShape.brep"); //debug + BRepTools::Write(result, "DCSAlignedIntersectionResult.brep");//debug + } + return result; +} + +TopoDS_Compound DrawComplexSection::alignSectionFaces(TopoDS_Shape faceIntersections) +{ + // Base::Console().Message("DCS::alignSectionFaces()\n"); + if (ProjectionStrategy.getValue() == 0) { + //Offset. Use regular section behaviour + return DrawViewSection::alignSectionFaces(faceIntersections); + } + + return TopoDS::Compound(mapToPage(faceIntersections)); +} + +TopoDS_Shape DrawComplexSection::getShapeToIntersect() +{ + if (ProjectionStrategy.getValue() == 0) {//Offset + return DrawViewSection::getShapeToIntersect(); + } + //Aligned + return m_preparedShape; +} + +TopoDS_Wire DrawComplexSection::makeProfileWire(App::DocumentObject *toolObj) +{ + TopoDS_Shape toolShape = Part::Feature::getShape(toolObj); + if (toolShape.IsNull()) { + return TopoDS_Wire(); + } + + TopoDS_Wire profileWire; + if (toolShape.ShapeType() == TopAbs_WIRE) { + profileWire = makeNoseToTailWire(TopoDS::Wire(toolShape)); + } + else {//we have already checked that the shape is a wire or an edge in isProfileObject + TopoDS_Edge edge = TopoDS::Edge(toolShape); + profileWire = BRepBuilderAPI_MakeWire(edge).Wire(); + } + return profileWire; +} + +//methods related to section line + +//project the profile onto the paper and convert to the working CS +gp_Dir DrawComplexSection::projectProfileWire(TopoDS_Wire profileWire, gp_Ax3 paperCS) +{ + // Base::Console().Message("DCS::projectProfileWire()\n"); + gp_Pln plane(paperCS); + TopoDS_Face paper = BRepBuilderAPI_MakeFace(plane); + BRepAlgo_NormalProjection projector(paper); + projector.Add(profileWire); + projector.Build(); + TopoDS_Shape projectedShape = projector.Projection(); + + TopoDS_Edge projectedSegment; + //we only need 1 projected edge to determine direction + TopExp_Explorer expEdges(projectedShape, TopAbs_EDGE); + for (; expEdges.More(); expEdges.Next()) { + projectedSegment = TopoDS::Edge(expEdges.Current()); + break; + } + if (debugSection()) { + BRepTools::Write(projectedSegment, "DCSprojectedSegment.brep");//debug + } + if (projectedSegment.IsNull()) { + Base::Console().Warning("DCS::projectProfileWire - projection of profile failed\n"); + return gp_Dir(1.0, 0.0, 0.0); + } + gp_Pnt gpProfileFirst = BRep_Tool::Pnt(TopExp::FirstVertex(projectedSegment)); + gp_Pnt gpProfileLast = BRep_Tool::Pnt(TopExp::LastVertex(projectedSegment)); + gp_Vec gProfileVec(gpProfileFirst, gpProfileLast); + gProfileVec.Normalize(); + return gp_Dir(gProfileVec); +} + +//make drawable td geometry for section line +BaseGeomPtrVector DrawComplexSection::makeSectionLineGeometry() +{ + // Base::Console().Message("DCS::makeSectionLineGeometry()\n"); + BaseGeomPtrVector result; + DrawViewPart *baseDvp = dynamic_cast(BaseView.getValue()); + if (baseDvp) { + TopoDS_Wire lineWire = makeSectionLineWire(); + TopoDS_Shape projectedWire = + GeometryObject::projectSimpleShape(lineWire, baseDvp->getProjectionCS()); + TopExp_Explorer expEdges(projectedWire, TopAbs_EDGE); + for (; expEdges.More(); expEdges.Next()) { + BaseGeomPtr edge = BaseGeom::baseFactory(TopoDS::Edge(expEdges.Current())); + result.push_back(edge); + } + } + return result; +} + +//get the end points of the section wire +std::pair DrawComplexSection::sectionLineEnds() +{ + // Base::Console().Message("DCS::sectionLineEnds()\n"); + std::pair result; + TopoDS_Wire lineWire = makeSectionLineWire(); + if (lineWire.IsNull()) { + return result; + } + + TopoDS_Vertex vFirst, vLast; + TopExp::Vertices(lineWire, vFirst, vLast); + gp_Pnt gpFirst = BRep_Tool::Pnt(vFirst); + gp_Pnt gpLast = BRep_Tool::Pnt(vLast); + Base::Vector3d first = Base::Vector3d(gpFirst.X(), gpFirst.Y(), gpFirst.Z()); + Base::Vector3d last = Base::Vector3d(gpLast.X(), gpLast.Y(), gpLast.Z()); + + DrawViewPart *baseDvp = dynamic_cast(BaseView.getValue()); + if (baseDvp) { + first = baseDvp->projectPoint(first); + last = baseDvp->projectPoint(last); + } + result.first = first; + result.second = last; + return result; +} + +//get directions of the section line arrows +std::pair DrawComplexSection::sectionArrowDirs() +{ + // Base::Console().Message("DCS::sectionArrowDirs()\n"); + std::pair result; + App::DocumentObject *toolObj = CuttingToolWireObject.getValue(); + TopoDS_Wire profileWire = makeProfileWire(toolObj); + if (profileWire.IsNull()) { + return result; + } + + TopoDS_Vertex tvFirst, tvLast; + TopExp::Vertices(profileWire, tvFirst, tvLast); + gp_Pnt gpFirst = BRep_Tool::Pnt(tvFirst); + gp_Pnt gpLast = BRep_Tool::Pnt(tvLast); + gp_Vec gProfileVector = gp_Vec(gpLast.XYZ()) - gp_Vec(gpFirst.XYZ()); + gp_Vec gSectionNormal = gp_Vec(DU::togp_Dir(SectionNormal.getValue())); + gp_Vec gExtrudeVector = (gSectionNormal.Crossed(gProfileVector)).Normalized(); + Base::Vector3d vClosestBasis = DrawUtil::closestBasis(gp_Dir(gExtrudeVector), getSectionCS()); + gp_Dir gExtrudeDir = gp_Dir(vClosestBasis.x, vClosestBasis.y, vClosestBasis.z); + + TopoDS_Shape toolFaceShape = extrudeWireToFace(profileWire, gExtrudeDir, 100.0); + if (toolFaceShape.IsNull()) { + return result; + } + + std::vector faces; + TopExp_Explorer expl(toolFaceShape, TopAbs_FACE); + for (; expl.More(); expl.Next()) { faces.push_back(TopoDS::Face(expl.Current())); } + + gp_Vec gDir0 = gp_Vec(getFaceNormal(faces.front())); + gp_Vec gDir1 = gp_Vec(getFaceNormal(faces.back())); + if (gDir0.Dot(gSectionNormal) > 0.0) { + //face normal is pointing generally in section normal direction, so we + //want the reverse for a view direction + gDir0.Reverse(); + } + if (gDir1.Dot(gSectionNormal) > 0.0) { + //face normal is pointing generally in section normal direction, so we + //want the reverse for a view direction + gDir1.Reverse(); + } + + //TODO: similar code elsewhere. Opportunity for reuse. + Base::Vector3d vDir0 = DU::toVector3d(gDir0); + Base::Vector3d vDir1 = DU::toVector3d(gDir1); + vDir0.Normalize(); + vDir1.Normalize(); + DrawViewPart *baseDvp = dynamic_cast(BaseView.getValue()); + if (baseDvp) { + vDir0 = baseDvp->projectPoint(vDir0, true); + vDir1 = baseDvp->projectPoint(vDir1, true); + } + + result.first = vDir0; + result.second = vDir1; + return result; +} + +//make a wire suitable for projection on a base view +TopoDS_Wire DrawComplexSection::makeSectionLineWire() +{ + TopoDS_Wire lineWire; + App::DocumentObject *toolObj = CuttingToolWireObject.getValue(); + DrawViewPart *baseDvp = dynamic_cast(BaseView.getValue()); + if (baseDvp) { + Base::Vector3d centroid = baseDvp->getCurrentCentroid(); + TopoDS_Shape sTrans = + TechDraw::moveShape(Part::Feature::getShape(toolObj), centroid * -1.0); + TopoDS_Shape sScaled = TechDraw::scaleShape(sTrans, baseDvp->getScale()); + //we don't mirror the scaled shape here as it will be mirrored by the projection + + if (sScaled.ShapeType() == TopAbs_WIRE) { + lineWire = makeNoseToTailWire(TopoDS::Wire(sScaled)); + } + else if (sScaled.ShapeType() == TopAbs_EDGE) { + TopoDS_Edge edge = TopoDS::Edge(sScaled); + lineWire = BRepBuilderAPI_MakeWire(edge).Wire(); + } + else { + //probably can't happen as cut profile has been checked before this + Base::Console().Message("DCS::makeSectionLineGeometry - profile is type: %d\n", + sScaled.ShapeType()); + return TopoDS_Wire(); + } + } + return lineWire; +} + +//find the points where the section line changes direction, and the direction +//of the profile before and after the point +ChangePointVector DrawComplexSection::getChangePointsFromSectionLine() +{ + // Base::Console().Message("DCS::getChangePointsFromSectionLine()\n"); + ChangePointVector result; + std::vector allPoints; + DrawViewPart *baseDvp = dynamic_cast(BaseView.getValue()); + if (baseDvp) { + TopoDS_Wire lineWire = makeSectionLineWire(); + TopoDS_Shape projectedWire = + GeometryObject::projectSimpleShape(lineWire, baseDvp->getProjectionCS()); + if (projectedWire.IsNull()) { + return result; + } + //get UNIQUE points along the projected profile + TopExp_Explorer expVertex(projectedWire, TopAbs_VERTEX); + gp_Pnt previousPoint(Precision::Infinite(), Precision::Infinite(), Precision::Infinite()); + for (; expVertex.More(); expVertex.Next()) { + TopoDS_Vertex vert = TopoDS::Vertex(expVertex.Current()); + gp_Pnt gPoint = BRep_Tool::Pnt(vert); + if (gPoint.IsEqual(previousPoint, 2.0 * EWTOLERANCE)) { + continue; + } + allPoints.push_back(gPoint); + previousPoint = gPoint; + } + + //make the intermediate marks + for (size_t iPoint = 1; iPoint < allPoints.size() - 1; iPoint++) { + gp_Pnt location = allPoints.at(iPoint); + gp_Dir preDir = gp_Dir(allPoints.at(iPoint - 1).XYZ() - allPoints.at(iPoint).XYZ()); + gp_Dir postDir = gp_Dir(allPoints.at(iPoint + 1).XYZ() - allPoints.at(iPoint).XYZ()); + ChangePoint point(location, preDir, postDir); + result.push_back(point); + } + + //make start and end marks + gp_Pnt location0 = allPoints.at(0); + gp_Pnt location1 = allPoints.at(1); + gp_Dir postDir = gp_Dir(location1.XYZ() - location0.XYZ()); + gp_Dir preDir = postDir.Reversed(); + ChangePoint startPoint(location0, preDir, postDir); + result.push_back(startPoint); + location0 = allPoints.at(allPoints.size() - 1); + location1 = allPoints.at(allPoints.size() - 2); + preDir = gp_Dir(location0.XYZ() - location1.XYZ()); + postDir = preDir.Reversed(); + ChangePoint endPoint(location0, preDir, postDir); + result.push_back(endPoint); + } + return result; +} + +gp_Ax2 DrawComplexSection::getCSFromBase(const std::string sectionName) const +{ + // Base::Console().Message("DCS::getCSFromBase()\n"); + App::DocumentObject *base = BaseView.getValue(); + if (!base + || !base->getTypeId().isDerivedFrom( + TechDraw::DrawViewPart::getClassTypeId())) {//is second clause necessary? + //if this DCS does not have a baseView, we must use the existing SectionCS + return getSectionCS(); + } + return DrawViewSection::getCSFromBase(sectionName); +} + +//get the "effective" (flattened) section plane for Aligned and +//the regular sectionPlane for Offset. +gp_Pln DrawComplexSection::getSectionPlane() const +{ + if (ProjectionStrategy.getValue() == 0) { + //Offset. Use regular section behaviour + return DrawViewSection::getSectionPlane(); + } + + //"Aligned" projection (Aligned Section) + //this is the same as DVS::getSectionPlane except that the plane origin is not the SectionOrigin + Base::Vector3d vSectionNormal = SectionNormal.getValue(); + gp_Dir gSectionNormal(vSectionNormal.x, vSectionNormal.y, vSectionNormal.z); + gp_Pnt gOrigin(0.0, 0.0, 0.0); + gp_Ax3 gPlaneCS(gOrigin, gSectionNormal); + + return gp_Pln(gPlaneCS); +} + +bool DrawComplexSection::isBaseValid() const +{ + App::DocumentObject *base = BaseView.getValue(); + if (!base) { + //complex section is not based on an existing DVP + return true; + } + if (!base->getTypeId().isDerivedFrom(TechDraw::DrawViewPart::getClassTypeId())) { + //this is probably an error somewhere. the valid options are base = a DVP, + //or no base + return false; + } + //have a base and it is a DVP + return true; +} + +//if the profile is not nicely positioned within the vertical span of the shape, we might not overlap +//the shape after extrusion. As long as the profile is within the extent of the shape in the +//extrude direction we should be ok. the extrude direction has to be perpendicular to the profile and SectionNormal +bool DrawComplexSection::validateProfilePosition(TopoDS_Wire profileWire, gp_Ax2 sectionCS, + gp_Dir &gClosestBasis) const +{ + // Base::Console().Message("DCS::validateProfilePosition()\n"); + TopoDS_Vertex tvFirst, tvLast; + TopExp::Vertices(profileWire, tvFirst, tvLast); + gp_Pnt gpFirst = BRep_Tool::Pnt(tvFirst);//a position point for the wire + gp_Pnt gpLast = BRep_Tool::Pnt(tvLast); + gp_Vec gProfileVector = gp_Vec(gpLast.XYZ()) - gp_Vec(gpFirst.XYZ()); + + //since bounding boxes are aligned with the cardinal directions, we need to find + //the appropriate direction to use when validating the profile position + gp_Vec gSectionVector = getSectionCS().Direction().Reversed(); + gp_Vec gExtrudeVector = gSectionVector.Crossed(gProfileVector); + Base::Vector3d vClosestBasis = DrawUtil::closestBasis(gp_Dir(gExtrudeVector), sectionCS); + gClosestBasis = gp_Dir(vClosestBasis.x, vClosestBasis.y, vClosestBasis.z); + + Bnd_Box shapeBox; + shapeBox.SetGap(0.0); + BRepBndLib::AddOptimal(m_saveShape, shapeBox); + double xMin = 0, xMax = 0, yMin = 0, yMax = 0, zMin = 0, zMax = 0; + shapeBox.Get(xMin, yMin, zMin, xMax, yMax, zMax); + double spanLow = xMin; + double spanHigh = xMax; + double spanCheck = gpFirst.X(); + if (gClosestBasis.IsParallel(sectionCS.YDirection(), Precision::Angular())) { + spanLow = yMin; + spanHigh = yMax; + spanCheck = gpFirst.Y(); + } + else if (gClosestBasis.IsParallel(sectionCS.Direction(), Precision::Angular())) { + spanLow = zMin; + spanHigh = zMax; + spanCheck = gpFirst.Z(); + } + + if (spanLow > spanCheck || spanHigh < spanCheck) { + //profile is not within span of shape + return false; + } + //profile is within span of shape + return true; +} + +bool DrawComplexSection::showSegment(gp_Dir segmentNormal) const +{ + if (ProjectionStrategy.getValue() < 2) { + //Offset or Aligned are always true + return true; + } + + Base::Vector3d vSectionNormal = SectionNormal.getValue(); + gp_Dir gSectionNormal(vSectionNormal.x, vSectionNormal.y, vSectionNormal.z); + if (DU::fpCompare(fabs(gSectionNormal.Dot(segmentNormal)), 0.0)) { + //segment normal is perpendicular to section normal, so segment is parallel to section normal, + //and for ProjectionStrategy "NoParallel", we don't display these segments. + return false; + } + return true; +} + +// general purpose geometry methods + +//make a "face" (not necessarily a TopoDS_Face since the extrusion of a wire is a shell) +//from a single open wire by displacing the wire extruding it +TopoDS_Shape DrawComplexSection::extrudeWireToFace(TopoDS_Wire &wire, gp_Dir extrudeDir, + double extrudeDist) +{ + gp_Trsf mov; + mov.SetTranslation(gp_Vec(extrudeDir) * (-extrudeDist)); + TopLoc_Location loc(mov); + wire.Move(loc); + + BRepPrimAPI_MakePrism mkPrism(wire, gp_Vec(extrudeDir) * 2.0 * extrudeDist); + + return mkPrism.Shape(); +} + +//returns the normal of the face to be extruded into a cutting tool +//the face is expected to be planar +gp_Dir DrawComplexSection::getFaceNormal(TopoDS_Face &face) +{ + BRepAdaptor_Surface adapt(face); + double uParmFirst = adapt.FirstUParameter(); + double uParmLast = adapt.LastUParameter(); + double vParmFirst = adapt.FirstVParameter(); + double vParmLast = adapt.LastVParameter(); + double uMid = (uParmFirst + uParmLast) / 2.0; + double vMid = (vParmFirst + vParmLast) / 2.0; + + BRepLProp_SLProps prop(adapt, uMid, vMid, 1, 0.01); + gp_Dir normalDir(0.0, 0.0, 1.0);//default + if (prop.IsNormalDefined()) { + normalDir = prop.Normal(); + } + return normalDir; +} + +bool DrawComplexSection::boxesIntersect(TopoDS_Face &face, TopoDS_Shape &shape) +{ + Bnd_Box box0, box1; + BRepBndLib::Add(face, box0); + box0.SetGap(0.1);//generous + BRepBndLib::Add(shape, box1); + box1.SetGap(0.1); + if (box0.IsOut(box1)) { + return false;//boxes don't intersect + } + return true; +} + +TopoDS_Shape DrawComplexSection::shapeShapeIntersect(const TopoDS_Shape &shape0, + const TopoDS_Shape &shape1) +{ + BRepAlgoAPI_Common anOp; + anOp.SetFuzzyValue(EWTOLERANCE); + TopTools_ListOfShape anArg1, anArg2; + anArg1.Append(shape0); + anArg2.Append(shape1); + anOp.SetArguments(anArg1); + anOp.SetTools(anArg2); + anOp.Build(); + return anOp.Shape();//always a compound +} + +//find all the intersecting regions of face and shape +std::vector DrawComplexSection::faceShapeIntersect(const TopoDS_Face &face, + const TopoDS_Shape &shape) +{ + // Base::Console().Message("DCS::faceShapeIntersect()\n"); + TopoDS_Shape intersect = shapeShapeIntersect(face, shape); + if (intersect.IsNull()) { + return std::vector(); + } + std::vector intersectFaceList; + TopExp_Explorer expFaces(intersect, TopAbs_FACE); + for (int i = 1; expFaces.More(); expFaces.Next(), i++) { + intersectFaceList.push_back(TopoDS::Face(expFaces.Current())); + } + return intersectFaceList; +} + +//ensure that the edges in the output wire are in nose to tail order +TopoDS_Wire DrawComplexSection::makeNoseToTailWire(TopoDS_Wire inWire) +{ + if (inWire.IsNull()) { + return inWire; + } + + std::list inList; + TopExp_Explorer expEdges(inWire, TopAbs_EDGE); + for (; expEdges.More(); expEdges.Next()) { + TopoDS_Edge edge = TopoDS::Edge(expEdges.Current()); + inList.push_back(edge); + } + + std::list sortedList; + if (inList.empty() || inList.size() == 1) { + return inWire; + } + else { + sortedList = DrawUtil::sort_Edges(EWTOLERANCE, inList); + } + + BRepBuilderAPI_MakeWire mkWire; + for (auto &edge : sortedList) { mkWire.Add(edge); } + return mkWire.Wire(); +} + +//static +bool DrawComplexSection::isProfileObject(App::DocumentObject *obj) +{ + //if the object's shape is a wire or an edge, then it can be a profile object + TopoDS_Shape shape = Part::Feature::getShape(obj); + if (shape.IsNull()) { + return false; + } + if (shape.ShapeType() == TopAbs_WIRE || shape.ShapeType() == TopAbs_EDGE) { + return true; + } + //don't know what this is, but it isn't suitable as a profile + return false; +} + +bool DrawComplexSection::isMultiSegmentProfile(App::DocumentObject *obj) +{ + //if the object's shape is a wire or an edge, then it can be a profile object + TopoDS_Shape shape = Part::Feature::getShape(obj); + if (shape.IsNull()) { + return false; + } + if (shape.ShapeType() == TopAbs_EDGE) { + //only have 1 edge, can't be multisegment; + return false; + } + if (shape.ShapeType() == TopAbs_WIRE) { + std::vector edgesInWire; + TopExp_Explorer expEdges(shape, TopAbs_EDGE); + for (; expEdges.More(); expEdges.Next()) { + TopoDS_Edge edge = TopoDS::Edge(expEdges.Current()); + BRepAdaptor_Curve adapt(edge); + if (adapt.GetType() == GeomAbs_Line) { + edgesInWire.push_back(edge); + } + } + if (edgesInWire.size() > 1) { + return true; + } + } + return false; +} + +//check if the profile has curves in it +bool DrawComplexSection::isLinearProfile(App::DocumentObject *obj) +{ + TopoDS_Shape shape = Part::Feature::getShape(obj); + if (shape.IsNull()) { + return false; + } + if (shape.ShapeType() == TopAbs_EDGE) { + //only have 1 edge + TopoDS_Edge edge = TopoDS::Edge(shape); + BRepAdaptor_Curve adapt(edge); + if (adapt.GetType() == GeomAbs_Line) { + return true; + } + return false; + } + + if (shape.ShapeType() == TopAbs_WIRE) { + std::vector edgesInWire; + TopExp_Explorer expEdges(shape, TopAbs_EDGE); + for (; expEdges.More(); expEdges.Next()) { + TopoDS_Edge edge = TopoDS::Edge(expEdges.Current()); + BRepAdaptor_Curve adapt(edge); + if (adapt.GetType() != GeomAbs_Line) { + return false; + } + } + //all the edges in the wire are lines + return true; + } + + //this shouldn't happen + return false; +} + +// Python Drawing feature --------------------------------------------------------- + +namespace App +{ +/// @cond DOXERR +PROPERTY_SOURCE_TEMPLATE(TechDraw::DrawComplexSectionPython, TechDraw::DrawComplexSection) +template<> const char *TechDraw::DrawComplexSectionPython::getViewProviderName() const +{ + return "TechDrawGui::ViewProviderDrawingView"; +} +/// @endcond + +// explicit template instantiation +template class TechDrawExport FeaturePythonT; +}// namespace App diff --git a/src/Mod/TechDraw/App/DrawComplexSection.h b/src/Mod/TechDraw/App/DrawComplexSection.h new file mode 100644 index 0000000000..f12b18ec8c --- /dev/null +++ b/src/Mod/TechDraw/App/DrawComplexSection.h @@ -0,0 +1,123 @@ +/*************************************************************************** + * Copyright (c) 2022 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 DrawComplexSection_h_ +#define DrawComplexSection_h_ + +#include + +#include +#include +#include +#include + +#include "DrawViewSection.h" + +namespace TechDraw +{ + +//changes in direction of complex section line +class ChangePoint +{ +public: + ChangePoint(QPointF location, QPointF preDirection, QPointF postDirection); + ChangePoint(gp_Pnt location, gp_Dir preDirection, gp_Dir postDirection); + ~ChangePoint() = default; + + QPointF getLocation() const { return m_location; } + QPointF getPreDirection() const { return m_preDirection; } + QPointF getPostDirection() const { return m_postDirection; } + void scale(double scaleFactor); + +private: + QPointF m_location; + QPointF m_preDirection; + QPointF m_postDirection; +}; + +using ChangePointVector = std::vector; + +class TechDrawExport DrawComplexSection: public DrawViewSection +{ + PROPERTY_HEADER_WITH_OVERRIDE(Part::DrawComplexSection); + +public: + DrawComplexSection(); + ~DrawComplexSection() = default; + + App::PropertyLink CuttingToolWireObject; + App::PropertyEnumeration ProjectionStrategy;//Offset or Aligned + + TopoDS_Shape getShapeToCut() override; + TopoDS_Shape makeCuttingTool(double dMax) override; + gp_Ax2 getCSFromBase(const std::string sectionName) const override; + bool isBaseValid() const override; + TopoDS_Compound findSectionPlaneIntersections(const TopoDS_Shape &cutShape) override; + TopoDS_Shape prepareShape(const TopoDS_Shape &cutShape, double shapeSize) override; + TopoDS_Shape getShapeToPrepare() const override; + void makeSectionCut(TopoDS_Shape &baseShape) override; + TopoDS_Shape getShapeToIntersect() override; + gp_Pln getSectionPlane() const override; + TopoDS_Compound alignSectionFaces(TopoDS_Shape faceIntersections) override; + std::pair sectionLineEnds() override; + + bool boxesIntersect(TopoDS_Face &face, TopoDS_Shape &shape); + TopoDS_Shape shapeShapeIntersect(const TopoDS_Shape &shape0, const TopoDS_Shape &shape1); + std::vector faceShapeIntersect(const TopoDS_Face &face, const TopoDS_Shape &shape); + TopoDS_Shape extrudeWireToFace(TopoDS_Wire &wire, gp_Dir extrudeDir, double extrudeDist); + TopoDS_Shape makeAlignedPieces(const TopoDS_Shape &rawShape, const TopoDS_Shape &toolFaceShape, + double extrudeDistance); + TopoDS_Shape distributeAlignedPieces(std::vector pieces); + TopoDS_Compound singleToolIntersections(const TopoDS_Shape &cutShape); + TopoDS_Compound piecewiseToolIntersections(const TopoDS_Shape &cutShape); + + BaseGeomPtrVector makeSectionLineGeometry(); + std::pair sectionArrowDirs(); + TopoDS_Wire makeSectionLineWire(); + + TopoDS_Wire makeProfileWire(App::DocumentObject *toolObj); + TopoDS_Wire makeNoseToTailWire(TopoDS_Wire inWire); + gp_Dir projectProfileWire(TopoDS_Wire profileWire, gp_Ax3 paperCS); + ChangePointVector getChangePointsFromSectionLine(); + + bool validateProfilePosition(TopoDS_Wire profileWire, gp_Ax2 sectionCS, + gp_Dir &gClosestBasis) const; + bool showSegment(gp_Dir segmentNormal) const; + + static bool isProfileObject(App::DocumentObject *obj); + static bool isMultiSegmentProfile(App::DocumentObject *obj); + static bool isLinearProfile(App::DocumentObject *obj); + +private: + gp_Dir getFaceNormal(TopoDS_Face &face); + + TopoDS_Shape m_toolFaceShape; + TopoDS_Wire m_profileWire; + + static const char *ProjectionStrategyEnums[]; +}; + +using DrawComplexSectionPython = App::FeaturePythonT; + +}//namespace TechDraw + +#endif diff --git a/src/Mod/TechDraw/App/DrawProjectSplit.cpp b/src/Mod/TechDraw/App/DrawProjectSplit.cpp index 90212cd882..72aaa7e602 100644 --- a/src/Mod/TechDraw/App/DrawProjectSplit.cpp +++ b/src/Mod/TechDraw/App/DrawProjectSplit.cpp @@ -512,6 +512,12 @@ std::vector DrawProjectSplit::scrubEdges(const std::vector DrawProjectSplit::scrubEdges(std::vector& origEdges, std::vector &closedEdges) { + if (origEdges.empty()) { + //how did this happen? if Scale is zero, all the edges will be zero length, + //but Scale property has constraint, so this shouldn't happen! +// Base::Console().Message("DPS::scrubEdges(2) - origEdges is empty\n"); //debug + return std::vector(); + } //HLR usually delivers overlapping edges. We need to refine edge overlaps //into non-overlapping pieces std::vector noOverlaps; diff --git a/src/Mod/TechDraw/App/DrawUtil.cpp b/src/Mod/TechDraw/App/DrawUtil.cpp index ee2a925442..1d011c010b 100644 --- a/src/Mod/TechDraw/App/DrawUtil.cpp +++ b/src/Mod/TechDraw/App/DrawUtil.cpp @@ -23,36 +23,38 @@ #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 -# include -# include -# include -# include -# include -# include -# include -# include -# include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #endif #include @@ -60,6 +62,7 @@ #include #include #include +#include #include #include "DrawUtil.h" @@ -348,19 +351,19 @@ Base::Vector3d DrawUtil::vertex2Vector(const TopoDS_Vertex& v) return Base::Vector3d(gp.X(), gp.Y(), gp.Z()); } +//TODO: make formatVector using toVector3d std::string DrawUtil::formatVector(const Base::Vector3d& v) { std::stringstream builder; - builder << std::fixed << std::setprecision(3) ; + builder << std::fixed << std::setprecision(Base::UnitsApi::getDecimals()) ; builder << " (" << v.x << ", " << v.y << ", " << v.z << ") "; -// builder << " (" << setw(6) << v.x << ", " << setw(6) << v.y << ", " << setw(6) << v.z << ") "; return builder.str(); } std::string DrawUtil::formatVector(const gp_Dir& v) { std::stringstream builder; - builder << std::fixed << std::setprecision(3) ; + builder << std::fixed << std::setprecision(Base::UnitsApi::getDecimals()) ; builder << " (" << v.X() << ", " << v.Y() << ", " << v.Z() << ") "; return builder.str(); } @@ -368,14 +371,14 @@ std::string DrawUtil::formatVector(const gp_Dir& v) std::string DrawUtil::formatVector(const gp_Dir2d& v) { std::stringstream builder; - builder << std::fixed << std::setprecision(3) ; + builder << std::fixed << std::setprecision(Base::UnitsApi::getDecimals()) ; builder << " (" << v.X() << ", " << v.Y() << ") "; return builder.str(); } std::string DrawUtil::formatVector(const gp_Vec& v) { std::stringstream builder; - builder << std::fixed << std::setprecision(3) ; + builder << std::fixed << std::setprecision(Base::UnitsApi::getDecimals()) ; builder << " (" << v.X() << ", " << v.Y() << ", " << v.Z() << ") "; return builder.str(); } @@ -383,7 +386,7 @@ std::string DrawUtil::formatVector(const gp_Vec& v) std::string DrawUtil::formatVector(const gp_Pnt& v) { std::stringstream builder; - builder << std::fixed << std::setprecision(3) ; + builder << std::fixed << std::setprecision(Base::UnitsApi::getDecimals()) ; builder << " (" << v.X() << ", " << v.Y() << ", " << v.Z() << ") "; return builder.str(); } @@ -391,7 +394,7 @@ std::string DrawUtil::formatVector(const gp_Pnt& v) std::string DrawUtil::formatVector(const gp_Pnt2d& v) { std::stringstream builder; - builder << std::fixed << std::setprecision(3) ; + builder << std::fixed << std::setprecision(Base::UnitsApi::getDecimals()) ; builder << " (" << v.X() << ", " << v.Y() << ") "; return builder.str(); } @@ -399,7 +402,7 @@ std::string DrawUtil::formatVector(const gp_Pnt2d& v) std::string DrawUtil::formatVector(const QPointF& v) { std::stringstream builder; - builder << std::fixed << std::setprecision(3) ; + builder << std::fixed << std::setprecision(Base::UnitsApi::getDecimals()) ; builder << " (" << v.x() << ", " << v.y() << ") "; return builder.str(); } @@ -511,6 +514,11 @@ Base::Vector3d DrawUtil::vecRotate(Base::Vector3d vec, return Base::Vector3d(xForm * (vec)); } +gp_Vec DrawUtil::closestBasis(gp_Vec inVec) +{ + return gp_Vec(togp_Dir(closestBasis(toVector3d(inVec)))); +} + Base::Vector3d DrawUtil::closestBasis(Base::Vector3d v) { Base::Vector3d result(0.0, -1, 0); @@ -522,11 +530,16 @@ Base::Vector3d DrawUtil::closestBasis(Base::Vector3d v) Base::Vector3d stdZr(0.0, 0.0, -1.0); //first check if already a basis - if (checkParallel(v, stdZ) || - checkParallel(v, stdY) || - checkParallel(v, stdX)) { + 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; @@ -537,23 +550,139 @@ Base::Vector3d DrawUtil::closestBasis(Base::Vector3d v) angleYr = stdYr.GetAngle(v); angleZr = stdZr.GetAngle(v); - angleMin = angleX; - if (angleY < angleMin) { - return stdY; + angleMin = std::min({angleX, angleY, angleZ, angleXr, angleYr, angleZr}); + if (angleX == angleMin) { + return Base::Vector3d(1.0, 0.0, 0.0); } - if (angleZ < angleMin) { - return stdZ; + + if (angleY == angleMin) { + return Base::Vector3d(0.0, 1.0, 0.0); } - if (angleXr < angleMin) { - return stdXr; + + if (angleZ == angleMin) { + return Base::Vector3d(0.0, 0.0, 1.0); } - if (angleYr < angleMin) { - return stdYr; + + if (angleXr == angleMin) { + return Base::Vector3d(1.0, 0.0, 0.0); } - if (angleZr < angleMin) { - return stdZr; + + if (angleYr == angleMin) { + return Base::Vector3d(0.0, 1.0, 0.0); } - return stdX; + + 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); + return closestBasis(gDir, coordSys); +} + +Base::Vector3d DrawUtil::closestBasis(gp_Dir gDir, gp_Ax2 coordSys) +{ + gp_Dir xCS = coordSys.XDirection(); + gp_Dir yCS = coordSys.YDirection(); + gp_Dir zCS = coordSys.Direction(); + + //first check if already a basis + if (gDir.Dot(xCS) == 1.0 || + gDir.Dot(yCS) == 1.0 || + gDir.Dot(zCS) == 1.0 ) { + //gDir is parallel with a basis + return Base::Vector3d( gDir.X(), gDir.Y(), gDir.Z()) ; + } + + if (gDir.Dot(xCS.Reversed()) == 1.0 || + gDir.Dot(yCS.Reversed()) == 1.0 || + gDir.Dot(zCS.Reversed()) == 1.0 ) { + //gDir is anti-parallel with a basis + return Base::Vector3d( -gDir.X(), -gDir.Y(), -gDir.Z()); + } + + //not a basis. find smallest angle with a basis. + double angleX, angleY, angleZ, angleXr, angleYr, angleZr, angleMin; + angleX = gDir.Angle(xCS); + angleY = gDir.Angle(yCS); + angleZ = gDir.Angle(zCS); + angleXr = gDir.Angle(xCS.Reversed()); + angleYr = gDir.Angle(yCS.Reversed()); + angleZr = gDir.Angle(zCS.Reversed()); + + angleMin = std::min({angleX, angleY, angleZ, angleXr, angleYr, angleZr}); + if (angleX == angleMin) { + return Base::Vector3d(xCS.X(), xCS.Y(), xCS.Z()); + } + + if (angleY == angleMin) { + return Base::Vector3d(yCS.X(), yCS.Y(), yCS.Z()); + } + + if (angleZ == angleMin) { + return Base::Vector3d(zCS.X(), zCS.Y(), zCS.Z()) ; + } + + if (angleXr == angleMin) { + return Base::Vector3d(-xCS.X(), -xCS.Y(), -xCS.Z()); + } + + if (angleYr == angleMin) { + return Base::Vector3d(-yCS.X(), -yCS.Y(), -yCS.Z()); + } + + if (angleZr == angleMin) { + return Base::Vector3d(-zCS.X(), -zCS.Y(), -zCS.Z()); + } + + //should not get to here + return Base::Vector3d(xCS.X(), xCS.Y(), xCS.Z()); +} + +double DrawUtil::getWidthInDirection(gp_Dir direction, TopoDS_Shape& shape) +{ + 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); + Base::Vector3d vClosestBasis = closestBasis(toVector3d(direction)); + + Bnd_Box shapeBox; + shapeBox.SetGap(0.0); + BRepBndLib::AddOptimal(shape, shapeBox); + double xMin = 0, xMax = 0, yMin = 0, yMax = 0, zMin = 0, zMax = 0; + if (shapeBox.IsVoid()) { + //this really shouldn't happen here as null shapes should have been caught + //long before this + Base::Console().Error("DU::getWidthInDirection - shapeBox is void\n"); + return 0.0; + } + shapeBox.Get(xMin, yMin, zMin, xMax, yMax, zMax); + + if (vClosestBasis.IsEqual(stdX, EWTOLERANCE) || + vClosestBasis.IsEqual(stdXr, EWTOLERANCE) ) { + return xMax - xMin; + } + + if (vClosestBasis.IsEqual(stdY, EWTOLERANCE) || + vClosestBasis.IsEqual(stdYr, EWTOLERANCE) ) { + return yMax - yMin; + } + + if (vClosestBasis.IsEqual(stdZ, EWTOLERANCE) || + vClosestBasis.IsEqual(stdZr, EWTOLERANCE) ) { + return zMax - zMin; + } + + return 0.0; } //based on Function provided by Joe Dowsett, 2014 @@ -873,6 +1002,102 @@ void DrawUtil::encodeXmlSpecialChars(std::string& inoutText) inoutText.swap(buffer); } +//Sort edges into nose to tail order. From Part/App/AppPartPy.cpp. gives back a sequence +//of nose to tail edges and a shrunken input sequence of edges (the unconnected left overs) +//struct EdgePoints { +// gp_Pnt v1, v2; +// std::list::iterator it; +// TopoDS_Edge edge; +//}; +std::list DrawUtil::sort_Edges(double tol3d, std::list& edges) +{ + tol3d = tol3d * tol3d; + std::list edge_points; + TopExp_Explorer xp; + for (std::list::iterator it = edges.begin(); it != edges.end(); ++it) { + EdgePoints ep; + xp.Init(*it,TopAbs_VERTEX); + ep.v1 = BRep_Tool::Pnt(TopoDS::Vertex(xp.Current())); + xp.Next(); + ep.v2 = BRep_Tool::Pnt(TopoDS::Vertex(xp.Current())); + ep.it = it; + ep.edge = *it; + edge_points.push_back(ep); + } + + if (edge_points.empty()) + return std::list(); + + std::list sorted; + gp_Pnt gpChainFirst, gpChainLast; + gpChainFirst = edge_points.front().v1; + gpChainLast = edge_points.front().v2; + + sorted.push_back(edge_points.front().edge); + edges.erase(edge_points.front().it); + edge_points.erase(edge_points.begin()); + + while (!edge_points.empty()) { + // search for adjacent edge + std::list::iterator itEdgePoint; + for (itEdgePoint = edge_points.begin(); itEdgePoint != edge_points.end(); ++itEdgePoint) { + if (itEdgePoint->v1.SquareDistance(gpChainLast) <= tol3d) { + //found a connection from end of chain to start of edge + gpChainLast = itEdgePoint->v2; + sorted.push_back(itEdgePoint->edge); + edges.erase(itEdgePoint->it); + edge_points.erase(itEdgePoint); + itEdgePoint = edge_points.begin(); + break; + } + else if (itEdgePoint->v2.SquareDistance(gpChainFirst) <= tol3d) { + //found a connection from start of chain to end of edge + gpChainFirst = itEdgePoint->v1; + sorted.push_front(itEdgePoint->edge); + edges.erase(itEdgePoint->it); + edge_points.erase(itEdgePoint); + itEdgePoint = edge_points.begin(); + break; + } + else if (itEdgePoint->v2.SquareDistance(gpChainLast) <= tol3d) { + //found a connection from end of chain to end of edge + gpChainLast = itEdgePoint->v1; + Standard_Real firstParam, lastParam; + const Handle(Geom_Curve) & curve = BRep_Tool::Curve(itEdgePoint->edge, firstParam, lastParam); + firstParam = curve->ReversedParameter(firstParam); + lastParam = curve->ReversedParameter(lastParam); + TopoDS_Edge edgeReversed = BRepBuilderAPI_MakeEdge(curve->Reversed(), firstParam, lastParam); + sorted.push_back(edgeReversed); + edges.erase(itEdgePoint->it); + edge_points.erase(itEdgePoint); + itEdgePoint = edge_points.begin(); + break; + } + else if (itEdgePoint->v1.SquareDistance(gpChainFirst) <= tol3d) { + //found a connection from start of chain to start of edge + gpChainFirst = itEdgePoint->v2; + Standard_Real firstParam, lastParam; + const Handle(Geom_Curve) & curve = BRep_Tool::Curve(itEdgePoint->edge, firstParam, lastParam); + firstParam = curve->ReversedParameter(firstParam); + lastParam = curve->ReversedParameter(lastParam); + TopoDS_Edge edgeReversed = BRepBuilderAPI_MakeEdge(curve->Reversed(), firstParam, lastParam); + sorted.push_front(edgeReversed); + edges.erase(itEdgePoint->it); + edge_points.erase(itEdgePoint); + itEdgePoint = edge_points.begin(); + break; + } + } + + if ((itEdgePoint == edge_points.end()) || (gpChainLast.SquareDistance(gpChainFirst) <= tol3d)) { + // no adjacent edge found or polyline is closed + return sorted; + } + } + + return sorted; +} + // Supplementary mathematical functions // ==================================== @@ -1367,7 +1592,7 @@ void DrawUtil::dumpCS3(const char* text, gp_Dir baseX = CS.XDirection(); gp_Dir baseY = CS.YDirection(); gp_Pnt baseOrg = CS.Location(); - Base::Console().Message("DU::dumpCSF - %s Loc: %s Axis: %s X: %s Y: %s\n", text, + Base::Console().Message("DU::dumpCS3 - %s Loc: %s Axis: %s X: %s Y: %s\n", text, DrawUtil::formatVector(baseOrg).c_str(), DrawUtil::formatVector(baseAxis).c_str(), DrawUtil::formatVector(baseX).c_str(), diff --git a/src/Mod/TechDraw/App/DrawUtil.h b/src/Mod/TechDraw/App/DrawUtil.h index 772bab8c5f..3e4cc5b815 100644 --- a/src/Mod/TechDraw/App/DrawUtil.h +++ b/src/Mod/TechDraw/App/DrawUtil.h @@ -70,6 +70,13 @@ namespace TechDraw { +//used by sort_Edges +struct EdgePoints { + gp_Pnt v1, v2; + std::list::iterator it; + TopoDS_Edge edge; +}; + /// Convenient utility functions for TechDraw Module class TechDrawExport DrawUtil { public: @@ -122,10 +129,15 @@ class TechDrawExport DrawUtil { double angle, Base::Vector3d axis, Base::Vector3d org = Base::Vector3d(0.0, 0.0, 0.0)); + static Base::Vector3d closestBasis(Base::Vector3d v); + 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 double getWidthInDirection(gp_Dir direction, TopoDS_Shape& shape); + static double getDefaultLineWeight(std::string s); -/* static Base::Vector3d vector23(const Base::Vector3d& v2) { return Base::Vector3d(v2.x, v2.y, 0.0); }*/ -/* static Base::Vector3d vector32(const Base::Vector3d& v3) { return Base::Vector3d(v3.x, v3.y); }*/ //! is pt between end1 and end2? static bool isBetween(const Base::Vector3d pt, const Base::Vector3d end1, const Base::Vector3d end2); //! find intersection in 2d for 2 lines in point+direction form @@ -133,8 +145,10 @@ class TechDrawExport DrawUtil { Base::Vector3d p2, Base::Vector3d d2); static Base::Vector2d Intersect2d(Base::Vector2d p1, Base::Vector2d d1, Base::Vector2d p2, Base::Vector2d d2); - static Base::Vector3d gpPnt2V3(const gp_Pnt gp) { return Base::Vector3d(gp.X(), gp.Y(), gp.Z()); } - static gp_Pnt V32gpPnt(const Base::Vector3d v) { return gp_Pnt(v.x, v.y, v.z); } + static Base::Vector3d toVector3d(const gp_Pnt gp) { return Base::Vector3d(gp.X(), gp.Y(), gp.Z()); } + static Base::Vector3d toVector3d(const gp_Dir gp) { return Base::Vector3d(gp.X(), gp.Y(), gp.Z()); } + static gp_Pnt togp_Pnt(const Base::Vector3d v) { return gp_Pnt(v.x, v.y, v.z); } + static gp_Dir togp_Dir(const Base::Vector3d v) { return gp_Dir(v.x, v.y, v.z); } static std::string shapeToString(TopoDS_Shape s); static TopoDS_Shape shapeFromString(std::string s); static Base::Vector3d invertY(Base::Vector3d v); @@ -148,7 +162,7 @@ class TechDrawExport DrawUtil { static bool circulation(Base::Vector3d A, Base::Vector3d B, Base::Vector3d C); static int countSubShapes(TopoDS_Shape shape, TopAbs_ShapeEnum subShape); static void encodeXmlSpecialChars(std::string& inoutText); - + static std::list sort_Edges(double tol3d, std::list& edges); // Supplementary mathematical functions static int sgn(double x); diff --git a/src/Mod/TechDraw/App/DrawView.cpp b/src/Mod/TechDraw/App/DrawView.cpp index 34516d80c8..57c4350fb2 100644 --- a/src/Mod/TechDraw/App/DrawView.cpp +++ b/src/Mod/TechDraw/App/DrawView.cpp @@ -118,6 +118,15 @@ void DrawView::onChanged(const App::Property* prop) //Coding note: calling execute, recompute or recomputeFeature inside an onChanged //method can create infinite loops if the called method changes a property. In general //don't do this! There are situations where it is OK, but careful analysis is a must. + + if (prop == &Scale && + Scale.getValue() < Precision::Confusion()) { + //this is not supposed to happen since Scale has constraints, but it may + //happen during changes made in PropertyEditor? + Scale.setValue(1.0); + return; + } + if (isRestoring()) { App::DocumentObject::onChanged(prop); return; diff --git a/src/Mod/TechDraw/App/DrawViewPart.cpp b/src/Mod/TechDraw/App/DrawViewPart.cpp index d3fda223ec..2cb20d6f82 100644 --- a/src/Mod/TechDraw/App/DrawViewPart.cpp +++ b/src/Mod/TechDraw/App/DrawViewPart.cpp @@ -25,29 +25,32 @@ #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 +#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 @@ -237,7 +240,7 @@ App::DocumentObjectExecReturn *DrawViewPart::execute(void) TopoDS_Shape shape = getSourceShape(); if (shape.IsNull()) { - Base::Console().Log("DVP::execute - %s - Source shape is Null.\n", + Base::Console().Message("DVP::execute - %s - Source shape is Null.\n", getNameInDocument()); return DrawView::execute(); } @@ -345,7 +348,7 @@ GeometryObject* DrawViewPart::makeGeometryForShape(TopoDS_Shape& shape) } //create a geometry object and trigger the HLR process in another thread -TechDraw::GeometryObject* DrawViewPart::buildGeometryObject(TopoDS_Shape& shape, gp_Ax2& viewAxis) +TechDraw::GeometryObject* DrawViewPart::buildGeometryObject(TopoDS_Shape& shape, const gp_Ax2 &viewAxis) { // Base::Console().Message("DVP::buildGeometryObject() - %s\n", getNameInDocument()); showProgressMessage(getNameInDocument(), "is finding hidden lines"); @@ -381,11 +384,17 @@ void DrawViewPart::onHlrFinished(void) // Base::Console().Message("DVP::onHlrFinished() - %s\n", getNameInDocument()); //now that the new GeometryObject is fully populated, we can replace the old one - if (geometryObject) { - delete geometryObject; + if (geometryObject && + m_tempGeometryObject) { + delete geometryObject; //remove the old + } + if (m_tempGeometryObject) { + geometryObject = m_tempGeometryObject; //replace with new + m_tempGeometryObject = nullptr; //superfluous? + } + if (!geometryObject) { + throw Base::RuntimeError("DrawViewPart has lost its geometry"); } - geometryObject = m_tempGeometryObject; - m_tempGeometryObject = nullptr; //superfluous //the last hlr related task is to make a bbox of the results bbox = geometryObject->calcBoundingBox(); @@ -468,7 +477,7 @@ void DrawViewPart::extractFaces() geometryObject->getVisibleFaceEdges(SmoothVisible.getValue(),SeamVisible.getValue()); if (goEdges.empty()) { - Base::Console().Message("DVP::extractFaces - %s - no face edges available!\n", getNameInDocument()); +// Base::Console().Message("DVP::extractFaces - %s - no face edges available!\n", getNameInDocument()); //debug return; } @@ -476,6 +485,13 @@ void DrawViewPart::extractFaces() std::vector closedEdges; std::vector cleanEdges = DrawProjectSplit::scrubEdges(goEdges, closedEdges); + if (cleanEdges.empty() && + closedEdges.empty()) { + //how does this happen? something wrong somewhere +// Base::Console().Message("DVP::extractFaces - no clean or closed wires\n"); //debug + return; + } + //use EdgeWalker to make wires from edges EdgeWalker eWalker; std::vector sortedWires; @@ -861,10 +877,55 @@ QRectF DrawViewPart::getRect() const return result; } +//returns a compound of all the visible projected edges +TopoDS_Shape DrawViewPart::getShape() const +{ + BRep_Builder builder; + TopoDS_Compound result; + builder.MakeCompound(result); + if (geometryObject) { + if (!geometryObject->getVisHard().IsNull()) { + builder.Add(result, geometryObject->getVisHard()); + } + if (!geometryObject->getVisOutline().IsNull()) { + builder.Add(result, geometryObject->getVisOutline()); + } + if (!geometryObject->getVisSeam().IsNull()) { + builder.Add(result, geometryObject->getVisSeam()); + } + if (!geometryObject->getVisSmooth().IsNull()) { + builder.Add(result, geometryObject->getVisSmooth()); + } + } + return result; +} + +//returns the (unscaled) size of the visible lines along the alignment vector +double DrawViewPart::getSizeAlongVector(Base::Vector3d alignmentVector) +{ + gp_Ax3 projectedCS3(getProjectionCS()); + projectedCS3.SetXDirection(DrawUtil::togp_Dir(alignmentVector)); + gp_Ax3 stdCS; //OXYZ + + gp_Trsf xPieceAlign; + xPieceAlign.SetTransformation(stdCS, projectedCS3); + BRepBuilderAPI_Transform mkTransAlign(getShape(), xPieceAlign); + TopoDS_Shape shapeAligned = mkTransAlign.Shape(); + + Bnd_Box shapeBox; + shapeBox.SetGap(0.0); + BRepBndLib::AddOptimal(shapeAligned, shapeBox); + double xMin = 0, xMax = 0, yMin = 0, yMax = 0, zMin = 0, zMax = 0; + shapeBox.Get(xMin, yMin, zMin, xMax, yMax, zMax); + double shapeWidth((xMax - xMin) / getScale()); + return shapeWidth; +} + //used to project a pt (ex SectionOrigin) onto paper plane Base::Vector3d DrawViewPart::projectPoint(const Base::Vector3d& pt, bool invert) const { -// Base::Console().Message("DVP::projectPoint()\n"); +// Base::Console().Message("DVP::projectPoint(%s, %d\n", +// DrawUtil::formatVector(pt).c_str(), invert); Base::Vector3d stdOrg(0.0, 0.0, 0.0); gp_Ax2 viewAxis = getProjectionCS(stdOrg); gp_Pnt gPt(pt.x, pt.y, pt.z); @@ -892,8 +953,27 @@ BaseGeomPtr DrawViewPart::projectEdge(const TopoDS_Edge& e) const projector.Add(e); projector.Build(); TopoDS_Shape s = projector.Projection(); -// Base::Console().Message("DVP::projectEdge - s.IsNull: %d\n", s.IsNull()); - BaseGeomPtr result; + return BaseGeom::baseFactory(TopoDS::Edge(s)); +} + +//simple projection of inWire with conversion of the result to TD geometry +BaseGeomPtrVector DrawViewPart::projectWire(const TopoDS_Wire& inWire) const +{ +// Base::Console().Message("DVP::projectWire() - inWire.IsNull: %d\n", inWire.IsNull()); + BaseGeomPtrVector result; + Base::Vector3d stdOrg(0.0, 0.0, 0.0); + + TopoDS_Face paper = BRepBuilderAPI_MakeFace(gp_Pln(getProjectionCS(stdOrg))); + BRepAlgo_NormalProjection projector(paper); + projector.Add(inWire); + projector.Build(); + BRepTools::Write(projector.Projection(), "DVPprojectedWire.brep"); //debug + + TopExp_Explorer expShape(projector.Projection(), TopAbs_EDGE); + for (; expShape.More(); expShape.Next()) { + BaseGeomPtr edge = BaseGeom::baseFactory(TopoDS::Edge(expShape.Current())); + result.push_back(edge); + } return result; } @@ -926,6 +1006,27 @@ bool DrawViewPart::hasGeometry(void) const return false; } +//convert a vector in local XY coords into a coordinate sytem in global +//coordinates aligned to the vector. +//Note that this CS may not have the ideal XDirection for the derived view +//(likely a DrawViewSection) and the user may need to adjust the XDirection +//in the derived view. +gp_Ax2 DrawViewPart::localVectorToCS(const Base::Vector3d localUnit) const +{ + gp_Pnt stdOrigin(0.0, 0.0, 0.0); + gp_Ax2 dvpCS = getProjectionCS(DrawUtil::toVector3d(stdOrigin)); + gp_Vec gLocalUnit = DrawUtil::togp_Dir(localUnit); + gp_Vec gLocalX(-gLocalUnit.Y(), gLocalUnit.X(), 0.0); //clockwise perp for 2d + + gp_Ax3 OXYZ; + gp_Trsf xLocalOXYZ; + xLocalOXYZ.SetTransformation(OXYZ, gp_Ax3(dvpCS)); + gp_Vec gLocalUnitOXYZ = gLocalUnit.Transformed(xLocalOXYZ); + gp_Vec gLocalXOXYZ = gLocalX.Transformed(xLocalOXYZ); + + return { stdOrigin, gp_Dir(gLocalUnitOXYZ), gp_Dir(gLocalXOXYZ) }; +} + gp_Ax2 DrawViewPart::getProjectionCS(const Base::Vector3d pt) const { // Base::Console().Message("DVP::getProjectionCS() - %s - %s\n", getNameInDocument(), Label.getValue()); diff --git a/src/Mod/TechDraw/App/DrawViewPart.h b/src/Mod/TechDraw/App/DrawViewPart.h index f75d9eef91..19e3caa1b4 100644 --- a/src/Mod/TechDraw/App/DrawViewPart.h +++ b/src/Mod/TechDraw/App/DrawViewPart.h @@ -142,17 +142,19 @@ public: virtual Base::Vector3d projectPoint(const Base::Vector3d& pt, bool invert = true) const; virtual BaseGeomPtr projectEdge(const TopoDS_Edge& e) const; + virtual BaseGeomPtrVector projectWire(const TopoDS_Wire& inWire) const; virtual gp_Ax2 getViewAxis(const Base::Vector3d& pt, const Base::Vector3d& direction, const bool flip=true) const; - virtual gp_Ax2 getProjectionCS(Base::Vector3d pt) const; + virtual gp_Ax2 getProjectionCS(Base::Vector3d pt = Base::Vector3d(0.0, 0.0, 0.0)) const; virtual Base::Vector3d getXDirection() const; //don't use XDirection.getValue() virtual Base::Vector3d getOriginalCentroid() const; virtual Base::Vector3d getCurrentCentroid() const; virtual Base::Vector3d getLegacyX(const Base::Vector3d& pt, const Base::Vector3d& axis, const bool flip = true) const; + gp_Ax2 localVectorToCS(const Base::Vector3d localUnit) const; bool handleFaces(); @@ -166,6 +168,9 @@ public: virtual TopoDS_Shape getSourceShapeFused() const; virtual std::vector getSourceShape2d() const; + TopoDS_Shape getShape() const; + double getSizeAlongVector(Base::Vector3d alignmentVector); + virtual void postHlrTasks(void); bool isIso() const; @@ -223,7 +228,7 @@ protected: void onChanged(const App::Property* prop) override; void unsetupObject() override; - virtual TechDraw::GeometryObject* buildGeometryObject(TopoDS_Shape& shape, gp_Ax2& viewAxis); + virtual TechDraw::GeometryObject* buildGeometryObject(TopoDS_Shape& shape, const gp_Ax2& viewAxis); virtual TechDraw::GeometryObject* makeGeometryForShape(TopoDS_Shape& shape); //const?? void partExec(TopoDS_Shape& shape); virtual void addShapes2d(void); diff --git a/src/Mod/TechDraw/App/DrawViewSection.cpp b/src/Mod/TechDraw/App/DrawViewSection.cpp index fe9b9e2daf..70638819e6 100644 --- a/src/Mod/TechDraw/App/DrawViewSection.cpp +++ b/src/Mod/TechDraw/App/DrawViewSection.cpp @@ -22,33 +22,55 @@ * * ***************************************************************************/ +//DrawViewSection processing overview + +//execute +// sectionExec(getShapeToCut()) + +//sectionExec +// makeSectionCut(baseShape) + +//makeSectionCut (separate thread) +// m_cuttingTool = makeCuttingTool (DVSTool.brep) +// m_cutPieces = (baseShape - m_cuttingTool) (DVSCutPieces.brep) + +//onSectionCutFinished +// m_preparedShape = prepareShape(m_cutPieces) - centered, scaled, rotated +// geometryObject = DVP::buildGeometryObject(m_preparedShape) (HLR) + +//postHlrTasks +// faceIntersections = findSectionPlaneIntersections +// m_sectionTopoDSFaces = alignSectionFaces(faceIntersections) +// m_tdSectionFaces = makeTDSectionFaces(m_sectionTopoDSFaces) + #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 +#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 @@ -58,21 +80,26 @@ #include #include -#include "DrawViewSection.h" +#include + #include "DrawGeomHatch.h" #include "DrawHatch.h" -#include "DrawProjGroupItem.h" #include "DrawUtil.h" -#include "Geometry.h" +#include "EdgeWalker.h" +#include "DrawProjGroupItem.h" #include "GeometryObject.h" +#include "DrawViewSection.h" using namespace TechDraw; +using DU = DrawUtil; + const char* DrawViewSection::SectionDirEnums[]= {"Right", "Left", "Up", "Down", + "Aligned", nullptr}; const char* DrawViewSection::CutSurfaceEnums[]= {"Hide", @@ -88,25 +115,34 @@ const char* DrawViewSection::CutSurfaceEnums[]= {"Hide", PROPERTY_SOURCE(TechDraw::DrawViewSection, TechDraw::DrawViewPart) DrawViewSection::DrawViewSection() : - m_waitingForCut(false) + m_waitingForCut(false), + m_shapeSize(0.0) { static const char *sgroup = "Section"; static const char *fgroup = "Cut Surface Format"; + static const char *ggroup = "Cut Operation"; + //general section properties ADD_PROPERTY_TYPE(SectionSymbol ,(""), sgroup, App::Prop_None, "The identifier for this section"); ADD_PROPERTY_TYPE(BaseView ,(nullptr), sgroup, App::Prop_None, "2D View source for this Section"); BaseView.setScope(App::LinkScope::Global); ADD_PROPERTY_TYPE(SectionNormal ,(0, 0,1.0) ,sgroup, App::Prop_None, "Section Plane normal direction"); //direction of extrusion of cutting prism ADD_PROPERTY_TYPE(SectionOrigin ,(0, 0,0) ,sgroup, App::Prop_None, "Section Plane Origin"); - SectionDirection.setEnums(SectionDirEnums); - ADD_PROPERTY_TYPE(SectionDirection, ((long)0), sgroup, App::Prop_None, "Direction in Base View for this Section"); - ADD_PROPERTY_TYPE(FuseBeforeCut ,(false), sgroup, App::Prop_None, "Merge Source(s) into a single shape before cutting"); + //TODO: SectionDirection is a legacy from when SectionViews were only available along + //cardinal directions. It should be made obsolete and replaced with Aligned sections and + //local unit vectors. + SectionDirection.setEnums(SectionDirEnums); + ADD_PROPERTY_TYPE(SectionDirection, ((long)0), sgroup, App::Prop_None, "Orientation of this Section in the Base View"); + + //properties related to the cut operation + ADD_PROPERTY_TYPE(FuseBeforeCut ,(false), ggroup, App::Prop_None, "Merge Source(s) into a single shape before cutting"); + ADD_PROPERTY_TYPE(TrimAfterCut ,(false), ggroup, App::Prop_None, "Trim the resulting shape after the section cut"); + + //properties related to the display of the cut surface CutSurfaceDisplay.setEnums(CutSurfaceEnums); ADD_PROPERTY_TYPE(CutSurfaceDisplay, (prefCutSurface()), fgroup, App::Prop_None, "Appearance of Cut Surface"); - -//initialize these to defaults ADD_PROPERTY_TYPE(FileHatchPattern ,(DrawHatch::prefSvgHatch()), fgroup, App::Prop_None, "The hatch pattern file for the cut surface"); ADD_PROPERTY_TYPE(FileGeomPattern ,(DrawGeomHatch::prefGeomHatchFile()), fgroup, App::Prop_None, "The PAT pattern file for geometric hatching"); @@ -126,6 +162,8 @@ DrawViewSection::DrawViewSection() : SvgIncluded.setStatus(App::Property::ReadOnly, true); PatIncluded.setStatus(App::Property::ReadOnly, true); + //SectionNormal is used instead to Direction + Direction.setStatus(App::Property::ReadOnly, true); } DrawViewSection::~DrawViewSection() @@ -168,7 +206,9 @@ void DrawViewSection::onChanged(const App::Property* prop) return; } - if (prop == &SectionSymbol) { + if (prop == &SectionNormal) { + Direction.setValue(SectionNormal.getValue()); + } else if (prop == &SectionSymbol) { std::string lblText = "Section " + std::string(SectionSymbol.getValue()) + " - " + @@ -200,29 +240,37 @@ void DrawViewSection::onChanged(const App::Property* prop) DrawView::onChanged(prop); } +TopoDS_Shape DrawViewSection::getShapeToCut() +{ +// Base::Console().Message("DVS::getShapeToCut()\n"); + App::DocumentObject* base = BaseView.getValue(); + TechDraw::DrawViewPart* dvp = nullptr; + if (!base || + !base->getTypeId().isDerivedFrom(TechDraw::DrawViewPart::getClassTypeId())) { + //this can probably only happen with scripting + return TopoDS_Shape(); + } else { + dvp = static_cast(base); + } + TopoDS_Shape shapeToCut = dvp->getSourceShape(); + if (FuseBeforeCut.getValue()) { + shapeToCut = dvp->getSourceShapeFused(); + } + return shapeToCut; +} + App::DocumentObjectExecReturn *DrawViewSection::execute() { +// Base::Console().Message("DVS::execute() - %s\n", getNameInDocument()); if (!keepUpdated()) { return App::DocumentObject::StdReturn; } - App::DocumentObject* base = BaseView.getValue(); - if (!base) { + if (!isBaseValid()) { return new App::DocumentObjectExecReturn("BaseView object not found"); } - TechDraw::DrawViewPart* dvp = nullptr; - if (!base->getTypeId().isDerivedFrom(TechDraw::DrawViewPart::getClassTypeId())) { - //this can probably only happen with scripting - return new App::DocumentObjectExecReturn("BaseView object is not a DrawViewPart object"); - } else { - dvp = static_cast(base); - } - - TopoDS_Shape baseShape = dvp->getSourceShape(); - if (FuseBeforeCut.getValue()) { - baseShape = dvp->getSourceShapeFused(); - } + TopoDS_Shape baseShape = getShapeToCut(); if (baseShape.IsNull()) { return DrawView::execute(); @@ -230,7 +278,6 @@ App::DocumentObjectExecReturn *DrawViewSection::execute() m_saveShape = baseShape; //save shape for 2nd pass -// checkXDirection(); bool haveX = checkXDirection(); if (!haveX) { //block touch/onChanged stuff @@ -246,6 +293,16 @@ App::DocumentObjectExecReturn *DrawViewSection::execute() return DrawView::execute(); } +bool DrawViewSection::isBaseValid() const +{ + App::DocumentObject* base = BaseView.getValue(); + if (base && + base->getTypeId().isDerivedFrom(TechDraw::DrawViewPart::getClassTypeId())) { + return true; + } + return false; +} + void DrawViewSection::sectionExec(TopoDS_Shape& baseShape) { // Base::Console().Message("DVS::sectionExec() - %s baseShape.IsNull: %d\n", @@ -289,112 +346,134 @@ void DrawViewSection::makeSectionCut(TopoDS_Shape &baseShape) Bnd_Box centerBox; BRepBndLib::AddOptimal(baseShape, centerBox); centerBox.SetGap(0.0); - gp_Pln pln = getSectionPlane(); - gp_Dir gpNormal = pln.Axis().Direction(); Base::Vector3d orgPnt = SectionOrigin.getValue(); if(!isReallyInBox(gp_Pnt(orgPnt.x, orgPnt.y, orgPnt.z), centerBox)) { Base::Console().Warning("DVS: SectionOrigin doesn't intersect part in %s\n", getNameInDocument()); } - - // make cutting tool - // Make the extrusion face - double dMax = sqrt(centerBox.SquareExtent()); - BRepBuilderAPI_MakeFace mkFace(pln, -dMax, dMax, -dMax, dMax); - TopoDS_Face aProjFace = mkFace.Face(); - if(aProjFace.IsNull()) { - Base::Console().Warning("DVS: Section face is NULL in %s\n", getNameInDocument()); - return; - } - gp_Vec extrudeDir = dMax * gp_Vec(gpNormal); - TopoDS_Shape prism = BRepPrimAPI_MakePrism(aProjFace, extrudeDir, false, true).Shape(); + m_shapeSize = sqrt(centerBox.SquareExtent()); // We need to copy the shape to not modify the BRepstructure BRepBuilderAPI_Copy BuilderCopy(baseShape); TopoDS_Shape myShape = BuilderCopy.Shape(); m_saveShape = myShape; //save shape for 2nd pass + if (debugSection()) { + BRepTools::Write(myShape, "DVSCopy.brep"); //debug + } + + m_cuttingTool = makeCuttingTool(m_shapeSize); + + if (debugSection()) { + BRepTools::Write(m_cuttingTool, "DVSTool.brep"); //debug + } + // perform cut BRep_Builder builder; - TopoDS_Compound pieces; - builder.MakeCompound(pieces); + TopoDS_Compound cutPieces; + builder.MakeCompound(cutPieces); TopExp_Explorer expl(myShape, TopAbs_SOLID); - int indb = 0; - int outdb = 0; for (; expl.More(); expl.Next()) { - indb++; const TopoDS_Solid& s = TopoDS::Solid(expl.Current()); - BRepAlgoAPI_Cut mkCut(s, prism); + BRepAlgoAPI_Cut mkCut(s, m_cuttingTool); if (!mkCut.IsDone()) { Base::Console().Warning("DVS: Section cut has failed in %s\n", getNameInDocument()); continue; } - TopoDS_Shape cut = mkCut.Shape(); - builder.Add(pieces, cut); - outdb++; + builder.Add(cutPieces, mkCut.Shape()); } - // pieces contains result of cutting each subshape in baseShape with tool - TopoDS_Shape rawShape = pieces; - + // cutPieces contains result of cutting each subshape in baseShape with tool + m_cutPieces = cutPieces; if (debugSection()) { - BRepTools::Write(myShape, "DVSCopy.brep"); //debug - BRepTools::Write(aProjFace, "DVSFace.brep"); //debug - BRepTools::Write(prism, "DVSTool.brep"); //debug - BRepTools::Write(pieces, "DVSPieces.brep"); //debug + BRepTools::Write(cutPieces, "DVSCutPieces1.brep"); //debug + } + + //second cut if requested. Sometimes the first cut includes extra uncut pieces. + if (trimAfterCut()) { + BRepAlgoAPI_Cut mkCut2(cutPieces, m_cuttingTool); + if (mkCut2.IsDone()) { + m_cutPieces = mkCut2.Shape(); + if (debugSection()) { + BRepTools::Write(m_cutPieces, "DVSCutPieces2.brep"); //debug + } + } } // check for error in cut Bnd_Box testBox; - BRepBndLib::AddOptimal(rawShape, testBox); + BRepBndLib::AddOptimal(m_cutPieces, testBox); testBox.SetGap(0.0); if (testBox.IsVoid()) { //prism & input don't intersect. rawShape is garbage, don't bother. Base::Console().Warning("DVS::makeSectionCut - prism & input don't intersect - %s\n", Label.getValue()); return; - } -// build display geometry as in DVP, with minor mods - TopoDS_Shape centeredShape; + waitingForCut(false); +} + +//position, scale and rotate shape for buildGeometryObject +TopoDS_Shape DrawViewSection::prepareShape(const TopoDS_Shape& rawShape, + double shapeSize) +{ +// Base::Console().Message("DVS::prepareShape - %s - rawShape.IsNull: %d shapeSize: %.3f\n", +// getNameInDocument(), rawShape.IsNull(), shapeSize); + (void) shapeSize; //shapeSize is not used in this base class, but is interesting for + //derived classes + // build display geometry as in DVP, with minor mods + TopoDS_Shape preparedShape; try { Base::Vector3d origin(0.0, 0.0, 0.0); - m_viewAxis = getProjectionCS(origin); + m_projectionCS = getProjectionCS(origin); gp_Pnt inputCenter; inputCenter = TechDraw::findCentroid(rawShape, - m_viewAxis); + m_projectionCS); Base::Vector3d centroid(inputCenter.X(), inputCenter.Y(), inputCenter.Z()); - centeredShape = TechDraw::moveShape(rawShape, + preparedShape = TechDraw::moveShape(rawShape, centroid * -1.0); - m_cutShape = centeredShape; + m_cutShape = preparedShape; m_saveCentroid = centroid; - m_scaledShape = TechDraw::scaleShape(centeredShape, - getScale()); + preparedShape = TechDraw::scaleShape(preparedShape, + getScale()); if (!DrawUtil::fpCompare(Rotation.getValue(), 0.0)) { - m_scaledShape = TechDraw::rotateShape(m_scaledShape, - m_viewAxis, + preparedShape = TechDraw::rotateShape(preparedShape, + m_projectionCS, Rotation.getValue()); } if (debugSection()) { - BRepTools::Write(m_cutShape, "DVSmCutShape.brep"); //debug - BRepTools::Write(m_scaledShape, "DVSScaled.brep"); //debug + BRepTools::Write(m_cutShape, "DVSCutShape.brep"); //debug // DrawUtil::dumpCS("DVS::makeSectionCut - CS to GO", viewAxis); } - m_rawShape = rawShape; //save for section face finding - } catch (Standard_Failure& e1) { - Base::Console().Warning("DVS::makeSectionCut - failed to build base shape %s - %s **\n", + Base::Console().Warning("DVS::prepareShape - failed to build shape %s - %s **\n", getNameInDocument(), e1.GetMessageString()); - return; } + return preparedShape; +} - waitingForCut(false); +TopoDS_Shape DrawViewSection::makeCuttingTool(double shapeSize) +{ +// Base::Console().Message("DVS::makeCuttingTool(%.3f) - %s\n", shapeSize, getNameInDocument()); + // Make the extrusion face + gp_Pln pln = getSectionPlane(); + gp_Dir gpNormal = pln.Axis().Direction(); + BRepBuilderAPI_MakeFace mkFace(pln, -shapeSize, shapeSize, -shapeSize, shapeSize); + TopoDS_Face aProjFace = mkFace.Face(); + if(aProjFace.IsNull()) { + return TopoDS_Shape(); + } + if (debugSection()){ + BRepTools::Write(aProjFace, "DVSSectionFace.brep"); //debug + } + gp_Vec extrudeDir = shapeSize * gp_Vec(gpNormal); + return BRepPrimAPI_MakePrism(aProjFace, extrudeDir, false, true).Shape(); } void DrawViewSection::onSectionCutFinished() @@ -404,10 +483,15 @@ void DrawViewSection::onSectionCutFinished() showProgressMessage(getNameInDocument(), "has finished making section cut"); + m_preparedShape = prepareShape(getShapeToPrepare(), m_shapeSize); + if (debugSection()) { + BRepTools::Write(m_preparedShape, "DVSPreparedShape.brep"); //debug + } + postSectionCutTasks(); //display geometry for cut shape is in geometryObject as in DVP - m_tempGeometryObject = buildGeometryObject(m_scaledShape, m_viewAxis); + m_tempGeometryObject = buildGeometryObject(m_preparedShape, getSectionCS()); } //activities that depend on updated geometry object @@ -430,78 +514,41 @@ void DrawViewSection::postHlrTasks(void) // build section face geometry - TopoDS_Compound faceIntersections = findSectionPlaneIntersections(m_rawShape); + TopoDS_Compound faceIntersections = findSectionPlaneIntersections(getShapeToIntersect()); if (faceIntersections.IsNull()) { requestPaint(); return; } - TopoDS_Shape centeredShapeF = TechDraw::moveShape(faceIntersections, + TopoDS_Shape centeredFaces = TechDraw::moveShape(faceIntersections, m_saveCentroid * -1.0); - TopoDS_Shape scaledSection = TechDraw::scaleShape(centeredShapeF, + TopoDS_Shape scaledSection = TechDraw::scaleShape(centeredFaces, getScale()); if (!DrawUtil::fpCompare(Rotation.getValue(), 0.0)) { scaledSection = TechDraw::rotateShape(scaledSection, - m_viewAxis, + getProjectionCS(), Rotation.getValue()); } + + m_sectionTopoDSFaces = alignSectionFaces(faceIntersections); if (debugSection()) { - BRepTools::Write(scaledSection, "DVSScaledFaces.brep"); //debug + BRepTools::Write(m_sectionTopoDSFaces, "DVSTopoSectionFaces.brep"); //debug } + m_tdSectionFaces = makeTDSectionFaces(m_sectionTopoDSFaces); - // scaledSection is compound of TopoDS_Face intersections, but aligned to pln(origin, sectionNormal) - // needs to be aligned to pln (origin, stdZ); - gp_Ax3 R3; - gp_Ax2 projCS = getSectionCS(); - gp_Ax3 proj3 = gp_Ax3(gp_Pnt(0.0, 0.0, 0.0), - projCS.Direction(), - projCS.XDirection()); - gp_Trsf fromR3; - fromR3.SetTransformation(R3, proj3); - BRepBuilderAPI_Transform xformer(fromR3); - xformer.Perform(scaledSection, true); - if (xformer.IsDone()) { - sectionTopoDSFaces = TopoDS::Compound(xformer.Shape()); - } else { - Base::Console().Message("DVS::sectionExec - face xform failed\n"); - } - - sectionTopoDSFaces = TopoDS::Compound(GeometryObject::invertGeometry(sectionTopoDSFaces)); //handle Qt -y - - //turn section faces into TD geometry - tdSectionFaces.clear(); - TopExp_Explorer sectionExpl(sectionTopoDSFaces, TopAbs_FACE); - for (; sectionExpl.More(); sectionExpl.Next()) { - const TopoDS_Face& face = TopoDS::Face(sectionExpl.Current()); - TechDraw::FacePtr sectionFace(std::make_shared()); - TopExp_Explorer expFace(face, TopAbs_WIRE); - for ( ; expFace.More(); expFace.Next()) { - TechDraw::Wire* w = new TechDraw::Wire(); - const TopoDS_Wire& wire = TopoDS::Wire(expFace.Current()); - TopExp_Explorer expWire(wire, TopAbs_EDGE); - for ( ; expWire.More(); expWire.Next()) { - const TopoDS_Edge& edge = TopoDS::Edge(expWire.Current()); - TechDraw::BaseGeomPtr e = BaseGeom::baseFactory(edge); - if (e) { - w->geoms.push_back(e); - } - } - sectionFace->wires.push_back(w); - } - tdSectionFaces.push_back(sectionFace); - } TechDraw::DrawViewPart* dvp = dynamic_cast(BaseView.getValue()); if (dvp) { dvp->requestPaint(); //to refresh section line } - requestPaint(); + requestPaint(); //this will be a duplicate paint if we are making a standalone ComplexSection } //activities that depend on a valid section cut void DrawViewSection::postSectionCutTasks() { +// Base::Console().Message("DVS::postSectionCutTasks()\n"); std::vector children = getInList(); for (auto& c: children) { if (c->getTypeId().isDerivedFrom(DrawViewPart::getClassTypeId())) { @@ -529,16 +576,24 @@ gp_Pln DrawViewSection::getSectionPlane() const } //! tries to find the intersection of the section plane with the shape giving a collection of planar faces +//! the original algo finds the intersections first then transforms them to match the centered, rotated +//! and scaled cut shape. Aligned complex sections need to intersect the final cut shape (which in this +//! case is a compound of individual cuts) with the "effective" (flattened) section plane. TopoDS_Compound DrawViewSection::findSectionPlaneIntersections(const TopoDS_Shape& shape) { // Base::Console().Message("DVS::findSectionPlaneIntersections() - %s\n", getNameInDocument()); if(shape.IsNull()){ // this shouldn't happen - Base::Console().Warning("DrawViewSection::findSectionPlaneInter - %s - input shape is Null\n", getNameInDocument()); +// Base::Console().Warning("DrawViewSection::findSectionPlaneInter - %s - input shape is Null\n", getNameInDocument()); return TopoDS_Compound(); } gp_Pln plnSection = getSectionPlane(); + if (debugSection()) { + BRepBuilderAPI_MakeFace mkFace(plnSection, -m_shapeSize, m_shapeSize, -m_shapeSize, m_shapeSize); + BRepTools::Write(mkFace.Face(), "DVSSectionPlane.brep"); //debug + BRepTools::Write(shape, "DVSShapeToIntersect.brep)"); + } BRep_Builder builder; TopoDS_Compound result; builder.MakeCompound(result); @@ -558,6 +613,102 @@ TopoDS_Compound DrawViewSection::findSectionPlaneIntersections(const TopoDS_Shap return result; } +//move section faces to line up with cut shape +TopoDS_Compound DrawViewSection::alignSectionFaces(TopoDS_Shape faceIntersections) +{ +// Base::Console().Message("DVS::alignSectionFaces()\n"); + TopoDS_Compound sectionFaces; + TopoDS_Shape centeredShape = TechDraw::moveShape(faceIntersections, + getOriginalCentroid() * -1.0); + + TopoDS_Shape scaledSection = TechDraw::scaleShape(centeredShape, + getScale()); + if (!DrawUtil::fpCompare(Rotation.getValue(), 0.0)) { + scaledSection = TechDraw::rotateShape(scaledSection, + getSectionCS(), + Rotation.getValue()); + } + + return mapToPage(scaledSection); +} + +TopoDS_Compound DrawViewSection::mapToPage(TopoDS_Shape& shapeToAlign) +{ + // shapeToAlign is compound of TopoDS_Face intersections, but aligned to pln(origin, sectionNormal) + // needs to be aligned to paper plane (origin, stdZ); + //project the faces in the shapeToAlign, build new faces from the resulting wires and + //combine everything into a compound of faces + + BRep_Builder builder; + TopoDS_Compound result; + builder.MakeCompound(result); + + TopExp_Explorer expFace(shapeToAlign, TopAbs_FACE); + for (int iFace = 1; expFace.More(); expFace.Next(), iFace++) { + const TopoDS_Face& face = TopoDS::Face(expFace.Current()); + std::vector faceWires; + TopExp_Explorer expWires(face, TopAbs_WIRE); + for ( ; expWires.More(); expWires.Next()) { + const TopoDS_Wire& wire = TopoDS::Wire(expWires.Current()); + TopoDS_Shape projectedShape = GeometryObject::projectSimpleShape(wire, getSectionCS()); + std::vector wireEdges; + //projectedShape is just a bunch of edges. we have to rebuild the wire. + TopExp_Explorer expEdges(projectedShape, TopAbs_EDGE); + for ( ; expEdges.More(); expEdges.Next()) { + const TopoDS_Edge& edge = TopoDS::Edge(expEdges.Current()); + wireEdges.push_back(edge); + } + TopoDS_Wire cleanWire = EdgeWalker::makeCleanWire(wireEdges, 2.0 * EWTOLERANCE); + faceWires.push_back(cleanWire); + } + + //first wire should be the outer boundary of the face + BRepBuilderAPI_MakeFace mkFace(faceWires.front()); + int wireCount = faceWires.size(); + for (int iWire = 1; iWire < wireCount; iWire++) { + //make holes in the face with the rest of the wires + mkFace.Add(faceWires.at(iWire)); + } + builder.Add(result, mkFace.Face()); + if (debugSection()) { + std::stringstream ss; + ss << "DVSFaceFromWires" << iFace << ".brep"; + BRepTools::Write(mkFace.Face(), ss.str().c_str()); //debug + } + } + + return result; +} + +//turn OCC section faces into TD geometry +std::vector DrawViewSection::makeTDSectionFaces(TopoDS_Compound topoDSFaces) +{ +// Base::Console().Message("DVS::makeTDSectionFaces()\n"); + std::vector tdSectionFaces; + TopExp_Explorer sectionExpl(topoDSFaces, TopAbs_FACE); + for (; sectionExpl.More(); sectionExpl.Next()) { + const TopoDS_Face& face = TopoDS::Face(sectionExpl.Current()); + TechDraw::FacePtr sectionFace(std::make_shared()); + TopExp_Explorer expFace(face, TopAbs_WIRE); + for ( ; expFace.More(); expFace.Next()) { + TechDraw::Wire* w = new TechDraw::Wire(); + const TopoDS_Wire& wire = TopoDS::Wire(expFace.Current()); + TopExp_Explorer expWire(wire, TopAbs_EDGE); + for ( ; expWire.More(); expWire.Next()) { + const TopoDS_Edge& edge = TopoDS::Edge(expWire.Current()); + TechDraw::BaseGeomPtr e = BaseGeom::baseFactory(edge); + if (e) { + w->geoms.push_back(e); + } + } + sectionFace->wires.push_back(w); + } + tdSectionFaces.push_back(sectionFace); + } + + return tdSectionFaces; +} + //calculate the ends of the section line in BaseView's coords std::pair DrawViewSection::sectionLineEnds() { @@ -577,20 +728,9 @@ std::pair DrawViewSection::sectionLineEnds() Base::Vector3d sectionOrg = SectionOrigin.getValue() - getBaseDVP()->getOriginalCentroid(); sectionOrg = getBaseDVP()->projectPoint(sectionOrg); //convert to base view CS - - //get the unscaled X and Y ranges of the base view geometry - auto bbx = getBaseDVP()->getBoundingBox(); - double xRange = bbx.MaxX - bbx.MinX; - xRange /= getBaseDVP()->getScale(); - double yRange = bbx.MaxY - bbx.MinY; - yRange /= getBaseDVP()->getScale(); - - sectionOrg = rotator.multVec(sectionOrg); - sectionLineDir = rotator.multVec(sectionLineDir); - - result = DrawUtil::boxIntersect2d(sectionOrg, sectionLineDir, xRange, yRange); //unscaled - result.first = unrotator.multVec(result.first); - result.second = unrotator.multVec(result.second); + double halfSize = getBaseDVP()->getSizeAlongVector(sectionLineDir) / 2.0; + result.first = sectionOrg + sectionLineDir * halfSize; + result.second = sectionOrg - sectionLineDir * halfSize; return result; } @@ -659,11 +799,40 @@ void DrawViewSection::setCSFromBase(const std::string sectionName) XDirection.setValue(vXDir); } +//set the section CS based on an XY vector in BaseViews CS +void DrawViewSection::setCSFromBase(const Base::Vector3d localUnit) +{ +// Base::Console().Message("DVS::setCSFromBase(%s)\n", DrawUtil::formatVector(localUnit).c_str()); + gp_Ax2 newSectionCS = getBaseDVP()->localVectorToCS(localUnit); + + Base::Vector3d vDir(newSectionCS.Direction().X(), + newSectionCS.Direction().Y(), + newSectionCS.Direction().Z()); + Direction.setValue(vDir); + SectionNormal.setValue(vDir); + Base::Vector3d vXDir(newSectionCS.XDirection().X(), + newSectionCS.XDirection().Y(), + newSectionCS.XDirection().Z()); + XDirection.setValue(vXDir); +} + +//reset the section CS based on an XY vector in current section CS +void DrawViewSection::setCSFromLocalUnit(const Base::Vector3d localUnit) +{ +// Base::Console().Message("DVS::setCSFromLocalUnit(%s)\n", DrawUtil::formatVector(localUnit).c_str()); + gp_Dir verticalDir = getSectionCS().YDirection(); + gp_Ax1 verticalAxis(DrawUtil::togp_Pnt(SectionOrigin.getValue()), verticalDir); + gp_Dir oldNormal = getSectionCS().Direction(); + gp_Dir newNormal = DrawUtil::togp_Dir(projectPoint(localUnit)); + double angle = oldNormal.AngleWithRef(newNormal, verticalDir); + gp_Ax2 newCS = getSectionCS().Rotated(verticalAxis, angle); + SectionNormal.setValue(DrawUtil::toVector3d(newCS.Direction())); + XDirection.setValue(DrawUtil::toVector3d(newCS.XDirection())); +} + gp_Ax2 DrawViewSection::getCSFromBase(const std::string sectionName) const { // Base::Console().Message("DVS::getCSFromBase(%s)\n", sectionName.c_str()); - Base::Vector3d sectionNormal; - Base::Vector3d sectionXDir; Base::Vector3d origin(0.0, 0.0, 0.0); Base::Vector3d sectOrigin = SectionOrigin.getValue(); @@ -693,6 +862,12 @@ gp_Ax2 DrawViewSection::getCSFromBase(const std::string sectionName) const } else if (sectionName == "Right") { dvsDir = dvpRight.Reversed(); dvsXDir = dvpDir; + } else if (sectionName == "Aligned") { + //if aligned, we don't get our direction from the base view + Base::Vector3d sectionNormal = SectionNormal.getValue(); + dvsDir = gp_Dir(sectionNormal.x, sectionNormal.y, sectionNormal.z); + Base::Vector3d sectionXDir = XDirection.getValue(); + dvsXDir = gp_Dir(sectionXDir.x, sectionXDir.y, sectionXDir.z); } else { Base::Console().Log("Error - DVS::getCSFromBase - bad sectionName: %s\n", sectionName.c_str()); dvsDir = dvpRight; @@ -739,6 +914,26 @@ gp_Ax2 DrawViewSection::getSectionCS() const return sectionCS; } +gp_Ax2 DrawViewSection::getProjectionCS(const Base::Vector3d pt) const +{ + Base::Vector3d vNormal = SectionNormal.getValue(); + gp_Dir gNormal(vNormal.x, + vNormal.y, + vNormal.z); + Base::Vector3d vXDir = getXDirection(); + gp_Dir gXDir(vXDir.x, + vXDir.y, + vXDir.z); + if (DrawUtil::fpCompare(fabs(gNormal.Dot(gXDir)), 1.0)) { + //can not build a gp_Ax2 from these values + throw Base::RuntimeError("DVS::getProjectionCS - SectionNormal and XDirection are parallel"); + } + gp_Pnt gOrigin(pt.x, + pt.y, + pt.z); + return { gOrigin, gNormal, gXDir }; +} + std::vector DrawViewSection::getDrawableLines(int i) { // Base::Console().Message("DVS::getDrawableLines(%d) - lineSets: %d\n", i, m_lineSets.size()); @@ -752,7 +947,7 @@ std::vector DrawViewSection::getDrawableLines(int i) TopoDS_Face DrawViewSection::getSectionTopoDSFace(int i) { TopoDS_Face result; - TopExp_Explorer expl(sectionTopoDSFaces, TopAbs_FACE); + TopExp_Explorer expl(m_sectionTopoDSFaces, TopAbs_FACE); int count = 1; for (; expl.More(); expl.Next(), count++) { if (count == i+1) { @@ -967,6 +1162,10 @@ bool DrawViewSection::showSectionEdges(void) return (hGrp->GetBool("ShowSectionEdges", true)); } +bool DrawViewSection::trimAfterCut() const +{ + return TrimAfterCut.getValue(); +} // Python Drawing feature --------------------------------------------------------- namespace App { diff --git a/src/Mod/TechDraw/App/DrawViewSection.h b/src/Mod/TechDraw/App/DrawViewSection.h index b7092a3616..00896d230a 100644 --- a/src/Mod/TechDraw/App/DrawViewSection.h +++ b/src/Mod/TechDraw/App/DrawViewSection.h @@ -69,8 +69,10 @@ public: App::PropertyLink BaseView; App::PropertyVector SectionNormal; App::PropertyVector SectionOrigin; - App::PropertyEnumeration SectionDirection; + App::PropertyString SectionSymbol; + + App::PropertyEnumeration SectionDirection; //to be made obsolete eventually App::PropertyEnumeration CutSurfaceDisplay; //new v019 App::PropertyFile FileHatchPattern; App::PropertyFile FileGeomPattern; //new v019 @@ -79,8 +81,8 @@ public: App::PropertyString NameGeomPattern; App::PropertyFloat HatchScale; - App::PropertyString SectionSymbol; App::PropertyBool FuseBeforeCut; + App::PropertyBool TrimAfterCut; //new v021 bool isReallyInBox (const Base::Vector3d v, const Base::BoundBox3d bb) const; bool isReallyInBox (const gp_Pnt p, const Bnd_Box& bb) const; @@ -94,26 +96,41 @@ public: short mustExecute() const override; void sectionExec(TopoDS_Shape& s); - void makeSectionCut(TopoDS_Shape &baseShape); + virtual void makeSectionCut(TopoDS_Shape &baseShape); void postHlrTasks(void) override; virtual void postSectionCutTasks(); void waitingForCut(bool s) { m_waitingForCut = s; } bool waitingForCut(void) const { return m_waitingForCut; } bool waitingForResult() const override; - std::vector getTDFaceGeometry() {return tdSectionFaces;} + virtual TopoDS_Shape makeCuttingTool(double shapeSize); + virtual TopoDS_Shape getShapeToCut(); + virtual bool isBaseValid() const; + virtual TopoDS_Shape prepareShape(const TopoDS_Shape& rawShape, + double shapeSize); + virtual TopoDS_Shape getShapeToPrepare() const { return m_cutPieces; } + //CS related methods + gp_Ax2 getProjectionCS(Base::Vector3d pt = Base::Vector3d(0.0, 0.0, 0.0)) const override; void setCSFromBase(const std::string sectionName); - gp_Ax2 getCSFromBase(const std::string sectionName) const; - + void setCSFromBase(Base::Vector3d localUnit); + void setCSFromLocalUnit(const Base::Vector3d localUnit); + virtual gp_Ax2 getCSFromBase(const std::string sectionName) const; gp_Ax2 getSectionCS() const; Base::Vector3d getXDirection() const override; //don't use XDirection.getValue() TechDraw::DrawViewPart* getBaseDVP() const; TechDraw::DrawProjGroupItem* getBaseDPGI() const; - TopoDS_Compound getSectionTFaces() { return sectionTopoDSFaces;} + //section face related methods + TopoDS_Compound getSectionTFaces() { return m_sectionTopoDSFaces;} + std::vector getTDFaceGeometry() {return m_tdSectionFaces;} TopoDS_Face getSectionTopoDSFace(int i); + virtual TopoDS_Compound alignSectionFaces(TopoDS_Shape faceIntersections); + TopoDS_Compound mapToPage(TopoDS_Shape& shapeToAlign); + virtual std::vector makeTDSectionFaces(TopoDS_Compound topoDSFaces); + virtual TopoDS_Shape getShapeToIntersect() { return m_cutPieces; } + void makeLineSets(void) ; std::vector getDrawableLines(int i = 0); std::vector getDecodedSpecsFromFile(std::string fileSpec, std::string myPattern); @@ -123,7 +140,7 @@ public: static const char* SectionDirEnums[]; static const char* CutSurfaceEnums[]; - std::pair sectionLineEnds(); + virtual std::pair sectionLineEnds(); bool showSectionEdges(void); @@ -131,16 +148,17 @@ public Q_SLOTS: void onSectionCutFinished(void); protected: - TopoDS_Compound sectionTopoDSFaces; //needed for hatching + TopoDS_Compound m_sectionTopoDSFaces; //needed for hatching std::vector m_lineSets; - std::vector tdSectionFaces; + std::vector m_tdSectionFaces; - gp_Pln getSectionPlane() const; - TopoDS_Compound findSectionPlaneIntersections(const TopoDS_Shape& shape); + virtual gp_Pln getSectionPlane() const; + virtual TopoDS_Compound findSectionPlaneIntersections(const TopoDS_Shape& shape); void getParameters(); bool debugSection() const; int prefCutSurface() const; + bool trimAfterCut() const; TopoDS_Shape m_cutShape; @@ -151,15 +169,16 @@ protected: void replaceSvgIncluded(std::string newSvgFile); void replacePatIncluded(std::string newPatFile); - TopoDS_Shape m_rawShape; - gp_Ax2 m_viewAxis; - TopoDS_Shape m_scaledShape; + TopoDS_Shape m_cutPieces; //the shape after cutting, but before centering & scaling + gp_Ax2 m_projectionCS; + TopoDS_Shape m_preparedShape; //the shape after cutting, centering, scaling etc QMetaObject::Connection connectCutWatcher; QFutureWatcher m_cutWatcher; QFuture m_cutFuture; bool m_waitingForCut; - + TopoDS_Shape m_cuttingTool; + double m_shapeSize; }; using DrawViewSectionPython = App::FeaturePythonT; diff --git a/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp b/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp index 3113cc3219..5636538f63 100644 --- a/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp +++ b/src/Mod/TechDraw/App/DrawViewSpreadsheet.cpp @@ -24,9 +24,9 @@ #include "PreCompiled.h" #ifndef _PreComp_ -# include -# include -# include +#include +#include +#include #endif #include @@ -34,10 +34,10 @@ #include #include -#include "DrawViewSpreadsheet.h" #include "DrawUtil.h" #include "Preferences.h" +#include "DrawViewSpreadsheet.h" using namespace TechDraw; diff --git a/src/Mod/TechDraw/App/Geometry.cpp b/src/Mod/TechDraw/App/Geometry.cpp index 6cd59fd153..cf347049b6 100644 --- a/src/Mod/TechDraw/App/Geometry.cpp +++ b/src/Mod/TechDraw/App/Geometry.cpp @@ -1246,8 +1246,8 @@ bool BSpline::isLine() return false; } - Base::Vector3d vs = DrawUtil::gpPnt2V3(s); - Base::Vector3d ve = DrawUtil::gpPnt2V3(e); + Base::Vector3d vs = DrawUtil::toVector3d(s); + Base::Vector3d ve = DrawUtil::toVector3d(e); double endLength = (vs - ve).Length(); int low = 0; int high = spline->NbPoles() - 1; @@ -1256,9 +1256,9 @@ bool BSpline::isLine() double lenTotal = 0.0; for (int i = 0; i < high; i++) { gp_Pnt p1 = poles(i); - Base::Vector3d v1 = DrawUtil::gpPnt2V3(p1); + Base::Vector3d v1 = DrawUtil::toVector3d(p1); gp_Pnt p2 = poles(i+1); - Base::Vector3d v2 = DrawUtil::gpPnt2V3(p2); + Base::Vector3d v2 = DrawUtil::toVector3d(p2); lenTotal += (v2-v1).Length(); } @@ -1415,7 +1415,7 @@ TopoDS_Edge BSpline::asCircle(bool& arc) gp_Circ circle1 = gce_circ1.Value(); double radius1 = circle1.Radius(); gp_Pnt center1 = circle1.Location(); - Base::Vector3d vc1 = DrawUtil::gpPnt2V3(center1); + Base::Vector3d vc1 = DrawUtil::toVector3d(center1); gce_MakeCirc gce_circ2 = gce_MakeCirc(pcm, pc2, e); if (gce_circ2.Status() != gce_Done) { @@ -1424,7 +1424,7 @@ TopoDS_Edge BSpline::asCircle(bool& arc) gp_Circ circle2 = gce_circ2.Value(); double radius2 = circle2.Radius(); gp_Pnt center2 = circle2.Location(); - Base::Vector3d vc2 = DrawUtil::gpPnt2V3(center2); + Base::Vector3d vc2 = DrawUtil::toVector3d(center2); // compare radii & centers double allowError = 0.001; //mm^-3 good enough for printing diff --git a/src/Mod/TechDraw/App/GeometryObject.cpp b/src/Mod/TechDraw/App/GeometryObject.cpp index 3c9f089f14..08535e40d2 100644 --- a/src/Mod/TechDraw/App/GeometryObject.cpp +++ b/src/Mod/TechDraw/App/GeometryObject.cpp @@ -24,32 +24,39 @@ #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 +#include #include #include +#include #include -#include #include -#include #include -#include +#include #include #include +#include #include #include #endif // #ifndef _PreComp_ @@ -70,6 +77,8 @@ using namespace TechDraw; using namespace std; +using DU = DrawUtil; + struct EdgePoints { gp_Pnt v1, v2; TopoDS_Edge edge; @@ -180,6 +189,7 @@ void GeometryObject::projectShape(const TopoDS_Shape& inShape, visHard = hlrToShape.VCompound(); BRepLib::BuildCurves3d(visHard); visHard = invertGeometry(visHard); +// BRepTools::Write(visHard, "GOvisHard.brep"); //debug } if (!hlrToShape.Rg1LineVCompound().IsNull()) { @@ -417,6 +427,46 @@ void GeometryObject::projectShapeWithPolygonAlgo(const TopoDS_Shape& input, makeTDGeometry(); } +//project the edges in shape onto XY.mirrored plane of CS. mimics the projection +//of the main hlr routine. Only the visible hard edges are returned, so this method +//is only suitable for simple shapes that have no hidden edges, like faces or wires. +//TODO: allow use of perspective projector +TopoDS_Shape GeometryObject::projectSimpleShape(const TopoDS_Shape &shape, + const gp_Ax2 &CS) +{ +// Base::Console().Message("GO::()\n"); + if(shape.IsNull()) { + throw Base::ValueError("GO::projectSimpleShape - input shape is NULL"); + } + + HLRBRep_Algo *brep_hlr = new HLRBRep_Algo(); + brep_hlr->Add(shape); + HLRAlgo_Projector projector( CS ); + brep_hlr->Projector(projector); + brep_hlr->Update(); + brep_hlr->Hide(); + + HLRBRep_HLRToShape hlrToShape(brep_hlr); + TopoDS_Shape hardEdges = hlrToShape.VCompound(); + BRepLib::BuildCurves3d(hardEdges); + hardEdges = invertGeometry(hardEdges); + + return hardEdges; +} + +//project the edges of a shape onto the XY plane of projCS. This does not give +//the same result as the hlr projections +TopoDS_Shape GeometryObject::simpleProjection(const TopoDS_Shape& shape, + const gp_Ax2& projCS) +{ + gp_Pln plane(projCS); + TopoDS_Face paper = BRepBuilderAPI_MakeFace(plane); + BRepAlgo_NormalProjection projector(paper); + projector.Add(shape); + projector.Build(); + return projector.Projection(); +} + TopoDS_Shape GeometryObject::projectFace(const TopoDS_Shape &face, const gp_Ax2 &CS) { @@ -510,11 +560,11 @@ void GeometryObject::addGeomFromCompound(TopoDS_Shape edgeCompound, edgeClass ca Base::Console().Log("GO::addGeomFromCompound - edge: %d is NULL\n", i); continue; } - if (DrawUtil::isZeroEdge(edge)) { + if (DU::isZeroEdge(edge)) { Base::Console().Log("GO::addGeomFromCompound - edge: %d is zeroEdge\n", i); continue; } - if (DrawUtil::isCrazy(edge)) { + if (DU::isCrazy(edge)) { Base::Console().Log("GO::addGeomFromCompound - edge: %d is crazy\n", i); continue; } @@ -860,7 +910,7 @@ gp_Ax2 TechDraw::getViewAxis(const Base::Vector3d origin, Base::Vector3d stdZ(0.0, 0.0, 1.0); Base::Vector3d stdOrg(0.0, 0.0, 0.0); Base::Vector3d cross = direction; - if (TechDraw::DrawUtil::checkParallel(direction, stdZ)) { + if (DU::checkParallel(direction, stdZ)) { cross = Base::Vector3d(1.0, 0.0, 0.0); } else { cross.Normalize(); @@ -913,7 +963,7 @@ gp_Ax2 TechDraw::legacyViewAxis1(const Base::Vector3d origin, } Base::Vector3d cross = flipDirection; // //special case - if (TechDraw::DrawUtil::checkParallel(flipDirection, stdZ)) { + if (DU::checkParallel(flipDirection, stdZ)) { cross = Base::Vector3d(1.0, 0.0, 0.0); } else { cross.Normalize(); @@ -940,6 +990,23 @@ gp_Ax2 TechDraw::legacyViewAxis1(const Base::Vector3d origin, return viewAxis; } +//! Returns the centroid of shape based on R3 +gp_Pnt TechDraw::findCentroid(const TopoDS_Shape& shape) +{ + Bnd_Box tBounds; + tBounds.SetGap(0.0); + BRepBndLib::AddOptimal(shape, tBounds, true, false); + + Standard_Real xMin, yMin, zMin, xMax, yMax, zMax; + tBounds.Get(xMin, yMin, zMin, xMax, yMax, zMax); + + Standard_Real x = (xMin + xMax) / 2.0, + y = (yMin + yMax) / 2.0, + z = (zMin + zMax) / 2.0; + + return gp_Pnt(x, y, z); +} + //! Returns the centroid of shape, as viewed according to direction gp_Pnt TechDraw::findCentroid(const TopoDS_Shape &shape, const Base::Vector3d &direction) @@ -955,8 +1022,6 @@ gp_Pnt TechDraw::findCentroid(const TopoDS_Shape &shape, const gp_Ax2 &viewAxis) { // Base::Console().Message("GO::findCentroid() - 2\n"); -// Base::Vector3d origin(0.0, 0.0, 0.0); -// gp_Ax2 viewAxis = getViewAxis(origin, direction); gp_Trsf tempTransform; tempTransform.SetTransformation(viewAxis); @@ -1020,7 +1085,7 @@ TopoDS_Shape TechDraw::mirrorShape(const TopoDS_Shape &input, // mirror about the Y axis gp_Trsf tempTransform; //BRepBuilderAPI_Transform will loop forever if asked to use 0.0 as scale - if (!(scale > 0.0)) { + if (scale <= 0.0) { tempTransform.SetScale(inputCenter, 1.0); } else { tempTransform.SetScale(inputCenter, scale); @@ -1042,8 +1107,8 @@ TopoDS_Shape TechDraw::mirrorShape(const TopoDS_Shape &input, //!rotates a shape about a viewAxis TopoDS_Shape TechDraw::rotateShape(const TopoDS_Shape &input, - gp_Ax2& viewAxis, - double rotAngle) + const gp_Ax2 &viewAxis, + double rotAngle) { TopoDS_Shape transShape; if (input.IsNull()) { @@ -1103,3 +1168,62 @@ TopoDS_Shape TechDraw::moveShape(const TopoDS_Shape &input, } return transShape; } + + +//!moves a shape with restricts on directions +TopoDS_Shape TechDraw::moveShapeRestricted(const TopoDS_Shape &input, + const Base::Vector3d& motion, + bool allowX, + bool allowY, + bool allowZ) +{ + gp_Vec gMotion(allowX ? motion.x : 0.0, + allowY ? motion.y : 0.0, + allowZ ? motion.z : 0.0); + TopoDS_Shape transShape; + try { + gp_Trsf xlate; + xlate.SetTranslation(gMotion); + + BRepBuilderAPI_Transform mkTrf(input, xlate); + transShape = mkTrf.Shape(); + } + catch (...) { + Base::Console().Log("GeometryObject::moveShapeRestricted - move failed.\n"); + return transShape; + } + return transShape; +} + +//!moves a shape with restricts on directions +TopoDS_Shape TechDraw::moveShapeRestricted(const TopoDS_Shape &input, + const Base::Vector3d& motion, + const Base::Vector3d& mask) +{ + gp_Vec gMotion(mask.x ? motion.x : 0.0, + mask.y ? motion.y : 0.0, + mask.z ? motion.z : 0.0); + + TopoDS_Shape transShape; + try { + gp_Trsf xlate; + xlate.SetTranslation(gMotion); + + BRepBuilderAPI_Transform mkTrf(input, xlate); + transShape = mkTrf.Shape(); + } + catch (...) { + Base::Console().Log("GeometryObject::moveShapeRestricted - move failed.\n"); + return transShape; + } + return transShape; +} + +TopoDS_Shape TechDraw::moveShapeRestricted(const TopoDS_Shape &input, + const gp_Vec& motion, + const gp_Vec& mask) +{ + return moveShapeRestricted(input, + DU::toVector3d(motion), + DU::toVector3d(mask)); +} diff --git a/src/Mod/TechDraw/App/GeometryObject.h b/src/Mod/TechDraw/App/GeometryObject.h index 82cecc0447..8ab04671b3 100644 --- a/src/Mod/TechDraw/App/GeometryObject.h +++ b/src/Mod/TechDraw/App/GeometryObject.h @@ -67,13 +67,24 @@ TopoDS_Shape TechDrawExport mirrorShape(const TopoDS_Shape &input, TopoDS_Shape TechDrawExport scaleShape(const TopoDS_Shape &input, double scale); TopoDS_Shape TechDrawExport rotateShape(const TopoDS_Shape &input, - gp_Ax2& viewAxis, - double rotAngle); + const gp_Ax2& viewAxis, + double rotAngle); TopoDS_Shape TechDrawExport moveShape(const TopoDS_Shape &input, const Base::Vector3d& motion); - +TopoDS_Shape TechDrawExport moveShapeRestricted(const TopoDS_Shape &input, + const Base::Vector3d& motion, + bool allowX = true, + bool allowY = true, + bool allowZ = true); +TopoDS_Shape TechDrawExport moveShapeRestricted(const TopoDS_Shape &input, + const Base::Vector3d& motion, + const Base::Vector3d& mask); +TopoDS_Shape TechDrawExport moveShapeRestricted(const TopoDS_Shape &input, + const gp_Vec& motion, + const gp_Vec& mask); //! Returns the centroid of shape, as viewed according to direction +gp_Pnt TechDrawExport findCentroid(const TopoDS_Shape& shape); gp_Pnt TechDrawExport findCentroid(const TopoDS_Shape &shape, const Base::Vector3d &direction); gp_Pnt TechDrawExport findCentroid(const TopoDS_Shape &shape, @@ -118,9 +129,12 @@ public: const gp_Ax2 &viewAxis); void projectShapeWithPolygonAlgo(const TopoDS_Shape &input, const gp_Ax2 &viewAxis); - TopoDS_Shape projectFace(const TopoDS_Shape &face, - const gp_Ax2 &CS); - + static TopoDS_Shape projectSimpleShape(const TopoDS_Shape &shape, + const gp_Ax2 &CS); + static TopoDS_Shape simpleProjection(const TopoDS_Shape& shape, + const gp_Ax2& projCS); + static TopoDS_Shape projectFace(const TopoDS_Shape &face, + const gp_Ax2 &CS); void makeTDGeometry(); void extractGeometry(edgeClass category, bool visible); void addFaceGeom(FacePtr f); diff --git a/src/Mod/TechDraw/App/PreCompiled.h b/src/Mod/TechDraw/App/PreCompiled.h index f922ddc982..526ec7f2d1 100644 --- a/src/Mod/TechDraw/App/PreCompiled.h +++ b/src/Mod/TechDraw/App/PreCompiled.h @@ -56,6 +56,7 @@ #include #include #include + #include #include #include diff --git a/src/Mod/TechDraw/App/Preferences.cpp b/src/Mod/TechDraw/App/Preferences.cpp index 59b87dbd80..aeae9fde3b 100644 --- a/src/Mod/TechDraw/App/Preferences.cpp +++ b/src/Mod/TechDraw/App/Preferences.cpp @@ -132,7 +132,28 @@ double Preferences::vertexScale() return result; } +int Preferences::scaleType() +{ + Base::Reference hGrp = App::GetApplication().GetUserParameter() + .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/General"); + int result = hGrp->GetInt("DefaultScaleType", 0); + return result; +} +double Preferences::scale() +{ + int prefScaleType = scaleType(); + if (prefScaleType == 0) { //page scale + Base::Reference hGrp = App::GetApplication().GetUserParameter() + .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/General"); + return hGrp->GetFloat("DefaultPageScale", 1.0); + } else if (prefScaleType == 1) { //custom scale + Base::Reference hGrp = App::GetApplication().GetUserParameter() + .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/General"); + return hGrp->GetFloat("DefaultViewScale", 1.0); + } + return 1.0; +} //lightgray #D3D3D3 diff --git a/src/Mod/TechDraw/App/Preferences.h b/src/Mod/TechDraw/App/Preferences.h index aaafd5fa5e..9a61ec95df 100644 --- a/src/Mod/TechDraw/App/Preferences.h +++ b/src/Mod/TechDraw/App/Preferences.h @@ -52,7 +52,8 @@ static App::Color selectColor(); static App::Color preselectColor(); static App::Color vertexColor(); static double vertexScale(); - +static int scaleType(); +static double scale(); static bool useGlobalDecimals(); static bool keepPagesUpToDate(); diff --git a/src/Mod/TechDraw/Gui/CMakeLists.txt b/src/Mod/TechDraw/Gui/CMakeLists.txt index 3322504464..2fdb6d46ec 100644 --- a/src/Mod/TechDraw/Gui/CMakeLists.txt +++ b/src/Mod/TechDraw/Gui/CMakeLists.txt @@ -87,6 +87,7 @@ set(TechDrawGui_UIC_SRCS SymbolChooser.ui TaskMoveView.ui TaskProjection.ui + TaskComplexSection.ui ) if(BUILD_QT5) @@ -222,6 +223,15 @@ SET(TechDrawGui_SRCS TaskProjection.cpp TaskProjection.h TaskProjection.ui + TaskComplexSection.cpp + TaskComplexSection.h + TaskComplexSection.ui + Widgets/CompassDialWidget.cpp + Widgets/CompassDialWidget.h + Widgets/CompassWidget.cpp + Widgets/CompassWidget.h + Widgets/VectorEditWidget.cpp + Widgets/VectorEditWidget.h ) SET(TechDrawGuiView_SRCS @@ -419,6 +429,7 @@ SET(TechDrawGuiTaskDlgs_SRCS TaskCustomizeFormat.ui TaskMoveView.ui TaskProjection.ui + TaskComplexSection.ui ) SOURCE_GROUP("MRTE" FILES ${MRTE_SRCS}) diff --git a/src/Mod/TechDraw/Gui/Command.cpp b/src/Mod/TechDraw/Gui/Command.cpp index c880df5c6e..52947210ed 100644 --- a/src/Mod/TechDraw/Gui/Command.cpp +++ b/src/Mod/TechDraw/Gui/Command.cpp @@ -21,19 +21,23 @@ #include "PreCompiled.h" #ifndef _PreComp_ -# include -# include -# include -# include +#include +#include +#include +#include +#include #endif #include #include #include + #include #include + #include #include +#include #include #include #include @@ -43,14 +47,18 @@ #include #include #include + #include + +#include #include #include #include +#include #include #include +#include #include -#include #include #include #include @@ -62,6 +70,7 @@ #include "QGVPage.h" #include "Rez.h" #include "TaskActiveView.h" +#include "TaskComplexSection.h" #include "TaskDetail.h" #include "TaskProjGroup.h" #include "TaskProjection.h" @@ -69,6 +78,8 @@ #include "ViewProviderViewPart.h" #include "TaskSectionView.h" +void execSimpleSection(Gui::Command* cmd); +void execComplexSection(Gui::Command* cmd); class Vertex; using namespace TechDrawGui; @@ -462,6 +473,98 @@ bool CmdTechDrawActiveView::isActive() return DrawGuiUtil::needPage(this, true); } +//=========================================================================== +// TechDraw_SectionGroup +//=========================================================================== + +DEF_STD_CMD_ACL(CmdTechDrawSectionGroup) + +CmdTechDrawSectionGroup::CmdTechDrawSectionGroup() + : Command("TechDraw_SectionGroup") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert a simple or complex Section View"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_SectionGroup"; + sStatusTip = sToolTipText; +} + +void CmdTechDrawSectionGroup::activated(int iMsg) +{ +// Base::Console().Message("CMD::SectionGrp - activated(%d)\n", iMsg); + Gui::TaskView::TaskDialog *dlg = Gui::Control().activeDialog(); + if (dlg) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Task In Progress"), + QObject::tr("Close active task dialog and try again.")); + return; + } + + Gui::ActionGroup* pcAction = qobject_cast(_pcAction); + pcAction->setIcon(pcAction->actions().at(iMsg)->icon()); + switch(iMsg) { + case 0: + execSimpleSection(this); + break; + case 1: + execComplexSection(this); + break; + default: + Base::Console().Message("CMD::SectionGrp - invalid iMsg: %d\n", iMsg); + }; +} + +Gui::Action * CmdTechDrawSectionGroup::createAction() +{ + Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); + pcAction->setDropDownMenu(true); + applyCommandData(this->className(), pcAction); + + QAction* p1 = pcAction->addAction(QString()); + p1->setIcon(Gui::BitmapFactory().iconFromTheme("actions/TechDraw_SectionView")); + p1->setObjectName(QString::fromLatin1("TechDraw_SectionView")); + p1->setWhatsThis(QString::fromLatin1("TechDraw_SectionView")); + QAction* p2 = pcAction->addAction(QString()); + p2->setIcon(Gui::BitmapFactory().iconFromTheme("actions/TechDraw_ComplexSection")); + p2->setObjectName(QString::fromLatin1("TechDraw_ComplexSection")); + p2->setWhatsThis(QString::fromLatin1("TechDraw_ComplexSection")); + + _pcAction = pcAction; + languageChange(); + + pcAction->setIcon(p1->icon()); + int defaultId = 0; + pcAction->setProperty("defaultAction", QVariant(defaultId)); + + return pcAction; +} + +void CmdTechDrawSectionGroup::languageChange() +{ + Command::languageChange(); + + if (!_pcAction) + return; + Gui::ActionGroup* pcAction = qobject_cast(_pcAction); + QList a = pcAction->actions(); + + QAction* arc1 = a[0]; + arc1->setText(QApplication::translate("CmdTechDrawSectionGroup", "Section View")); + arc1->setToolTip(QApplication::translate("TechDraw_SectionView", "Insert simple Section View")); + arc1->setStatusTip(arc1->toolTip()); + QAction* arc2 = a[1]; + arc2->setText(QApplication::translate("CmdTechDrawSectionGroup", "Complex Section")); + arc2->setToolTip(QApplication::translate("TechDraw_ComplexSection", "Insert complex Section View")); + arc2->setStatusTip(arc2->toolTip()); +} + +bool CmdTechDrawSectionGroup::isActive() +{ + bool havePage = DrawGuiUtil::needPage(this); + bool haveView = DrawGuiUtil::needView(this, false); + return (havePage && haveView); +} + //=========================================================================== // TechDraw_SectionView //=========================================================================== @@ -483,22 +586,14 @@ CmdTechDrawSectionView::CmdTechDrawSectionView() void CmdTechDrawSectionView::activated(int iMsg) { Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { + Gui::TaskView::TaskDialog *dlg = Gui::Control().activeDialog(); + if (dlg) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Task In Progress"), + QObject::tr("Close active task dialog and try again.")); return; } - std::vector baseObj = getSelection().getObjectsOfType(TechDraw::DrawViewPart::getClassTypeId()); - if (baseObj.empty()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select at least 1 DrawViewPart object as Base.")); - return; - } - TechDraw::DrawViewPart* dvp = static_cast(*baseObj.begin()); - Gui::Control().showDialog(new TaskDlgSectionView(dvp)); - - updateActive(); //ok here since dialog doesn't call doc.recompute() - commitCommand(); + execSimpleSection(this); } bool CmdTechDrawSectionView::isActive() @@ -512,6 +607,154 @@ bool CmdTechDrawSectionView::isActive() return (havePage && haveView && !taskInProgress); } +void execSimpleSection(Gui::Command* cmd) +{ + TechDraw::DrawPage* page = DrawGuiUtil::findPage(cmd); + if (!page) { + return; + } + + std::vector baseObj = cmd->getSelection().getObjectsOfType(TechDraw::DrawViewPart::getClassTypeId()); + if (baseObj.empty()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select at least 1 DrawViewPart object as Base.")); + return; + } + TechDraw::DrawViewPart* dvp = static_cast(*baseObj.begin()); + Gui::Control().showDialog(new TaskDlgSectionView(dvp)); + + cmd->updateActive(); //ok here since dialog doesn't call doc.recompute() + cmd->commitCommand(); +} + +//=========================================================================== +// TechDraw_ComplexSection +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawComplexSection) + +CmdTechDrawComplexSection::CmdTechDrawComplexSection() + : Command("TechDraw_ComplexSection") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Complex Section"); + sToolTipText = QT_TR_NOOP("Insert a Complex Section"); + sWhatsThis = "TechDraw_ComplexSection"; + sStatusTip = sToolTipText; + sPixmap = "actions/TechDraw_ComplexSection"; +} + +void CmdTechDrawComplexSection::activated(int iMsg) +{ + Q_UNUSED(iMsg); + Gui::TaskView::TaskDialog *dlg = Gui::Control().activeDialog(); + if (dlg) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Task In Progress"), + QObject::tr("Close active task dialog and try again.")); + return; + } + + execComplexSection(this); +} + +bool CmdTechDrawComplexSection::isActive() +{ + return DrawGuiUtil::needPage(this); +} + +//Complex Sections can be created without a baseView, so the gathering of input +//for the dialog is more involved that simple section +void execComplexSection(Gui::Command* cmd) +{ + TechDraw::DrawPage* page = DrawGuiUtil::findPage(cmd); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + TechDraw::DrawViewPart* baseView(nullptr); + std::vector shapes; + std::vector xShapes; + App::DocumentObject* profileObject(nullptr); + std::vector profileSubs; + 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(TechDraw::DrawViewPart::getClassTypeId())) { + //use the dvp's Sources as sources for this ComplexSection & + //check the subelement(s) to see if they can be used as a profile + baseView = static_cast(obj); + if (!sel.getSubNames().empty()) { + //need to add profile subs as parameter + profileObject = baseView; + profileSubs = sel.getSubNames(); + } + 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's 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. + if (TechDraw::DrawComplexSection::isProfileObject(obj)) { + profileObject = obj; + } else { + shapes.push_back(obj); + } + } + + if (shapes.empty() && + xShapes.empty() && + !baseView) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("No Base View, Shapes, Groups or Links in this selection")); + return; + } + if (!profileObject) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("No profile object found in selection")); + return; + } + + Gui::Control().showDialog(new TaskDlgComplexSection(page, baseView, + shapes, xShapes, + profileObject, profileSubs)); +} + //=========================================================================== // TechDraw_DetailView //=========================================================================== @@ -714,63 +957,6 @@ bool CmdTechDrawProjectionGroup::isActive() return (havePage && !taskInProgress); } -//=========================================================================== -// TechDraw_NewMulti **deprecated** -//=========================================================================== - -//DEF_STD_CMD_A(CmdTechDrawNewMulti); - -//CmdTechDrawNewMulti::CmdTechDrawNewMulti() -// : Command("TechDraw_NewMulti") -//{ -// sAppModule = "TechDraw"; -// sGroup = QT_TR_NOOP("TechDraw"); -// sMenuText = QT_TR_NOOP("Insert multi-part view in drawing"); -// sToolTipText = QT_TR_NOOP("Insert a new View of a multiple Parts in the active drawing"); -// sWhatsThis = "TechDraw_NewMulti"; -// sStatusTip = sToolTipText; -// sPixmap = "actions/TechDraw_Multiview"; -//} - -//void CmdTechDrawNewMulti::activated(int iMsg) -//{ -// Q_UNUSED(iMsg); -// TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); -// if (!page) { -// return; -// } - -// std::vector shapes = getSelection().getObjectsOfType(App::DocumentObject::getClassTypeId()); -// if (shapes.empty()) { -// QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), -// QObject::tr("Can not MultiView from this selection.")); -// return; -// } - -// std::string PageName = page->getNameInDocument(); - -// Gui::WaitCursor wc; - -// openCommand(QT_TRANSLATE_NOOP("Command", "Create view")); -// std::string FeatName = getUniqueObjectName("MultiView"); -// doCommand(Doc, "App.activeDocument().addObject('TechDraw::DrawViewMulti', '%s')", FeatName.c_str()); -// App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); -// auto multiView( static_cast(docObj) ); -// multiView->Sources.setValues(shapes); -// doCommand(Doc, "App.activeDocument().%s.addView(App.activeDocument().%s)", PageName.c_str(), FeatName.c_str()); -// updateActive(); -// commitCommand(); -//} - -//bool CmdTechDrawNewMulti::isActive(void) -//{ -// return DrawGuiUtil::needPage(this); -//} - -//=========================================================================== -// TechDraw_Balloon -//=========================================================================== - //! common checks of Selection for Dimension commands //non-empty selection, no more than maxObjs selected and at least 1 DrawingPage exists bool _checkSelectionBalloon(Gui::Command* cmd, unsigned maxObjs) { @@ -1496,7 +1682,9 @@ void CreateTechDrawCommands() rcCmdMgr.addCommand(new CmdTechDrawPrintAll()); rcCmdMgr.addCommand(new CmdTechDrawView()); rcCmdMgr.addCommand(new CmdTechDrawActiveView()); + rcCmdMgr.addCommand(new CmdTechDrawSectionGroup()); rcCmdMgr.addCommand(new CmdTechDrawSectionView()); + rcCmdMgr.addCommand(new CmdTechDrawComplexSection()); rcCmdMgr.addCommand(new CmdTechDrawDetailView()); rcCmdMgr.addCommand(new CmdTechDrawProjectionGroup()); rcCmdMgr.addCommand(new CmdTechDrawClipGroup()); diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotation.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotation.ui index ecec6074dd..f6e75910e7 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotation.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotation.ui @@ -6,8 +6,8 @@ 0 0 - 440 - 447 + 450 + 541 @@ -33,21 +33,34 @@ - - - + + + - true + false - Section Line Standard + Detail View Outline Shape - - + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + 0 @@ -56,43 +69,137 @@ - 184 - 22 + 0 + 0 + + + + + true + + + + Show arc center marks in views + + + Show Center Marks + + + true + + + ShowCenterMarks + + + Mod/TechDraw/Decorations + + + + + + + + 0 + 0 + + + + + 0 + 0 - Standard to be used to draw section lines + Show arc centers in printed output - - 1 + + Print Center Marks - SectionLineStandard + PrintCenterMarks - Mod/TechDraw/Standards + Mod/TechDraw/Decorations + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Style for balloon leader line ends + + + BalloonArrow + + + Mod/TechDraw/Decorations + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Outline shape for detail views + + + MattingStyle + + + /Mod/TechDraw/Decorations - ANSI + Circle + + + + :/icons/circular.svg:/icons/circular.svg - ISO + Square + + + + :/icons/square.svg:/icons/square.svg - - + + true - Section Line Style + Balloon Shape @@ -178,76 +285,87 @@ - - + + true - Section Cut Surface + Section Line Standard - - + + + + + 0 + 0 + + - 0 + 184 22 - Default appearance of cut surface in section view + Standard to be used to draw section lines - 2 + 1 - CutSurfaceDisplay + SectionLineStandard - /Mod/TechDraw/Decorations + Mod/TechDraw/Standards - Hide + ANSI - Solid Color - - - - - SVG Hatch - - - - - PAT Hatch + ISO - - + + true - Line group used to set line widths + Line style of detail highlight on base view - Line Width Group + Detail Highlight Style - + + + + + true + + + + Length of horizontal portion of Balloon leader + + + Ballon Leader Kink Length + + + + @@ -272,77 +390,7 @@ - - - - - false - - - - Detail View Outline Shape - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - Outline shape for detail views - - - MattingStyle - - - /Mod/TechDraw/Decorations - - - - Circle - - - - :/icons/circular.svg:/icons/circular.svg - - - - - Square - - - - :/icons/square.svg:/icons/square.svg - - - - - - - - - true - - - - Line style of detail highlight on base view - - - Detail Highlight Style - - - - + @@ -415,33 +463,8 @@ - - - - - true - - - - Center Line Style - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + + 0 @@ -455,86 +478,66 @@ - Type for centerlines + Length of balloon leader line kink - - 2 + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 5.000000000000000 - CenterLine + BalloonKink - /Mod/TechDraw/Decorations + Mod/TechDraw/Dimensions - - - NeverShow - - - - :/icons/arrownone.svg:/icons/arrownone.svg - - - - - Continuous - - - - :/icons/continuous-line.svg:/icons/continuous-line.svg - - - - - Dash - - - - :/icons/dash-line.svg:/icons/dash-line.svg - - - - - Dot - - - - :/icons/dot-line.svg:/icons/dot-line.svg - - - - - DashDot - - - - :/icons/dashDot-line.svg:/icons/dashDot-line.svg - - - - - DashDotDot - - - - :/icons/dashDotDot-line.svg:/icons/dashDotDot-line.svg - - - - + + true - Balloon Shape + Balloon Leader End - + + + + + 0 + 0 + + + + + 0 + 0 + + + + Restrict Filled Triangle line end to vertical or horizontal directions + + + Balloon Orthogonal Triangle + + + true + + + PyramidOrtho + + + Mod/TechDraw/Decorations + + + + @@ -631,121 +634,101 @@ - - + + + + + 0 + 0 + + + + + 0 + 0 + + + + Type for centerlines + + + 2 + + + CenterLine + + + /Mod/TechDraw/Decorations + + + + NeverShow + + + + :/icons/arrownone.svg:/icons/arrownone.svg + + + + + Continuous + + + + :/icons/continuous-line.svg:/icons/continuous-line.svg + + + + + Dash + + + + :/icons/dash-line.svg:/icons/dash-line.svg + + + + + Dot + + + + :/icons/dot-line.svg:/icons/dot-line.svg + + + + + DashDot + + + + :/icons/dashDot-line.svg:/icons/dashDot-line.svg + + + + + DashDotDot + + + + :/icons/dashDotDot-line.svg:/icons/dashDotDot-line.svg + + + + + + true - Balloon Leader End + Section Cut Surface - - - - - 0 - 0 - - - - - 0 - 0 - - - - Style for balloon leader line ends - - - BalloonArrow - - - Mod/TechDraw/Decorations - - - - - - - - true - - - - Length of horizontal portion of Balloon leader - - - Ballon Leader Kink Length - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - Length of balloon leader line kink - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 5.000000000000000 - - - BalloonKink - - - Mod/TechDraw/Dimensions - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - Restrict Filled Triangle line end to vertical or horizontal directions - - - Balloon Orthogonal Triangle - - - true - - - PyramidOrtho - - - Mod/TechDraw/Decorations - - - - + @@ -776,71 +759,112 @@ - - - - - 0 - 0 - + + + + + true + + + Section Line Style + + + + + + + + true + + + + Center Line Style + + + + + 0 - 0 + 22 + + Default appearance of cut surface in section view + + + 2 + + + CutSurfaceDisplay + + + /Mod/TechDraw/Decorations + + + + Hide + + + + + Solid Color + + + + + SVG Hatch + + + + + PAT Hatch + + + + + + true - Show arc center marks in views + Line group used to set line widths - Show Center Marks + Line Width Group + + + + + + + + true + + + + Show or hide marks at direction changes on ComplexSection lines. + + + Complex Section Line Marks true - ShowCenterMarks + SectionLineMarks Mod/TechDraw/Decorations - - - - - 0 - 0 - - - - - 0 - 0 - - - - Show arc centers in printed output - - - Print Center Marks - - - PrintCenterMarks - - - Mod/TechDraw/Decorations - - - - + @@ -854,7 +878,7 @@ - <html><head/><body><p><span style=" font-weight:600;">Note:</span> Items in <span style=" font-style:italic;">italics</span> are default values for new objects. They have no effect on existing objects.</p></body></html> + <html><head/><body><p><span style=" font-weight:600;">Note:</span> Items in <span style=" font-style:italic;">italics</span> are default values for new objects. They have no effect on existing objects.</p></body></html> true diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotationImp.cpp b/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotationImp.cpp index 863294f18f..622df06b9a 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotationImp.cpp +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDrawAnnotationImp.cpp @@ -62,6 +62,7 @@ void DlgPrefsTechDrawAnnotationImp::saveSettings() ui->cbPrintCenterMarks->onSave(); ui->cbPyramidOrtho->onSave(); ui->cbSectionLineStd->onSave(); + ui->cbComplexMarks->onSave(); ui->cbShowCenterMarks->onSave(); ui->pcbLineGroup->onSave(); ui->pcbBalloonArrow->onSave(); @@ -100,6 +101,7 @@ void DlgPrefsTechDrawAnnotationImp::loadSettings() ui->cbPrintCenterMarks->onRestore(); ui->cbPyramidOrtho->onRestore(); ui->cbSectionLineStd->onRestore(); + ui->cbComplexMarks->onRestore(); ui->cbShowCenterMarks->onRestore(); ui->pcbLineGroup->onRestore(); ui->pcbBalloonArrow->onRestore(); diff --git a/src/Mod/TechDraw/Gui/PreferencesGui.cpp b/src/Mod/TechDraw/Gui/PreferencesGui.cpp index 61e3d04627..09d3103607 100644 --- a/src/Mod/TechDraw/Gui/PreferencesGui.cpp +++ b/src/Mod/TechDraw/Gui/PreferencesGui.cpp @@ -181,6 +181,13 @@ Qt::PenStyle PreferencesGui::sectionLineStyle() return sectStyle; } +bool PreferencesGui::sectionLineMarks() +{ + Base::Reference hGrp = App::GetApplication().GetUserParameter(). + GetGroup("BaseApp")->GetGroup("Preferences")-> + GetGroup("Mod/TechDraw/Decorations"); + return hGrp->GetBool("SectionLineMarks", true); +} QString PreferencesGui::weldingDirectory() { diff --git a/src/Mod/TechDraw/Gui/PreferencesGui.h b/src/Mod/TechDraw/Gui/PreferencesGui.h index 7ea07d762b..a42d1ca900 100644 --- a/src/Mod/TechDraw/Gui/PreferencesGui.h +++ b/src/Mod/TechDraw/Gui/PreferencesGui.h @@ -62,6 +62,7 @@ static double dimArrowSize(); static double edgeFuzz(); static Qt::PenStyle sectionLineStyle(); +static bool sectionLineMarks(); static QString weldingDirectory(); diff --git a/src/Mod/TechDraw/Gui/QGIDecoration.h b/src/Mod/TechDraw/Gui/QGIDecoration.h index a32228b494..3e9cb8a328 100644 --- a/src/Mod/TechDraw/Gui/QGIDecoration.h +++ b/src/Mod/TechDraw/Gui/QGIDecoration.h @@ -55,6 +55,7 @@ public: virtual void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = nullptr ); virtual void draw(); void setWidth(double w); + double getWidth() { return m_width; } void setStyle(Qt::PenStyle s); void setColor(QColor c); QColor getColor(void) { return m_colNormal; } diff --git a/src/Mod/TechDraw/Gui/QGISectionLine.cpp b/src/Mod/TechDraw/Gui/QGISectionLine.cpp index 20bdfcecd5..e6dd3ec762 100644 --- a/src/Mod/TechDraw/Gui/QGISectionLine.cpp +++ b/src/Mod/TechDraw/Gui/QGISectionLine.cpp @@ -23,6 +23,7 @@ #include "PreCompiled.h" #ifndef _PreComp_ #include +#include #include #include #include @@ -43,23 +44,30 @@ #include "QGIView.h" #include "PreferencesGui.h" #include "Rez.h" +#include "ZVALUE.h" #define ANSISTANDARD 0 #define ISOSTANDARD 1 +#define SINGLEDIRECTIONMODE 0 //both arrows point along the section normal +#define MULTIDIRECTIONMODE 1 //the arrows point along the normal of their section line segment using namespace TechDrawGui; using namespace TechDraw; -QGISectionLine::QGISectionLine() +QGISectionLine::QGISectionLine() : + m_pathMode(false), + m_arrowMode() { m_symbol = ""; m_symSize = 0.0; - m_extLen = 1.5 * Rez::guiX(QGIArrow::getPrefArrowSize()); + m_extLen = 1.5 * Rez::guiX(QGIArrow::getPrefArrowSize()); //is there a standard for this?? m_arrowSize = QGIArrow::getPrefArrowSize(); m_line = new QGraphicsPathItem(); addToGroup(m_line); + m_extend = new QGraphicsPathItem(); + addToGroup(m_extend); m_arrow1 = new QGIArrow(); addToGroup(m_arrow1); m_arrow2 = new QGIArrow(); @@ -69,7 +77,7 @@ QGISectionLine::QGISectionLine() m_symbol2 = new QGCustomText(); addToGroup(m_symbol2); - setWidth(Rez::guiX(0.75)); + setWidth(Rez::guiX(0.75)); //a default? setStyle(getSectionStyle()); setColor(getSectionColor()); @@ -85,14 +93,25 @@ void QGISectionLine::draw() extensionEndsISO(); } - makeLine(); + if (!pathMode()) { + makeSectionLine(); + } + makeExtensionLine(); makeArrows(); makeSymbols(); + makeChangePointMarks(); update(); } -void QGISectionLine::makeLine() +void QGISectionLine::makeExtensionLine() { + QPen extendPen; + extendPen.setWidthF(getWidth()); + extendPen.setColor(getSectionColor()); + extendPen.setStyle(Qt::SolidLine); + extendPen.setCapStyle(Qt::FlatCap); + m_extend->setPen(extendPen); + QPainterPath pp; pp.moveTo(m_beginExt1); @@ -100,7 +119,13 @@ void QGISectionLine::makeLine() pp.moveTo(m_beginExt2); pp.lineTo(m_endExt2); + m_extend->setPath(pp); +} +//make the traditional straight section line +void QGISectionLine::makeSectionLine() +{ + QPainterPath pp; pp.moveTo(m_start); pp.lineTo(m_end); m_line->setPath(pp); @@ -119,62 +144,56 @@ void QGISectionLine::makeArrows() //make Euro (ISO) Arrows void QGISectionLine::makeArrowsISO() { - double arrowRotation = 0.0; - m_arrowDir.Normalize(); - double angle = atan2f(m_arrowDir.y, m_arrowDir.x); - if (angle < 0.0) { - angle = 2 * M_PI + angle; - } - arrowRotation = 360.0 - angle * (180.0/M_PI); //convert to Qt rotation (clockwise degrees) - m_arrow1->setStyle(0); m_arrow1->setSize(QGIArrow::getPrefArrowSize()); m_arrow1->setPos(m_start); - m_arrow1->draw(); - m_arrow1->setRotation(arrowRotation); //rotation = 0 ==> -> horizontal, pointing right - m_arrow2->setStyle(0); m_arrow2->setSize(QGIArrow::getPrefArrowSize()); m_arrow2->setPos(m_end); + + if (m_arrowMode == SINGLEDIRECTIONMODE) { + double arrowRotation = getArrowRotation(m_arrowDir); + m_arrow1->setRotation(arrowRotation); //rotation = 0 ==> -> horizontal, pointing right + m_arrow2->setRotation(arrowRotation); + } else { + double arrowRotation1 = getArrowRotation(m_arrowDir1); + m_arrow1->setRotation(arrowRotation1); + double arrowRotation2 = getArrowRotation(m_arrowDir2); + m_arrow2->setRotation(arrowRotation2); + } + m_arrow1->draw(); m_arrow2->draw(); - m_arrow2->setRotation(arrowRotation); } //make traditional (ASME) section arrows void QGISectionLine::makeArrowsTrad() { - double arrowRotation = 0.0; - m_arrowDir.Normalize(); - double angle = atan2f(m_arrowDir.y, m_arrowDir.x); - if (angle < 0.0) { - angle = 2 * M_PI + angle; - } - arrowRotation = 360.0 - angle * (180.0/M_PI); //convert to Qt rotation (clockwise degrees) - - QPointF posArrow1, posArrow2; - QPointF offsetDir(m_arrowDir.x, -m_arrowDir.y); //remember Y dir is flipped - - double oblique = 1.0; - if ( !DrawUtil::fpCompare((m_arrowDir.x + m_arrowDir.y), 1.0) ) { - oblique = 1.25; - } - double offsetLength = (m_extLen * oblique) + Rez::guiX(QGIArrow::getPrefArrowSize()); - QPointF offsetVec = offsetLength * offsetDir; - - posArrow1 = m_start + offsetVec; - posArrow2 = m_end + offsetVec; - m_arrow1->setStyle(0); m_arrow1->setSize(QGIArrow::getPrefArrowSize()); - m_arrow1->setPos(posArrow1); - m_arrow1->draw(); - m_arrow1->setRotation(arrowRotation); //rotation = 0 ==> -> horizontal, pointing right - m_arrow2->setStyle(0); m_arrow2->setSize(QGIArrow::getPrefArrowSize()); - m_arrow2->setPos(posArrow2); + + if (m_arrowMode == SINGLEDIRECTIONMODE) { + double arrowRotation = getArrowRotation(m_arrowDir); + m_arrow1->setRotation(arrowRotation); //rotation = 0 ==> -> horizontal, pointing right + m_arrow2->setRotation(arrowRotation); + m_arrowPos1 = getArrowPosition(m_arrowDir, m_start); + m_arrow1->setPos(m_arrowPos1); + m_arrowPos2 = getArrowPosition(m_arrowDir, m_end); + m_arrow2->setPos(m_arrowPos2); + } else { + double arrowRotation1 = getArrowRotation(m_arrowDir1); + m_arrow1->setRotation(arrowRotation1); + m_arrowPos1 = getArrowPosition(m_arrowDir1, m_start); + m_arrow1->setPos(m_arrowPos1); + double arrowRotation2 = getArrowRotation(m_arrowDir2); + m_arrow2->setRotation(arrowRotation2); + m_arrowPos2 = getArrowPosition(m_arrowDir2, m_end); + m_arrow2->setPos(m_arrowPos2); + } + + m_arrow1->draw(); m_arrow2->draw(); - m_arrow2->setRotation(arrowRotation); } void QGISectionLine::makeSymbols() @@ -187,10 +206,10 @@ void QGISectionLine::makeSymbols() } } +//arrows go at arrowhead position. void QGISectionLine::makeSymbolsTrad() { prepareGeometryChange(); -// m_symFont.setPixelSize(QGIView::calculateFontPixelSize(m_symSize)); int fontSize = QGIView::exactFontSize(Base::Tools::toStdString(m_symFont.family()), m_symSize); m_symFont.setPixelSize(fontSize); m_symbol1->setFont(m_symFont); @@ -199,33 +218,28 @@ void QGISectionLine::makeSymbolsTrad() m_symbol2->setPlainText(QString::fromUtf8(m_symbol)); QRectF symRect = m_symbol1->boundingRect(); - double symWidth = symRect.width(); double symHeight = symRect.height(); - double symbolFudge = 0.75; - double angle = atan2f(m_arrowDir.y, m_arrowDir.x); - if (angle < 0.0) { - angle = 2 * M_PI + angle; - } - Base::Vector3d adjustVector(cos(angle) * symWidth, sin(angle) * symHeight, 0.0); - adjustVector = DrawUtil::invertY(adjustVector) * symbolFudge; - QPointF qAdjust(adjustVector.x, adjustVector.y); + double gap = 0.5 * symHeight; //symHeight as surrogate for char box - QPointF posSymbol1 = m_arrow1->pos() + qAdjust; - m_symbol1->centerAt(posSymbol1); + QPointF motion1(m_arrowDir1.x, -m_arrowDir1.y); //move in same direction as arrow + QPointF motion2(m_arrowDir2.x, -m_arrowDir2.y); //Qt y coords! - QPointF posSymbol2 = m_arrow2->pos() + qAdjust; - m_symbol2->centerAt(posSymbol2); + QPointF symPos1 = m_arrowPos1 + motion1 * gap; + QPointF symPos2 = m_arrowPos2 + motion2 * gap; - m_symbol1->setTransformOriginPoint(m_symbol1->mapFromParent(posSymbol1)); - m_symbol1->setRotation(360.0 - rotation()); - m_symbol2->setTransformOriginPoint(m_symbol2->mapFromParent(posSymbol2)); + m_symbol1->centerAt(symPos1); + m_symbol2->centerAt(symPos2); + + m_symbol1->setTransformOriginPoint(m_symbol1->mapFromParent(symPos1)); + m_symbol1->setRotation(360.0 - rotation()); //to Qt angle + m_symbol2->setTransformOriginPoint(m_symbol2->mapFromParent(symPos2)); m_symbol2->setRotation(360.0 - rotation()); } +//symbols go at ends of extensions void QGISectionLine::makeSymbolsISO() { prepareGeometryChange(); -// m_symFont.setPixelSize(QGIView::calculateFontPixelSize(m_symSize)); int fontSize = QGIView::exactFontSize(Base::Tools::toStdString(m_symFont.family()), m_symSize); m_symFont.setPixelSize(fontSize); m_symbol1->setFont(m_symFont); @@ -233,79 +247,111 @@ void QGISectionLine::makeSymbolsISO() m_symbol2->setFont(m_symFont); m_symbol2->setPlainText(QString::fromUtf8(m_symbol)); - QPointF symPosStart, symPosEnd; - //no normalize() for QPointF - QPointF dist = (m_start - m_end); - double lenDist = sqrt(dist.x()*dist.x() + dist.y()*dist.y()); - QPointF offsetDir = dist / lenDist; - QRectF symRect = m_symbol1->boundingRect(); - double symWidth = symRect.width(); double symHeight = symRect.height(); + double gap = 0.5 * symHeight; //symHeight as surrogate for char box - double symbolFudge = 0.75; - double angle = atan2f(offsetDir.y(), offsetDir.x()); - if (angle < 0.0) { - angle = 2.0 * M_PI + angle; - } - Base::Vector3d adjustVector(cos(angle) * symWidth, sin(angle) * symHeight, 0.0); - adjustVector = adjustVector * symbolFudge; - QPointF qAdjust(adjustVector.x, adjustVector.y); + QPointF motion1(-m_arrowDir1.x, m_arrowDir1.y); //move away from extension end + QPointF motion2(-m_arrowDir2.x, m_arrowDir2.y); //Qt y coords! - symPosStart = m_start + qAdjust; - symPosEnd = m_end - qAdjust; + QPointF symPos1 = m_endExt1 + motion1 * gap; + QPointF symPos2 = m_endExt2 + motion2 * gap; - m_symbol1->centerAt(symPosStart); - m_symbol2->centerAt(symPosEnd); + m_symbol1->centerAt(symPos1); + m_symbol2->centerAt(symPos2); - m_symbol1->setTransformOriginPoint(m_symbol1->mapFromParent(symPosStart)); + m_symbol1->setTransformOriginPoint(m_symbol1->mapFromParent(symPos1)); m_symbol1->setRotation(360.0 - rotation()); - m_symbol2->setTransformOriginPoint(m_symbol2->mapFromParent(symPosEnd)); + m_symbol2->setTransformOriginPoint(m_symbol2->mapFromParent(symPos2)); m_symbol2->setRotation(360.0 - rotation()); - } +//extension lines are on the stock side of the section line void QGISectionLine::extensionEndsTrad() { - QPointF offsetDir(m_arrowDir.x, -m_arrowDir.y); + if (m_arrowMode == SINGLEDIRECTIONMODE) { + QPointF offsetDir(m_arrowDir.x, -m_arrowDir.y); //inverted Y + offsetDir = normalizeQPointF(offsetDir); - //extensions for oblique section line needs to be a bit longer - double oblique = 1.0; - if ( !DrawUtil::fpCompare((m_arrowDir.x + m_arrowDir.y), 1.0) ) { - oblique = 1.25; + //draw from section line endpoint + QPointF offsetEnd = m_extLen * offsetDir; + m_beginExt1 = m_start; + m_endExt1 = m_start + offsetEnd; + m_beginExt2 = m_end; + m_endExt2 = m_end + offsetEnd; + } else { + //extension lines run from point on section line to arrowhead + m_beginExt1 = m_start; + m_endExt1 = getArrowPosition(m_arrowDir1, m_start); + m_beginExt2 = m_end; + m_endExt2 = getArrowPosition(m_arrowDir2, m_end); } - - //draw from section line endpoint - QPointF offsetEnd = oblique * m_extLen * offsetDir; - m_beginExt1 = m_start; - m_endExt1 = m_start + offsetEnd; - m_beginExt2 = m_end; - m_endExt2 = m_end + offsetEnd; } +//the extension lines are on the waste side of the section line! void QGISectionLine::extensionEndsISO() { - //lines are offset to other side of section line! - QPointF offsetDir(m_arrowDir.x, -m_arrowDir.y); - offsetDir = offsetDir * -1.0; + if (m_arrowMode == SINGLEDIRECTIONMODE) { + QPointF offsetDir(-m_arrowDir.x, m_arrowDir.y); //reversed and inverted y + offsetDir = normalizeQPointF(offsetDir); - //extensions for oblique section line needs to be a bit longer? - //this is just esthetics - double oblique = 1.0; - if ( !DrawUtil::fpCompare((m_arrowDir.x + m_arrowDir.y), 1.0) ) { - oblique = 1.10; + //draw from section line endpoint less arrow length + QPointF offsetStart = offsetDir * Rez::guiX(QGIArrow::getPrefArrowSize()); + QPointF offsetEnd = m_extLen * offsetDir; + + m_beginExt1 = m_start + offsetStart; + m_endExt1 = m_start + offsetStart + offsetEnd; + m_beginExt2 = m_end + offsetStart; + m_endExt2 = m_end + offsetStart + offsetEnd; + } else { + //extension lines run in reverse of arrow direction from base of arrowhead for distance m_extLen + QPointF offsetDir1(-m_arrowDir1.x, m_arrowDir1.y); //reversed and inverted y + offsetDir1 = normalizeQPointF(offsetDir1); + QPointF offsetStart1 = offsetDir1 * Rez::guiX(QGIArrow::getPrefArrowSize()); + QPointF offsetEnd1 = m_extLen * offsetDir1; + m_beginExt1 = m_start + offsetStart1; + m_endExt1 = m_start + offsetStart1 + offsetEnd1; + + QPointF offsetDir2(-m_arrowDir2.x, m_arrowDir2.y); //reversed and inverted y + offsetDir2 = normalizeQPointF(offsetDir2); + QPointF offsetStart2 = offsetDir2 * Rez::guiX(QGIArrow::getPrefArrowSize()); + QPointF offsetEnd2 = m_extLen * offsetDir2; + m_beginExt2 = m_end + offsetStart2; + m_endExt2 = m_end + offsetStart2 + offsetEnd2; } - - //draw from section line endpoint less arrow length - QPointF offsetStart = offsetDir * Rez::guiX(QGIArrow::getPrefArrowSize()); - QPointF offsetEnd = oblique * m_extLen * offsetDir; - - m_beginExt1 = m_start + offsetStart; - m_endExt1 = m_start + offsetStart + offsetEnd; - m_beginExt2 = m_end + offsetStart; - m_endExt2 = m_end + offsetStart + offsetEnd; } +void QGISectionLine::makeChangePointMarks() +{ +// Base::Console().Message("QGISL::makeChangePointMarks()\n"); + double segmentLength = 0.50 * QGIArrow::getPrefArrowSize(); + QPen cPointPen; + //TODO: this should really be 2.0 * thickLineWidth, but we only have one + //width available (which should be 'thin', for the section line) + cPointPen.setWidthF(2.0 * getWidth()); + cPointPen.setColor(getSectionColor()); + cPointPen.setStyle(Qt::SolidLine); + for (auto& cPoint : m_changePointData) { + QGraphicsPathItem* cPointItem = new QGraphicsPathItem(); + addToGroup(cPointItem); + + QPainterPath pPath; + QPointF location = cPoint.getLocation(); + QPointF start = location + cPoint.getPreDirection() * segmentLength; + QPointF end = location + cPoint.getPostDirection() * segmentLength; + pPath.moveTo(Rez::guiPt(start)); + pPath.lineTo(Rez::guiPt(location)); + pPath.lineTo(Rez::guiPt(end)); + + cPointItem->setPath(pPath); + cPointItem->setPen(cPointPen); + cPointItem->setZValue(ZVALUE::SECTIONLINE + 1); + cPointItem->setPos(0.0, 0.0); + m_changePointMarks.push_back(cPointItem); + } +} + + void QGISectionLine::setEnds(Base::Vector3d l1, Base::Vector3d l2) { m_l1 = l1; @@ -333,8 +379,46 @@ void QGISectionLine::setDirection(double xDir, double yDir) void QGISectionLine::setDirection(Base::Vector3d dir) { + m_arrowMode = SINGLEDIRECTIONMODE; m_arrowDir = dir; m_arrowDir.Normalize(); + m_arrowDir1 = dir; + m_arrowDir1.Normalize(); + m_arrowDir2 = dir; + m_arrowDir2.Normalize(); +} + +void QGISectionLine::setArrowDirections(Base::Vector3d dir1, Base::Vector3d dir2) +{ + m_arrowMode = MULTIDIRECTIONMODE; + m_arrowDir1 = dir1; + m_arrowDir1.Normalize(); + m_arrowDir2 = dir2; + m_arrowDir2.Normalize(); +} + +//convert an arrow direction vector into a Qt rotation angle degrees +double QGISectionLine::getArrowRotation(Base::Vector3d arrowDir) +{ + arrowDir.Normalize(); + double angle = atan2f(arrowDir.y, arrowDir.x); + if (angle < 0.0) { + angle = 2 * M_PI + angle; + } + double arrowRotation = 360.0 - angle * (180.0/M_PI); //convert to Qt rotation (clockwise degrees) + return arrowRotation; +} + +QPointF QGISectionLine::getArrowPosition(Base::Vector3d arrowDir, QPointF refPoint) +{ + QPointF qArrowDir(arrowDir.x, -arrowDir.y); //remember Y dir is flipped + qArrowDir = normalizeQPointF(qArrowDir); + + double offsetLength = m_extLen + Rez::guiX(QGIArrow::getPrefArrowSize()); + QPointF offsetVec = offsetLength * qArrowDir; + + QPointF arrowPos = refPoint + offsetVec; + return arrowPos; } void QGISectionLine::setFont(QFont f, double fsize) @@ -343,6 +427,44 @@ void QGISectionLine::setFont(QFont f, double fsize) m_symSize = fsize; } +void QGISectionLine::setPath(QPainterPath& path) +{ + m_line->setPath(path); +} + +void QGISectionLine::setChangePoints(TechDraw::ChangePointVector changePointData) +{ + m_changePointData = changePointData; + clearChangePointMarks(); +} + +void QGISectionLine::clearChangePoints() +{ + clearChangePointMarks(); + m_changePointData.clear(); +} + +void QGISectionLine::clearChangePointMarks() +{ + if (!m_changePointMarks.empty()) { + for (auto& cPoint : m_changePointMarks) { + cPoint->hide(); + scene()->removeItem(cPoint); + delete cPoint; + } + m_changePointMarks.clear(); + } +} + +//QPointF does not have length or normalize methods +QPointF QGISectionLine::normalizeQPointF(QPointF inPoint) +{ + double x2 = inPoint.x() * inPoint.x(); + double y2 = inPoint.y() * inPoint.y(); + double root = sqrt(x2 + y2); + return inPoint / root; +} + void QGISectionLine::setSectionColor(QColor c) { setColor(c); @@ -416,11 +538,6 @@ void QGISectionLine::setTools() m_line->setPen(m_pen); -// m_arrow1->setPen(m_pen); -// m_arrow2->setPen(m_pen); -// m_arrow1->setBrush(m_brush); -// m_arrow2->setBrush(m_brush); - m_arrow1->setNormalColor(m_colCurrent); m_arrow1->setFillColor(m_colCurrent); m_arrow1->setPrettyNormal(); diff --git a/src/Mod/TechDraw/Gui/QGISectionLine.h b/src/Mod/TechDraw/Gui/QGISectionLine.h index fa23302ad3..c2bd2e1767 100644 --- a/src/Mod/TechDraw/Gui/QGISectionLine.h +++ b/src/Mod/TechDraw/Gui/QGISectionLine.h @@ -28,9 +28,12 @@ #include #include #include +#include #include +#include + #include "QGCustomText.h" #include "QGIDecoration.h" @@ -53,34 +56,46 @@ public: void setEnds(Base::Vector3d l1, Base::Vector3d l2); void setBounds(double x1, double y1, double x2, double y2); + void setPath(QPainterPath& path); void setSymbol(char* sym); void setDirection(double xDir, double yDir); void setDirection(Base::Vector3d dir); + void setArrowDirections(Base::Vector3d dir1, Base::Vector3d dir2); void setFont(QFont f, double fsize); void setSectionStyle(int style); void setSectionColor(QColor c); - + void setPathMode(bool mode) { m_pathMode = mode; } + bool pathMode() { return m_pathMode; } + void setChangePoints(TechDraw::ChangePointVector changePoints); + void clearChangePoints(); virtual void draw(); protected: QColor getSectionColor(); Qt::PenStyle getSectionStyle(); - void makeLine(); + void makeSectionLine(); + void makeExtensionLine(); void makeArrows(); void makeArrowsTrad(); void makeArrowsISO(); void makeSymbols(); void makeSymbolsTrad(); void makeSymbolsISO(); + void makeChangePointMarks(); void setTools(); int getPrefSectionStandard(); void extensionEndsISO(); void extensionEndsTrad(); + double getArrowRotation(Base::Vector3d arrowDir); + QPointF getArrowPosition(Base::Vector3d arrowDir, QPointF refPoint); + void clearChangePointMarks(); + static QPointF normalizeQPointF(QPointF inPoint); private: char* m_symbol; - QGraphicsPathItem* m_line; //primpath? + QGraphicsPathItem* m_line; + QGraphicsPathItem* m_extend; QGIArrow* m_arrow1; QGIArrow* m_arrow2; QGCustomText* m_symbol1; @@ -92,15 +107,21 @@ private: QFont m_symFont; double m_symSize; double m_arrowSize; - //QColor m_color; double m_extLen; -// int m_sectionFormat; //0 = ASME, 1 = ISO Base::Vector3d m_l1; //end of main section line Base::Vector3d m_l2; //end of main section line QPointF m_beginExt1; //start of extension line 1 QPointF m_endExt1; //end of extension line 1 QPointF m_beginExt2; //start of extension line 2 QPointF m_endExt2; //end of extension line 1 + bool m_pathMode; //use external path for line + int m_arrowMode; //0 = 1 direction for both arrows, 1 = direction for each arrow + Base::Vector3d m_arrowDir1; + Base::Vector3d m_arrowDir2; + QPointF m_arrowPos1; + QPointF m_arrowPos2; + std::vector m_changePointMarks; + TechDraw::ChangePointVector m_changePointData; }; } diff --git a/src/Mod/TechDraw/Gui/QGIViewPart.cpp b/src/Mod/TechDraw/Gui/QGIViewPart.cpp index 6f56bf32f5..e4e7f41dca 100644 --- a/src/Mod/TechDraw/Gui/QGIViewPart.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewPart.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -446,10 +447,10 @@ void QGIViewPart::drawViewPart() if (!vp) return; - float lineWidth = vp->LineWidth.getValue() * lineScaleFactor; - float lineWidthHid = vp->HiddenWidth.getValue() * lineScaleFactor; - float lineWidthIso = vp->IsoWidth.getValue() * lineScaleFactor; -// float lineWidthExtra = viewPart->ExtraWidth.getValue() * lineScaleFactor; + float lineWidth = vp->LineWidth.getValue() * lineScaleFactor; //thick + float lineWidthHid = vp->HiddenWidth.getValue() * lineScaleFactor; //thin + float lineWidthIso = vp->IsoWidth.getValue() * lineScaleFactor; //graphic +// float lineWidthExtra = viewPart->ExtraWidth.getValue() * lineScaleFactor; //extra bool showAll = vp->ShowAllEdges.getValue(); prepareGeometryChange(); @@ -803,7 +804,11 @@ void QGIViewPart::drawAllSectionLines() if (vp->ShowSectionLine.getValue()) { auto refs = viewPart->getSectionRefs(); for (auto& r:refs) { - drawSectionLine(r, true); + if (r->isDerivedFrom(DrawComplexSection::getClassTypeId())) { + drawComplexSectionLine(r, true); + } else { + drawSectionLine(r, true); + } } } } @@ -820,8 +825,10 @@ void QGIViewPart::drawSectionLine(TechDraw::DrawViewSection* viewSection, bool b return; auto vp = static_cast(getViewProvider(getViewObject())); - if (!vp) + if (!vp) { return; + } + float lineWidthThin = vp->HiddenWidth.getValue() * lineScaleFactor; //thin if (b) { QGISectionLine* sectionLine = new QGISectionLine(); @@ -829,7 +836,7 @@ void QGIViewPart::drawSectionLine(TechDraw::DrawViewSection* viewSection, bool b sectionLine->setSymbol(const_cast(viewSection->SectionSymbol.getValue())); sectionLine->setSectionStyle(vp->SectionLineStyle.getValue()); sectionLine->setSectionColor(vp->SectionLineColor.getValue().asValue()); - + sectionLine->setPathMode(false); //find the ends of the section line double scale = viewPart->getScale(); std::pair sLineEnds = viewSection->sectionLineEnds(); @@ -839,11 +846,8 @@ void QGIViewPart::drawSectionLine(TechDraw::DrawViewSection* viewSection, bool b //which way to the arrows point? Base::Vector3d lineDir = l2 - l1; lineDir.Normalize(); - Base::Vector3d normalDir = viewSection->SectionNormal.getValue(); - Base::Vector3d projNormal = viewPart->projectPoint(normalDir); - projNormal.Normalize(); Base::Vector3d arrowDir = viewSection->SectionNormal.getValue(); - arrowDir = - viewPart->projectPoint(arrowDir); //arrows point reverse of sectionNormal(extrusion dir) + arrowDir = - viewPart->projectPoint(arrowDir); //arrows point reverse of sectionNormal sectionLine->setDirection(arrowDir.x, -arrowDir.y); //invert Y //make the section line a little longer @@ -853,7 +857,7 @@ void QGIViewPart::drawSectionLine(TechDraw::DrawViewSection* viewSection, bool b //set the general parameters sectionLine->setPos(0.0, 0.0); - sectionLine->setWidth(Rez::guiX(vp->LineWidth.getValue())); + sectionLine->setWidth(lineWidthThin); double fontSize = Preferences::dimFontSizeMM(); sectionLine->setFont(getFont(), fontSize); sectionLine->setZValue(ZVALUE::SECTIONLINE); @@ -862,6 +866,73 @@ void QGIViewPart::drawSectionLine(TechDraw::DrawViewSection* viewSection, bool b } } +void QGIViewPart::drawComplexSectionLine(TechDraw::DrawViewSection* viewSection, bool b) +{ + Q_UNUSED(b); + TechDraw::DrawViewPart *viewPart = static_cast(getViewObject()); + if (!viewPart) + return; + if (!viewSection) + return; + auto vp = static_cast(getViewProvider(getViewObject())); + if (!vp) { + return; + } + float lineWidthThin = vp->HiddenWidth.getValue() * lineScaleFactor; //thin + + auto dcs = static_cast(viewSection); + BaseGeomPtrVector edges = dcs->makeSectionLineGeometry(); + QPainterPath wirePath; + QPainterPath firstSeg = drawPainterPath(edges.front()); + wirePath.connectPath(firstSeg); + int edgeCount = edges.size(); + //NOTE: if the edges are not in nose to tail order, Qt will insert extra segments + //that will overlap the segments we add. for interupted line styles, this + //will make the line look continuous. This is prevented in + //DrawComplexSection::makeSectionLineGeometry by calling makeNoseToTailWire + for (int i = 1; i < edgeCount; i++) { + QPainterPath edgePath = drawPainterPath(edges.at(i)); + wirePath.connectPath(edgePath); + } + + std::pair ends = dcs->sectionLineEnds(); + Base::Vector3d vStart = Rez::guiX(ends.first); //already scaled by dcs + Base::Vector3d vEnd = Rez::guiX(ends.second); + + QGISectionLine* sectionLine = new QGISectionLine(); + addToGroup(sectionLine); + sectionLine->setSymbol(const_cast(viewSection->SectionSymbol.getValue())); + sectionLine->setSectionStyle(vp->SectionLineStyle.getValue()); + sectionLine->setSectionColor(vp->SectionLineColor.getValue().asValue()); + sectionLine->setPathMode(true); + sectionLine->setPath(wirePath); + sectionLine->setEnds(vStart, vEnd); + if (vp->SectionLineMarks.getValue()) { + sectionLine->setChangePoints(dcs->getChangePointsFromSectionLine()); + } else { + sectionLine->clearChangePoints(); + } + if (dcs->ProjectionStrategy.isValue("Offset")) { + Base::Vector3d arrowDirOffset = viewSection->SectionNormal.getValue(); + arrowDirOffset = - viewPart->projectPoint(arrowDirOffset); //arrows are opposite section normal + sectionLine->setDirection(arrowDirOffset.x, -arrowDirOffset.y); //invert y for Qt + } else { + std::pair dirsAligned = dcs->sectionArrowDirs(); + dirsAligned.first = DrawUtil::invertY(dirsAligned.first); + dirsAligned.second = DrawUtil::invertY(dirsAligned.second); + sectionLine->setArrowDirections(dirsAligned.first, dirsAligned.second); + } + + //set the general parameters + sectionLine->setPos(0.0, 0.0); + sectionLine->setWidth(lineWidthThin); + double fontSize = Preferences::dimFontSizeMM(); + sectionLine->setFont(getFont(), fontSize); + sectionLine->setZValue(ZVALUE::SECTIONLINE); + sectionLine->setRotation(- viewPart->Rotation.getValue()); + sectionLine->draw(); +} + //TODO: use Cosmetic::CenterLine object for this to make it usable for dims. void QGIViewPart::drawCenterLines(bool b) { @@ -885,35 +956,28 @@ void QGIViewPart::drawCenterLines(bool b) centerLine = new QGICenterLine(); addToGroup(centerLine); centerLine->setPos(0.0, 0.0); - //this should work from the viewPart's bbox, not the border -// double scale = viewPart->getScale(); double width = Rez::guiX(viewPart->getBoxX()); sectionSpan = width + sectionFudge; -// sectionSpan = m_border->rect().width() + sectionFudge; xVal = sectionSpan / 2.0; yVal = 0.0; centerLine->setIntersection(horiz && vert); centerLine->setBounds(-xVal, -yVal, xVal, yVal); centerLine->setWidth(Rez::guiX(vp->HiddenWidth.getValue())); centerLine->setZValue(ZVALUE::SECTIONLINE); -// centerLine->setRotation(viewPart->Rotation.getValue()); centerLine->draw(); } if (vert) { centerLine = new QGICenterLine(); addToGroup(centerLine); centerLine->setPos(0.0, 0.0); -// double scale = viewPart->getScale(); double height = Rez::guiX(viewPart->getBoxY()); sectionSpan = height + sectionFudge; -// sectionSpan = (m_border->rect().height() - m_label->boundingRect().height()) + sectionFudge; xVal = 0.0; yVal = sectionSpan / 2.0; centerLine->setIntersection(horiz && vert); centerLine->setBounds(-xVal, -yVal, xVal, yVal); centerLine->setWidth(Rez::guiX(vp->HiddenWidth.getValue())); centerLine->setZValue(ZVALUE::SECTIONLINE); -// centerLine->setRotation(viewPart->Rotation.getValue()); centerLine->draw(); } } diff --git a/src/Mod/TechDraw/Gui/QGIViewPart.h b/src/Mod/TechDraw/Gui/QGIViewPart.h index b502d34cad..46d7c1e7df 100644 --- a/src/Mod/TechDraw/Gui/QGIViewPart.h +++ b/src/Mod/TechDraw/Gui/QGIViewPart.h @@ -68,6 +68,7 @@ public: QRectF boundingRect() const override; virtual void drawAllSectionLines(); virtual void drawSectionLine(TechDraw::DrawViewSection* s, bool b); + virtual void drawComplexSectionLine(TechDraw::DrawViewSection* viewSection, bool b); virtual void drawCenterLines(bool b); virtual void drawHighlight(TechDraw::DrawViewDetail* viewDetail, bool b); virtual void drawMatting(); diff --git a/src/Mod/TechDraw/Gui/Resources/TechDraw.qrc b/src/Mod/TechDraw/Gui/Resources/TechDraw.qrc index eaa2d3845e..3e1c093eea 100644 --- a/src/Mod/TechDraw/Gui/Resources/TechDraw.qrc +++ b/src/Mod/TechDraw/Gui/Resources/TechDraw.qrc @@ -51,6 +51,7 @@ icons/actions/TechDraw_View.svg icons/actions/TechDraw_WeldSymbol.svg icons/actions/TechDraw_SurfaceFinishSymbols.svg + icons/actions/TechDraw_ComplexSection.svg icons/arrow-ccw.svg icons/arrow-cw.svg icons/arrow-down.svg @@ -180,6 +181,8 @@ icons/preferences-techdraw.svg + + translations/TechDraw_af.qm translations/TechDraw_ar.qm diff --git a/src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_ComplexSection.svg b/src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_ComplexSection.svg new file mode 100644 index 0000000000..d0e0e2fd7f --- /dev/null +++ b/src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_ComplexSection.svg @@ -0,0 +1,873 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/Resources/icons/actions/TechDraw_SectionView.svg b/src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_SectionView.svg index 457315a5d2..3283742566 100644 --- a/src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_SectionView.svg +++ b/src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_SectionView.svg @@ -1,169 +1,670 @@ - - - - - - + + + + + + - - - - - + + + + + - - - - - + + + + - - - - + + + + - - - - - - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - - - - + + + + - - - - + + + + - - - - - - - + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + + + + + + + + + + - - - + + + + - - - - - - - - - - - - - - + + + + + - - - - + + + - - - - - + + + - - - - + + + - + + + + + + + + - - - - + - + image/svg+xml - - + + [agryson] Alexander Gryson http://agryson.net - + TechDraw_SectionView 2016-01-14 http://www.freecadweb.org/wiki/index.php?title=Artwork @@ -184,33 +685,112 @@ - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/src/Mod/TechDraw/Gui/TaskComplexSection.cpp b/src/Mod/TechDraw/Gui/TaskComplexSection.cpp new file mode 100644 index 0000000000..685bf0d77b --- /dev/null +++ b/src/Mod/TechDraw/Gui/TaskComplexSection.cpp @@ -0,0 +1,812 @@ +/*************************************************************************** + * Copyright (c) 2022 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_ +#endif // #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 + +#include + +#include "DrawGuiUtil.h" +#include "Widgets/CompassWidget.h" +#include "Widgets/VectorEditWidget.h" + +#include "TaskComplexSection.h" + +using namespace Gui; +using namespace TechDraw; +using namespace TechDrawGui; + +//ctor for creation +TaskComplexSection::TaskComplexSection(TechDraw::DrawPage* page, + TechDraw::DrawViewPart* baseView, + std::vector shapes, + std::vector xShapes, + App::DocumentObject* profileObject, + std::vector profileSubs) : + ui(new Ui_TaskComplexSection), + m_page(page), + m_baseView(baseView), + m_section(nullptr), + m_shapes(shapes), + m_xShapes(xShapes), + m_profileObject(profileObject), + m_profileSubs(profileSubs), + m_dirName("Aligned"), + m_createMode(true), + m_applyDeferred(0), + m_angle(0.0) +{ + m_sectionName = std::string(); + m_doc = m_baseView->getDocument(); + + m_saveBaseName = m_baseView->getNameInDocument(); + m_savePageName = m_baseView->findParentPage()->getNameInDocument(); + m_localUnit = Base::Vector3d(0.0, 1.0, 0.0); + + ui->setupUi(this); + + saveSectionState(); + setUiPrimary(); + + m_applyDeferred = 0; //setting the direction widgets causes an increment of the deferred count, + //so we reset the counter and the message. + ui->lPendingUpdates->setText(QString()); +} + +//ctor for edit +TaskComplexSection::TaskComplexSection(TechDraw::DrawComplexSection* complexSection) : + ui(new Ui_TaskComplexSection), + m_page(nullptr), + m_baseView(nullptr), + m_section(complexSection), + m_profileObject(nullptr), + m_dirName("Aligned"), + m_createMode(false), + m_applyDeferred(0), + m_angle(0.0) +{ + m_sectionName = m_section->getNameInDocument(); + m_doc = m_section->getDocument(); + m_baseView = dynamic_cast (m_section->BaseView.getValue()); + if (m_baseView) { + m_saveBaseName = m_baseView->getNameInDocument(); + m_savePageName = m_baseView->findParentPage()->getNameInDocument(); + } + m_shapes = m_section->Source.getValues(); + m_xShapes = m_section->XSource.getValues(); + m_profileObject = m_section->CuttingToolWireObject.getValue(); + m_localUnit = Base::Vector3d(0.0, 1.0, 0.0); + + ui->setupUi(this); + + saveSectionState(); + setUiEdit(); + + m_applyDeferred = 0; //setting the direction widgets causes an increment of the deferred count, + //so we reset the counter and the message. + ui->lPendingUpdates->setText(QString()); +} + +void TaskComplexSection::setUiPrimary() +{ + setWindowTitle(QObject::tr("New Complex Section")); + if (m_baseView) { + ui->sbScale->setValue(m_baseView->getScale()); + ui->cmbScaleType->setCurrentIndex(m_baseView->ScaleType.getValue()); + } else { + ui->sbScale->setValue(Preferences::scale()); + ui->cmbScaleType->setCurrentIndex(Preferences::scaleType()); + } + ui->cmbStrategy->setCurrentIndex(0); + + setUiCommon(); + + if (m_baseView) { + ui->leBaseView->setText(Base::Tools::fromStdString(m_baseView->getNameInDocument())); + //if there is a baseView, we don't know the sectionNormal yet and have to wait until + //one is picked in the dialog + Base::Vector3d defaultNormal(-1.0, 0.0, 0.0); + m_saveNormal = defaultNormal; + m_localUnit = defaultNormal; + m_saveXDir = Base::Vector3d(0.0, 1.0, 0.0); + ui->leBaseView->setText(Base::Tools::fromStdString(m_baseView->getNameInDocument())); + m_viewDirectionWidget->setValue(defaultNormal * -1.0); + } else { + //if there is no baseView, we use the 3d view to determine the SectionNormal + //and XDirection. + std::pair dirs = DrawGuiUtil::get3DDirAndRot(); + m_saveNormal = dirs.first; + m_localUnit = dirs.first; + m_saveXDir = dirs.second; + m_viewDirectionWidget->setValue(m_saveNormal * -1.0); //this will propogate to m_compass + } +} + +void TaskComplexSection::setUiEdit() +{ + setWindowTitle(QObject::tr("Edit Complex Section")); + + if (m_baseView) { + ui->leBaseView->setText(Base::Tools::fromStdString(m_baseView->getNameInDocument())); + } + ui->cmbStrategy->setCurrentIndex(m_section->ProjectionStrategy.getValue()); + ui->leSymbol->setText(Base::Tools::fromStdString(m_section->SectionSymbol.getValue())); + ui->sbScale->setValue(m_section->Scale.getValue()); + ui->cmbScaleType->setCurrentIndex(m_section->ScaleType.getValue()); + + setUiCommon(); + + Base::Vector3d sectionNormalVec = m_section->SectionNormal.getValue(); + if (m_baseView) { + ui->leBaseView->setText(Base::Tools::fromStdString(m_baseView->getNameInDocument())); + Base::Vector3d projectedViewDirection = m_baseView->projectPoint(sectionNormalVec, false); + double viewAngle = atan2(-projectedViewDirection.y, + -projectedViewDirection.x); + m_compass->setDialAngle(viewAngle * 180.0 / M_PI); + m_viewDirectionWidget->setValue(projectedViewDirection * -1.0); + } else { + //no local angle makes sense if there is no baseView? + m_viewDirectionWidget->setValue(sectionNormalVec * -1.0); + } +} + +void TaskComplexSection::setUiCommon() +{ + ui->leSectionObjects->setText(sourcesToString()); + ui->leProfileObject->setText(Base::Tools::fromStdString(m_profileObject->getNameInDocument()) + + QString::fromUtf8(" / ") + + Base::Tools::fromStdString(m_profileObject->Label.getValue())); + + m_compass = new CompassWidget(this); + auto layout = ui->compassLayout; + layout->addWidget(m_compass); + + m_viewDirectionWidget = new VectorEditWidget(this); + m_viewDirectionWidget->setLabel(QObject::tr("Current View Direction")); + auto editLayout = ui->viewDirectionLayout; + editLayout->addWidget(m_viewDirectionWidget); + + connect(ui->pbRight, SIGNAL(clicked(bool)), m_compass, SLOT(setToEast())); + connect(ui->pbLeft, SIGNAL(clicked(bool)), m_compass, SLOT(setToWest())); + connect(ui->pbUp, SIGNAL(clicked(bool)), m_compass, SLOT(setToNorth())); + connect(ui->pbDown, SIGNAL(clicked(bool)), m_compass, SLOT(setToSouth())); + connect(m_compass, SIGNAL(angleChanged(double)), this, SLOT(slotChangeAngle(double))); + + connect(ui->pbUp, SIGNAL(clicked(bool)), this, SLOT(onUpClicked())); + connect(ui->pbDown, SIGNAL(clicked(bool)), this, SLOT(onDownClicked())); + connect(ui->pbRight, SIGNAL(clicked(bool)), this, SLOT(onRightClicked())); + connect(ui->pbLeft, SIGNAL(clicked(bool)), this, SLOT(onLeftClicked())); + + connect(ui->pbUpdateNow, SIGNAL(clicked(bool)), this, SLOT(updateNowClicked())); + connect(ui->cbLiveUpdate, SIGNAL(clicked(bool)), this, SLOT(liveUpdateClicked())); + + connect(ui->pbSectionObjects, SIGNAL(clicked()), this, SLOT(onSectionObjectsUseSelectionClicked())); + connect(ui->pbProfileObject, SIGNAL(clicked()), this, SLOT(onProfileObjectsUseSelectionClicked())); + + connect(m_compass, SIGNAL(angleChanged(double)), this, SLOT(slotChangeAngle(double))); + connect(m_viewDirectionWidget, SIGNAL(valueChanged(Base::Vector3d)), + this, SLOT(slotViewDirectionChanged(Base::Vector3d))); +} + +//save the start conditions +void TaskComplexSection::saveSectionState() +{ +// Base::Console().Message("TCS::saveSectionState()\n"); + if (m_section) { + m_saveSymbol = m_section->SectionSymbol.getValue(); + m_saveScale = m_section->getScale(); + m_saveScaleType = m_section->ScaleType.getValue(); + m_saveNormal = m_section->SectionNormal.getValue(); + m_saveDirection = m_section->Direction.getValue(); + m_localUnit = m_saveNormal; + m_saveXDir = m_section->XDirection.getValue(); + m_saveOrigin = m_section->SectionOrigin.getValue(); + m_saveDirName = m_section->SectionDirection.getValueAsString(); + m_saved = true; + } + if (m_baseView) { + m_shapes = m_baseView->Source.getValues(); + m_xShapes = m_baseView->XSource.getValues(); + } +} + +//restore the start conditions +void TaskComplexSection::restoreSectionState() +{ +// Base::Console().Message("TCS::restoreSectionState()\n"); + if (!m_section) + return; + + m_section->SectionSymbol.setValue(m_saveSymbol); + m_section->Scale.setValue(m_saveScale); + m_section->ScaleType.setValue(m_saveScaleType); + m_section->SectionNormal.setValue(m_saveNormal); + m_section->Direction.setValue(m_saveDirection); + m_section->XDirection.setValue(m_saveXDir); + m_section->SectionOrigin.setValue(m_saveOrigin); + m_section->SectionDirection.setValue(m_saveDirName.c_str()); +} + +void TaskComplexSection::onSectionObjectsUseSelectionClicked() +{ + std::vector selection = Gui::Selection().getSelectionEx(); + std::vector newSelection; + std::vector newXSelection; + for (auto& sel : selection) { + if (sel.getObject()->isDerivedFrom(App::LinkElement::getClassTypeId()) || + sel.getObject()->isDerivedFrom(App::LinkGroup::getClassTypeId()) || + sel.getObject()->isDerivedFrom(App::Link::getClassTypeId()) ) { + newXSelection.push_back(sel.getObject()); + } else { + newSelection.push_back(sel.getObject()); + } + } + m_shapes = newSelection; + m_xShapes = newXSelection; + ui->leSectionObjects->setText(sourcesToString()); +} + +//the VectorEditWidget reports a change in direction +void TaskComplexSection::slotViewDirectionChanged(Base::Vector3d newDirection) +{ +// Base::Console().Message("TCS::slotViewDirectionChanged(%s)\n", +// DrawUtil::formatVector(newDirection).c_str()); + Base::Vector3d projectedViewDirection = m_baseView->projectPoint(newDirection, false); + projectedViewDirection.Normalize(); + double viewAngle = atan2(projectedViewDirection.y, + projectedViewDirection.x); + m_compass->setDialAngle(viewAngle * 180.0 / M_PI); + checkAll(false); + applyAligned(projectedViewDirection); +} + +//the CompassWidget reports the view direction. This is the reverse of the +//SectionNormal +void TaskComplexSection::slotChangeAngle(double newAngle) +{ +// Base::Console().Message("TCS::slotAngleChanged(%.3f)\n", newAngle); + double angleRadians = newAngle * M_PI / 180.0; + if (m_baseView) { + double unitX = cos(angleRadians); + double unitY = sin(angleRadians); + Base::Vector3d localUnit(unitX, unitY, 0.0); + m_viewDirectionWidget->setValue(localUnit); + checkAll(false); + applyAligned(localUnit); + } else { + //save the angle for later use + m_angle = angleRadians; + } +} +void TaskComplexSection::onUpClicked() +{ +// Base::Console().Message("TCS::onUpClicked()\n"); + checkAll(false); + applyAligned(Base::Vector3d(0.0, 1.0, 0.0)); +} + +void TaskComplexSection::onDownClicked() +{ +// Base::Console().Message("TCS::onDownClicked()\n"); + checkAll(false); + applyAligned(Base::Vector3d(0.0, -1.0, 0.0)); +} + +void TaskComplexSection::onLeftClicked() +{ +// Base::Console().Message("TCS::onLeftClicked()\n"); + checkAll(false); + applyAligned(Base::Vector3d(-1.0, 0.0, 0.0)); +} + +void TaskComplexSection::onRightClicked() +{ +// Base::Console().Message("TCS::onRightClicked()\n"); + checkAll(false); + applyAligned(Base::Vector3d(1.0, 0.0, 0.0)); +} + +void TaskComplexSection::onIdentifierChanged() +{ + checkAll(false); + apply(); +} + +void TaskComplexSection::onScaleChanged() +{ + checkAll(false); + apply(); +} + +void TaskComplexSection::onProfileObjectsUseSelectionClicked() +{ + std::vector selection = Gui::Selection().getSelectionEx(); + //check for single selection and ability to make profile from selected object + if (!selection.empty()) { + m_profileObject = selection.front().getObject(); + ui->leProfileObject->setText(Base::Tools::fromStdString(m_profileObject->getNameInDocument()) + + QString::fromUtf8(" / ") + + Base::Tools::fromStdString(m_profileObject->Label.getValue())); + } +} +void TaskComplexSection::scaleTypeChanged(int index) +{ + if (index == 0) { + // Page Scale Type + ui->sbScale->setEnabled(false); + if (m_baseView->findParentPage()) { + ui->sbScale->setValue(m_baseView->findParentPage()->Scale.getValue()); + ui->sbScale->setEnabled(false); + } + } else if (index == 1) { + // Automatic Scale Type + ui->sbScale->setEnabled(false); + if (m_section) { + ui->sbScale->setValue(m_section->autoScale()); + } + } else if (index == 2) { + // Custom Scale Type + ui->sbScale->setEnabled(true); + if (m_section) { + ui->sbScale->setValue(m_section->Scale.getValue()); + ui->sbScale->setEnabled(true); + } + } else { + Base::Console().Log("Error - TaskComplexSection::scaleTypeChanged - unknown scale type: %d\n", index); + return; + } +} + +void TaskComplexSection::checkAll(bool check) +{ + ui->pbUp->setChecked(check); + ui->pbDown->setChecked(check); + ui->pbRight->setChecked(check); + ui->pbLeft->setChecked(check); +} + +void TaskComplexSection::enableAll(bool enable) +{ + ui->leSymbol->setEnabled(enable); + ui->sbScale->setEnabled(enable); + ui->cmbScaleType->setEnabled(enable); + QString qScaleType = ui->cmbScaleType->currentText(); + //Allow or prevent scale changing initially + if (qScaleType == QString::fromUtf8("Custom")) { + ui->sbScale->setEnabled(true); + } + else { + ui->sbScale->setEnabled(false); + } +} + +void TaskComplexSection::liveUpdateClicked() { + apply(true); +} + +void TaskComplexSection::updateNowClicked() { + apply(true); +} + +QString TaskComplexSection::sourcesToString() +{ + QString result; + QString separator(QString::fromUtf8(", ")); + QString currentSeparator; + if (m_baseView) { + for (auto& obj : m_baseView->Source.getValues()) { + result += currentSeparator + + Base::Tools::fromStdString(obj->getNameInDocument()) + + QString::fromUtf8(" / ") + + Base::Tools::fromStdString(obj->Label.getValue()); + currentSeparator = separator; + } + currentSeparator = QString(); + for (auto& obj : m_baseView->XSource.getValues()) { + result += currentSeparator + + Base::Tools::fromStdString(obj->getNameInDocument()) + + QString::fromUtf8(" / ") + + Base::Tools::fromStdString(obj->Label.getValue()); + } + } else { + for (auto& obj : m_shapes) { + result += currentSeparator + + Base::Tools::fromStdString(obj->getNameInDocument()) + + QString::fromUtf8(" / ") + + Base::Tools::fromStdString(obj->Label.getValue()); + } + currentSeparator = QString(); + for (auto& obj : m_xShapes) { + result += currentSeparator + + Base::Tools::fromStdString(obj->getNameInDocument()) + + QString::fromUtf8(" / ") + + Base::Tools::fromStdString(obj->Label.getValue()); + } + } + return result; +} + +//****************************************************************************** +bool TaskComplexSection::apply(bool forceUpdate) +{ +// Base::Console().Message("TCS::apply() - liveUpdate: %d forece: %d\n", +// ui->cbLiveUpdate->isChecked(), forceUpdate); + if(!ui->cbLiveUpdate->isChecked() && + !forceUpdate) { + //nothing to do + m_applyDeferred++; + QString msgLiteral = QString::fromUtf8(QT_TRANSLATE_NOOP("TaskPojGroup", " updates pending")); + QString msgNumber = QString::number(m_applyDeferred); + ui->lPendingUpdates->setText(msgNumber + msgLiteral); + return false; + } + + Gui::WaitCursor wc; + if (!m_section) { + createComplexSection(); + } + + if (isSectionValid()) { + updateComplexSection(); + } else { + failNoObject(); + } + + m_section->recomputeFeature(); + if (isBaseValid()) { + m_baseView->requestPaint(); + } + + enableAll(true); + checkAll(false); + + wc.restoreCursor(); + m_applyDeferred = 0; + ui->lPendingUpdates->setText(QString()); + return true; +} + +void TaskComplexSection::applyAligned(Base::Vector3d localUnit) +{ +// Base::Console().Message("TCS::applyAligned(%s)\n", +// DrawUtil::formatVector(localUnit).c_str()); + m_dirName = "Aligned"; + m_localUnit = localUnit; + enableAll(true); + apply(); +} + +//******************************************************************* + +//pointer to created view is not returned, but stored in m_section +void TaskComplexSection::createComplexSection() +{ +// Base::Console().Message("TCS::createComplexSection()\n"); + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create ComplexSection")); + if (!m_section) { + m_sectionName = m_page->getDocument()->getUniqueObjectName("ComplexSection"); + std::string sectionType = "TechDraw::DrawComplexSection"; + + Command::doCommand(Command::Doc, "App.ActiveDocument.addObject('%s', '%s')", + sectionType.c_str(), m_sectionName.c_str()); + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.addView(App.ActiveDocument.%s)", + m_page->getNameInDocument(), m_sectionName.c_str()); + + QString qTemp = ui->leSymbol->text(); + std::string temp = Base::Tools::toStdString(qTemp); + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.SectionSymbol = '%s'", + m_sectionName.c_str(), + temp.c_str()); + std::string lblText = "Section " + + temp + + " - " + + temp; + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.Label = '%s'", + m_sectionName.c_str(), + lblText.c_str()); + + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.Scale = %0.6f", + m_sectionName.c_str(), + ui->sbScale->value()); + int scaleType = ui->cmbScaleType->currentIndex(); + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.ScaleType = %d", + m_sectionName.c_str(), scaleType); + int projectionStrategy = ui->cmbStrategy->currentIndex(); + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.ProjectionStrategy = %d", + m_sectionName.c_str(), projectionStrategy); + + Command::doCommand(Command::Doc, "App.activeDocument().%s.SectionOrigin = FreeCAD.Vector(0.0, 0.0, 0.0)", + m_sectionName.c_str()); + Command::doCommand(Command::Doc, "App.activeDocument().%s.SectionDirection = 'Aligned'", + m_sectionName.c_str()); + + App::DocumentObject* newObj = m_page->getDocument()->getObject(m_sectionName.c_str()); + m_section = dynamic_cast(newObj); + if (!newObj || !m_section) { + throw Base::RuntimeError("TaskComplexSection - new section object not found"); + } + if (m_baseView) { + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.BaseView = App.ActiveDocument.%s", + m_sectionName.c_str(), m_baseView->getNameInDocument()); + if (m_localUnit.Length() != 0.0) { + m_section->setCSFromBase(m_localUnit * -1.0); + } + m_section->Source.setValues(m_baseView->Source.getValues()); + m_section->XSource.setValues(m_baseView->XSource.getValues()); + } else { + //if we have not changed the direction, we should use the 3d directions saved in the + //constructor + if (m_localUnit.IsEqual(m_saveNormal, EWTOLERANCE)) { + m_section->SectionNormal.setValue(m_saveNormal); + m_section->XDirection.setValue(m_saveXDir); + } else { + //if we have changed the direction, use the local unit to create a CS + m_section->setCSFromLocalUnit(m_localUnit * -1.0); + m_localUnit = m_saveNormal; //don't repeat this work next time through + } + m_section->Source.setValues(m_shapes); + m_section->XSource.setValues(m_xShapes); + } + m_section->CuttingToolWireObject.setValue(m_profileObject); + m_section->SectionDirection.setValue("Aligned"); + m_section->Source.setValues(m_shapes); + m_section->XSource.setValues(m_xShapes); + } + Gui::Command::commitCommand(); +} + +void TaskComplexSection::updateComplexSection() +{ +// Base::Console().Message("TCS:;updateComplexSection()\n"); + if (!isSectionValid()) { + failNoObject(); + return; + } + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Edit SectionView")); + if (m_section) { + QString qTemp = ui->leSymbol->text(); + std::string temp = Base::Tools::toStdString(qTemp); + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.SectionSymbol = '%s'", + m_sectionName.c_str(), + temp.c_str()); + std::string lblText = "Section " + + temp + + " - " + + temp; + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.Label = '%s'", + m_sectionName.c_str(), + lblText.c_str()); + + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.Scale = %0.6f", + m_sectionName.c_str(), + ui->sbScale->value()); + int scaleType = ui->cmbScaleType->currentIndex(); + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.ScaleType = %d", + m_sectionName.c_str(), scaleType); + int projectionStrategy = ui->cmbStrategy->currentIndex(); + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.ProjectionStrategy = %d", + m_sectionName.c_str(), projectionStrategy); + Command::doCommand(Command::Doc, "App.activeDocument().%s.SectionDirection = 'Aligned'", + m_sectionName.c_str()); + m_section->CuttingToolWireObject.setValue(m_profileObject); + m_section->SectionDirection.setValue("Aligned"); + if (m_baseView) { + if (!m_localUnit.IsEqual(m_saveNormal, EWTOLERANCE) && + m_localUnit.Length() != 0.0) { + //if we have changed the view direction (and by extension the + //section normal, update the feature with the new value. + //m_localUnit should always be valid, but will cause an exception + //if it is null. + m_section->setCSFromBase(m_localUnit * -1.0); + } + m_section->Source.setValues(m_baseView->Source.getValues()); + m_section->XSource.setValues(m_baseView->XSource.getValues()); + } else { + //without a baseView, our choice of SectionNormal and XDirection may well be wrong + //if we have not changed the direction, we leave things as they are + if (!m_localUnit.IsEqual(m_saveNormal, EWTOLERANCE)) { + //just do our best. + m_section->setCSFromLocalUnit(m_localUnit * -1.0); + m_localUnit = m_saveNormal; //don't repeat this work next time through + } + + m_section->Source.setValues(m_shapes); + m_section->XSource.setValues(m_xShapes); + } + } + Gui::Command::commitCommand(); +} + +void TaskComplexSection::failNoObject(void) +{ + QString qsectionName = Base::Tools::fromStdString(m_sectionName); + QString qbaseName = Base::Tools::fromStdString(m_saveBaseName); + QString msg = tr("Can not continue. Object * %1 or %2 not found.").arg(qsectionName, qbaseName); + QMessageBox::critical(Gui::getMainWindow(), QObject::tr("Operation Failed"), msg); + Gui::Control().closeDialog(); +} + +bool TaskComplexSection::isBaseValid() +{ + if (!m_baseView) + return false; + + App::DocumentObject* baseObj = m_doc->getObject(m_saveBaseName.c_str()); + if (!baseObj) + return false; + + return true; +} + +bool TaskComplexSection::isSectionValid() +{ + if (!m_section) + return false; + + App::DocumentObject* sectionObj = m_doc->getObject(m_sectionName.c_str()); + if (!sectionObj) + return false; + + return true; +} +//****************************************************************************** +bool TaskComplexSection::accept() +{ +// Base::Console().Message("TCS::accept()\n"); + apply(true); + Gui::Command::doCommand(Gui::Command::Gui,"Gui.ActiveDocument.resetEdit()"); + return true; +} + +bool TaskComplexSection::reject() +{ + if (!m_section) { //no section created, nothing to undo + Gui::Command::doCommand(Gui::Command::Gui, "Gui.ActiveDocument.resetEdit()"); + return false; + } + + if (!isSectionValid()) { //section !exist. nothing to undo + if (isBaseValid()) { + m_baseView->requestPaint(); + } + Gui::Command::doCommand(Gui::Command::Gui, "Gui.ActiveDocument.resetEdit()"); + return false; + } + + if (m_createMode) { + std::string SectionName = m_section->getNameInDocument(); + Gui::Command::doCommand(Gui::Command::Gui, + "App.ActiveDocument.%s.removeView(App.ActiveDocument.%s)", + m_savePageName.c_str(), SectionName.c_str()); + Gui::Command::doCommand(Gui::Command::Gui, + "App.ActiveDocument.removeObject('%s')", + SectionName.c_str()); + } else { + restoreSectionState(); + m_section->recomputeFeature(); + m_section->requestPaint(); + } + + if (isBaseValid()) { + m_baseView->requestPaint(); + } + Gui::Command::updateActive(); + Gui::Command::doCommand(Gui::Command::Gui, "Gui.ActiveDocument.resetEdit()"); + + return false;} + +void TaskComplexSection::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) { + ui->retranslateUi(this); + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +TaskDlgComplexSection::TaskDlgComplexSection(TechDraw::DrawPage* page, + TechDraw::DrawViewPart *baseView, + std::vector shapes, + std::vector xShapes, + App::DocumentObject* profileObject, + std::vector profileSubs) + : TaskDialog() +{ + widget = new TaskComplexSection(page, baseView, shapes, xShapes, profileObject, profileSubs); + taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("actions/TechDraw_ComplexSection"), + widget->windowTitle(), true, nullptr); + taskbox->groupLayout()->addWidget(widget); + Content.push_back(taskbox); +} + +TaskDlgComplexSection::TaskDlgComplexSection(TechDraw::DrawComplexSection* complexSection) + : TaskDialog() +{ + widget = new TaskComplexSection(complexSection); + taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("actions/TechDraw_ComplexSection"), + widget->windowTitle(), true, nullptr); + taskbox->groupLayout()->addWidget(widget); + Content.push_back(taskbox); +} + +TaskDlgComplexSection::~TaskDlgComplexSection() +{ +} + +void TaskDlgComplexSection::update() +{ +// widget->updateTask(); +} + +//==== calls from the TaskView =============================================================== +void TaskDlgComplexSection::open() +{ +} + +bool TaskDlgComplexSection::accept() +{ + widget->accept(); + return true; +} + +bool TaskDlgComplexSection::reject() +{ + widget->reject(); + return true; +} + +#include diff --git a/src/Mod/TechDraw/Gui/TaskComplexSection.h b/src/Mod/TechDraw/Gui/TaskComplexSection.h new file mode 100644 index 0000000000..cc8f3ed792 --- /dev/null +++ b/src/Mod/TechDraw/Gui/TaskComplexSection.h @@ -0,0 +1,183 @@ +/*************************************************************************** + * Copyright (c) 2022 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_TASKCOMPLEXSECTION_H +#define TECHDRAWGUI_TASKCOMPLEXSECTION_H + +#include + +#include + +#include +#include +#include + +namespace App +{ +class DocumentObject; +} + +namespace TechDraw +{ +class DrawPage; +class DrawView; +class DrawViewPart; +class DrawComplexSection; +} + +namespace TechDrawGui +{ +class CompassWidget; +class VectorEditWidget; + +class Ui_TaskComplexSection; + +class TaskComplexSection : public QWidget +{ + Q_OBJECT + +public: + TaskComplexSection(TechDraw::DrawPage* page, + TechDraw::DrawViewPart* baseView, + std::vector shapes, + std::vector xShapes, + App::DocumentObject* profileObject, + std::vector profileSubs); + TaskComplexSection(TechDraw::DrawComplexSection* complexSection); + ~TaskComplexSection() = default; + + virtual bool accept(); + virtual bool reject(); + +protected: + void changeEvent(QEvent *event) override; + void saveSectionState(); + void restoreSectionState(); + + bool apply(bool forceUpdate = false); + void applyQuick(std::string dir); + void applyAligned(Base::Vector3d localUnit); + + void setUiPrimary(); + void setUiEdit(); + void setUiCommon(); + + void checkAll(bool check); + void enableAll(bool enable); + + void failNoObject(); + bool isBaseValid(); + bool isSectionValid(); + + void updateUi(); + +protected Q_SLOTS: + void onSectionObjectsUseSelectionClicked(); + void onProfileObjectsUseSelectionClicked(); + void onUpClicked(); + void onDownClicked(); + void onLeftClicked(); + void onRightClicked(); + void onIdentifierChanged(); + void onScaleChanged(); + void scaleTypeChanged(int index); + void liveUpdateClicked(); + void updateNowClicked(); + void slotChangeAngle(double newAngle); + void slotViewDirectionChanged(Base::Vector3d newDirection); + +private: + void createComplexSection(); + void updateComplexSection(); + + QString sourcesToString(); + std::unique_ptr ui; + + TechDraw::DrawPage* m_page; + App::Document* m_doc; + TechDraw::DrawViewPart* m_baseView; + TechDraw::DrawComplexSection* m_section; + std::vector m_shapes; + std::vector m_xShapes; + App::DocumentObject* m_profileObject; + std::vector m_profileSubs; + std::string m_dirName; + std::string m_sectionName; + Base::Vector3d m_saveNormal; + Base::Vector3d m_saveXDir; + std::string m_saveBaseName; + std::string m_savePageName; + std::string m_saveSymbol; + std::string m_saveDirName; + Base::Vector3d m_saveDirection; + Base::Vector3d m_saveOrigin; + double m_saveScale; + int m_saveScaleType; + bool m_saved; + bool m_createMode; + Base::Vector3d m_normal; + + int m_applyDeferred; + Base::Vector3d m_localUnit; + CompassWidget* m_compass; + double m_angle; + VectorEditWidget* m_viewDirectionWidget; + +}; + +class TaskDlgComplexSection : public Gui::TaskView::TaskDialog +{ + Q_OBJECT + +public: + TaskDlgComplexSection(TechDraw::DrawPage* page, + TechDraw::DrawViewPart* baseView, + std::vector shapes, + std::vector xShapes, + App::DocumentObject* profileObject, + std::vector profileSubs); + TaskDlgComplexSection(TechDraw::DrawComplexSection* page); + ~TaskDlgComplexSection() override; + +public: + /// is called the TaskView when the dialog is opened + void open() override; + /// is called by the framework if an button is clicked which has no accept or reject role + /// is called by the framework if the dialog is accepted (Ok) + bool accept() override; + /// is called by the framework if the dialog is rejected (Cancel) + bool reject() override; + /// is called by the framework if the user presses the help button + bool isAllowedAlterDocument() const override + { return false; } + void update(); + +protected: + +private: + TaskComplexSection * widget; + Gui::TaskView::TaskBox* taskbox; +}; + +} //namespace TechDrawGui + +#endif // #ifndef TECHDRAWGUI_TASKCOMPLEXSECTION_H diff --git a/src/Mod/TechDraw/Gui/TaskComplexSection.ui b/src/Mod/TechDraw/Gui/TaskComplexSection.ui new file mode 100644 index 0000000000..68957ecb2e --- /dev/null +++ b/src/Mod/TechDraw/Gui/TaskComplexSection.ui @@ -0,0 +1,410 @@ + + + TechDrawGui::TaskComplexSection + + + + 0 + 0 + 370 + 612 + + + + + 0 + 0 + + + + Complex Section + + + + :/icons/actions/TechDraw_ComplexSection.svg:/icons/actions/TechDraw_ComplexSection.svg + + + + + + + 0 + 0 + + + + Object Selection + + + + + + + + Objects to section + + + + + + + Use Selection + + + + + + + Profile object + + + + + + + true + + + + + + + true + + + + + + + Use Selection + + + + + + + + + + + + Qt::Horizontal + + + + + + + Section Parameters + + + + + + + + + 0 + 26 + + + + Scale Page/Auto/Custom + + + + Page + + + + + Automatic + + + + + Custom + + + + + + + + Scale + + + + + + + Scale Type + + + + + + + + 0 + 26 + + + + 1.000000000000000 + + + + + + + Projection Strategy + + + + + + + + 0 + 0 + + + + + 0 + 26 + + + + + 0 + 0 + + + + Offset + + + 0 + + + + Offset + + + + + Aligned + + + + + NoParallel + + + + + + + + Identifier + + + + + + + + 0 + 26 + + + + Identifier for this section + + + + + + + BaseView + + + + + + + false + + + + + + + + + + + + Qt::Horizontal + + + + + + + Set View Direction + + + + + + + + + + + Preset view direction looking up. + + + + + + + :/icons/actions/section-up.svg + + + + + 48 + 48 + + + + + + + + Preset view direction looking down. + + + + + + + :/icons/actions/section-down.svg + + + + + 48 + 48 + + + + + + + + Preset view direction looking left. + + + + + + + :/icons/actions/section-left.svg + + + + + 48 + 48 + + + + + + + + Preset view direction looking right. + + + + + + + :/icons/actions/section-right.svg + + + + + 48 + 48 + + + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + Preview + + + + + + + + Check to update display after every property change. + + + Live Update + + + + + + + Rebuild display now. May be slow for complex models. + + + Update Now + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Gui/TaskSectionView.cpp b/src/Mod/TechDraw/Gui/TaskSectionView.cpp index 552f6dd0a9..19439d0ca4 100644 --- a/src/Mod/TechDraw/Gui/TaskSectionView.cpp +++ b/src/Mod/TechDraw/Gui/TaskSectionView.cpp @@ -30,7 +30,6 @@ #include #endif // #ifndef _PreComp_ - #include #include #include @@ -58,7 +57,8 @@ //#include "ViewProviderViewPart.h" #include - +#include "Widgets/CompassWidget.h" +#include "Widgets/VectorEditWidget.h" #include "TaskSectionView.h" using namespace Gui; @@ -71,10 +71,11 @@ TaskSectionView::TaskSectionView(TechDraw::DrawViewPart* base) : m_base(base), m_section(nullptr), m_saveScale(1.0), + m_dirName(""), m_doc(nullptr), m_createMode(true), m_saved(false), - m_abort(false) + m_applyDeferred(0) { //existence of base is guaranteed by CmdTechDrawSectionView (Command.cpp) @@ -85,15 +86,12 @@ TaskSectionView::TaskSectionView(TechDraw::DrawViewPart* base) : m_savePageName = m_base->findParentPage()->getNameInDocument(); ui->setupUi(this); - - connect(ui->pbUp, SIGNAL(clicked(bool)), this, SLOT(onUpClicked())); - connect(ui->pbDown, SIGNAL(clicked(bool)), this, SLOT(onDownClicked())); - connect(ui->pbRight, SIGNAL(clicked(bool)), this, SLOT(onRightClicked())); - connect(ui->pbLeft, SIGNAL(clicked(bool)), this, SLOT(onLeftClicked())); - setUiPrimary(); -} + m_applyDeferred = 0; //setting the direction widgets causes an increment of the deferred count, + //so we reset the counter and the message. + ui->lPendingUpdates->setText(QString()); +} //ctor for edit TaskSectionView::TaskSectionView(TechDraw::DrawViewSection* section) : @@ -104,7 +102,7 @@ TaskSectionView::TaskSectionView(TechDraw::DrawViewSection* section) : m_doc(nullptr), m_createMode(false), m_saved(false), - m_abort(false) + m_applyDeferred(0) { //existence of section is guaranteed by ViewProviderViewSection.setEdit @@ -112,76 +110,82 @@ TaskSectionView::TaskSectionView(TechDraw::DrawViewSection* section) : m_sectionName = m_section->getNameInDocument(); App::DocumentObject* newObj = m_section->BaseView.getValue(); m_base = dynamic_cast(newObj); - if (!newObj || !m_base) + if (!newObj || !m_base) { throw Base::RuntimeError("TaskSectionView - BaseView not found"); + } m_saveBaseName = m_base->getNameInDocument(); m_savePageName = m_base->findParentPage()->getNameInDocument(); ui->setupUi(this); - connect(ui->pbUp, SIGNAL(clicked(bool)), this, SLOT(onUpClicked())); - connect(ui->pbDown, SIGNAL(clicked(bool)), this, SLOT(onDownClicked())); - connect(ui->pbRight, SIGNAL(clicked(bool)), this, SLOT(onRightClicked())); - connect(ui->pbLeft, SIGNAL(clicked(bool)), this, SLOT(onLeftClicked())); - m_dirName = m_section->SectionDirection.getValueAsString(); saveSectionState(); setUiEdit(); + + m_applyDeferred = 0; //setting the direction widgets causes an increment of the deferred count, + //so we reset the counter and the message. + ui->lPendingUpdates->setText(QString()); } void TaskSectionView::setUiPrimary() { // Base::Console().Message("TSV::setUiPrimary()\n"); setWindowTitle(QObject::tr("Create Section View")); - std::string temp = m_base->getNameInDocument(); - QString qTemp = Base::Tools::fromStdString(temp); - ui->leBaseView->setText(qTemp); - ui->sbScale->setValue(m_base->getScale()); ui->cmbScaleType->setCurrentIndex(m_base->ScaleType.getValue()); + + //Allow or prevent scale changing initially + if (m_base->ScaleType.isValue("Custom")) { + ui->sbScale->setEnabled(true); + } + else { + ui->sbScale->setEnabled(false); + } + Base::Vector3d origin = m_base->getOriginalCentroid(); - ui->sbOrgX->setUnit(Base::Unit::Length); - ui->sbOrgX->setValue(origin.x); - ui->sbOrgY->setUnit(Base::Unit::Length); - ui->sbOrgY->setValue(origin.y); - ui->sbOrgZ->setUnit(Base::Unit::Length); - ui->sbOrgZ->setValue(origin.z); + setUiCommon(origin); - // before the user did not select an orientation, - // the section properties cannot be changed - this->setToolTip(QObject::tr("Select at first an orientation")); - enableAll(false); - - connect(ui->leSymbol, SIGNAL(editingFinished()), this, SLOT(onIdentifierChanged())); - - // the UI file uses keyboardTracking = false so that a recomputation - // will only be triggered when the arrow keys of the spinboxes are used - connect(ui->sbScale, SIGNAL(valueChanged(double)), this, SLOT(onScaleChanged())); - connect(ui->sbOrgX, SIGNAL(valueChanged(double)), this, SLOT(onXChanged())); - connect(ui->sbOrgY, SIGNAL(valueChanged(double)), this, SLOT(onYChanged())); - connect(ui->sbOrgZ, SIGNAL(valueChanged(double)), this, SLOT(onZChanged())); - - connect(ui->cmbScaleType, SIGNAL(currentIndexChanged(int)), this, SLOT(scaleTypeChanged(int))); + m_viewDirectionWidget->setValue(Base::Vector3d(1.0, 0.0, 0.0)); } void TaskSectionView::setUiEdit() { // Base::Console().Message("TSV::setUiEdit()\n"); setWindowTitle(QObject::tr("Edit Section View")); + std::string temp = m_section->SectionSymbol.getValue(); + QString qTemp = Base::Tools::fromStdString(temp); + ui->leSymbol->setText(qTemp); + ui->sbScale->setValue(m_section->getScale()); + ui->cmbScaleType->setCurrentIndex(m_section->ScaleType.getValue()); + //Allow or prevent scale changing initially + if (m_section->ScaleType.isValue("Custom")) { + ui->sbScale->setEnabled(true); + } + else { + ui->sbScale->setEnabled(false); + } + + Base::Vector3d origin = m_section->SectionOrigin.getValue(); + setUiCommon(origin); + + // convert section normal to view angle + Base::Vector3d sectionNormalVec = m_section->SectionNormal.getValue(); + Base::Vector3d projectedViewDirection = m_base->projectPoint(sectionNormalVec, false); + double viewAngle = atan2(-projectedViewDirection.y, + -projectedViewDirection.x); + m_compass->setDialAngle(viewAngle * 180.0 / M_PI); + m_viewDirectionWidget->setValue(sectionNormalVec * -1.0); +} + +void TaskSectionView::setUiCommon(Base::Vector3d origin) +{ std::string temp = m_base->getNameInDocument(); QString qTemp = Base::Tools::fromStdString(temp); ui->leBaseView->setText(qTemp); - temp = m_section->SectionSymbol.getValue(); - qTemp = Base::Tools::fromStdString(temp); - ui->leSymbol->setText(qTemp); - ui->sbScale->setValue(m_section->getScale()); - ui->cmbScaleType->setCurrentIndex(m_section->ScaleType.getValue()); - - Base::Vector3d origin = m_section->SectionOrigin.getValue(); ui->sbOrgX->setUnit(Base::Unit::Length); ui->sbOrgX->setValue(origin.x); ui->sbOrgY->setUnit(Base::Unit::Length); @@ -189,33 +193,58 @@ void TaskSectionView::setUiEdit() ui->sbOrgZ->setUnit(Base::Unit::Length); ui->sbOrgZ->setValue(origin.z); + enableAll(false); + connect(ui->leSymbol, SIGNAL(editingFinished()), this, SLOT(onIdentifierChanged())); + //TODO: use event filter instead of keyboard tracking to capture enter/return keys // the UI file uses keyboardTracking = false so that a recomputation // will only be triggered when the arrow keys of the spinboxes are used + //if this is not done, recomputes are triggered on each key press giving + //unaccceptable UX connect(ui->sbScale, SIGNAL(valueChanged(double)), this, SLOT(onScaleChanged())); connect(ui->sbOrgX, SIGNAL(valueChanged(double)), this, SLOT(onXChanged())); connect(ui->sbOrgY, SIGNAL(valueChanged(double)), this, SLOT(onYChanged())); connect(ui->sbOrgZ, SIGNAL(valueChanged(double)), this, SLOT(onZChanged())); connect(ui->cmbScaleType, SIGNAL(currentIndexChanged(int)), this, SLOT(scaleTypeChanged(int))); + + connect(ui->pbUp, SIGNAL(clicked(bool)), this, SLOT(onUpClicked())); + connect(ui->pbDown, SIGNAL(clicked(bool)), this, SLOT(onDownClicked())); + connect(ui->pbRight, SIGNAL(clicked(bool)), this, SLOT(onRightClicked())); + connect(ui->pbLeft, SIGNAL(clicked(bool)), this, SLOT(onLeftClicked())); + + connect(ui->pbUpdateNow, SIGNAL(clicked(bool)), this, SLOT(updateNowClicked())); + connect(ui->cbLiveUpdate, SIGNAL(clicked(bool)), this, SLOT(liveUpdateClicked())); + + m_compass = new CompassWidget(this); + auto layout = ui->compassLayout; + layout->addWidget(m_compass); + connect(m_compass, SIGNAL(angleChanged(double)), this, SLOT(slotChangeAngle(double))); + + m_viewDirectionWidget = new VectorEditWidget(this); + m_viewDirectionWidget->setLabel(QObject::tr("Current View Direction")); + auto editLayout = ui->viewDirectionLayout; + editLayout->addWidget(m_viewDirectionWidget); + connect(m_viewDirectionWidget, SIGNAL(valueChanged(Base::Vector3d)), + this, SLOT(slotViewDirectionChanged(Base::Vector3d))); } //save the start conditions void TaskSectionView::saveSectionState() { // Base::Console().Message("TSV::saveSectionState()\n"); - if (!m_section) - return; - - m_saveSymbol = m_section->SectionSymbol.getValue(); - m_saveScale = m_section->getScale(); - m_saveScaleType = m_section->ScaleType.getValue(); - m_saveNormal = m_section->SectionNormal.getValue(); - m_saveDirection = m_section->Direction.getValue(); - m_saveOrigin = m_section->SectionOrigin.getValue(); - m_saveDirName = m_section->SectionDirection.getValueAsString(); - m_saved = true; + if (m_section) { + m_saveSymbol = m_section->SectionSymbol.getValue(); + m_saveScale = m_section->getScale(); + m_saveScaleType = m_section->ScaleType.getValue(); + m_saveNormal = m_section->SectionNormal.getValue(); + m_normal = m_saveNormal; + m_saveDirection = m_section->Direction.getValue(); + m_saveOrigin = m_section->SectionOrigin.getValue(); + m_saveDirName = m_section->SectionDirection.getValueAsString(); + m_saved = true; + } } //restore the start conditions @@ -234,36 +263,72 @@ void TaskSectionView::restoreSectionState() m_section->SectionDirection.setValue(m_saveDirName.c_str()); } +//the VectorEditWidget reports a change in direction +void TaskSectionView::slotViewDirectionChanged(Base::Vector3d newDirection) +{ +// Base::Console().Message("TSV::slotViewDirectionChanged(%s)\n", +// DrawUtil::formatVector(newDirection).c_str()); + Base::Vector3d projectedViewDirection = m_base->projectPoint(newDirection, false); + projectedViewDirection.Normalize(); + double viewAngle = atan2(projectedViewDirection.y, + projectedViewDirection.x); + m_compass->setDialAngle(viewAngle * 180.0 / M_PI); + checkAll(false); + applyAligned(projectedViewDirection); +} + +//the CompassWidget reports that the view direction angle has changed +void TaskSectionView::slotChangeAngle(double newAngle) +{ +// Base::Console().Message("TSV::slotChangeAngle(%.3f)\n", newAngle); + double angleRadians = newAngle * M_PI / 180.0; + double unitX = cos(angleRadians); + double unitY = sin(angleRadians); + Base::Vector3d localUnit(unitX, unitY, 0.0); + m_viewDirectionWidget->setValueNoNotify(localUnit); + checkAll(false); + applyAligned(localUnit); +} + +//preset view directions void TaskSectionView::onUpClicked() { // Base::Console().Message("TSV::onUpClicked()\n"); + m_compass->setToNorth(); + Base::Vector3d localUnit(0.0, 1.0, 0.0); + m_viewDirectionWidget->setValue(localUnit); checkAll(false); - ui->pbUp->setChecked(true); - applyQuick("Up"); + applyAligned(localUnit); } void TaskSectionView::onDownClicked() { // Base::Console().Message("TSV::onDownClicked()\n"); + m_compass->setToSouth(); + Base::Vector3d localUnit(0.0, -1.0, 0.0); + m_viewDirectionWidget->setValue(localUnit); checkAll(false); - ui->pbDown->setChecked(true); - applyQuick("Down"); + applyAligned(localUnit); } void TaskSectionView::onLeftClicked() { // Base::Console().Message("TSV::onLeftClicked()\n"); + m_compass->setToWest(); + Base::Vector3d localUnit(-1.0, 0.0, 0.0); + m_viewDirectionWidget->setValue(localUnit); checkAll(false); - ui->pbLeft->setChecked(true); - applyQuick("Left"); + applyAligned(localUnit); } void TaskSectionView::onRightClicked() { // Base::Console().Message("TSV::onRightClicked()\n"); + m_compass->setToEast(); + Base::Vector3d localUnit(1.0, 0.0, 0.0); + m_viewDirectionWidget->setValue(localUnit); checkAll(false); - ui->pbRight->setChecked(true); - applyQuick("Right"); + applyAligned(localUnit); } void TaskSectionView::onIdentifierChanged() @@ -278,18 +343,17 @@ void TaskSectionView::onScaleChanged() apply(); } +//SectionOrigin changed void TaskSectionView::onXChanged() { checkAll(false); apply(); } - void TaskSectionView::onYChanged() { checkAll(false); apply(); } - void TaskSectionView::onZChanged() { checkAll(false); @@ -303,6 +367,7 @@ void TaskSectionView::scaleTypeChanged(int index) ui->sbScale->setEnabled(false); if (m_base->findParentPage()) { ui->sbScale->setValue(m_base->findParentPage()->Scale.getValue()); + ui->sbScale->setEnabled(false); } } else if (index == 1) { // Automatic Scale Type @@ -315,6 +380,7 @@ void TaskSectionView::scaleTypeChanged(int index) ui->sbScale->setEnabled(true); if (m_section) { ui->sbScale->setValue(m_section->Scale.getValue()); + ui->sbScale->setEnabled(true); } } else { Base::Console().Log("Error - TaskSectionView::scaleTypeChanged - unknown scale type: %d\n", index); @@ -338,25 +404,68 @@ void TaskSectionView::enableAll(bool enable) ui->sbOrgY->setEnabled(enable); ui->sbOrgZ->setEnabled(enable); ui->cmbScaleType->setEnabled(enable); + QString qScaleType = ui->cmbScaleType->currentText(); + //Allow or prevent scale changing initially + if (qScaleType == QString::fromUtf8("Custom")) { + ui->sbScale->setEnabled(true); + } + else { + ui->sbScale->setEnabled(false); + } +} + +void TaskSectionView::liveUpdateClicked() { + apply(true); +} + +void TaskSectionView::updateNowClicked() { + apply(true); } //****************************************************************************** -bool TaskSectionView::apply() +bool TaskSectionView::apply(bool forceUpdate) { -// Base::Console().Message("TSV::apply() - m_dirName: %s\n", m_dirName.c_str()); +// Base::Console().Message("TSV::apply() - liveUpdate: %d force: %d deferred: %d\n", +// ui->cbLiveUpdate->isChecked(), forceUpdate, m_applyDeferred); + if(!ui->cbLiveUpdate->isChecked() && + !forceUpdate) { + //nothing to do + m_applyDeferred++; + QString msgLiteral = QString::fromUtf8(QT_TRANSLATE_NOOP("TaskPojGroup", " updates pending")); + QString msgNumber = QString::number(m_applyDeferred); + ui->lPendingUpdates->setText(msgNumber + msgLiteral); + return false; + } + + Gui::WaitCursor wc; if (m_dirName.empty()) { - std::string msg = + //this should never happen + std::string msg = Base::Tools::toStdString(tr("Nothing to apply. No section direction picked yet")); Base::Console().Error((msg + "\n").c_str()); return false; } - if (!m_section) //didn't create the feature yet - //this can't happen as applyQuick has to be called by the direction - //setting process - return false; + if (!m_section) { + m_section = createSectionView(); + } + if (isSectionValid()) { + updateSectionView(); + } else { + failNoObject(); + } + + m_section->recomputeFeature(); + if (isBaseValid()) { + m_base->requestPaint(); + } + + enableAll(true); checkAll(false); - applyQuick(m_dirName); + + wc.restoreCursor(); + m_applyDeferred = 0; + ui->lPendingUpdates->setText(QString()); return true; } @@ -364,39 +473,27 @@ void TaskSectionView::applyQuick(std::string dir) { // Base::Console().Message("TSV::applyQuick(%s)\n", dir.c_str()); m_dirName = dir; - if (!m_section) - createSectionView(); - - if (!isSectionValid()) { - failNoObject(m_sectionName); - return; - } - - updateSectionView(); enableAll(true); - - m_section->recomputeFeature(); - if (isBaseValid()) - m_base->requestPaint(); + apply(); } -void TaskSectionView::applyAligned() +void TaskSectionView::applyAligned(Base::Vector3d localUnit) { - Base::Console().Message("TSV::applyAligned() - not implemented yet\n"); -// Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Apply Aligned")); +// Base::Console().Message("TSV::applyAligned()\n"); m_dirName = "Aligned"; - //fiddle with directions here + m_localUnit = localUnit; + enableAll(true); + apply(); } //********************************************************************* -//pointer to created view is not returned, but stored in m_section -void TaskSectionView::createSectionView() +TechDraw::DrawViewSection* TaskSectionView::createSectionView(void) { // Base::Console().Message("TSV::createSectionView()\n"); if (!isBaseValid()) { - failNoObject(m_baseName); - return; + failNoObject(); + return nullptr; } std::string sectionName; @@ -416,7 +513,7 @@ void TaskSectionView::createSectionView() Command::doCommand(Command::Doc, "App.ActiveDocument.%s.Source = App.ActiveDocument.%s.Source", m_sectionName.c_str(), baseName.c_str()); Command::doCommand(Command::Doc, - "App.ActiveDocument.%s.SectionOrigin = FreeCAD.Vector(%.3f, %.3f, %.3f)", + "App.ActiveDocument.%s.SectionOrigin = FreeCAD.Vector(%.6f, %.6f, %.6f)", m_sectionName.c_str(), ui->sbOrgX->value().getValue(), ui->sbOrgY->value().getValue(), @@ -427,22 +524,32 @@ void TaskSectionView::createSectionView() int scaleType = ui->cmbScaleType->currentIndex(); Command::doCommand(Command::Doc, "App.ActiveDocument.%s.ScaleType = %d", m_sectionName.c_str(), scaleType); + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.SectionDirection = '%s'", + m_sectionName.c_str(), m_dirName.c_str()); App::DocumentObject* newObj = m_base->getDocument()->getObject(m_sectionName.c_str()); m_section = dynamic_cast(newObj); - if (!newObj || !m_section) + if (!newObj || !m_section) { throw Base::RuntimeError("TaskSectionView - new section object not found"); + } + if (m_dirName == "Aligned") { + //m_localUnit is a view direction so we need to reverse it to make a + //section normal + m_section->setCSFromBase(m_localUnit * -1.0); + } else { + //Note: DirectionName is to be deprecated in the future + m_section->setCSFromBase(m_dirName.c_str()); + } } Gui::Command::commitCommand(); - - return; + return m_section; } void TaskSectionView::updateSectionView() { // Base::Console().Message("TSV::updateSectionView() - m_sectionName: %s\n", m_sectionName.c_str()); if (!isSectionValid()) { - failNoObject(m_sectionName); + failNoObject(); return; } @@ -474,18 +581,28 @@ void TaskSectionView::updateSectionView() int scaleType = ui->cmbScaleType->currentIndex(); Command::doCommand(Command::Doc, "App.ActiveDocument.%s.ScaleType = %d", m_sectionName.c_str(), scaleType); - m_section->setCSFromBase(m_dirName.c_str()); + Command::doCommand(Command::Doc, "App.ActiveDocument.%s.SectionDirection = '%s'", + m_sectionName.c_str(), m_dirName.c_str()); + if (m_dirName == "Aligned") { + //m_localUnit is a view direction so we need to reverse it to make a + //section normal + m_section->setCSFromBase(m_localUnit * -1.0); + } else { + //Note: DirectionName is to be deprecated in the future + m_section->setCSFromBase(m_dirName.c_str()); + } + } Gui::Command::commitCommand(); } -void TaskSectionView::failNoObject(std::string objectName) +void TaskSectionView::failNoObject(void) { - QString qObjectName = Base::Tools::fromStdString(objectName); - QString msg = tr("Can not continue. Object * %1 * not found.").arg(qObjectName); + QString qsectionName = Base::Tools::fromStdString(m_sectionName); + QString qbaseName = Base::Tools::fromStdString(m_baseName); + QString msg = tr("Can not continue. Object * %1 or %2 not found.").arg(qsectionName, qbaseName); QMessageBox::critical(Gui::getMainWindow(), QObject::tr("Operation Failed"), msg); Gui::Control().closeDialog(); - m_abort = true; } bool TaskSectionView::isBaseValid() @@ -517,12 +634,8 @@ bool TaskSectionView::isSectionValid() bool TaskSectionView::accept() { // Base::Console().Message("TSV::accept()\n"); - if (m_abort) { - return true; - } - apply(); - Gui::Command::updateActive(); - Gui::Command::doCommand(Gui::Command::Gui, "Gui.ActiveDocument.resetEdit()"); + apply(true); + Gui::Command::doCommand(Gui::Command::Gui,"Gui.ActiveDocument.resetEdit()"); return true; } @@ -589,6 +702,7 @@ TaskDlgSectionView::TaskDlgSectionView(TechDraw::DrawViewSection* section) : widget = new TaskSectionView(section); taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("actions/TechDraw_SectionView"), widget->windowTitle(), true, nullptr); + taskbox->groupLayout()->addWidget(widget); Content.push_back(taskbox); } diff --git a/src/Mod/TechDraw/Gui/TaskSectionView.h b/src/Mod/TechDraw/Gui/TaskSectionView.h index 4a676a5446..a6a0353a31 100644 --- a/src/Mod/TechDraw/Gui/TaskSectionView.h +++ b/src/Mod/TechDraw/Gui/TaskSectionView.h @@ -39,6 +39,9 @@ namespace TechDraw { namespace TechDrawGui { +class CompassWidget; +class VectorEditWidget; + class TaskSectionView : public QWidget { Q_OBJECT @@ -56,20 +59,21 @@ protected: void saveSectionState(); void restoreSectionState(); - bool apply(); + bool apply(bool forceUpdate = false); void applyQuick(std::string dir); - void applyAligned(); + void applyAligned(Base::Vector3d localUnit); - void createSectionView(); + TechDraw::DrawViewSection* createSectionView(); void updateSectionView(); void setUiPrimary(); void setUiEdit(); + void setUiCommon(Base::Vector3d origin); void checkAll(bool check); void enableAll(bool enable); - void failNoObject(std::string objectName); + void failNoObject(); bool isBaseValid(); bool isSectionValid(); @@ -84,6 +88,10 @@ protected Q_SLOTS: void onYChanged(); void onZChanged(); void scaleTypeChanged(int index); + void liveUpdateClicked(); + void updateNowClicked(); + void slotChangeAngle(double newAngle); + void slotViewDirectionChanged(Base::Vector3d newDirection); private: std::unique_ptr ui; @@ -113,8 +121,10 @@ private: std::string m_saveBaseName; std::string m_savePageName; - bool m_abort; - + int m_applyDeferred; + Base::Vector3d m_localUnit; + CompassWidget* m_compass; + VectorEditWidget* m_viewDirectionWidget; }; class TaskDlgSectionView : public Gui::TaskView::TaskDialog diff --git a/src/Mod/TechDraw/Gui/TaskSectionView.ui b/src/Mod/TechDraw/Gui/TaskSectionView.ui index 2b4a8962eb..d92b8204cd 100644 --- a/src/Mod/TechDraw/Gui/TaskSectionView.ui +++ b/src/Mod/TechDraw/Gui/TaskSectionView.ui @@ -7,7 +7,7 @@ 0 0 370 - 368 + 508 @@ -82,7 +82,7 @@ - + @@ -140,6 +140,9 @@ Scale factor for the section view + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + false @@ -157,7 +160,7 @@ - + @@ -167,11 +170,14 @@ - + - Section Orientation + Set View Direction + + + @@ -183,7 +189,7 @@ - Looking up + Preset view direction looking up. @@ -219,7 +225,7 @@ - Looking down + Preset view direction looking down. @@ -249,7 +255,7 @@ - Looking left + Preset view direction looking left. @@ -279,7 +285,7 @@ - Looking right + Preset view direction looking right. @@ -302,6 +308,9 @@ + + + @@ -313,14 +322,14 @@ - + Position from the 3D origin of the object in the view Section Plane Location - + @@ -369,6 +378,9 @@ 22 + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + false @@ -410,6 +422,9 @@ 22 + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + false @@ -451,6 +466,9 @@ 22 + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + false @@ -464,6 +482,56 @@ + + + + Qt::Horizontal + + + + + + + Preview + + + + + + + + <html><head/><body><p>Rebuild display now. May be slow for complex models.</p></body></html> + + + Update Now + + + + + + + Check to update display after every property change. + + + Live Update + + + + + + + false + + + + + + + + + + + @@ -474,7 +542,7 @@ - + diff --git a/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp b/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp index 1e8e806db6..dc7fb6c202 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp +++ b/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp @@ -76,6 +76,7 @@ ViewProviderViewPart::ViewProviderViewPart() static const char *group = "Lines"; static const char *dgroup = "Decoration"; static const char *hgroup = "Highlight"; + static const char *sgroup = "Section Line"; //default line weights @@ -104,12 +105,14 @@ ViewProviderViewPart::ViewProviderViewPart() ADD_PROPERTY_TYPE(CenterScale, (defScale), dgroup, App::Prop_None, "Center mark size adjustment, if enabled"); //properties that affect Section Line - ADD_PROPERTY_TYPE(ShowSectionLine ,(true) ,dgroup, App::Prop_None, "Show/hide section line if applicable"); + ADD_PROPERTY_TYPE(ShowSectionLine ,(true) ,sgroup, App::Prop_None, "Show/hide section line if applicable"); SectionLineStyle.setEnums(LineStyleEnums); - ADD_PROPERTY_TYPE(SectionLineStyle, (PreferencesGui::sectionLineStyle()), dgroup, App::Prop_None, + ADD_PROPERTY_TYPE(SectionLineStyle, (PreferencesGui::sectionLineStyle()), sgroup, App::Prop_None, "Set section line style if applicable"); - ADD_PROPERTY_TYPE(SectionLineColor, (prefSectionColor()), dgroup, App::Prop_None, + ADD_PROPERTY_TYPE(SectionLineColor, (prefSectionColor()), sgroup, App::Prop_None, "Set section line color if applicable"); + ADD_PROPERTY_TYPE(SectionLineMarks, (PreferencesGui::sectionLineMarks()), sgroup, App::Prop_None, + "Show marks at direction changes for ComplexSection"); //properties that affect Detail Highlights HighlightLineStyle.setEnums(LineStyleEnums); @@ -139,6 +142,7 @@ void ViewProviderViewPart::onChanged(const App::Property* prop) prop == &(ShowSectionLine) || prop == &(SectionLineStyle) || prop == &(SectionLineColor) || + prop == &(SectionLineMarks) || prop == &(HighlightLineStyle) || prop == &(HighlightLineColor) || prop == &(HorizCenterLine) || diff --git a/src/Mod/TechDraw/Gui/ViewProviderViewPart.h b/src/Mod/TechDraw/Gui/ViewProviderViewPart.h index c0ec6602ac..69bfa031c0 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderViewPart.h +++ b/src/Mod/TechDraw/Gui/ViewProviderViewPart.h @@ -22,8 +22,8 @@ #ifndef DRAWINGGUI_VIEWPROVIDERVIEWPART_H #define DRAWINGGUI_VIEWPROVIDERVIEWPART_H - -#include + +#include #include @@ -55,6 +55,7 @@ public: App::PropertyBool ShowSectionLine; App::PropertyEnumeration SectionLineStyle; App::PropertyColor SectionLineColor; + App::PropertyBool SectionLineMarks; App::PropertyEnumeration HighlightLineStyle; App::PropertyColor HighlightLineColor; App::PropertyFloat HighlightAdjust; diff --git a/src/Mod/TechDraw/Gui/ViewProviderViewSection.cpp b/src/Mod/TechDraw/Gui/ViewProviderViewSection.cpp index 9303fae91c..0f9d3b0fed 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderViewSection.cpp +++ b/src/Mod/TechDraw/Gui/ViewProviderViewSection.cpp @@ -36,10 +36,12 @@ #include #include +#include #include #include #include "TaskSectionView.h" +#include "TaskComplexSection.h" #include "ViewProviderViewSection.h" #include "QGIView.h" @@ -123,6 +125,12 @@ bool ViewProviderViewSection::setEdit(int ModNum) } // clear the selection (convenience) Gui::Selection().clearSelection(); + + auto dcs = dynamic_cast(getViewObject()); + if (dcs) { + Gui::Control().showDialog(new TaskDlgComplexSection(dcs)); + return true; + } Gui::Control().showDialog(new TaskDlgSectionView(getViewObject())); return true; } @@ -133,7 +141,6 @@ bool ViewProviderViewSection::doubleClicked() return true; } - void ViewProviderViewSection::getParameters() { Base::Reference hGrp = App::GetApplication().GetUserParameter() diff --git a/src/Mod/TechDraw/Gui/Widgets/CompassDialWidget.cpp b/src/Mod/TechDraw/Gui/Widgets/CompassDialWidget.cpp new file mode 100644 index 0000000000..031f6e3d9d --- /dev/null +++ b/src/Mod/TechDraw/Gui/Widgets/CompassDialWidget.cpp @@ -0,0 +1,242 @@ +/*************************************************************************** + * Copyright (c) 2022 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 * + * * + ***************************************************************************/ + +//largely based on a python widget from: +//https://github.com/tcalmant/demo-ipopo-qt/blob/master/pc/details/compass.py + +#include + +#include + +#include +#include + +#include "CompassDialWidget.h" + +using namespace TechDrawGui; +using CardinalMap = std::map; + +CompassDialWidget::CompassDialWidget(QWidget* parent) : QWidget(parent), + m_markInterval(15), + m_defaultSize(75), + m_defaultMargin(10), + m_designRadius(64) +{ + setObjectName(QString::fromUtf8("Compass")); + m_rect = QRect(0, 0, m_defaultSize, m_defaultSize); + m_angle = 0.0; + m_margin = m_defaultMargin; + m_designDiameter = 2 * m_designRadius; + + QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + sizePolicy.setHorizontalStretch(1); + sizePolicy.setVerticalStretch(1); + setSizePolicy(sizePolicy); + + repaint(); +} + +QSize CompassDialWidget::sizeHint() const +{ + return m_rect.size(); +} + +QSize CompassDialWidget::minimumSizeHint() const +{ + return QRect(0, 0, m_defaultSize, m_defaultSize).size(); +} + +void CompassDialWidget::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + QPainter painter(this); + drawWidget(painter); + QWidget::paintEvent(event); +} + +void CompassDialWidget::drawWidget(QPainter& painter) +{ + painter.setRenderHint(QPainter::Antialiasing, true); + + // Draw the background + drawBackground(painter); + + //Draw the cardinal points + drawMarkings(painter); + + //Draw the needle + drawNeedle(painter); +} + +void CompassDialWidget::drawBackground(QPainter& painter) +{ + painter.save(); + painter.setPen(QPen(Qt::NoPen)); + // Clear the background + painter.fillRect(m_rect, palette().brush((QPalette::Window))); + painter.restore(); +} + +//Draws the cardinal points on painter. This widget was designed such that +//the dial gradations extend to 50 units from center and the compass point text +//begins at 52 units from the center. With the font size set to 12 pixels, this +//gives a design area of a circle with a radius of approximately 64 units. All of +//the constants reflect this design size. +void CompassDialWidget::drawMarkings(QPainter& painter) +{ + CardinalMap CompassPointText( { {0, "Y"}, {45, "XY"}, {90, "X"}, {135, "X-Y"}, {180, "-Y"}, + {225, "-X-Y"}, {270, "-X"}, {315, "-XY"} } ); + painter.save(); + int markInterval(15); + + //Move to the center of the compass + painter.translate(width() / 2, height() / 2); + double scale = std::min((float) width() / (float) (m_designDiameter + 2.0 * m_margin), + (float) height() / (float) (m_designDiameter + 2.0 * m_margin)); + painter.scale(scale, scale); + + // Setup the fonts and the painter + QFont widgetFont = font(); + widgetFont.setPixelSize(12); + QFontMetrics metrics(widgetFont); + + //outer circle + int circleWidth = 2.0 * (m_designRadius + m_margin); + int circleHeight = 2.0 * (m_designRadius + m_margin); + QRect circleRect(-circleWidth / 2, -circleHeight / 2, circleWidth, circleHeight); + painter.drawEllipse(circleRect); + + painter.setFont(widgetFont); + painter.setPen(QPen(palette().color(QPalette::WindowText))); + + int iDegree = 0; + while ( iDegree < 360 ) { + if (iDegree % 45 == 0) { + //Named direction (every 45°) + painter.drawLine(0, -40, 0, -50); //this has to depend on m_rect or size? + QString qPointText = Base::Tools::fromStdString(CompassPointText.at(iDegree)); + painter.drawText(-metrics.boundingRect(qPointText).width() / 2.0, -52, qPointText); + // what is -52? line end point y = -50 + 2 for margin? + } else { + //Small line + painter.drawLine(0, -45, 0, -50); + } + + //Next line (+15°) + painter.rotate(markInterval); + iDegree += markInterval; + } + + painter.restore(); +} + +//Draws a needle on painter +void CompassDialWidget::drawNeedle(QPainter& painter) +{ + painter.save(); + + //Move to the center of the compass + painter.translate(width() / 2, height() / 2); + + //Rotate to the correct angle + painter.rotate(m_angle); + double scale = std::min((float) width() / (float) (m_designDiameter + 2.0 * m_margin), + (float) height() / (float) (m_designDiameter + 2.0 * m_margin)); + painter.scale(scale, scale); + + //Setup the painter + QPen needlePen(palette().color(QPalette::WindowText)); + needlePen.setWidth(2); + needlePen.setStyle(Qt::DashDotLine); + painter.setPen(needlePen); + painter.setBrush(palette().color(QPalette::WindowText)); + + //Draw the section line + int sectionLineExtent(25); + painter.drawLine(0, sectionLineExtent, 0, -sectionLineExtent); + needlePen.setStyle(Qt::SolidLine); + painter.setPen(needlePen); + int viewDirectionExtent(15); + painter.drawLine(-viewDirectionExtent, sectionLineExtent, 0, sectionLineExtent); + painter.drawLine(-viewDirectionExtent, -sectionLineExtent, 0, -sectionLineExtent); + + //Draw the arrowheads of the needle section line + needlePen.setWidth(1); + needlePen.setStyle(Qt::SolidLine); + painter.setPen(needlePen); + int arrowLength(5); + int arrowWidth(3); //actual 1/2 width + painter.drawPolygon( + QPolygon( { QPoint(0, sectionLineExtent), + QPoint(-arrowLength, sectionLineExtent + arrowWidth), + QPoint(-arrowLength, sectionLineExtent - arrowWidth), + QPoint(0, sectionLineExtent) } ) ); + painter.drawPolygon( + QPolygon( { QPoint(0, -sectionLineExtent), + QPoint(-arrowLength, -(sectionLineExtent + arrowWidth)), + QPoint(-arrowLength, -(sectionLineExtent - arrowWidth)), + QPoint(0, -sectionLineExtent) } ) ); + + //draw the actual needle + needlePen.setStyle(Qt::SolidLine); + painter.setPen(needlePen); + painter.setBrush(palette().color(QPalette::BrightText)); + int needleExtent(40); + painter.drawPolygon( + QPolygon( { QPoint(needleExtent, 0), + QPoint(0, 5), + QPoint(-15, 2), + QPoint(-15, -2), + QPoint(0, -5), + QPoint(needleExtent, 0) } ) ); + + //draw needle pivot + painter.setBrush(palette().color(QPalette::WindowText)); + int pivotSize(4); + QRect pivotRect(-pivotSize / 2, -pivotSize / 2, pivotSize, pivotSize); + painter.drawEllipse(pivotRect); + + //draw needle point + painter.setBrush(QBrush(Qt::red)); + int pointLength(5); + int pointWidth(3); + painter.drawPolygon( + QPolygon( { QPoint(needleExtent, 0), + QPoint(needleExtent - pointLength, pointWidth), + QPoint(needleExtent - pointLength, -pointWidth), + QPoint(needleExtent, 0) } ) ); + + painter.restore(); +} + +//convert a conventional angle to a Qt angle and set the dial accordingly +void CompassDialWidget::setAngle(double newAngle) +{ + m_angle = fmod(360.0 - newAngle, 360.0); + repaint(); +} + +void CompassDialWidget::setSize(int newSize) +{ + m_rect = QRect(0, 0, newSize, newSize); + repaint(); +} diff --git a/src/Mod/TechDraw/Gui/Widgets/CompassDialWidget.h b/src/Mod/TechDraw/Gui/Widgets/CompassDialWidget.h new file mode 100644 index 0000000000..88c553f180 --- /dev/null +++ b/src/Mod/TechDraw/Gui/Widgets/CompassDialWidget.h @@ -0,0 +1,71 @@ +/*************************************************************************** + * Copyright (c) 2022 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 * + * * + ***************************************************************************/ + +//based on a python widget from: +//https://github.com/tcalmant/demo-ipopo-qt/blob/master/pc/details/compass.py + +#ifndef COMPASSDIALWIDGET_H +#define COMPASSDIALWIDGET_H + +#include +#include + +namespace TechDrawGui { + +class CompassDialWidget : public QWidget +{ + Q_OBJECT + +public: + CompassDialWidget(QWidget* parent = 0); + ~CompassDialWidget() = default; + + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + double angle() const { return m_angle; } + void setAngle(double newAngle); + void setSize(int newSize); + +public Q_SLOTS: + void slotChangeAngle(double angle) { setAngle(angle); } + void resetAngle() { setAngle(0.0); } + +protected: + void paintEvent(QPaintEvent* event) override; + void drawWidget(QPainter& painter); + void drawNeedle(QPainter& painter); + void drawMarkings(QPainter& painter); + void drawBackground(QPainter& painter); + +private: + QRect m_rect; + double m_angle; + double m_margin; + double m_markInterval; + int m_defaultSize; + int m_defaultMargin; + int m_designRadius; + int m_designDiameter; +}; + +} //namespace TechDrawGui +#endif // COMPASSDIALWIDGET_H diff --git a/src/Mod/TechDraw/Gui/Widgets/CompassWidget.cpp b/src/Mod/TechDraw/Gui/Widgets/CompassWidget.cpp new file mode 100644 index 0000000000..78f15a38a9 --- /dev/null +++ b/src/Mod/TechDraw/Gui/Widgets/CompassWidget.cpp @@ -0,0 +1,254 @@ +/*************************************************************************** + * Copyright (c) 2022 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 * + * * + ***************************************************************************/ + +// The CompassWidget has several children widgets - a CompassDialWidget, a fine +// adjustment QDoubleSpinBox and a QPushButton that acts as an enter key +// for the spin box. +#include "PreCompiled.h" +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#include +#include +#endif + +#include + +#include +#include + +#include "CompassDialWidget.h" +#include "CompassWidget.h" + +using namespace TechDrawGui; + +CompassWidget::CompassWidget(QWidget *parent) + : QWidget(parent), m_minimumWidth(200), m_minimumHeight(200), m_defaultMargin(10), m_angle(0.0), + m_advanceIncrement(10.0) +{ + setObjectName(QString::fromUtf8("Compass")); + m_rect = QRect(0, 0, m_minimumWidth, m_minimumHeight); + buildWidget(); + compassDial->setSize(m_minimumHeight - 2 * m_defaultMargin); + + connect(pbUseCompassSetting, &QPushButton::pressed, this, &CompassWidget::slotUseSpinboxValue); + dsbAngle->installEventFilter(this); + + connect(pbCWAdvance, &QPushButton::pressed, this, &CompassWidget::slotCWAdvance); + connect(pbCCWAdvance, &QPushButton::pressed, this, &CompassWidget::slotCCWAdvance); +} + +//trap Enter press in dsbAngle so as not to invoke task accept processing +bool CompassWidget::eventFilter(QObject *target, QEvent *event) +{ + if (target == dsbAngle) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { + dsbAngle->interpretText(); + slotSpinBoxEnter(dsbAngle->value()); + return true; + } + } + } + return QWidget::eventFilter(target, event); +} + +void CompassWidget::buildWidget() +{ + resize(m_minimumWidth, m_minimumHeight); + QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(sizePolicy.hasHeightForWidth()); + setSizePolicy(sizePolicy); + setMinimumSize(QSize(m_minimumWidth, m_minimumHeight)); + compassLayout = new QVBoxLayout(this); + compassLayout->setObjectName(QString::fromUtf8("CompassLayout")); + + compassDialLayout = new QHBoxLayout(); + compassDialLayout->setObjectName(QString::fromUtf8("compassDialLayout")); + + pbCWAdvance = new QPushButton(this); + pbCWAdvance->setObjectName(QString::fromUtf8("pbCWAdvance")); + QIcon icon1; + icon1.addFile(QString::fromUtf8(":/icons/arrow-cw.svg"), QSize(), QIcon::Normal, QIcon::On); + pbCWAdvance->setIcon(icon1); + compassDialLayout->addWidget(pbCWAdvance); + + compassDial = new CompassDialWidget(this); + compassDial->setObjectName(QString::fromUtf8("CompassDial")); + compassDialLayout->addWidget(compassDial); + + pbCCWAdvance = new QPushButton(this); + pbCCWAdvance->setObjectName(QString::fromUtf8("pbCCWAdvance")); + QIcon icon2; + icon2.addFile(QString::fromUtf8(":/icons/arrow-ccw.svg"), QSize(), QIcon::Normal, QIcon::On); + pbCCWAdvance->setIcon(icon2); + compassDialLayout->addWidget(pbCCWAdvance); + + compassDialLayout->setStretch(1, 2); + compassLayout->addLayout(compassDialLayout); + + compassControlLayout = new QHBoxLayout(); + compassControlLayout->setObjectName(QString::fromUtf8("compassControlLayout")); + compassControlLabel = new QLabel(this); + compassControlLabel->setObjectName(QString::fromUtf8("compassControlLabel")); + QSizePolicy sizePolicy2(QSizePolicy::Minimum, QSizePolicy::Minimum); + sizePolicy2.setHorizontalStretch(0); + sizePolicy2.setVerticalStretch(0); + sizePolicy2.setHeightForWidth(compassControlLabel->sizePolicy().hasHeightForWidth()); + compassControlLabel->setSizePolicy(sizePolicy2); + + compassControlLayout->addWidget(compassControlLabel); + + dsbAngle = new QDoubleSpinBox(this); + dsbAngle->setObjectName(QString::fromUtf8("dsbAngle")); + sizePolicy2.setHeightForWidth(dsbAngle->sizePolicy().hasHeightForWidth()); + dsbAngle->setSizePolicy(sizePolicy2); + dsbAngle->setMinimumSize(QSize(75, 26)); + dsbAngle->setMouseTracking(true); + dsbAngle->setFocusPolicy(Qt::ClickFocus); + dsbAngle->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter); + dsbAngle->setKeyboardTracking(false); + dsbAngle->setSuffix(QString::fromUtf8("\302\260")); + dsbAngle->setMaximum(360.000000000000000); + dsbAngle->setMinimum(-360.000000000000000); + + compassControlLayout->addWidget(dsbAngle); + + pbUseCompassSetting = new QPushButton(this); + pbUseCompassSetting->setObjectName(QString::fromUtf8("pbUseCompassSetting")); + QIcon icon; + icon.addFile(QString::fromUtf8(":/icons/edit_OK.svg"), QSize(), QIcon::Normal, QIcon::On); + pbUseCompassSetting->setIcon(icon); + pbUseCompassSetting->setText(QString()); + + compassControlLayout->addWidget(pbUseCompassSetting); + + compassControlLayout->setStretch(0, 3); + compassControlLayout->setStretch(1, 2); + + compassLayout->addLayout(compassControlLayout); + + retranslateUi(); +} + +void CompassWidget::retranslateUi() +{ + compassControlLabel->setText( + QApplication::translate("CompassWidget", "View Direction as Angle", nullptr)); +#ifndef QT_NO_TOOLTIP + dsbAngle->setToolTip(QApplication::translate( + "CompassWidget", "The view direction angle relative to +X in the BaseView.", nullptr)); + pbUseCompassSetting->setToolTip(QApplication::translate( + "CompassWidget", "Use the current view direction to set the Section Normal.", nullptr)); + pbCWAdvance->setToolTip(QApplication::translate( + "CompassWidget", "Advance the view direction in clockwise direction.", nullptr)); + pbCCWAdvance->setToolTip(QApplication::translate( + "CompassWidget", "Advance the view direction in anti-clockwise direction.", nullptr)); +#endif// QT_NO_TOOLTIP +} + +QSize CompassWidget::sizeHint() const { return m_rect.size(); } + +QSize CompassWidget::minimumSizeHint() const +{ + return QRect(0, 0, m_minimumWidth, m_minimumHeight).size(); +} + +void CompassWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + QWidget::paintEvent(event); +} + +//general purpose angle update from external source. +void CompassWidget::setDialAngle(double newAngle) +{ + // Base::Console().Message("CW::setDialAngle(%.3f)\n", newAngle); + m_angle = newAngle; + if (compassDial) { + compassDial->setAngle(m_angle); + } + if (dsbAngle) { + dsbAngle->setValue(m_angle); + } +} + +//slot for updates from spinbox arrows or typing. +void CompassWidget::slotSpinBoxUpdate(double newAngle) +{ + // Base::Console().Message("CW::slotSpinBoxUpdate(%.3f)\n", newAngle); + m_angle = newAngle; + Q_EMIT angleChanged(m_angle); + if (compassDial) { + compassDial->setAngle(m_angle); + } +} + +//slot for updates from spinbox on Enter/Return press. +void CompassWidget::slotSpinBoxEnter(double newAngle) +{ + // Base::Console().Message("CW::slotSpinBoxEnter(%.3f)\n", newAngle); + if (dsbAngle) { + m_angle = newAngle; + Q_EMIT angleChanged(m_angle); + if (compassDial) { + compassDial->setAngle(m_angle); + } + } +} + +//slot for OK button press +void CompassWidget::slotUseSpinboxValue() +{ + // Base::Console().Message("CW::slotUseSpinboxValue()\n"); + if (dsbAngle) { + dsbAngle->interpretText(); + m_angle = dsbAngle->value(); + Q_EMIT angleChanged(m_angle); + } + if (compassDial) { + compassDial->setAngle(m_angle); + } +} + +void CompassWidget::slotCWAdvance() +{ + double angle = m_angle - m_advanceIncrement; + if (angle < -360.0) { + angle = angle + 360.0; + } + setDialAngle(angle); +} + +void CompassWidget::slotCCWAdvance() +{ + double angle = m_angle + m_advanceIncrement; + setDialAngle(fmod(angle, 360.0)); +} + +void CompassWidget::setAdvanceIncrement(double newIncrement) { m_advanceIncrement = newIncrement; } diff --git a/src/Mod/TechDraw/Gui/Widgets/CompassWidget.h b/src/Mod/TechDraw/Gui/Widgets/CompassWidget.h new file mode 100644 index 0000000000..191235f16e --- /dev/null +++ b/src/Mod/TechDraw/Gui/Widgets/CompassWidget.h @@ -0,0 +1,105 @@ +/*************************************************************************** + * Copyright (c) 2022 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 COMPASSWIDGET_H +#define COMPASSWIDGET_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QLabel; +class QHBoxLayout; +class QPushButton; +class QVBoxLayout; +QT_END_NAMESPACE + +namespace TechDrawGui { + +class CompassDialWidget; + +class CompassWidget : public QWidget +{ + Q_OBJECT + Q_PROPERTY( double angle READ dialAngle WRITE setDialAngle NOTIFY angleChanged) + +public: + CompassWidget(QWidget* parent = 0); + ~CompassWidget() = default; + + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + bool eventFilter(QObject *target, QEvent *event) override; + void retranslateUi(); + double dialAngle() const { return m_angle; } + void setDialAngle(double newAngle); + void setAdvanceIncrement(double newIncrement); + double advanceIncrement() const { return m_advanceIncrement; } + +Q_SIGNALS: + void angleChanged(double angle); + void angleSet(double angle); + +public Q_SLOTS: + void slotChangeAngle(double angle) { setDialAngle(angle); } + void slotSpinBoxUpdate(double newAngle); + void slotSpinBoxEnter(double newAngle); + void slotUseSpinboxValue(); + void resetAngle() { setDialAngle(0.0); } //conventional angles + void setToEast() { setDialAngle(0.0); } + void setToNorth() { setDialAngle(90.0); } + void setToWest() { setDialAngle(180.0); } + void setToSouth() { setDialAngle(270.0); } + void slotCWAdvance(); + void slotCCWAdvance(); + +protected: + void paintEvent(QPaintEvent* event) override; + void buildWidget(); + double changeAngleConvention(double CWY) const; + +private: + QRect m_rect; + int m_minimumWidth; + int m_minimumHeight; + int m_defaultMargin; + double m_angle; + double m_advanceIncrement; + + QVBoxLayout* compassLayout; + QHBoxLayout* compassDialLayout; + QHBoxLayout* compassControlLayout; + + CompassDialWidget* compassDial; +// DoubleSpinBoxNoEnter* dsbAngle; + QDoubleSpinBox* dsbAngle; + QLabel* compassControlLabel; + QPushButton* pbUseCompassSetting; + QPushButton* pbCWAdvance; + QPushButton* pbCCWAdvance; +}; + +} //namespace TechDrawGui +#endif // COMPASSWIDGET_H + diff --git a/src/Mod/TechDraw/Gui/Widgets/VectorEditWidget.cpp b/src/Mod/TechDraw/Gui/Widgets/VectorEditWidget.cpp new file mode 100644 index 0000000000..15b68ad713 --- /dev/null +++ b/src/Mod/TechDraw/Gui/Widgets/VectorEditWidget.cpp @@ -0,0 +1,265 @@ +/*************************************************************************** + * Copyright (c) 2022 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 * + * * + ***************************************************************************/ + +// A widget for editing Vector3d without taking up too much space in the UI. + +#include "PreCompiled.h" +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include + +#include + +#include +#include +#include + +#include + +#include + +#include "VectorEditWidget.h" + +using namespace TechDrawGui; +using namespace TechDraw; + +VectorEditWidget::VectorEditWidget(QWidget* parent) : QWidget(parent), + m_minimumWidth(200), + m_minimumHeight(30), + m_expandedHeight(155), + m_blockNotify(false) +{ + m_size = QSize(m_minimumWidth, m_minimumHeight); + setObjectName(QString::fromUtf8("VectorEdit")); + buildWidget(); + + connect(tbExpand, SIGNAL(toggled(bool)), this, SLOT(slotExpandButtonToggled(bool))); + connect(dsbX, SIGNAL(valueChanged(double)), this, SLOT(xValueChanged(double))); + connect(dsbY, SIGNAL(valueChanged(double)), this, SLOT(yValueChanged(double))); + connect(dsbZ, SIGNAL(valueChanged(double)), this, SLOT(zValueChanged(double))); + + dsbX->installEventFilter(this); + dsbY->installEventFilter(this); + dsbZ->installEventFilter(this); +} + +//trap Enter press in dsb? so as not to invoke task accept processing +bool VectorEditWidget::eventFilter(QObject *target, QEvent *event) +{ + if (target == dsbX || + target == dsbY || + target == dsbZ) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Return || + keyEvent->key() == Qt::Key_Enter) { + QDoubleSpinBox* dsb = static_cast(target); + dsb->interpretText(); + Q_EMIT dsb->valueChanged(dsb->value()); + return true; + } + } + } + return QWidget::eventFilter(target, event); +} +void VectorEditWidget::setLabel(std::string newLabel) +{ + QString qNewLabelString = Base::Tools::fromStdString(newLabel); + lvectorName->setText(qNewLabelString); +} + +void VectorEditWidget::setLabel(QString newLabel) +{ + lvectorName->setText(newLabel); +} + +void VectorEditWidget::setValue(Base::Vector3d newValue) +{ +// Base::Console().Message("VEW::setValue(%s)\n", DrawUtil::formatVector(newValue).c_str()); + m_value = newValue; + dsbX->setValue(m_value.x); + dsbY->setValue(m_value.y); + dsbZ->setValue(m_value.z); + updateDisplay(); +} + +void VectorEditWidget::setValueNoNotify(Base::Vector3d newValue) +{ +// Base::Console().Message("VEW::setValue(%s)\n", DrawUtil::formatVector(newValue).c_str()); + m_value = newValue; + m_blockNotify = true; + dsbX->setValue(m_value.x); + dsbY->setValue(m_value.y); + dsbZ->setValue(m_value.z); + m_blockNotify = false; + updateDisplay(); +} + +void VectorEditWidget::slotExpandButtonToggled(bool checked) +{ +// Base::Console().Message("VEW::slotExpand - checked: %d\n", checked); + if (checked) { + vectorEditLayout->addLayout(VectorEditItemLayout); + vectorEditLayout->addItem(verticalSpacer); + m_size = QSize(m_minimumWidth, m_expandedHeight); + + } else { + vectorEditLayout->removeItem(VectorEditItemLayout); + vectorEditLayout->removeItem(verticalSpacer); + m_size = QSize(m_minimumWidth, m_minimumHeight); + } +} + +void VectorEditWidget::xValueChanged(double newValue) +{ +// Base::Console().Message("VEW::xValueChanged(%.3f)\n", newValue); + m_value.x = newValue; + updateDisplay(); + if (!m_blockNotify) { + Q_EMIT valueChanged(m_value); + } +} +void VectorEditWidget::yValueChanged(double newValue) +{ +// Base::Console().Message("VEW::yValueChanged(%.3f)\n", newValue); + m_value.y = newValue; + updateDisplay(); + if (!m_blockNotify) { + Q_EMIT valueChanged(m_value); + } +} +void VectorEditWidget::zValueChanged(double newValue) +{ +// Base::Console().Message("VEW::zValueChanged(%.3f)\n", newValue); + m_value.z = newValue; + updateDisplay(); + if (!m_blockNotify) { + Q_EMIT valueChanged(m_value); + } +} + +void VectorEditWidget::updateDisplay() +{ +// Base::Console().Message("VEW::updateDisplay() - m_value: %s\n", DrawUtil::formatVector(m_value).c_str()); + QString qNewDisplayString = Base::Tools::fromStdString(DrawUtil::formatVector(m_value)); + leVectorDisplay->setText(qNewDisplayString); +} + +QSize VectorEditWidget::minimumSizeHint() const +{ + return m_size; +} + +void VectorEditWidget::buildWidget() +{ + if (objectName().isEmpty()) + setObjectName(QString::fromUtf8("VectorEdit")); + QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + setSizePolicy(sizePolicy); + + vectorEditLayout = new QVBoxLayout(this); + vectorEditLayout->setObjectName(QString::fromUtf8("vectorEditLayout")); + vectorEditLayout->setContentsMargins(0, 0, 0, 0); + VectorEditButtonLayout = new QHBoxLayout(); + VectorEditButtonLayout->setSpacing(0); + VectorEditButtonLayout->setObjectName(QString::fromUtf8("VectorEditButtonLayout")); + + lvectorName = new QLabel(this); + lvectorName->setObjectName(QString::fromUtf8("lvectorName")); + VectorEditButtonLayout->addWidget(lvectorName); + + leVectorDisplay = new QLineEdit(this); + leVectorDisplay->setObjectName(QString::fromUtf8("leVectorDisplay")); + VectorEditButtonLayout->addWidget(leVectorDisplay); + + tbExpand = new QToolButton(this); + tbExpand->setObjectName(QString::fromUtf8("tbExpand")); + tbExpand->setText(QString::fromUtf8("...")); + tbExpand->setCheckable(true); + VectorEditButtonLayout->addWidget(tbExpand); + + VectorEditButtonLayout->setStretch(0, 1); + VectorEditButtonLayout->setStretch(1, 1); + vectorEditLayout->addLayout(VectorEditButtonLayout); + + VectorEditItemLayout = new QGridLayout(); + VectorEditItemLayout->setObjectName(QString::fromUtf8("VectorEditItemLayout")); + + lX = new QLabel(); + lX->setObjectName(QString::fromUtf8("lX")); + lX->setText(QString::fromUtf8("X:")); + VectorEditItemLayout->addWidget(lX, 0, 0, 1, 1); + + dsbX = new Gui::DoubleSpinBox(); + dsbX->setObjectName(QString::fromUtf8("dsbX")); + dsbX->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + dsbX->setKeyboardTracking(false); + dsbX->setMaximum(std::numeric_limits::max()); + dsbX->setMinimum(std::numeric_limits::lowest()); + dsbX->setDecimals(Base::UnitsApi::getDecimals()); + VectorEditItemLayout->addWidget(dsbX, 0, 1, 1, 1); + + lY = new QLabel(); + lY->setObjectName(QString::fromUtf8("lY")); + lY->setText(QString::fromUtf8("Y:")); + VectorEditItemLayout->addWidget(lY, 1, 0, 1, 1); + + dsbY = new Gui::DoubleSpinBox(); + dsbY->setObjectName(QString::fromUtf8("dsbY")); + dsbY->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + dsbY->setKeyboardTracking(false); + dsbY->setMaximum(std::numeric_limits::max()); + dsbY->setMinimum(std::numeric_limits::lowest()); + dsbY->setDecimals(Base::UnitsApi::getDecimals()); + VectorEditItemLayout->addWidget(dsbY, 1, 1, 1, 1); + + lZ = new QLabel(); + lZ->setObjectName(QString::fromUtf8("lZ")); + lZ->setText(QString::fromUtf8("Z:")); + VectorEditItemLayout->addWidget(lZ, 2, 0, 1, 1); + + dsbZ = new Gui::DoubleSpinBox(); + dsbZ->setObjectName(QString::fromUtf8("dsbZ")); + dsbZ->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + dsbZ->setKeyboardTracking(false); + dsbZ->setMaximum(std::numeric_limits::max()); + dsbZ->setMinimum(std::numeric_limits::lowest()); + dsbZ->setDecimals(Base::UnitsApi::getDecimals()); + VectorEditItemLayout->addWidget(dsbZ, 2, 1, 1, 1); + + verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); +} + + diff --git a/src/Mod/TechDraw/Gui/Widgets/VectorEditWidget.h b/src/Mod/TechDraw/Gui/Widgets/VectorEditWidget.h new file mode 100644 index 0000000000..f6c7e38b36 --- /dev/null +++ b/src/Mod/TechDraw/Gui/Widgets/VectorEditWidget.h @@ -0,0 +1,107 @@ +/*************************************************************************** + * Copyright (c) 2022 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 VECTOREDITWIDGET_H +#define VECTOREDITWIDGET_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QVBoxLayout; +class QHBoxLayout; +class QGridLayout; +class QLabel; +class QLineEdit; +class QToolButton; +class QSpacerItem; +QT_END_NAMESPACE + +namespace Gui { +class DoubleSpinBox; +} + +#include + +namespace TechDrawGui { + +class VectorEditWidget : public QWidget +{ + Q_OBJECT + +public: + VectorEditWidget(QWidget* parent = 0); + ~VectorEditWidget() = default; + + QSize minimumSizeHint() const override; + bool eventFilter(QObject *target, QEvent *event) override; + + void setLabel(std::string newLabel); + void setLabel(QString newLabel); + Base::Vector3d value() const { return m_value; } + +Q_SIGNALS: + void valueChanged(Base::Vector3d newValue); + +public Q_SLOTS: + void setValue(Base::Vector3d newValue); + void setValueNoNotify(Base::Vector3d newValue); + +protected: + void buildWidget(); + +protected Q_SLOTS: + void slotExpandButtonToggled(bool checked); + void xValueChanged(double newValue); + void yValueChanged(double newValue); + void zValueChanged(double newValue); + + void updateDisplay(); + +private: + int m_minimumWidth; + int m_minimumHeight; + int m_expandedHeight; + bool m_blockNotify; + + QSize m_size; + + Base::Vector3d m_value; + + QVBoxLayout *vectorEditLayout; + QHBoxLayout *VectorEditButtonLayout; + QLabel *lvectorName; + QLineEdit *leVectorDisplay; + QToolButton *tbExpand; + QGridLayout *VectorEditItemLayout; + Gui::DoubleSpinBox *dsbX; + Gui::DoubleSpinBox *dsbY; + Gui::DoubleSpinBox *dsbZ; + QLabel *lX; + QLabel *lY; + QLabel *lZ; + QSpacerItem *verticalSpacer; +}; + +} //namespace TechDrawGui +#endif // VECTOREDITWIDGET_H diff --git a/src/Mod/TechDraw/Gui/Workbench.cpp b/src/Mod/TechDraw/Gui/Workbench.cpp index 3c41a7d4a7..ed0cb7533d 100644 --- a/src/Mod/TechDraw/Gui/Workbench.cpp +++ b/src/Mod/TechDraw/Gui/Workbench.cpp @@ -189,6 +189,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const *draw << "TechDraw_ActiveView"; *draw << "TechDraw_ProjectionGroup"; *draw << "TechDraw_SectionView"; + *draw << "TechDraw_ComplexSection"; *draw << "TechDraw_DetailView"; *draw << "Separator"; *draw << "TechDraw_DraftView"; @@ -245,7 +246,9 @@ Gui::ToolBarItem* Workbench::setupToolBars() const *views << "TechDraw_View"; *views << "TechDraw_ActiveView"; *views << "TechDraw_ProjectionGroup"; - *views << "TechDraw_SectionView"; + *views << "TechDraw_SectionGroup"; +// *views << "TechDraw_SectionView"; +// *views << "TechDraw_ComplexSection"; *views << "TechDraw_DetailView"; *views << "TechDraw_DraftView"; *views << "TechDraw_ArchView"; @@ -389,9 +392,10 @@ Gui::ToolBarItem* Workbench::setupCommandBars() const views->setCommand("Views"); *views << "TechDraw_View"; *views << "TechDraw_ActiveView"; -// *views << "TechDraw_NewMulti"; //deprecated *views << "TechDraw_ProjectionGroup"; - *views << "TechDraw_SectionView"; + *views << "TechDraw_SectionGroup"; +// *views << "TechDraw_SectionView"; +// *views << "TechDraw_ComplexSection"; *views << "TechDraw_DetailView"; *views << "TechDraw_DraftView"; *views << "TechDraw_SpreadsheetView";