/*************************************************************************** * Copyright (c) 2015 Stefan Tröger * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ # include # include # include # include # 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 #include #include #include #include #include //#include "Body.h" #include "FeaturePipe.h" using namespace PartDesign; const char* Pipe::TypeEnums[] = {"FullPath","UpToFace",NULL}; const char* Pipe::TransitionEnums[] = {"Transformed","Right corner", "Round corner",NULL}; const char* Pipe::ModeEnums[] = {"Standard", "Fixed", "Frenet", "Auxiliary", "Binormal", NULL}; const char* Pipe::TransformEnums[] = {"Constant", "Multisection", "Linear", "S-shape", "Interpolation", NULL}; PROPERTY_SOURCE(PartDesign::Pipe, PartDesign::ProfileBased) Pipe::Pipe() { ADD_PROPERTY_TYPE(Sections,(0),"Sweep",App::Prop_None,"List of sections"); Sections.setSize(0); ADD_PROPERTY_TYPE(Spine,(0),"Sweep",App::Prop_None,"Path to sweep along"); ADD_PROPERTY_TYPE(SpineTangent,(false),"Sweep",App::Prop_None,"Include tangent edges into path"); ADD_PROPERTY_TYPE(AuxillerySpine,(0),"Sweep",App::Prop_None,"Secondary path to orient sweep"); ADD_PROPERTY_TYPE(AuxillerySpineTangent,(false),"Sweep",App::Prop_None,"Include tangent edges into secondary path"); ADD_PROPERTY_TYPE(AuxilleryCurvelinear, (true), "Sweep", App::Prop_None,"Calculate normal between equidistant points on both spines"); ADD_PROPERTY_TYPE(Mode,(long(0)),"Sweep",App::Prop_None,"Profile mode"); ADD_PROPERTY_TYPE(Binormal,(Base::Vector3d()),"Sweep",App::Prop_None,"Binormal vector for corresponding orientation mode"); ADD_PROPERTY_TYPE(Transition,(long(0)),"Sweep",App::Prop_None,"Transition mode"); ADD_PROPERTY_TYPE(Transformation,(long(0)),"Sweep",App::Prop_None,"Section transformation mode"); Mode.setEnums(ModeEnums); Transition.setEnums(TransitionEnums); Transformation.setEnums(TransformEnums); } short Pipe::mustExecute() const { if (Sections.isTouched()) return 1; if (Spine.isTouched()) return 1; if (Mode.isTouched()) return 1; if (Transition.isTouched()) return 1; return ProfileBased::mustExecute(); } App::DocumentObjectExecReturn *Pipe::execute(void) { std::vector wires; try { wires = getProfileWires(); } catch (const Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); } TopoDS_Shape sketchshape = getVerifiedFace(); if (sketchshape.IsNull()) return new App::DocumentObjectExecReturn("Pipe: No valid sketch or face as first section"); else { //TODO: currently we only allow planar faces. the reason for this is that with other faces in front, we could //not use the current simulate approach and build the start and end face from the wires. As the shell //begins always at the spine and not the profile, the sketchshape cannot be used directly as front face. //We would need a method to translate the front shape to match the shell starting position somehow... TopoDS_Face face = TopoDS::Face(sketchshape); BRepAdaptor_Surface adapt(face); if(adapt.GetType() != GeomAbs_Plane) return new App::DocumentObjectExecReturn("Pipe: Only planar faces supported"); } // if the Base property has a valid shape, fuse the pipe into it TopoDS_Shape base; try { base = getBaseShape(); } catch (const Base::Exception&) { base = TopoDS_Shape(); } try { //setup the location this->positionByPrevious(); TopLoc_Location invObjLoc = this->getLocation().Inverted(); if(!base.IsNull()) base.Move(invObjLoc); //build the paths App::DocumentObject* spine = Spine.getValue(); if (!(spine && spine->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId()))) return new App::DocumentObjectExecReturn("No spine linked."); std::vector subedge = Spine.getSubValues(); TopoDS_Shape path; const Part::TopoShape& shape = static_cast(spine)->Shape.getValue(); buildPipePath(shape, subedge, path); path.Move(invObjLoc); // auxiliary TopoDS_Shape auxpath; if(Mode.getValue()==3) { App::DocumentObject* auxspine = AuxillerySpine.getValue(); if (!(auxspine && auxspine->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId()))) return new App::DocumentObjectExecReturn("No auxiliary spine linked."); std::vector auxsubedge = AuxillerySpine.getSubValues(); const Part::TopoShape& auxshape = static_cast(auxspine)->Shape.getValue(); buildPipePath(auxshape, auxsubedge, auxpath); auxpath.Move(invObjLoc); } //build up multisections auto multisections = Sections.getValues(); std::vector> wiresections; for(TopoDS_Wire& wire : wires) wiresections.emplace_back(1, wire); //maybe we need a sacling law Handle(Law_Function) scalinglaw; //see if we shall use multiple sections if(Transformation.getValue() == 1) { //TODO: we need to order the sections to prevent occ from crahsing, as makepieshell connects //the sections in the order of adding for(App::DocumentObject* obj : multisections) { if(!obj->isDerivedFrom(Part::Feature::getClassTypeId())) return new App::DocumentObjectExecReturn("All sections need to be part features"); TopExp_Explorer ex; size_t i=0; for (ex.Init(static_cast(obj)->Shape.getValue(), TopAbs_WIRE); ex.More(); ex.Next()) { if(i>=wiresections.size()) return new App::DocumentObjectExecReturn("Multisections need to have the same amount of inner wires as the base section"); wiresections[i].push_back(TopoDS::Wire(ex.Current())); ++i; } if(iSet(0,1,1,ScalingData[0].x); scalinglaw = lin; } else if(Transformation.getValue() == 3) { if(ScalingData.getValues().size()<1) return new App::DocumentObjectExecReturn("No valid data given for S-shape scaling mode"); Handle(Law_S) s = new Law_S(); s->Set(0,1,ScalingData[0].y, 1, ScalingData[0].x, ScalingData[0].z); scalinglaw = s; }*/ //build all shells std::vector shells; std::vector frontwires, backwires; for(std::vector& wires : wiresections) { BRepOffsetAPI_MakePipeShell mkPS(TopoDS::Wire(path)); setupAlgorithm(mkPS, auxpath); if(!scalinglaw) { for(TopoDS_Wire& wire : wires) { wire.Move(invObjLoc); mkPS.Add(wire); } } else { for(TopoDS_Wire& wire : wires) { wire.Move(invObjLoc); mkPS.SetLaw(wire, scalinglaw); } } if (!mkPS.IsReady()) return new App::DocumentObjectExecReturn("Pipe could not be built"); shells.push_back(mkPS.Shape()); if (!mkPS.Shape().Closed()) { // shell is not closed - use simulate to get the end wires TopTools_ListOfShape sim; mkPS.Simulate(2, sim); frontwires.push_back(TopoDS::Wire(sim.First())); backwires.push_back(TopoDS::Wire(sim.Last())); } } BRepBuilderAPI_MakeSolid mkSolid; if (!frontwires.empty()) { // build the end faces, sew the shell and build the final solid TopoDS_Shape front = Part::FaceMakerCheese::makeFace(frontwires); TopoDS_Shape back = Part::FaceMakerCheese::makeFace(backwires); BRepBuilderAPI_Sewing sewer; sewer.SetTolerance(Precision::Confusion()); sewer.Add(front); sewer.Add(back); for(TopoDS_Shape& s : shells) sewer.Add(s); sewer.Perform(); mkSolid.Add(TopoDS::Shell(sewer.SewedShape())); } else { // shells are already closed - add them directly for (TopoDS_Shape& s : shells) { mkSolid.Add(TopoDS::Shell(s)); } } if(!mkSolid.IsDone()) return new App::DocumentObjectExecReturn("Result is not a solid"); TopoDS_Shape result = mkSolid.Shape(); BRepClass3d_SolidClassifier SC(result); SC.PerformInfinitePoint(Precision::Confusion()); if (SC.State() == TopAbs_IN) { result.Reverse(); } //result.Move(invObjLoc); AddSubShape.setValue(result); if(base.IsNull()) { Shape.setValue(getSolid(result)); return App::DocumentObject::StdReturn; } if(getAddSubType() == FeatureAddSub::Additive) { BRepAlgoAPI_Fuse mkFuse(base, result); if (!mkFuse.IsDone()) return new App::DocumentObjectExecReturn("Adding the pipe failed"); // we have to get the solids (fuse sometimes creates compounds) TopoDS_Shape boolOp = this->getSolid(mkFuse.Shape()); // lets check if the result is a solid if (boolOp.IsNull()) return new App::DocumentObjectExecReturn("Resulting shape is not a solid"); int solidCount = countSolids(boolOp); if (solidCount > 1) { return new App::DocumentObjectExecReturn("Pipe: Result has multiple solids. This is not supported at this time."); } boolOp = refineShapeIfActive(boolOp); Shape.setValue(getSolid(boolOp)); } else if(getAddSubType() == FeatureAddSub::Subtractive) { BRepAlgoAPI_Cut mkCut(base, result); if (!mkCut.IsDone()) return new App::DocumentObjectExecReturn("Subtracting the pipe failed"); // we have to get the solids (fuse sometimes creates compounds) TopoDS_Shape boolOp = this->getSolid(mkCut.Shape()); // lets check if the result is a solid if (boolOp.IsNull()) return new App::DocumentObjectExecReturn("Resulting shape is not a solid"); int solidCount = countSolids(boolOp); if (solidCount > 1) { return new App::DocumentObjectExecReturn("Pipe: Result has multiple solids. This is not supported at this time."); } boolOp = refineShapeIfActive(boolOp); Shape.setValue(getSolid(boolOp)); } return App::DocumentObject::StdReturn; } catch (Standard_Failure& e) { return new App::DocumentObjectExecReturn(e.GetMessageString()); } catch (...) { return new App::DocumentObjectExecReturn("A fatal error occurred when making the pipe"); } } void Pipe::setupAlgorithm(BRepOffsetAPI_MakePipeShell& mkPipeShell, TopoDS_Shape& auxshape) { mkPipeShell.SetTolerance(Precision::Confusion()); switch(Transition.getValue()) { case 0: mkPipeShell.SetTransitionMode(BRepBuilderAPI_Transformed); break; case 1: mkPipeShell.SetTransitionMode(BRepBuilderAPI_RightCorner); break; case 2: mkPipeShell.SetTransitionMode(BRepBuilderAPI_RoundCorner); break; } bool auxiliary = false; const Base::Vector3d& bVec = Binormal.getValue(); switch(Mode.getValue()) { case 1: mkPipeShell.SetMode(gp_Ax2(gp_Pnt(0,0,0), gp_Dir(0,0,1), gp_Dir(1,0,0))); break; case 2: mkPipeShell.SetMode(true); break; case 3: auxiliary = true; break; case 4: mkPipeShell.SetMode(gp_Dir(bVec.x,bVec.y,bVec.z)); break; } if(auxiliary) { mkPipeShell.SetMode(TopoDS::Wire(auxshape), AuxilleryCurvelinear.getValue()); //mkPipeShell.SetMode(TopoDS::Wire(auxshape), AuxilleryCurvelinear.getValue(), BRepFill_ContactOnBorder); } } void Pipe::getContiniusEdges(Part::TopoShape /*TopShape*/, std::vector< std::string >& /*SubNames*/) { /* TopTools_IndexedMapOfShape mapOfEdges; TopTools_IndexedDataMapOfShapeListOfShape mapEdgeEdge; TopExp::MapShapesAndAncestors(TopShape.getShape(), TopAbs_EDGE, TopAbs_EDGE, mapEdgeEdge); TopExp::MapShapes(TopShape.getShape(), TopAbs_EDGE, mapOfEdges); Base::Console().Message("Initial edges:\n"); for(int i=0; i(SubNames.at(i)); if (aSubName.size() > 4 && aSubName.substr(0,4) == "Edge") { TopoDS_Edge edge = TopoDS::Edge(TopShape.getSubShape(aSubName.c_str())); const TopTools_ListOfShape& los = mapEdgeEdge.FindFromKey(edge); if(los.Extent() != 2) { SubNames.erase(SubNames.begin()+i); continue; } const TopoDS_Shape& face1 = los.First(); const TopoDS_Shape& face2 = los.Last(); GeomAbs_Shape cont = BRep_Tool::Continuity(TopoDS::Edge(edge), TopoDS::Face(face1), TopoDS::Face(face2)); if (cont != GeomAbs_C0) { SubNames.erase(SubNames.begin()+i); continue; } i++; } // empty name or any other sub-element else { SubNames.erase(SubNames.begin()+i); } } Base::Console().Message("Final edges:\n"); for(int i=0; i& subedge, TopoDS_Shape& path) { if (!shape.getShape().IsNull()) { try { if (!subedge.empty()) { //if(SpineTangent.getValue()) //getContiniusEdges(shape, subedge); BRepBuilderAPI_MakeWire mkWire; for (std::vector::const_iterator it = subedge.begin(); it != subedge.end(); ++it) { TopoDS_Shape subshape = shape.getSubShape(it->c_str()); mkWire.Add(TopoDS::Edge(subshape)); } path = mkWire.Wire(); } else if (shape.getShape().ShapeType() == TopAbs_EDGE) { path = shape.getShape(); } else if (shape.getShape().ShapeType() == TopAbs_WIRE) { BRepBuilderAPI_MakeWire mkWire(TopoDS::Wire(shape.getShape())); path = mkWire.Wire(); } else if (shape.getShape().ShapeType() == TopAbs_COMPOUND) { TopoDS_Iterator it(shape.getShape()); for (; it.More(); it.Next()) { if (it.Value().IsNull()) throw Base::ValueError("In valid element in spine."); if ((it.Value().ShapeType() != TopAbs_EDGE) && (it.Value().ShapeType() != TopAbs_WIRE)) { throw Base::TypeError("Element in spine is neither an edge nor a wire."); } } Handle(TopTools_HSequenceOfShape) hEdges = new TopTools_HSequenceOfShape(); Handle(TopTools_HSequenceOfShape) hWires = new TopTools_HSequenceOfShape(); for (TopExp_Explorer xp(shape.getShape(), TopAbs_EDGE); xp.More(); xp.Next()) hEdges->Append(xp.Current()); ShapeAnalysis_FreeBounds::ConnectEdgesToWires(hEdges, Precision::Confusion(), Standard_True, hWires); int len = hWires->Length(); if (len != 1) throw Base::ValueError("Spine is not connected."); path = hWires->Value(1); } else { throw Base::TypeError("Spine is neither an edge nor a wire."); } } catch (Standard_Failure&) { throw Base::CADKernelError("Invalid spine."); } } } PROPERTY_SOURCE(PartDesign::AdditivePipe, PartDesign::Pipe) AdditivePipe::AdditivePipe() { addSubType = Additive; } PROPERTY_SOURCE(PartDesign::SubtractivePipe, PartDesign::Pipe) SubtractivePipe::SubtractivePipe() { addSubType = Subtractive; }