Download src/Mod/TechDraw/App/DrawComplexSection.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 69 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/TechDraw/App/DrawComplexSection.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/TechDraw/App/DrawComplexSection.cpp
-
curl -L -o DrawComplexSection.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/TechDraw/App/DrawComplexSection.cpp
69 kB
| /*************************************************************************** | |
| * Copyright (c) 2022 WandererFan <wandererfan@gmail.com> * | |
| * * | |
| * This file is part of the FreeCAD CAx development system. * | |
| * * | |
| * This library is free software; you can redistribute it and/or * | |
| * modify it under the terms of the GNU Library General Public * | |
| * License as published by the Free Software Foundation; either * | |
| * version 2 of the License, or (at your option) any later version. * | |
| * * | |
| * This library is distributed in the hope that it will be useful, * | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
| * GNU Library General Public License for more details. * | |
| * * | |
| * You should have received a copy of the GNU Library General Public * | |
| * License along with this library; see the file COPYING.LIB. If not, * | |
| * write to the Free Software Foundation, Inc., 59 Temple Place, * | |
| * Suite 330, Boston, MA 02111-1307, USA * | |
| * * | |
| ***************************************************************************/ | |
| //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 | |
| using namespace TechDraw; | |
| using namespace std; | |
| using DU = DrawUtil; | |
| //=========================================================================== | |
| // DrawComplexSection | |
| //=========================================================================== | |
| //NOLINTBEGIN | |
| PROPERTY_SOURCE(TechDraw::DrawComplexSection, TechDraw::DrawViewSection) | |
| const char* DrawComplexSection::ProjectionStrategyEnums[] = {"Offset", "Aligned", "NoParallel", | |
| nullptr}; | |
| //NOLINTEND | |
| DrawComplexSection::DrawComplexSection() : | |
| m_waitingForAlign(false) | |
| { | |
| static const char* fgroup = "Cutting Tool"; | |
| //NOLINTBEGIN | |
| 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"); | |
| //NOLINTEND | |
| } | |
| TopoDS_Shape DrawComplexSection::makeCuttingTool(double dMax) | |
| { | |
| TopoDS_Wire profileWire = makeProfileWire(); | |
| if (profileWire.IsNull()) { | |
| throw Base::RuntimeError("Can not make wire from cutting tool (1)"); | |
| } | |
| if (debugSection()) { | |
| BRepTools::Write(profileWire, "DCSmakeCuttingTool_profileWire.brep");//debug | |
| } | |
| // use "canBuild(profile, sectionnormal)" or validateProfileDirection? | |
| if (ProjectionStrategy.getValue() == 0) { | |
| // Offset. Warn if profile is not quite aligned with section normal. if | |
| // the profile and normal are misaligned, the check below for empty "solids" | |
| // will not be correct. | |
| constexpr double AngleThresholdDeg{5.0}; | |
| // bool isOK = | |
| validateOffsetProfile(profileWire, SectionNormal.getValue(), AngleThresholdDeg); | |
| } | |
| // TODO: this check should be in TaskComplexSection also. here it prevents a hard occ crash | |
| // if the sketch can't be made into an appropriate face/prism. | |
| if (CuttingToolWireObject.getValue()->isDerivedFrom(Base::Type::fromName("Sketcher::SketchObject"))) { | |
| if (!validateSketchNormal(CuttingToolWireObject.getValue())) { | |
| Base::Console().warning("cutting object not aligned with section normal in %s\n", Label.getValue()); | |
| } | |
| } | |
| if (BRep_Tool::IsClosed(profileWire)) { | |
| return makeCuttingToolFromClosedProfile(profileWire, dMax); | |
| } | |
| TopoDS_Shape cuttingTool = cuttingToolFromProfile(profileWire, dMax); | |
| if (debugSection()) { | |
| BRepTools::Write(cuttingTool, "DCSmakeCuttingTool_cuttingToo.brep");//debug | |
| } | |
| // save the tool face for shading/hatching of cut surface | |
| auto extrudeDir = Base::convertTo<gp_Dir>(getReferenceAxis()); | |
| gp_Vec extrudeVec = 2 * dMax * extrudeDir; | |
| m_toolFaceShape = BRepPrimAPI_MakePrism(profileWire, extrudeVec).Shape(); | |
| m_toolFaceShape = ShapeUtils::moveShape(m_toolFaceShape, Base::convertTo<Base::Vector3d>(extrudeDir) * -dMax); | |
| if (debugSection()) { | |
| BRepTools::Write(m_toolFaceShape, "DCSmakeCuttingTool_m_toolFaceShape.brep"); //debug | |
| } | |
| if (cuttingTool.ShapeType() == TopAbs_COMPSOLID || | |
| cuttingTool.ShapeType() == TopAbs_COMPOUND) { | |
| //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. | |
| return removeEmptyShapes(cuttingTool); | |
| } | |
| return cuttingTool; | |
| } | |
| TopoDS_Shape DrawComplexSection::getShapeToPrepare() const | |
| { | |
| if (ProjectionStrategy.getValue() == 0) { | |
| //Offset. Use regular section behaviour | |
| return DrawViewSection::getShapeToPrepare(); | |
| } | |
| //Aligned (or NoParallel) 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) | |
| { | |
| if (ProjectionStrategy.getValue() == 0) { | |
| //Offset. Use regular section behaviour | |
| return DrawViewSection::prepareShape(cutShape, shapeSize); | |
| } | |
| //"Aligned" projection (Aligned Section) | |
| if (m_alignResult.IsNull()) { | |
| return {}; | |
| } | |
| // our shape is already centered "left/right" and "up/down" so we don't need to | |
| // center it here | |
| m_preparedShape = ShapeUtils::scaleShape(m_alignResult, getScale()); | |
| if (!DrawUtil::fpCompare(Rotation.getValue(), 0.0)) { | |
| m_preparedShape = | |
| ShapeUtils::rotateShape(m_preparedShape, getProjectionCS(), Rotation.getValue()); | |
| } | |
| if (debugSection()) { | |
| BRepTools::Write(m_preparedShape, "DCSprepareShape_preparedShape.brep"); //debug | |
| } | |
| return m_preparedShape; | |
| } | |
| void DrawComplexSection::makeSectionCut(const TopoDS_Shape& baseShape) | |
| { | |
| if (ProjectionStrategy.getValue() == 0) { | |
| //Offset. Use regular section behaviour | |
| return DrawViewSection::makeSectionCut(baseShape); | |
| } | |
| try { | |
| connectAlignWatcher = | |
| QObject::connect(&m_alignWatcher, &QFutureWatcherBase::finished, &m_alignWatcher, | |
| [this] { this->onSectionCutFinished(); }); | |
| // We create a lambda closure to hold a copy of baseShape. | |
| // This is important because this variable might be local to the calling | |
| // function and might get destructed before the parallel processing finishes. | |
| auto lambda = [this, baseShape]{this->makeAlignedPieces(baseShape);}; | |
| m_alignFuture = QtConcurrent::run(std::move(lambda)); | |
| m_alignWatcher.setFuture(m_alignFuture); | |
| waitingForAlign(true); | |
| } | |
| catch (...) { | |
| Base::Console().warning("%s failed to make alignedPieces\n", Label.getValue()); | |
| return; | |
| } | |
| return DrawViewSection::makeSectionCut(baseShape); | |
| } | |
| void DrawComplexSection::onSectionCutFinished() | |
| { | |
| if (m_cutFuture.isRunning() || //waitingForCut() | |
| m_alignFuture.isRunning()) {//waitingForAlign() | |
| //can not continue yet. return until the other thread ends | |
| return; | |
| } | |
| DrawViewSection::onSectionCutFinished(); | |
| QObject::disconnect(connectAlignWatcher); | |
| } | |
| //! for Aligned strategy, cut the rawShape by each segment of the tool profile and arrange the | |
| //! cut results in order. | |
| void DrawComplexSection::makeAlignedPieces(const TopoDS_Shape& rawShape) | |
| { | |
| if (!canBuild(getSectionCS(), CuttingToolWireObject.getValue())) { | |
| throw Base::RuntimeError("Profile is parallel to Section Normal"); | |
| } | |
| TopoDS_Wire profileWire = makeProfileWire(); | |
| if (profileWire.IsNull()) { | |
| throw Base::RuntimeError("ComplexSection failed to make profileWire"); | |
| } | |
| auto edgesAll = getUniqueEdges(profileWire); | |
| if (edgesAll.empty()) { | |
| // this is bad! | |
| throw Base::RuntimeError("Complex section: profile wire has no edges."); | |
| } | |
| // the reversers control left to right vs right to left (or top to bottom vs bottom to top) | |
| // arrangement of the cut pieces. | |
| double horizReverser{1.0}; | |
| double verticalReverser{1.0}; | |
| gp_Vec gProfileVec = makeProfileVector(profileWire); | |
| auto isProfileVertical = getReversers(gProfileVec, horizReverser, verticalReverser); | |
| // we should wind up with one entry per segment of the profile | |
| // this should be done with map and a response class | |
| std::vector<TopoDS_Shape> pieces(edgesAll.size()); // results of cutting source with each segment's tool shape | |
| std::vector<double> pieceXSizeAll(edgesAll.size()); //size in sectionCS.XDirection (width) | |
| std::vector<double> pieceYSizeAll(edgesAll.size()); //size in sectionCS.YDirection (height) | |
| std::vector<double> pieceZSizeAll(edgesAll.size()); //size in sectionCS.Direction (depth) | |
| std::vector<double> pieceVerticalAll(edgesAll.size()); // displacement of piece in vertical direction | |
| auto uSectionNormal = SectionNormal.getValue(); | |
| uSectionNormal.Normalize(); | |
| auto uRotateAxis = getReferenceAxis(); | |
| uRotateAxis.Normalize(); | |
| // these normals will be pointing into a tool made from the profile. We want the normal pointing into | |
| // the remaining material shape, so we will reverse the normal at time of use. | |
| std::vector<std::pair<int, Base::Vector3d>> faceNormals = getSegmentViewDirections(profileWire, uSectionNormal); | |
| // faceNormals are not in the same order as the faces(sometimes??). | |
| TopExp_Explorer expFaces(m_toolFaceShape, TopAbs_FACE); | |
| for (int iPiece = 0; expFaces.More(); expFaces.Next(), iPiece++) { | |
| TopoDS_Face face = TopoDS::Face(expFaces.Current()); | |
| if (!isFacePlanar(face)) { | |
| // TODO: continue blocks curved profile segments (which doesn't work right). | |
| continue; | |
| } | |
| // faceNormals point into toolShape. we want to intersect with remaining material, so reverse | |
| // the normal. | |
| std::pair<int, Base::Vector3d> segmentPair = findNormalForFace(face, faceNormals, edgesAll); | |
| int segmentIndex = segmentPair.first; | |
| Base::Vector3d segmentNormal = segmentPair.second * -1; | |
| segmentNormal.Normalize(); | |
| // always true for aligned, but not for no-parallel | |
| if (!showSegment(segmentNormal)) { | |
| continue; | |
| } | |
| double pieceVertical{0}; | |
| TopoDS_Shape rotatedPiece = cutAndRotatePiece(rawShape, face, segmentIndex, segmentNormal, pieceVertical); | |
| if (debugSection()) { | |
| stringstream ss; | |
| ss << "DCSmakeAlignedPieces_cutAndRotatedPiece" << segmentIndex << ".brep"; | |
| BRepTools::Write(rotatedPiece, ss.str().c_str());//debug | |
| } | |
| AlignedSizeResponse sizeResponse = getAlignedSize(rotatedPiece, segmentIndex); | |
| Base::Vector3d pieceSize = sizeResponse.pieceSize; | |
| pieceXSizeAll.at(segmentIndex) = pieceSize.x; // size in ProjectionCS. | |
| pieceYSizeAll.at(segmentIndex) = pieceSize.y; | |
| pieceZSizeAll.at(segmentIndex) = pieceSize.z; | |
| pieceVerticalAll.at(segmentIndex) = pieceVertical; | |
| if (debugSection()) { | |
| stringstream ss; | |
| ss << "DCSAlignedPiece" << segmentIndex << ".brep"; | |
| BRepTools::Write(sizeResponse.alignedPiece, ss.str().c_str());//debug | |
| } | |
| auto pieceOnPlane = movePieceToPaperPlane(sizeResponse.alignedPiece, sizeResponse.zMax); | |
| if (debugSection()) { | |
| stringstream ss; | |
| ss << "DCSpieceOnPlane" << segmentIndex << ".brep"; | |
| BRepTools::Write(pieceOnPlane, ss.str().c_str());//debug | |
| } | |
| // pieceOnPlane is on the paper plane, with piece centroid at the origin | |
| pieces.at(segmentIndex) = pieceOnPlane; | |
| } | |
| if (pieces.empty()) { | |
| m_alignResult = TopoDS_Compound(); | |
| Base::Console().message("DCS::makeAlignedPieces - no result\n"); | |
| return; | |
| } | |
| //space the pieces "horizontally" or "vertically" in OXYZ | |
| double movementReverser = isProfileVertical ? verticalReverser : horizReverser; | |
| auto movementAxis = gp_Vec(gp::OX().Direction()); | |
| auto alignmentAxis = gp_Vec(gp::OY().Direction().Reversed()); | |
| if (isProfileVertical) { | |
| movementAxis = gp_Vec(gp::OY().Direction()); | |
| alignmentAxis = gp_Vec(gp::OX().Direction()); | |
| } | |
| gp_Vec gMovementVector = movementAxis * movementReverser; | |
| size_t stopAt = pieces.size(); | |
| double cursorPosition = 0.0; | |
| for (size_t iPiece = 0; iPiece < stopAt; iPiece++) { | |
| double pieceSizeMoveDist = pieceXSizeAll.at(iPiece); | |
| if (isProfileVertical) { | |
| pieceSizeMoveDist = pieceYSizeAll.at(iPiece); | |
| } | |
| auto movedPiece = distributePiece(pieces.at(iPiece), pieceSizeMoveDist, pieceVerticalAll.at(iPiece), | |
| alignmentAxis, gMovementVector, cursorPosition); | |
| pieces.at(iPiece) = movedPiece; | |
| cursorPosition += pieceSizeMoveDist; | |
| if (debugSection()) { | |
| stringstream ss; | |
| ss << "DCSMovedPiece" << iPiece << ".brep"; | |
| BRepTools::Write(pieces.at(iPiece), ss.str().c_str());//debug | |
| } | |
| } | |
| //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 = Base::convertTo<Base::Vector3d>(gMovementVector) * cursorPosition / -2; | |
| TopoDS_Shape centeredCompound = ShapeUtils::moveShape(comp, centerVector); | |
| if (debugSection()) { | |
| BRepTools::Write(centeredCompound, "DCSmap40CenteredCompound.brep");//debug | |
| } | |
| // re-align our shape with the projection CS | |
| gp_Ax3 stdCS; //OXYZ | |
| gp_Trsf xPieceAlign; | |
| xPieceAlign.SetTransformation(getProjectionCS(), stdCS); | |
| BRepBuilderAPI_Transform mkTransAlign(centeredCompound, xPieceAlign); | |
| TopoDS_Shape alignedCompound = mkTransAlign.Shape(); | |
| if (debugSection()) { | |
| BRepTools::Write(alignedCompound, "DCSmap50AlignedCompound.brep");//debug | |
| } | |
| m_alignResult = 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 (not implemented). | |
| TopoDS_Compound | |
| DrawComplexSection::findSectionPlaneIntersections(const TopoDS_Shape& shapeToIntersect) | |
| { | |
| if (shapeToIntersect.IsNull()) { | |
| // this shouldn't happen | |
| Base::Console().warning("DCS::findSectionPlaneInter - %s - cut shape is Null\n", | |
| getNameInDocument()); | |
| return {}; | |
| } | |
| if (ProjectionStrategy.getValue() == 0) {//Offset | |
| return singleToolIntersections(shapeToIntersect); | |
| } | |
| return alignedToolIntersections(shapeToIntersect); | |
| } | |
| //Intersect cutShape with each segment of the cutting tool | |
| TopoDS_Compound DrawComplexSection::singleToolIntersections(const TopoDS_Shape& cutShape) | |
| { | |
| App::DocumentObject* toolObj = CuttingToolWireObject.getValue(); | |
| if (!isLinearProfile(toolObj)) { | |
| //TODO: special handling required here | |
| // Base::Console().message("DCS::singleToolIntersection - profile has curves\n"); | |
| } | |
| BRep_Builder builder; | |
| TopoDS_Compound result; | |
| builder.MakeCompound(result); | |
| 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<TopoDS_Face> commonFaces = faceShapeIntersect(face, m_toolFaceShape); | |
| for (auto& cFace : commonFaces) { | |
| builder.Add(result, cFace); | |
| } | |
| } | |
| return result; | |
| } | |
| //Intersect cutShape with the effective (flattened paper plane) cutting plane to generate cut surface faces | |
| TopoDS_Compound DrawComplexSection::alignedToolIntersections(const TopoDS_Shape& cutShape) | |
| { | |
| 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::alignedToolIntersection - profile has curves\n"); | |
| } | |
| gp_Pln effectivePlane = getSectionPlane(); | |
| //aligned 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<TopoDS_Face> commonFaces = faceShapeIntersect(face, cuttingFace); | |
| for (auto& cFace : commonFaces) { | |
| builder.Add(result, cFace); | |
| } | |
| } | |
| if (debugSection()) { | |
| BRepTools::Write(result, "DCSmakeAlignedPieces_AlignedIntersectionResult.brep");//debug | |
| } | |
| return result; | |
| } | |
| TopoDS_Compound DrawComplexSection::alignSectionFaces(const TopoDS_Shape& faceIntersections) | |
| { | |
| 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_Shape DrawComplexSection::getShapeForDetail() const | |
| { | |
| if (ProjectionStrategy.getValue() == 0) {//Offset | |
| return DrawViewSection::getShapeForDetail(); | |
| } | |
| //Aligned | |
| return m_preparedShape; | |
| } | |
| TopoDS_Wire DrawComplexSection::makeProfileWire() const | |
| { | |
| App::DocumentObject* toolObj = CuttingToolWireObject.getValue(); | |
| return makeProfileWire(toolObj); | |
| } | |
| TopoDS_Wire DrawComplexSection::makeProfileWire(App::DocumentObject* toolObj) | |
| { | |
| if (!isProfileObject(toolObj)) { | |
| return {}; | |
| } | |
| TopoDS_Shape toolShape = Part::Feature::getShape(toolObj, Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform); | |
| if (toolShape.IsNull()) { | |
| return {}; | |
| } | |
| TopoDS_Wire profileWire; | |
| if (toolShape.ShapeType() == TopAbs_WIRE) { | |
| profileWire = makeNoseToTailWire(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; | |
| } | |
| gp_Vec DrawComplexSection::makeProfileVector(const TopoDS_Wire& profileWire) | |
| { | |
| auto ends = getWireEnds(profileWire); | |
| auto vec = ends.second - ends.first; | |
| vec.Normalize(); | |
| return Base::convertTo<gp_Vec>(vec); | |
| } | |
| //make drawable td geometry for section line | |
| BaseGeomPtrVector DrawComplexSection::makeSectionLineGeometry() | |
| { | |
| BaseGeomPtrVector result; | |
| auto* baseDvp = freecad_cast<DrawViewPart*>(BaseView.getValue()); | |
| if (baseDvp) { | |
| TopoDS_Wire lineWire = makeSectionLineWire(); | |
| // projectedWire will be Y inverted | |
| 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<Base::Vector3d, Base::Vector3d> DrawComplexSection::sectionLineEnds() | |
| { | |
| std::pair<Base::Vector3d, Base::Vector3d> result; | |
| TopoDS_Wire lineWire = makeSectionLineWire(); | |
| if (lineWire.IsNull()) { | |
| return result; | |
| } | |
| auto ends = getWireEnds(lineWire); | |
| Base::Vector3d first = ends.first; | |
| Base::Vector3d last = ends.second; | |
| auto* baseDvp = freecad_cast<DrawViewPart*>(BaseView.getValue()); | |
| if (baseDvp) { | |
| first = baseDvp->projectPoint(first); | |
| last = baseDvp->projectPoint(last); | |
| } | |
| result.first = first; | |
| result.second = last; | |
| return result; | |
| } | |
| //! get the directions (in 3d) of the section line arrows. | |
| //! the arrows on the section line are line of sight - from eye to preserved material. In a simple section, | |
| //! this is opposite to the section normal. In the complex section, we need the perpendicular direction most | |
| //! opposite to the SectionNormal. The arrows should be perpendicular to their profile segment. | |
| std::pair<Base::Vector3d, Base::Vector3d> DrawComplexSection::sectionLineArrowDirs() | |
| { | |
| App::DocumentObject* toolObj = CuttingToolWireObject.getValue(); | |
| TopoDS_Wire profileWire = makeProfileWire(toolObj); | |
| if (profileWire.IsNull()) { | |
| throw Base::RuntimeError("Complex section profile wire is null"); | |
| } | |
| std::vector<std::pair<int, Base::Vector3d>> segmentNormals = | |
| getSegmentViewDirections(profileWire, SectionNormal.getValue()); | |
| if (segmentNormals.empty()) { | |
| throw Base::RuntimeError("A complex section failed to create profile segment view directions"); | |
| } | |
| Base::Vector3d firstArrowDir = segmentNormals.front().second * -1; | |
| Base::Vector3d lastArrowDir = segmentNormals.back().second * -1; | |
| return { firstArrowDir, lastArrowDir }; | |
| } | |
| //! makes a representation of the 3d arrow directions on the base view's coord system | |
| std::pair<Base::Vector3d, Base::Vector3d> | |
| DrawComplexSection::sectionLineArrowDirsMapped() | |
| { | |
| std::pair<Base::Vector3d, Base::Vector3d> arrowDirsRaw = sectionLineArrowDirs(); | |
| TopoDS_Edge firstMapped = mapEdgeToBase(arrowDirsRaw.first); | |
| TopoDS_Edge lastMapped = mapEdgeToBase(arrowDirsRaw.second); | |
| std::pair<Base::Vector3d, Base::Vector3d> arrowEnds = getSegmentEnds(firstMapped); | |
| Base::Vector3d firstDir = arrowEnds.second - arrowEnds.first; | |
| arrowEnds = getSegmentEnds(lastMapped); | |
| Base::Vector3d lastDir = arrowEnds.second - arrowEnds.first; | |
| return { firstDir, lastDir }; | |
| } | |
| //! find an axis for measuring rotation vs the line of sight | |
| Base::Vector3d DrawComplexSection::getReferenceAxis() const | |
| { | |
| Base::Vector3d rawDirection = getBaseDVP()->Direction.getValue(); | |
| rawDirection.Normalize(); | |
| return DU::closestBasisOriented(rawDirection); | |
| } | |
| //make a wire suitable for projection on a base view | |
| TopoDS_Wire DrawComplexSection::makeSectionLineWire() | |
| { | |
| TopoDS_Wire lineWire; | |
| App::DocumentObject* toolObj = CuttingToolWireObject.getValue(); | |
| auto* baseDvp = freecad_cast<DrawViewPart*>(BaseView.getValue()); | |
| if (baseDvp) { | |
| TopoDS_Shape toolShape = Part::Feature::getShape(toolObj, Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform); | |
| if (toolShape.IsNull()) { | |
| // CuttingToolWireObject is likely still restoring and has no shape yet | |
| return {}; | |
| } | |
| Base::Vector3d centroid = baseDvp->getCurrentCentroid(); | |
| TopoDS_Shape sTrans = | |
| ShapeUtils::ShapeUtils::moveShape(toolShape, centroid * -1.0); | |
| TopoDS_Shape sScaled = ShapeUtils::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(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().warning("DCS::makeSectionLineGeometry - profile is type: %d\n", | |
| static_cast<int>(sScaled.ShapeType())); | |
| return {}; | |
| } | |
| } | |
| 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() | |
| { | |
| ChangePointVector result; | |
| std::vector<gp_Pnt> allPoints; | |
| auto* baseDvp = freecad_cast<DrawViewPart*>(BaseView.getValue()); | |
| if (baseDvp) { | |
| TopoDS_Wire lineWire = makeSectionLineWire(); | |
| // projectedWire will be Y inverted | |
| 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 * 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 | |
| { | |
| App::DocumentObject* base = BaseView.getValue(); | |
| if (!base | |
| || !base->isDerivedFrom<TechDraw::DrawViewPart>()) {//is second clause necessary? | |
| //if this DCS does not have a baseView, we must use the existing SectionCS | |
| return getSectionCS(); | |
| } | |
| return DrawViewSection::getCSFromBase(sectionName); | |
| } | |
| // check for profile segments that are almost, but not quite in the same direction | |
| // as the section normal direction. this often indicates a problem with the direction | |
| // being slightly wrong. see https://forum.freecad.org/viewtopic.php?t=79017&sid=612a62a60f5db955ee071a7aaa362dbb | |
| bool DrawComplexSection::validateOffsetProfile(const TopoDS_Wire& profile, Base::Vector3d direction, double angleThresholdDeg) const | |
| { | |
| constexpr double HalfCircleDegrees{180.0}; | |
| double angleThresholdRad = angleThresholdDeg * std::numbers::pi / HalfCircleDegrees; | |
| TopExp_Explorer explEdges(profile, TopAbs_EDGE); | |
| for (; explEdges.More(); explEdges.Next()) { | |
| std::pair<Base::Vector3d, Base::Vector3d> segmentEnds = getSegmentEnds(TopoDS::Edge(explEdges.Current())); | |
| Base::Vector3d segmentDir = segmentEnds.second - segmentEnds.first; | |
| double angleRad = segmentDir.GetAngle(direction); | |
| if (angleRad < angleThresholdRad && | |
| angleRad > 0.0) { | |
| // profile segment is slightly skewed. possible bad SectionNormal? | |
| Base::Console().warning("%s profile is slightly skewed. Check SectionNormal low decimal places\n", | |
| getNameInDocument()); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| // next two methods could be templated (??) to handle edge/wire | |
| std::pair<Base::Vector3d, Base::Vector3d> | |
| DrawComplexSection::getSegmentEnds(const TopoDS_Edge& segment) | |
| { | |
| TopoDS_Vertex tvFirst; | |
| TopoDS_Vertex tvLast; | |
| TopExp::Vertices(segment, tvFirst, tvLast); | |
| gp_Pnt gpFirst = BRep_Tool::Pnt(tvFirst); | |
| gp_Pnt gpLast = BRep_Tool::Pnt(tvLast); | |
| std::pair<Base::Vector3d, Base::Vector3d> result; | |
| result.first = Base::convertTo<Base::Vector3d>(gpFirst); | |
| result.second = Base::convertTo<Base::Vector3d>(gpLast); | |
| return result; | |
| } | |
| std::pair<Base::Vector3d, Base::Vector3d> | |
| DrawComplexSection::getWireEnds(const TopoDS_Wire& wire) | |
| { | |
| TopoDS_Vertex tvFirst; | |
| TopoDS_Vertex tvLast; | |
| TopExp::Vertices(wire, tvFirst, tvLast); | |
| gp_Pnt gpFirst = BRep_Tool::Pnt(tvFirst); | |
| gp_Pnt gpLast = BRep_Tool::Pnt(tvLast); | |
| std::pair<Base::Vector3d, Base::Vector3d> result; | |
| result.first = Base::convertTo<Base::Vector3d>(gpFirst); | |
| result.second = Base::convertTo<Base::Vector3d>(gpLast); | |
| return result; | |
| } | |
| //get the "effective" (flattened paper plane) 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 {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->isDerivedFrom<TechDraw::DrawViewPart>()) { | |
| //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. | |
| bool DrawComplexSection::validateProfilePosition(const TopoDS_Wire& profileWire, const gp_Ax2& sectionCS) const | |
| { | |
| auto wireEnds = getWireEnds(profileWire); | |
| auto gpFirst = Base::convertTo<gp_Pnt>(wireEnds.first); | |
| gp_Vec gProfileVector = makeProfileVector(profileWire); | |
| //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); | |
| auto 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; //NOLINT | |
| 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); | |
| //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 !DU::fpCompare(fabs(gSectionNormal.Dot(segmentNormal)), 0.0); | |
| } | |
| bool DrawComplexSection::showSegment(const Base::Vector3d& segmentNormal) const | |
| { | |
| return showSegment(Base::convertTo<gp_Dir>(segmentNormal)); | |
| } | |
| //Can we make a ComplexSection using this profile and sectionNormal? | |
| bool DrawComplexSection::canBuild(gp_Ax2 sectionCS, App::DocumentObject* profileObject) | |
| { | |
| if (!isProfileObject(profileObject)) { | |
| return false; | |
| } | |
| TopoDS_Shape shape = Part::Feature::getShape(profileObject, Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform); | |
| if (BRep_Tool::IsClosed(shape)) { | |
| //closed profiles don't have a profile vector but should always make a section? | |
| return true; | |
| } | |
| // profile should be ortho to section normal, but this is a weak test | |
| gp_Vec gProfileVec = makeProfileVector(makeProfileWire(profileObject)); | |
| double dot = fabs(gProfileVec.Dot(sectionCS.Direction())); | |
| return !DU::fpCompare(dot, 1.0, EWTOLERANCE); | |
| } | |
| // general purpose geometry methods | |
| // returns the normal of the face. | |
| 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; | |
| double vMid = (vParmFirst + vParmLast) / 2; | |
| constexpr double PropTolerance{0.01}; | |
| BRepLProp_SLProps prop(adapt, uMid, vMid, 1, PropTolerance); | |
| 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) | |
| { | |
| constexpr double OverlapTolerance{0.1}; | |
| Bnd_Box box0; | |
| Bnd_Box box1; | |
| BRepBndLib::Add(face, box0); | |
| box0.SetGap(OverlapTolerance);//generous | |
| BRepBndLib::Add(shape, box1); | |
| box1.SetGap(OverlapTolerance); | |
| return !box0.IsOut(box1); | |
| } | |
| TopoDS_Shape DrawComplexSection::shapeShapeIntersect(const TopoDS_Shape& shape0, | |
| const TopoDS_Shape& shape1) | |
| { | |
| FCBRepAlgoAPI_Common anOp; | |
| anOp.SetFuzzyValue(EWTOLERANCE); | |
| TopTools_ListOfShape anArg1; | |
| TopTools_ListOfShape anArg2; | |
| anArg1.Append(shape0); | |
| anArg2.Append(shape1); | |
| anOp.SetArguments(anArg1); | |
| anOp.SetTools(anArg2); | |
| anOp.Build(); | |
| TopoDS_Shape result = anOp.Shape();//always a compound | |
| if (isTrulyEmpty(result)) { | |
| return {}; | |
| } | |
| return result; | |
| } | |
| //find all the intersecting regions of face and shape | |
| std::vector<TopoDS_Face> DrawComplexSection::faceShapeIntersect(const TopoDS_Face& face, | |
| const TopoDS_Shape& shape) | |
| { | |
| TopoDS_Shape intersect = shapeShapeIntersect(face, shape); | |
| if (intersect.IsNull()) { | |
| return {}; | |
| } | |
| std::vector<TopoDS_Face> 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 | |
| //! this doesn't need to be a wire, it could be just a shape? | |
| TopoDS_Wire DrawComplexSection::makeNoseToTailWire(const TopoDS_Shape& inShape) | |
| { | |
| if (inShape.IsNull()) { | |
| return {}; | |
| } | |
| std::list<TopoDS_Edge> inList; | |
| TopExp_Explorer expEdges(inShape, TopAbs_EDGE); | |
| for (; expEdges.More(); expEdges.Next()) { | |
| TopoDS_Edge edge = TopoDS::Edge(expEdges.Current()); | |
| inList.push_back(edge); | |
| } | |
| std::list<TopoDS_Edge> sortedList; | |
| if (inList.empty() || inList.size() == 1) { | |
| return {}; | |
| } | |
| 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, Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform); | |
| 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, Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform); | |
| 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<TopoDS_Edge> 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, Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform); | |
| if (shape.IsNull()) { | |
| return false; | |
| } | |
| if (shape.ShapeType() == TopAbs_EDGE) { | |
| //only have 1 edge | |
| TopoDS_Edge edge = TopoDS::Edge(shape); | |
| BRepAdaptor_Curve adapt(edge); | |
| return (adapt.GetType() == GeomAbs_Line); | |
| } | |
| if (shape.ShapeType() == TopAbs_WIRE) { | |
| std::vector<TopoDS_Edge> 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; | |
| } | |
| //a compound with no content is not considered IsNull by OCC. A more thorough check | |
| //is required. | |
| //https://dev.opencascade.org/content/compound-empty | |
| bool DrawComplexSection::isTrulyEmpty(const TopoDS_Shape& inShape) | |
| { | |
| bool hasContent = !inShape.IsNull() && TopoDS_Iterator(inShape).More(); | |
| return !hasContent; | |
| } | |
| TopoDS_Shape DrawComplexSection::removeEmptyShapes(const TopoDS_Shape& roughTool) | |
| { | |
| 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; | |
| } | |
| //! reversers determine if the cut pieces are arranged left to right or right to left (down to up/up to down) | |
| //! returns true if the profile vector is vertical. | |
| bool DrawComplexSection::getReversers(const gp_Vec& gProfileVec, double& horizReverser, double& verticalReverser) | |
| { | |
| bool isProfileVertical = true; | |
| auto sectionCS = getSectionCS(); | |
| auto sectionCSX = sectionCS.XDirection(); | |
| auto sectionCSY = sectionCS.YDirection(); | |
| auto verticalDot = gProfileVec.Dot(sectionCSY); | |
| if (DU::fpCompare(fabs(verticalDot), 0, EWTOLERANCE)) { | |
| //profile is +/- normal to Y. | |
| isProfileVertical = false; | |
| } | |
| horizReverser = 1.0; //profile vector points to right, so we move to right | |
| if (gProfileVec.Dot(sectionCSX) < 0.0) { | |
| //profileVec does not point towards stdX (right in paper space) | |
| horizReverser = -1.0; | |
| } | |
| verticalReverser = 1.0;//profile vector points to top, so we will move pieces upwards | |
| if (gProfileVec.Dot(sectionCSY) < 0.0) { | |
| //profileVec does not point towards stdY (up in paper space) | |
| verticalReverser = -1.0; | |
| } | |
| return isProfileVertical; | |
| } | |
| //! 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 transformation | |
| //! does not affect the original. | |
| AlignedSizeResponse DrawComplexSection::getAlignedSize(const TopoDS_Shape& pieceRotated, | |
| int iPiece) const | |
| { | |
| gp_Ax3 stdCS; //OXYZ | |
| BRepBuilderAPI_Copy BuilderPieceCopy(pieceRotated); | |
| TopoDS_Shape copyPieceRotatedShape = BuilderPieceCopy.Shape(); | |
| gp_Trsf xPieceAlign; | |
| xPieceAlign.SetTransformation(stdCS, getProjectionCS()); | |
| BRepBuilderAPI_Transform mkTransAlign(copyPieceRotatedShape, xPieceAlign); | |
| TopoDS_Shape pieceAligned = mkTransAlign.Shape(); | |
| // we may have shifted our piece off center, so we better recenter here | |
| gp_Trsf xPieceRecenter; | |
| gp_Vec rotatedCentroid = gp_Vec(ShapeUtils::findCentroid(pieceAligned).XYZ()); | |
| xPieceRecenter.SetTranslation(rotatedCentroid * -1.0); | |
| BRepBuilderAPI_Transform mkTransRecenter(pieceAligned, xPieceRecenter, true); | |
| pieceAligned = mkTransRecenter.Shape(); | |
| if (debugSection()) { | |
| stringstream ss; | |
| ss << "DCSDpieceAligned" << iPiece << ".brep"; | |
| BRepTools::Write(pieceAligned, ss.str().c_str());//debug | |
| ss.clear(); | |
| ss.str(std::string()); | |
| } | |
| Bnd_Box shapeBox; | |
| shapeBox.SetGap(0.0); | |
| BRepBndLib::AddOptimal(pieceAligned, shapeBox); | |
| double xMin = 0, xMax = 0, yMin = 0, yMax = 0, zMin = 0, zMax = 0; //NOLINT | |
| shapeBox.Get(xMin, yMin, zMin, xMax, yMax, zMax); | |
| double pieceXSize(xMax - xMin); | |
| double pieceYSize(yMax - yMin); | |
| double pieceZSize(zMax - zMin); | |
| Base::Vector3d pieceSize{pieceXSize, pieceYSize, pieceZSize}; | |
| return {pieceAligned, pieceSize, zMax}; | |
| } | |
| //! cut the rawShape with a tool derived from the segmentFace and align the result with the | |
| //! page plane. Also supplies the piece size. | |
| TopoDS_Shape DrawComplexSection::cutAndRotatePiece(const TopoDS_Shape& rawShape, | |
| const TopoDS_Face& segmentFace, | |
| int iPiece, // for debug only | |
| Base::Vector3d uOrientedSegmentNormal, | |
| double& pieceVertical) | |
| { | |
| auto segmentNormal = Base::convertTo<gp_Vec>(uOrientedSegmentNormal); | |
| auto rotateAxis = Base::convertTo<gp_Vec>(getReferenceAxis()); | |
| gp_Vec extrudeVec = segmentNormal * m_shapeSize; | |
| BRepPrimAPI_MakePrism mkPrism(segmentFace, extrudeVec); | |
| TopoDS_Shape segmentTool = mkPrism.Shape(); | |
| TopoDS_Shape intersect = shapeShapeIntersect(segmentTool, rawShape); | |
| if (intersect.IsNull()) { | |
| return {}; | |
| } | |
| if (debugSection()) { | |
| stringstream ss; | |
| ss << "DCSAintersect" << iPiece << ".brep"; | |
| BRepTools::Write(intersect, ss.str().c_str());//debug | |
| ss.clear(); | |
| ss.str(std::string()); | |
| } | |
| // move intersection shape to the origin so we can rotate it without worrying about | |
| // center of rotation. | |
| gp_Trsf xPieceCenter; | |
| gp_Vec pieceCentroid = gp_Vec(ShapeUtils::findCentroid(intersect).XYZ()); | |
| // save the amount we moved this piece in the vertical direction so we can | |
| // put it back in the right place later | |
| gp_Vec maskedVertical = DU::maskDirection(pieceCentroid, rotateAxis); | |
| maskedVertical = pieceCentroid - maskedVertical; | |
| pieceVertical = maskedVertical.X() + maskedVertical.Y() + maskedVertical.Z(); | |
| xPieceCenter.SetTranslation(pieceCentroid * -1.0); | |
| BRepBuilderAPI_Transform mkTransXLate(intersect, xPieceCenter, true); | |
| TopoDS_Shape pieceCentered = mkTransXLate.Shape(); | |
| if (debugSection()) { | |
| stringstream ss; | |
| ss << "DCSBpieceCentered" << iPiece << ".brep"; | |
| BRepTools::Write(pieceCentered, ss.str().c_str());//debug | |
| ss.clear(); | |
| ss.str(std::string()); | |
| } | |
| //rotate the intersection so interesting face is aligned with what will | |
| // become the paper plane. | |
| double faceAngle = | |
| gp_Vec(getSectionCS().Direction().Reversed()).AngleWithRef(segmentNormal, rotateAxis); | |
| gp_Ax1 faceAxis(gp_Pnt(0.0, 0.0, 0.0), rotateAxis); | |
| gp_Ax3 stdCS; | |
| gp_Ax3 pieceCS; | |
| pieceCS.Rotate(faceAxis, faceAngle); | |
| gp_Trsf xPieceRotate; | |
| xPieceRotate.SetTransformation(stdCS, pieceCS); | |
| BRepBuilderAPI_Transform mkTransRotate(pieceCentered, xPieceRotate, true); | |
| TopoDS_Shape pieceRotated = mkTransRotate.Shape(); | |
| return pieceRotated; | |
| } | |
| TopoDS_Shape DrawComplexSection::movePieceToPaperPlane(const TopoDS_Shape& piece, double sizeMax) | |
| { | |
| //now we need to move the piece so that the interesting face is coincident | |
| //with the paper plane (stdXY). This will be a move along stdZ by -zMax. | |
| gp_Vec toPaperPlane = gp::OZ().Direction().XYZ() * sizeMax * -1.0; | |
| gp_Trsf xPieceToPlane; | |
| xPieceToPlane.SetTranslation(toPaperPlane); | |
| BRepBuilderAPI_Transform mkTransDisplace(piece, xPieceToPlane, true); | |
| TopoDS_Shape pieceToPlane = mkTransDisplace.Shape(); | |
| // piece is on the paper plane, with piece centroid at the origin | |
| return pieceToPlane; | |
| } | |
| //! move the piece to its position across the page (X for a left right profile) | |
| TopoDS_Shape DrawComplexSection::distributePiece(const TopoDS_Shape& piece, | |
| double pieceSizeInDirection, | |
| double verticalDisplace, | |
| const gp_Vec& alignmentAxis, | |
| const gp_Vec& gMovementVector, | |
| double cursorPosition) | |
| { | |
| double pieceTotalDistanceToMove = cursorPosition + pieceSizeInDirection / 2; | |
| gp_Vec alignmentVector = alignmentAxis * verticalDisplace * -1; | |
| gp_Vec netDisplacementVector = gMovementVector * pieceTotalDistanceToMove + alignmentVector; | |
| gp_Trsf xPieceDistribute; | |
| xPieceDistribute.SetTranslation(netDisplacementVector); | |
| BRepBuilderAPI_Transform mkTransDistribute(piece, xPieceDistribute, true); | |
| auto distributedPiece = mkTransDistribute.Shape(); | |
| return distributedPiece; | |
| } | |
| // are these in the correct order? no guarantee. profile wires should be in nose to tail order | |
| std::vector<TopoDS_Edge> DrawComplexSection::getUniqueEdges(const TopoDS_Wire& wireIn) | |
| { | |
| std::vector<TopoDS_Edge> ret; | |
| TopTools_IndexedMapOfShape shapeMap; | |
| TopExp_Explorer Ex(wireIn, TopAbs_EDGE); | |
| while (Ex.More()) { | |
| shapeMap.Add(Ex.Current()); | |
| Ex.Next(); | |
| } | |
| for (Standard_Integer k = 1; k <= shapeMap.Extent(); k++) { | |
| const TopoDS_Shape& shape = shapeMap(k); | |
| auto edge = TopoDS::Edge(shape); | |
| ret.push_back(edge); | |
| } | |
| return ret; | |
| } | |
| //! Find the correct view directions for the profile segments by making a face from the profile and | |
| //! extruding it into a solid, then examining the faces of the solid. | |
| std::vector<std::pair<int, Base::Vector3d>> | |
| DrawComplexSection::getSegmentViewDirections(const TopoDS_Wire& profileWire, | |
| Base::Vector3d sectionNormal) const | |
| { | |
| auto edgesAll = getUniqueEdges(profileWire); | |
| if (edgesAll.empty()) { | |
| // this is bad! | |
| throw Base::RuntimeError("Complex section: profile wire has no edges."); | |
| } | |
| // self-protection code should be elsewhere | |
| if (!checkSectionCS()) { | |
| // results will likely be incorrect | |
| // this message will show for every recompute of the complex section. | |
| Base::Console().warning("Coordinate system for ComplexSection is invalid. Check SectionNormal, Direction or XDirection.\n"); | |
| } | |
| auto profileVector = Base::convertTo<Base::Vector3d>(makeProfileVector(profileWire)); | |
| auto parallelDot = profileVector.Dot(sectionNormal); | |
| if (DU::fpCompare(std::fabs(parallelDot), 1, EWTOLERANCE)) { | |
| Base::Console().warning("Section normal is parallel to profile vector. Results may be incorrect.\n"); | |
| } | |
| TopoDS_Shape profileSolidTool = cuttingToolFromProfile(profileWire, m_shapeSize); | |
| // this should be the flattened profile? should not matter with disttoshape version of isonface. | |
| std::vector<TopoDS_Edge> relocatedEdgesAll = getUniqueEdges(profileWire); | |
| if (debugSection()) { | |
| BRepTools::Write(profileSolidTool, "DCStoolFromProfile.brep");//debug | |
| } | |
| std::vector<Base::Vector3d> profileNormals; | |
| std::vector<std::pair<int, Base::Vector3d>> normalKV; | |
| TopExp_Explorer expFaces(profileSolidTool, TopAbs_FACE); | |
| // are all these shenanigans necessary? | |
| // no guarantee of order from TopExp_Explorer?? Need to match faces to the profile segment that | |
| // generated it? | |
| for (int iFace = 0; expFaces.More(); expFaces.Next(), iFace++) { | |
| auto shape = expFaces.Current(); | |
| auto face = TopoDS::Face(shape); | |
| auto normal = Base::convertTo<Base::Vector3d>(getFaceNormal(face)); | |
| if (face.Orientation() == TopAbs_FORWARD) { | |
| // face on solid with Forward orientation will have a normal that points out of the | |
| // solid. With Reverse orientation the normal will point into the solid. We want the | |
| // direction into the solid. | |
| // "Face normal shows where body material is – it lies ‘behind' the face. In a correct solid body all face normals go ‘outward' (see below):" | |
| // https://opencascade.blogspot.com/2009/02/continued.html | |
| normal *= -1; | |
| } | |
| // skip top and bottom faces of the prism | |
| auto topOrBottomDot = std::fabs(normal.Dot(getReferenceAxis())); | |
| if (DU::fpCompare(topOrBottomDot, 1, EWTOLERANCE)) { | |
| continue; | |
| } | |
| if (!isFacePlanar(face)) { | |
| // TODO: continue blocks curved profile segments | |
| // ?? but also blocks "surface of extrusion" | |
| continue; | |
| } | |
| // this is an interesting face, get the normal and edge | |
| int iSegment{0}; | |
| for (auto& segment : edgesAll) { | |
| if (faceContainsEndpoints(segment, face)) { | |
| std::pair<int, Base::Vector3d> newEntry; | |
| newEntry.first = iSegment; | |
| newEntry.second = normal; | |
| normalKV.push_back(newEntry); | |
| break; | |
| } | |
| iSegment++; | |
| } | |
| } | |
| std::sort(normalKV.begin(), normalKV.end(), DrawComplexSection::normalLess); | |
| return normalKV; | |
| } | |
| //! true if the endpoints of edgeToMatch are vertexes of faceToSearch | |
| bool DrawComplexSection::faceContainsEndpoints(const TopoDS_Edge& edgeToMatch, const TopoDS_Face& faceToSearch) | |
| { | |
| std::pair<Base::Vector3d, Base::Vector3d> edgeEnds = getSegmentEnds(edgeToMatch); | |
| bool matchedFirst = pointOnFace(edgeEnds.first, faceToSearch); | |
| bool matchedLast = pointOnFace(edgeEnds.second, faceToSearch); | |
| return matchedFirst && matchedLast; | |
| } | |
| //! extrudes a face made from a wire along the reference axis | |
| TopoDS_Shape DrawComplexSection::profileToSolid(const TopoDS_Wire& closedProfileWire, | |
| double dMax) const | |
| { | |
| BRepBuilderAPI_MakeFace mkFace(closedProfileWire); | |
| if (!mkFace.IsDone()) { | |
| throw Base::RuntimeError("Complex section could not create face from closed profile"); | |
| } | |
| auto extrudeVector = getReferenceAxis() * dMax * 2; | |
| BRepPrimAPI_MakePrism mkPrism(mkFace.Face(), Base::convertTo<gp_Vec>(extrudeVector)); | |
| auto profileSolid = mkPrism.Shape(); | |
| return profileSolid; | |
| } | |
| //! transform an edge to the base view's projection coordinate system | |
| TopoDS_Edge DrawComplexSection::mapEdgeToBase(const TopoDS_Edge& inEdge) | |
| { | |
| App::DocumentObject* baseObj = BaseView.getValue(); | |
| auto* baseDvp = freecad_cast<DrawViewPart*>(baseObj); | |
| BRepBuilderAPI_Copy BuilderEdgeCopy(inEdge); | |
| TopoDS_Edge edgeCopy = TopoDS::Edge(BuilderEdgeCopy.Shape()); | |
| gp_Ax3 stdCS; //OXYZ | |
| gp_Trsf xmapEdgeToBase; | |
| xmapEdgeToBase.SetTransformation(stdCS, baseDvp->getProjectionCS()); | |
| BRepBuilderAPI_Transform mkMappedEdge(edgeCopy, xmapEdgeToBase); | |
| TopoDS_Edge mappedEdge = TopoDS::Edge(mkMappedEdge.Shape()); | |
| return mappedEdge; | |
| } | |
| TopoDS_Edge DrawComplexSection::mapEdgeToBase(const Base::Vector3d& inVector) | |
| { | |
| if (inVector.Length() == 0) { | |
| throw Base::RuntimeError("Complex section received a request to map a null edge"); | |
| } | |
| gp_Pnt origin(0,0,0); | |
| gp_Pnt endPoint{Base::convertTo<gp_Pnt>(inVector)}; | |
| TopoDS_Edge edgeToMap = BRepBuilderAPI_MakeEdge(origin, endPoint); | |
| return mapEdgeToBase(edgeToMap); | |
| } | |
| //! +/- same code as in Import::SketchExportHelper | |
| std::pair< Base::Vector3d, Base::Vector3d> DrawComplexSection::sketchNormalAndX(App::DocumentObject* sketchObj) | |
| { | |
| auto sketch = dynamic_cast<Part::Feature*>(sketchObj); | |
| if (!sketch || | |
| !sketchObj->isDerivedFrom(Base::Type::fromName("Sketcher::SketchObject"))) { | |
| // should be a throw? up to the caller to enforce this?? | |
| return { Base::Vector3d(0,0,0), Base::Vector3d(0,0,0) }; | |
| } | |
| auto plm = sketch->Placement.getValue(); | |
| Base::Rotation rot = plm.getRotation(); | |
| Base::Vector3d stdZ {0.0, 0.0, 1.0}; | |
| Base::Vector3d sketchNormal; | |
| rot.multVec(stdZ, sketchNormal); | |
| Base::Vector3d stdX {1.0, 0.0, 0.0}; | |
| Base::Vector3d sketchX; | |
| rot.multVec(stdX, sketchX); | |
| return {sketchNormal, sketchX}; | |
| } | |
| //! true if sketch normal is +/- parallel to base view's projection direction. | |
| bool DrawComplexSection::validateSketchNormal(App::DocumentObject* sketchObject) const | |
| { | |
| if (!sketchObject || | |
| !sketchObject->isDerivedFrom(Base::Type::fromName("Sketcher::SketchObject"))) { | |
| return false; | |
| } | |
| std::pair<Base::Vector3d, Base::Vector3d> normalX = sketchNormalAndX(sketchObject); | |
| auto* baseDvp = freecad_cast<DrawViewPart*>(BaseView.getValue()); | |
| double dot = std::fabs((normalX.first).Dot(baseDvp->Direction.getValue())); | |
| return DU::fpCompare(dot, 1, EWTOLERANCE); | |
| } | |
| // find the index of the profile segment that corresponds to a face in the cutting tool | |
| int DrawComplexSection::getSegmentIndex(const TopoDS_Face& face, | |
| const std::vector<TopoDS_Edge>& edgesAll) | |
| { | |
| int iSegment{0}; | |
| for (auto& segment : edgesAll) { | |
| if (faceContainsEndpoints(segment, face)) { | |
| return iSegment; | |
| } | |
| iSegment++; | |
| } | |
| return -1; // not found! | |
| } | |
| //! find the normal for a face in a key-value pair of (index, normal). | |
| std::pair<int, Base::Vector3d> | |
| DrawComplexSection::findNormalForFace(const TopoDS_Face& face, | |
| const std::vector<std::pair<int, Base::Vector3d>>& normalKV, | |
| const std::vector<TopoDS_Edge>& segmentEdges) | |
| { | |
| size_t index = getSegmentIndex(face, segmentEdges); | |
| if (index < 0 || | |
| index >= segmentEdges.size()) { //NOLINT | |
| throw Base::RuntimeError("DCS::findNormalForFace - did not find normal for face!"); | |
| } | |
| for (auto& keyValue : normalKV) { | |
| if (static_cast<size_t>(keyValue.first) == index) { | |
| return keyValue; | |
| } | |
| } | |
| throw Base::RuntimeError("DCS::findNormalForFace - no keyValue pair for segment!"); | |
| } | |
| bool DrawComplexSection::pointOnFace(Base::Vector3d point, const TopoDS_Face& face) | |
| { | |
| TopoDS_Vertex vert = BRepBuilderAPI_MakeVertex(Base::convertTo<gp_Pnt>(point)); | |
| double dist = DU::simpleMinDist(vert, face); | |
| return (dist < EWTOLERANCE); | |
| } | |
| //! make a cutting tool from a closed shape. up to the caller to make sure the face is sensible. | |
| TopoDS_Shape DrawComplexSection::makeCuttingToolFromClosedProfile(const TopoDS_Wire& profileWire, double dMax) | |
| { | |
| TopoDS_Face toolFace; | |
| try { | |
| BRepBuilderAPI_MakeFace mkFace(profileWire); | |
| toolFace = mkFace.Face(); | |
| if (toolFace.IsNull()) { | |
| return {}; | |
| } | |
| } | |
| catch (...) { | |
| Base::Console().error("%s could not make tool from closed profile\n", Label.getValue()); | |
| return {}; | |
| } | |
| gp_Dir gpNormal = getFaceNormal(toolFace); | |
| auto extrudeDir = 2 * dMax * gpNormal; | |
| TopoDS_Shape prism = BRepPrimAPI_MakePrism(toolFace, extrudeDir).Shape(); | |
| prism = ShapeUtils::moveShape(prism, Base::convertTo<Base::Vector3d>(gpNormal) * -dMax); | |
| return prism; | |
| } | |
| bool DrawComplexSection::validateProfileAlignment(const TopoDS_Wire& profileWire) const | |
| { | |
| // use "canBuild(profile, sectionnormal)"? | |
| if (ProjectionStrategy.getValue() == 0) { | |
| // Offset. Warn if profile is not quite aligned with section normal. if | |
| // the profile and normal are misaligned, the check below for empty "solids" | |
| // will not be correct. | |
| // just a warning here, so don't fail on this | |
| constexpr double AngleThresholdDeg{5.0}; | |
| if (!validateOffsetProfile(profileWire, SectionNormal.getValue(), AngleThresholdDeg)) { | |
| Base::Console().warning("%s: profile and section normal are misaligned\n", Label.getValue()); | |
| } | |
| } | |
| // TODO: this check should be in TaskComplexSection also. In complex section, it prevents a hard occ crash | |
| // if the sketch can't be made into an appropriate face/prism. | |
| if (CuttingToolWireObject.getValue()->isDerivedFrom(Base::Type::fromName("Sketcher::SketchObject"))) { | |
| if (!validateSketchNormal(CuttingToolWireObject.getValue())) { | |
| Base::Console().error("%s: cutting object not aligned with section normal\n", Label.getValue()); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| //! make a cutting tool from the profile and section normal. | |
| TopoDS_Shape DrawComplexSection::cuttingToolFromProfile(const TopoDS_Wire& inProfileWire, | |
| double dMax) const | |
| { | |
| TopoDS_Wire profileWireClosed = closeProfileForCut(inProfileWire, dMax); | |
| if (debugSection()) { | |
| BRepTools::Write(profileWireClosed, "DCSprofileWireClosed.brep"); //debug | |
| } | |
| TopoDS_Shape solid = profileToSolid(profileWireClosed, dMax); | |
| solid = ShapeUtils::moveShape(solid, getReferenceAxis() * -dMax); | |
| return solid; | |
| } | |
| TopoDS_Wire DrawComplexSection::closeProfileForCut(const TopoDS_Wire& profileWire, | |
| double dMax) const | |
| { | |
| // TODO: do these conversions gp_Pnt <-> Base::Vector3d <-> QPointF cause our problems with low | |
| // digits? | |
| auto* baseDvp = freecad_cast<DrawViewPart*>(BaseView.getValue()); | |
| TopoDS_Shape flatShape = GeometryObject::simpleProjection(profileWire, baseDvp->getProjectionCS()); | |
| TopoDS_Wire flatWire = makeNoseToTailWire(flatShape); | |
| if (debugSection()) { | |
| BRepTools::Write(flatWire, "DCSflatCloseWire.brep"); //debug | |
| } | |
| std::pair<Base::Vector3d, Base::Vector3d> pvEnds = getWireEnds(flatWire); | |
| Base::Vector3d firstPWPoint = pvEnds.first; | |
| Base::Vector3d lastPWPoint = pvEnds.second; | |
| Base::Vector3d midPWPoint = (firstPWPoint + lastPWPoint) / 2; | |
| Base::Vector3d SNPoint = SectionNormal.getValue() * dMax; | |
| Base::Vector3d awayDirection = SNPoint - midPWPoint; // from midpoint to snpoint | |
| awayDirection.Normalize(); | |
| std::vector<TopoDS_Edge> profileEdges = DU::shapeToVector(flatWire); | |
| TopoDS_Edge firstEdge = profileEdges.front(); | |
| std::pair<Base::Vector3d, Base::Vector3d> edgeEnds = getSegmentEnds(firstEdge); | |
| Base::Vector3d firstExtendDir = edgeEnds.first - edgeEnds.second; | |
| firstExtendDir.Normalize(); | |
| Base::Vector3d firstExtendStartPoint = edgeEnds.second; | |
| double firstInternalDistance = (midPWPoint - firstExtendStartPoint).Length(); | |
| Base::Vector3d firstExtendEndPoint = firstExtendStartPoint + firstExtendDir * (dMax - firstInternalDistance); | |
| TopoDS_Edge firstReplacementEdge = BRepBuilderAPI_MakeEdge(Base::convertTo<gp_Pnt>(firstExtendStartPoint), | |
| Base::convertTo<gp_Pnt>(firstExtendEndPoint)); | |
| TopoDS_Edge lastEdge = profileEdges.back(); | |
| edgeEnds = getSegmentEnds(lastEdge); | |
| Base::Vector3d lastExtendDir = edgeEnds.second - edgeEnds.first; | |
| lastExtendDir.Normalize(); | |
| Base::Vector3d lastExtendStartPoint = edgeEnds.first; | |
| double lastInternalDistance = (midPWPoint - lastExtendStartPoint).Length(); | |
| Base::Vector3d lastExtendEndPoint = lastExtendStartPoint + lastExtendDir * (dMax - lastInternalDistance); | |
| TopoDS_Edge lastReplacementEdge = BRepBuilderAPI_MakeEdge(Base::convertTo<gp_Pnt>(lastExtendStartPoint), | |
| Base::convertTo<gp_Pnt>(lastExtendEndPoint)); | |
| Base::Vector3d pointOnArc = midPWPoint + awayDirection * dMax; | |
| Handle(Geom_TrimmedCurve) circleArc; | |
| try { | |
| GC_MakeArcOfCircle mkArc(Base::convertTo<gp_Pnt>(lastExtendEndPoint), | |
| Base::convertTo<gp_Pnt>(pointOnArc), | |
| Base::convertTo<gp_Pnt>(firstExtendEndPoint)); | |
| circleArc = mkArc.Value(); | |
| if (!mkArc.IsDone()) { | |
| throw Base::RuntimeError("Complex section failed to create arc"); | |
| } | |
| } | |
| catch (...) { | |
| throw Base::RuntimeError("Complex section failed to create circular arc to close profile"); | |
| } | |
| TopoDS_Edge circleEdge = BRepBuilderAPI_MakeEdge(circleArc); | |
| // replace first and last edges and add circular arc | |
| std::vector<TopoDS_Edge> oldProfileEdges = DU::shapeToVector(flatWire); | |
| std::vector<TopoDS_Edge> newProfileEdges; | |
| newProfileEdges.emplace_back(firstReplacementEdge); | |
| if (oldProfileEdges.size() > 2) { | |
| newProfileEdges.insert(newProfileEdges.end(), oldProfileEdges.begin()+1, oldProfileEdges.end()-1); | |
| } | |
| newProfileEdges.emplace_back(lastReplacementEdge); | |
| newProfileEdges.emplace_back(circleEdge); | |
| BRepBuilderAPI_MakeWire mkWire; | |
| for (auto& edge : newProfileEdges) { | |
| mkWire.Add(edge); | |
| } | |
| return mkWire.Wire(); | |
| } | |
| bool DrawComplexSection::isFacePlanar(const TopoDS_Face& face) | |
| { | |
| BRepAdaptor_Surface adaptSurface(face); | |
| const GeomAdaptor_Surface& surf = adaptSurface.Surface(); | |
| Handle(Geom_Surface) hsurf = surf.Surface(); | |
| return GeomLib_IsPlanarSurface(hsurf).IsPlanar(); | |
| } | |
| //! compare 2 normal entries for sorting on segment index | |
| bool DrawComplexSection::normalLess(const std::pair<int, Base::Vector3d>& n1, const std::pair<int, Base::Vector3d>& n2) | |
| { | |
| return n1.first < n2.first; | |
| } | |
| // 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<TechDraw::DrawComplexSection>; | |
| }// namespace App | |