/*************************************************************************** * Copyright (c) 2010 Juergen Riegel * * * * 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 # include # include # include # include # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "FeatureSketchBased.h" #include "DatumPlane.h" #include "DatumLine.h" using namespace PartDesign; // sort bounding boxes according to diagonal length class ProfileBased::Wire_Compare : public std::binary_function { public: bool operator() (const TopoDS_Wire& w1, const TopoDS_Wire& w2) { Bnd_Box box1, box2; if (!w1.IsNull()) { BRepBndLib::Add(w1, box1); box1.SetGap(0.0); } if (!w2.IsNull()) { BRepBndLib::Add(w2, box2); box2.SetGap(0.0); } return box1.SquareExtent() < box2.SquareExtent(); } }; PROPERTY_SOURCE(PartDesign::ProfileBased, PartDesign::FeatureAddSub) ProfileBased::ProfileBased() { ADD_PROPERTY_TYPE(Profile,(0),"SketchBased", App::Prop_None, "Reference to sketch"); ADD_PROPERTY_TYPE(Midplane,(0),"SketchBased", App::Prop_None, "Extrude symmetric to sketch face"); ADD_PROPERTY_TYPE(Reversed, (0),"SketchBased", App::Prop_None, "Reverse extrusion direction"); ADD_PROPERTY_TYPE(UpToFace,(0),"SketchBased",(App::PropertyType)(App::Prop_None),"Face where feature will end"); } short ProfileBased::mustExecute() const { if (Profile.isTouched() || Midplane.isTouched() || Reversed.isTouched() || UpToFace.isTouched()) return 1; return PartDesign::FeatureAddSub::mustExecute(); } void ProfileBased::positionByPrevious(void) { Part::Feature* feat = getBaseObject(/* silent = */ true); if (feat) { this->Placement.setValue(feat->Placement.getValue()); } else { //no base. Use either Sketch support's placement, or sketch's placement itself. Part::Part2DObject *sketch = getVerifiedSketch(); App::DocumentObject* support = sketch->Support.getValue(); if(support && support->isDerivedFrom(App::GeoFeature::getClassTypeId())) { this->Placement.setValue(static_cast(support)->Placement.getValue()); } else { this->Placement.setValue( sketch->Placement.getValue() ); } } } void ProfileBased::transformPlacement(const Base::Placement &transform) { Part::Feature* feat = getBaseObject(/* silent = */ true); if (feat) { feat->transformPlacement(transform); } else { Part::Part2DObject *sketch = getVerifiedSketch(); sketch->transformPlacement(transform); } positionByPrevious(); } Part::Part2DObject* ProfileBased::getVerifiedSketch(bool silent) const { App::DocumentObject* result = Profile.getValue(); const char* err = nullptr; if (!result) { err = "No profile linked at all"; } else { if (!result->getTypeId().isDerivedFrom(Part::Part2DObject::getClassTypeId())) { err = "Linked object is not a Sketch or Part2DObject"; result = nullptr; } } if (!silent && err) { throw Base::Exception (err); } return static_cast(result); } Part::Feature* ProfileBased::getVerifiedObject(bool silent) const { App::DocumentObject* result = Profile.getValue(); const char* err = nullptr; if (!result) { err = "No object linked"; } else { if (!result->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) err = "Linked object is not a Sketch, Part2DObject or Feature"; } if (!silent && err) { throw Base::Exception (err); } return static_cast(result); } TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const { App::DocumentObject* result = Profile.getValue(); const char* err = nullptr; if (!result) { err = "No profile linked"; } else { if (result->getTypeId().isDerivedFrom(Part::Part2DObject::getClassTypeId())) { auto wires = getProfileWires(); return makeFace(wires); } else if(result->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) { if(Profile.getSubValues().empty()) err = "Linked object has no subshape specified"; else { const Part::TopoShape& shape = Profile.getValue()->Shape.getShape(); TopoDS_Shape sub = shape.getSubShape(Profile.getSubValues()[0].c_str()); if(sub.ShapeType() == TopAbs_FACE) return TopoDS::Face(sub); else if(sub.ShapeType() == TopAbs_WIRE) { auto wire = TopoDS::Wire(sub); if(!wire.Closed()) err = "Linked wire is not closed"; else { BRepBuilderAPI_MakeFace mk(wire); mk.Build(); return TopoDS::Face(mk.Shape()); } } else err = "Linked Subshape cannot be used"; } } else err = "Linked object is neither Sketch, Part2DObject or Part::Feature"; } if (!silent && err) { throw Base::Exception (err); } return TopoDS_Face(); } std::vector ProfileBased::getProfileWires() const { std::vector result; if(!Profile.getValue() || !Profile.getValue()->isDerivedFrom(Part::Feature::getClassTypeId())) throw Base::Exception("No valid profile linked"); TopoDS_Shape shape; if(Profile.getValue()->isDerivedFrom(Part::Part2DObject::getClassTypeId())) shape = Profile.getValue()->Shape.getValue(); else { if(Profile.getSubValues().empty()) throw Base::Exception("No valid subelement linked in Part::Feature"); shape = Profile.getValue()->Shape.getShape().getSubShape(Profile.getSubValues().front().c_str()); } if (shape.IsNull()) throw Base::Exception("Linked shape object is empty"); // this is a workaround for an obscure OCC bug which leads to empty tessellations // for some faces. Making an explicit copy of the linked shape seems to fix it. // The error almost happens when re-computing the shape but sometimes also for the // first time BRepBuilderAPI_Copy copy(shape); shape = copy.Shape(); if (shape.IsNull()) throw Base::Exception("Linked shape object is empty"); TopExp_Explorer ex; for (ex.Init(shape, TopAbs_WIRE); ex.More(); ex.Next()) { result.push_back(TopoDS::Wire(ex.Current())); } if (result.empty()) // there can be several wires throw Base::Exception("Linked shape object is not a wire"); return result; } // Note: We cannot return a reference, because it will become Null. // Not clear where, because we check for IsNull() here, but as soon as it is passed out of // this method, it becomes null! const TopoDS_Face ProfileBased::getSupportFace() const { const Part::Part2DObject* sketch = getVerifiedSketch(); if (sketch->MapMode.getValue() == Attacher::mmFlatFace && sketch->Support.getValue()) { const auto &Support = sketch->Support; App::DocumentObject* ref = Support.getValue(); Part::Feature *part = static_cast(ref); if (part && part->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) { const std::vector &sub = Support.getSubValues(); assert(sub.size()==1); // get the selected sub shape (a Face) const Part::TopoShape &shape = part->Shape.getShape(); if (shape.getShape().IsNull()) throw Base::Exception("Sketch support shape is empty!"); TopoDS_Shape sh = shape.getSubShape(sub[0].c_str()); if (sh.IsNull()) throw Base::Exception("Null shape in SketchBased::getSupportFace()!"); const TopoDS_Face face = TopoDS::Face(sh); if (face.IsNull()) throw Base::Exception("Null face in SketchBased::getSupportFace()!"); BRepAdaptor_Surface adapt(face); if (adapt.GetType() != GeomAbs_Plane) throw Base::Exception("No planar face in SketchBased::getSupportFace()!"); return face; } } return TopoDS::Face(Feature::makeShapeFromPlane(sketch)); } int ProfileBased::getSketchAxisCount(void) const { Part::Part2DObject *sketch = static_cast(Profile.getValue()); if (!sketch) return -1; // the link to the sketch is lost return sketch->getAxisCount(); } Part::Feature *ProfileBased::getBaseObject(bool silent) const { // Test the base's class feature. Part::Feature *rv = Feature::getBaseObject(/* silent = */ true); if (rv) { return rv; } // getVerifiedObject() may throw it's own exception if fail Part::Feature* obj = getVerifiedObject(silent); if(!obj) return nullptr; if (!obj->isDerivedFrom(Part::Part2DObject::getClassTypeId())) return obj; //due to former test we know we have a 2d object Part::Part2DObject* sketch = getVerifiedSketch(silent); const char* err = nullptr; App::DocumentObject* spt = sketch->Support.getValue(); if (spt) { if (spt->isDerivedFrom(Part::Feature::getClassTypeId())) { rv = static_cast(spt); } else { err = "No base set, sketch support is not Part::Feature"; } } else { err = "No base set, no sketch support either"; } if (!silent && err) { throw Base::Exception (err); } return rv; } void ProfileBased::onChanged(const App::Property* prop) { if (prop == &Profile) { // if attached to a sketch then mark it as read-only this->Placement.setStatus(App::Property::ReadOnly, Profile.getValue() != 0); } FeatureAddSub::onChanged(prop); } bool ProfileBased::isInside(const TopoDS_Wire& wire1, const TopoDS_Wire& wire2) const { Bnd_Box box1; BRepBndLib::Add(wire1, box1); box1.SetGap(0.0); Bnd_Box box2; BRepBndLib::Add(wire2, box2); box2.SetGap(0.0); if (box1.IsOut(box2)) return false; double prec = Precision::Confusion(); BRepBuilderAPI_MakeFace mkFace(wire1); if (!mkFace.IsDone()) Standard_Failure::Raise("Failed to create a face from wire in sketch"); TopoDS_Face face = validateFace(mkFace.Face()); BRepAdaptor_Surface adapt(face); IntTools_FClass2d class2d(face, prec); Handle_Geom_Surface surf = new Geom_Plane(adapt.Plane()); ShapeAnalysis_Surface as(surf); TopExp_Explorer xp(wire2,TopAbs_VERTEX); while (xp.More()) { TopoDS_Vertex v = TopoDS::Vertex(xp.Current()); gp_Pnt p = BRep_Tool::Pnt(v); gp_Pnt2d uv = as.ValueOfUV(p, prec); if (class2d.Perform(uv) == TopAbs_IN) return true; // TODO: We can make a check to see if all points are inside or all outside // because otherwise we have some intersections which is not allowed else return false; xp.Next(); } return false; } TopoDS_Face ProfileBased::validateFace(const TopoDS_Face& face) const { BRepCheck_Analyzer aChecker(face); if (!aChecker.IsValid()) { TopoDS_Wire outerwire = ShapeAnalysis::OuterWire(face); TopTools_IndexedMapOfShape myMap; myMap.Add(outerwire); TopExp_Explorer xp(face,TopAbs_WIRE); ShapeFix_Wire fix; fix.SetFace(face); fix.Load(outerwire); fix.Perform(); BRepBuilderAPI_MakeFace mkFace(fix.WireAPIMake()); while (xp.More()) { if (!myMap.Contains(xp.Current())) { fix.Load(TopoDS::Wire(xp.Current())); fix.Perform(); mkFace.Add(fix.WireAPIMake()); } xp.Next(); } aChecker.Init(mkFace.Face()); if (!aChecker.IsValid()) { ShapeFix_Shape fix(mkFace.Face()); fix.SetPrecision(Precision::Confusion()); fix.SetMaxTolerance(Precision::Confusion()); fix.SetMaxTolerance(Precision::Confusion()); fix.Perform(); fix.FixWireTool()->Perform(); fix.FixFaceTool()->Perform(); TopoDS_Face fixedFace = TopoDS::Face(fix.Shape()); aChecker.Init(fixedFace); if (!aChecker.IsValid()) Standard_Failure::Raise("Failed to validate broken face"); return fixedFace; } return mkFace.Face(); } return face; } TopoDS_Shape ProfileBased::makeFace(std::list& wires) const { BRepBuilderAPI_MakeFace mkFace(wires.front()); const TopoDS_Face& face = mkFace.Face(); if (face.IsNull()) return face; gp_Dir axis(0,0,1); BRepAdaptor_Surface adapt(face); if (adapt.GetType() == GeomAbs_Plane) { axis = adapt.Plane().Axis().Direction(); } wires.pop_front(); for (std::list::iterator it = wires.begin(); it != wires.end(); ++it) { BRepBuilderAPI_MakeFace mkInnerFace(*it); const TopoDS_Face& inner_face = mkInnerFace.Face(); if (inner_face.IsNull()) return inner_face; // failure gp_Dir inner_axis(0,0,1); BRepAdaptor_Surface adapt(inner_face); if (adapt.GetType() == GeomAbs_Plane) { inner_axis = adapt.Plane().Axis().Direction(); } // It seems that orientation is always 'Forward' and we only have to reverse // if the underlying plane have opposite normals. if (axis.Dot(inner_axis) < 0) it->Reverse(); mkFace.Add(*it); } return validateFace(mkFace.Face()); } TopoDS_Shape ProfileBased::makeFace(const std::vector& w) const { if (w.empty()) return TopoDS_Shape(); //FIXME: Need a safe method to sort wire that the outermost one comes last // Currently it's done with the diagonal lengths of the bounding boxes #if 1 std::vector wires = w; std::sort(wires.begin(), wires.end(), Wire_Compare()); std::list wire_list; wire_list.insert(wire_list.begin(), wires.rbegin(), wires.rend()); #else //bug #0001133: try alternative sort algorithm std::list unsorted_wire_list; unsorted_wire_list.insert(unsorted_wire_list.begin(), w.begin(), w.end()); std::list wire_list; Wire_Compare wc; while (!unsorted_wire_list.empty()) { std::list::iterator w_ref = unsorted_wire_list.begin(); std::list::iterator w_it = unsorted_wire_list.begin(); for (++w_it; w_it != unsorted_wire_list.end(); ++w_it) { if (wc(*w_ref, *w_it)) w_ref = w_it; } wire_list.push_back(*w_ref); unsorted_wire_list.erase(w_ref); } #endif // separate the wires into several independent faces std::list< std::list > sep_wire_list; while (!wire_list.empty()) { std::list sep_list; TopoDS_Wire wire = wire_list.front(); wire_list.pop_front(); sep_list.push_back(wire); std::list::iterator it = wire_list.begin(); while (it != wire_list.end()) { if (isInside(wire, *it)) { sep_list.push_back(*it); it = wire_list.erase(it); } else { ++it; } } sep_wire_list.push_back(sep_list); } if (sep_wire_list.size() == 1) { std::list& wires = sep_wire_list.front(); return makeFace(wires); } else if (sep_wire_list.size() > 1) { TopoDS_Compound comp; BRep_Builder builder; builder.MakeCompound(comp); for (std::list< std::list >::iterator it = sep_wire_list.begin(); it != sep_wire_list.end(); ++it) { TopoDS_Shape aFace = makeFace(*it); if (!aFace.IsNull()) builder.Add(comp, aFace); } return comp; } else { return TopoDS_Shape(); // error } } void ProfileBased::getUpToFaceFromLinkSub(TopoDS_Face& upToFace, const App::PropertyLinkSub& refFace) { App::DocumentObject* ref = refFace.getValue(); std::vector subStrings = refFace.getSubValues(); if (ref == NULL) throw Base::Exception("SketchBased: Up to face: No face selected"); if (ref->getTypeId().isDerivedFrom(App::Plane::getClassTypeId())) { upToFace = TopoDS::Face(makeShapeFromPlane(ref)); return; } else if (ref->getTypeId().isDerivedFrom(PartDesign::Plane::getClassTypeId())) { Part::Datum* datum = static_cast(ref); upToFace = TopoDS::Face(datum->getShape()); return; } if (!ref->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) throw Base::Exception("SketchBased: Up to face: Must be face of a feature"); Part::TopoShape baseShape = static_cast(ref)->Shape.getShape(); if (subStrings.empty() || subStrings[0].empty()) throw Base::Exception("SketchBased: Up to face: No face selected"); // TODO: Check for multiple UpToFaces? upToFace = TopoDS::Face(baseShape.getSubShape(subStrings[0].c_str())); if (upToFace.IsNull()) throw Base::Exception("SketchBased: Up to face: Failed to extract face"); } void ProfileBased::getUpToFace(TopoDS_Face& upToFace, const TopoDS_Shape& support, const TopoDS_Face& supportface, const TopoDS_Shape& sketchshape, const std::string& method, const gp_Dir& dir, const double offset) { if ((method == "UpToLast") || (method == "UpToFirst")) { // Check for valid support object if (support.IsNull()) throw Base::Exception("SketchBased: Up to face: No support in Sketch and no base feature!"); std::vector cfaces = Part::findAllFacesCutBy(support, sketchshape, dir); if (cfaces.empty()) throw Base::Exception("SketchBased: Up to face: No faces found in this direction"); // Find nearest/furthest face std::vector::const_iterator it, it_near, it_far; it_near = it_far = cfaces.begin(); for (it = cfaces.begin(); it != cfaces.end(); it++) if (it->distsq > it_far->distsq) it_far = it; else if (it->distsq < it_near->distsq) it_near = it; upToFace = (method == "UpToLast" ? it_far->face : it_near->face); } // Check whether the face has limits or not. Unlimited faces have no wire // Note: Datum planes are always unlimited TopExp_Explorer Ex(upToFace,TopAbs_WIRE); if (Ex.More()) { // Remove the limits of the upToFace so that the extrusion works even if sketchshape is larger // than the upToFace bool remove_limits = false; for (Ex.Init(sketchshape,TopAbs_FACE); Ex.More(); Ex.Next()) { // Get outermost wire of sketch face TopoDS_Face sketchface = TopoDS::Face(Ex.Current()); TopoDS_Wire outerWire = ShapeAnalysis::OuterWire(sketchface); if (!checkWireInsideFace(outerWire, upToFace, dir)) { remove_limits = true; break; } } if (remove_limits) { // Note: Using an unlimited face every time gives unnecessary failures for concave faces TopLoc_Location loc = upToFace.Location(); BRepAdaptor_Surface adapt(upToFace, Standard_False); BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface() #if OCC_VERSION_HEX >= 0x060502 , Precision::Confusion() #endif ); if (!mkFace.IsDone()) throw Base::Exception("SketchBased: Up To Face: Failed to create unlimited face"); upToFace = TopoDS::Face(mkFace.Shape()); upToFace.Location(loc); } } // Check that the upToFace does not intersect the sketch face and // is not parallel to the extrusion direction (for simplicity, supportface is used instead of sketchshape) BRepAdaptor_Surface adapt1(TopoDS::Face(supportface)); BRepAdaptor_Surface adapt2(TopoDS::Face(upToFace)); if (adapt2.GetType() == GeomAbs_Plane) { if (adapt1.Plane().Axis().IsNormal(adapt2.Plane().Axis(), Precision::Confusion())) throw Base::Exception("SketchBased: Up to face: Must not be parallel to extrusion direction!"); } // We must measure from sketchshape, not supportface, here BRepExtrema_DistShapeShape distSS(sketchshape, upToFace); if (distSS.Value() < Precision::Confusion()) throw Base::Exception("SketchBased: Up to face: Must not intersect sketch!"); // Move the face in the extrusion direction // TODO: For non-planar faces, we could consider offsetting the surface if (fabs(offset) > Precision::Confusion()) { if (adapt2.GetType() == GeomAbs_Plane) { gp_Trsf mov; mov.SetTranslation(offset * gp_Vec(dir)); TopLoc_Location loc(mov); upToFace.Move(loc); } else { throw Base::Exception("SketchBased: Up to Face: Offset not supported yet for non-planar faces"); } } } void ProfileBased::generatePrism(TopoDS_Shape& prism, const TopoDS_Shape& sketchshape, const std::string& method, const gp_Dir& dir, const double L, const double L2, const bool midplane, const bool reversed) { if (method == "Length" || method == "TwoLengths" || method == "ThroughAll") { double Ltotal = L; double Loffset = 0.; if (method == "ThroughAll") // "ThroughAll" is modelled as a very long, but finite prism to avoid problems with pockets // Note: 1E6 created problems once... Ltotal = 1E4; if (method == "TwoLengths") { // midplane makes no sense here Loffset = -L2; Ltotal += L2; } else if (midplane) Loffset = -Ltotal/2; TopoDS_Shape from = sketchshape; if (method == "TwoLengths" || midplane) { gp_Trsf mov; mov.SetTranslation(Loffset * gp_Vec(dir)); TopLoc_Location loc(mov); from = sketchshape.Moved(loc); } else if (reversed) Ltotal *= -1.0; // Its better not to use BRepFeat_MakePrism here even if we have a support because the // resulting shape creates problems with Pocket BRepPrimAPI_MakePrism PrismMaker(from, Ltotal*gp_Vec(dir), 0,1); // finite prism if (!PrismMaker.IsDone()) throw Base::Exception("SketchBased: Length: Could not extrude the sketch!"); prism = PrismMaker.Shape(); } else { throw Base::Exception("SketchBased: Internal error: Unknown method for generatePrism()"); } } const bool ProfileBased::checkWireInsideFace(const TopoDS_Wire& wire, const TopoDS_Face& face, const gp_Dir& dir) { // Project wire onto the face (face, not surface! So limits of face apply) // FIXME: The results of BRepProj_Projection do not seem to be very stable. Sometimes they return no result // even in the simplest projection case. // FIXME: Checking for Closed() is wrong because this has nothing to do with the wire itself being closed // But ShapeAnalysis_Wire::CheckClosed() doesn't give correct results either. BRepProj_Projection proj(wire, face, dir); return (proj.More() && proj.Current().Closed()); } const bool ProfileBased::checkLineCrossesFace(const gp_Lin &line, const TopoDS_Face &face) { #if 1 BRepBuilderAPI_MakeEdge mkEdge(line); TopoDS_Wire wire = ShapeAnalysis::OuterWire(face); BRepExtrema_DistShapeShape distss(wire, mkEdge.Shape(), Precision::Confusion()); if (distss.IsDone()) { if (distss.Value() > Precision::Confusion()) return false; // build up map vertex->edge TopTools_IndexedDataMapOfShapeListOfShape vertex2Edge; TopExp::MapShapesAndAncestors(wire, TopAbs_VERTEX, TopAbs_EDGE, vertex2Edge); for (Standard_Integer i=1; i<= distss.NbSolution(); i++) { if (distss.PointOnShape1(i).Distance(distss.PointOnShape2(i)) > Precision::Confusion()) continue; BRepExtrema_SupportType type = distss.SupportTypeShape1(i); if (type == BRepExtrema_IsOnEdge) { TopoDS_Edge edge = TopoDS::Edge(distss.SupportOnShape1(i)); BRepAdaptor_Curve adapt(edge); // create a plane (pnt,dir) that goes through the intersection point and is built of // the vectors of the sketch normal and the rotation axis const gp_Dir& normal = BRepAdaptor_Surface(face).Plane().Axis().Direction(); gp_Dir dir = line.Direction().Crossed(normal); gp_Pnt pnt = distss.PointOnShape1(i); Standard_Real t; distss.ParOnEdgeS1(i, t); gp_Pnt p_eps1 = adapt.Value(std::max(adapt.FirstParameter(), t-10*Precision::Confusion())); gp_Pnt p_eps2 = adapt.Value(std::min(adapt.LastParameter(), t+10*Precision::Confusion())); // now check if we get a change in the sign of the distances Standard_Real dist_p_eps1_pnt = gp_Vec(p_eps1, pnt).Dot(gp_Vec(dir)); Standard_Real dist_p_eps2_pnt = gp_Vec(p_eps2, pnt).Dot(gp_Vec(dir)); // distance to the plane must be noticable if (fabs(dist_p_eps1_pnt) > 5*Precision::Confusion() && fabs(dist_p_eps2_pnt) > 5*Precision::Confusion()) { if (dist_p_eps1_pnt * dist_p_eps2_pnt < 0) return true; } } else if (type == BRepExtrema_IsVertex) { // for a vertex check the two adjacent edges if there is a change of sign TopoDS_Vertex vertex = TopoDS::Vertex(distss.SupportOnShape1(i)); const TopTools_ListOfShape& edges = vertex2Edge.FindFromKey(vertex); if (edges.Extent() == 2) { // create a plane (pnt,dir) that goes through the intersection point and is built of // the vectors of the sketch normal and the rotation axis BRepAdaptor_Surface adapt(face); const gp_Dir& normal = adapt.Plane().Axis().Direction(); gp_Dir dir = line.Direction().Crossed(normal); gp_Pnt pnt = distss.PointOnShape1(i); // from the first edge get a point next to the intersection point const TopoDS_Edge& edge1 = TopoDS::Edge(edges.First()); BRepAdaptor_Curve adapt1(edge1); Standard_Real dist1 = adapt1.Value(adapt1.FirstParameter()).SquareDistance(pnt); Standard_Real dist2 = adapt1.Value(adapt1.LastParameter()).SquareDistance(pnt); gp_Pnt p_eps1; if (dist1 < dist2) p_eps1 = adapt1.Value(adapt1.FirstParameter() + 2*Precision::Confusion()); else p_eps1 = adapt1.Value(adapt1.LastParameter() - 2*Precision::Confusion()); // from the second edge get a point next to the intersection point const TopoDS_Edge& edge2 = TopoDS::Edge(edges.Last()); BRepAdaptor_Curve adapt2(edge2); Standard_Real dist3 = adapt2.Value(adapt2.FirstParameter()).SquareDistance(pnt); Standard_Real dist4 = adapt2.Value(adapt2.LastParameter()).SquareDistance(pnt); gp_Pnt p_eps2; if (dist3 < dist4) p_eps2 = adapt2.Value(adapt2.FirstParameter() + 2*Precision::Confusion()); else p_eps2 = adapt2.Value(adapt2.LastParameter() - 2*Precision::Confusion()); // now check if we get a change in the sign of the distances Standard_Real dist_p_eps1_pnt = gp_Vec(p_eps1, pnt).Dot(gp_Vec(dir)); Standard_Real dist_p_eps2_pnt = gp_Vec(p_eps2, pnt).Dot(gp_Vec(dir)); // distance to the plane must be noticable if (fabs(dist_p_eps1_pnt) > Precision::Confusion() && fabs(dist_p_eps2_pnt) > Precision::Confusion()) { if (dist_p_eps1_pnt * dist_p_eps2_pnt < 0) return true; } } } } } return false; #else // This is not as easy as it looks, because a distance of zero might be OK if // the axis touches the sketchshape in in a linear edge or a vertex // Note: This algorithm does not catch cases where the sketchshape touches the // axis in two or more points // Note: And it only works on closed outer wires TopoDS_Wire outerWire = ShapeAnalysis::OuterWire(face); BRepBuilderAPI_MakeEdge mkEdge(line); if (!mkEdge.IsDone()) throw Base::Exception("Revolve: Unexpected OCE failure"); BRepAdaptor_Curve axis(TopoDS::Edge(mkEdge.Shape())); TopExp_Explorer ex; int intersections = 0; std::vector intersectionpoints; // Note: We need to look at evey edge separately to catch coincident lines for (ex.Init(outerWire, TopAbs_EDGE); ex.More(); ex.Next()) { BRepAdaptor_Curve edge(TopoDS::Edge(ex.Current())); Extrema_ExtCC intersector(axis, edge); if (intersector.IsDone()) { for (int i = 1; i <= intersector.NbExt(); i++) { #if OCC_VERSION_HEX >= 0x060500 if (intersector.SquareDistance(i) < Precision::Confusion()) { #else if (intersector.Value(i) < Precision::Confusion()) { #endif if (intersector.IsParallel()) { // A line that is coincident with the axis produces three intersections // 1 with the line itself and 2 with the adjacent edges intersections -= 2; } else { Extrema_POnCurv p1, p2; intersector.Points(i, p1, p2); intersectionpoints.push_back(p1.Value()); intersections++; } } } } } // Note: We might check this inside the loop but then we have to rely on TopExp_Explorer // returning the wire's edges in adjacent order (because of the coincident line checking) if (intersections > 1) { // Check that we don't touch the sketchface just in two identical vertices if ((intersectionpoints.size() == 2) && (intersectionpoints[0].IsEqual(intersectionpoints[1], Precision::Confusion()))) return false; else return true; } return false; #endif } void ProfileBased::remapSupportShape(const TopoDS_Shape& newShape) { TopTools_IndexedMapOfShape faceMap; TopExp::MapShapes(newShape, TopAbs_FACE, faceMap); // here we must reset the placement otherwise the geometric matching doesn't work Part::TopoShape shape = this->Shape.getValue(); TopoDS_Shape sh = shape.getShape(); sh.Location(TopLoc_Location()); shape.setShape(sh); std::vector refs = this->getInList(); for (std::vector::iterator it = refs.begin(); it != refs.end(); ++it) { std::vector props; (*it)->getPropertyList(props); for (std::vector::iterator jt = props.begin(); jt != props.end(); ++jt) { if (!(*jt)->isDerivedFrom(App::PropertyLinkSub::getClassTypeId())) continue; App::PropertyLinkSub* link = static_cast(*jt); if (link->getValue() != this) continue; std::vector subValues = link->getSubValues(); std::vector newSubValues; for (std::vector::iterator it = subValues.begin(); it != subValues.end(); ++it) { std::string shapetype; if (it->size() > 4 && it->substr(0,4) == "Face") { shapetype = "Face"; } else if (it->size() > 4 && it->substr(0,4) == "Edge") { shapetype = "Edge"; } else if (it->size() > 6 && it->substr(0,6) == "Vertex") { shapetype = "Vertex"; } else { newSubValues.push_back(*it); continue; } bool success = false; TopoDS_Shape element; try { element = shape.getSubShape(it->c_str()); } catch (Standard_Failure) { // This shape doesn't even exist, so no chance to do some tests newSubValues.push_back(*it); continue; } try { // as very first test check if old face and new face are parallel planes TopoDS_Shape newElement = Part::TopoShape(newShape).getSubShape(it->c_str()); if (isParallelPlane(element, newElement)) { newSubValues.push_back(*it); success = true; } } catch (Standard_Failure) { } // try an exact matching if (!success) { for (int i=1; isetValue(this, newSubValues); } } } namespace PartDesign { struct gp_Pnt_Less : public std::binary_function { bool operator()(const gp_Pnt& p1, const gp_Pnt& p2) const { if (fabs(p1.X() - p2.X()) > Precision::Confusion()) return p1.X() < p2.X(); if (fabs(p1.Y() - p2.Y()) > Precision::Confusion()) return p1.Y() < p2.Y(); if (fabs(p1.Z() - p2.Z()) > Precision::Confusion()) return p1.Z() < p2.Z(); return false; // points are considered to be equal } }; } bool ProfileBased::isQuasiEqual(const TopoDS_Shape& s1, const TopoDS_Shape& s2) const { if (s1.ShapeType() != s2.ShapeType()) return false; TopTools_IndexedMapOfShape map1, map2; TopExp::MapShapes(s1, TopAbs_VERTEX, map1); TopExp::MapShapes(s2, TopAbs_VERTEX, map2); if (map1.Extent() != map2.Extent()) return false; std::vector p1; for (int i=1; i<=map1.Extent(); i++) { const TopoDS_Vertex& v = TopoDS::Vertex(map1.FindKey(i)); p1.push_back(BRep_Tool::Pnt(v)); } std::vector p2; for (int i=1; i<=map2.Extent(); i++) { const TopoDS_Vertex& v = TopoDS::Vertex(map2.FindKey(i)); p2.push_back(BRep_Tool::Pnt(v)); } std::sort(p1.begin(), p1.end(), gp_Pnt_Less()); std::sort(p2.begin(), p2.end(), gp_Pnt_Less()); if (p1.size() != p2.size()) return false; std::vector::iterator it = p1.begin(), jt = p2.begin(); for (; it != p1.end(); ++it, ++jt) { if (!(*it).IsEqual(*jt, Precision::Confusion())) return false; } return true; } bool ProfileBased::isEqualGeometry(const TopoDS_Shape& s1, const TopoDS_Shape& s2) const { if (s1.ShapeType() == TopAbs_FACE && s2.ShapeType() == TopAbs_FACE) { BRepAdaptor_Surface a1(TopoDS::Face(s1)); BRepAdaptor_Surface a2(TopoDS::Face(s2)); if (a1.GetType() == GeomAbs_Plane && a2.GetType() == GeomAbs_Plane) { gp_Pln p1 = a1.Plane(); gp_Pln p2 = a2.Plane(); if (p1.Distance(p2.Location()) < Precision::Confusion()) { const gp_Dir& d1 = p1.Axis().Direction(); const gp_Dir& d2 = p2.Axis().Direction(); if (d1.IsParallel(d2, Precision::Confusion())) return true; } } } else if (s1.ShapeType() == TopAbs_EDGE && s2.ShapeType() == TopAbs_EDGE) { } else if (s1.ShapeType() == TopAbs_VERTEX && s2.ShapeType() == TopAbs_VERTEX) { gp_Pnt p1 = BRep_Tool::Pnt(TopoDS::Vertex(s1)); gp_Pnt p2 = BRep_Tool::Pnt(TopoDS::Vertex(s2)); return p1.Distance(p2) < Precision::Confusion(); } return false; } bool ProfileBased::isParallelPlane(const TopoDS_Shape& s1, const TopoDS_Shape& s2) const { if (s1.ShapeType() == TopAbs_FACE && s2.ShapeType() == TopAbs_FACE) { BRepAdaptor_Surface a1(TopoDS::Face(s1)); BRepAdaptor_Surface a2(TopoDS::Face(s2)); if (a1.GetType() == GeomAbs_Plane && a2.GetType() == GeomAbs_Plane) { gp_Pln p1 = a1.Plane(); gp_Pln p2 = a2.Plane(); const gp_Dir& d1 = p1.Axis().Direction(); const gp_Dir& d2 = p2.Axis().Direction(); if (d1.IsParallel(d2, Precision::Confusion())) return true; } } return false; } const double ProfileBased::getReversedAngle(const Base::Vector3d &b, const Base::Vector3d &v) { try { Part::Feature* obj = getVerifiedObject(); TopoDS_Shape sketchshape = getVerifiedFace(); // get centre of gravity of the sketch face GProp_GProps props; BRepGProp::SurfaceProperties(sketchshape, props); gp_Pnt cog = props.CentreOfMass(); Base::Vector3d p_cog(cog.X(), cog.Y(), cog.Z()); // get direction to cog from its projection on the revolve axis Base::Vector3d perp_dir = p_cog - p_cog.Perpendicular(b, v); // get cross product of projection direction with revolve axis direction Base::Vector3d cross = v % perp_dir; // get sketch vector pointing away from support material Base::Placement SketchPos = obj->Placement.getValue(); Base::Rotation SketchOrientation = SketchPos.getRotation(); Base::Vector3d SketchNormal(0,0,1); SketchOrientation.multVec(SketchNormal,SketchNormal); return SketchNormal * cross; } catch (...) { return Reversed.getValue(); } } void ProfileBased::getAxis(const App::DocumentObject *pcReferenceAxis, const std::vector &subReferenceAxis, Base::Vector3d& base, Base::Vector3d& dir) { dir = Base::Vector3d(0,0,0); // If unchanged signals that no valid axis was found if (pcReferenceAxis == NULL) return; Part::Part2DObject* sketch = getVerifiedSketch(); Base::Placement SketchPlm = sketch->Placement.getValue(); Base::Vector3d SketchPos = SketchPlm.getPosition(); Base::Rotation SketchOrientation = SketchPlm.getRotation(); Base::Vector3d SketchVector(0,0,1); SketchOrientation.multVec(SketchVector,SketchVector); gp_Pln sketchplane(gp_Pnt(SketchPos.x, SketchPos.y, SketchPos.z), gp_Dir(SketchVector.x, SketchVector.y, SketchVector.z)); // get reference axis if (pcReferenceAxis->getTypeId().isDerivedFrom(PartDesign::Line::getClassTypeId())) { const PartDesign::Line* line = static_cast(pcReferenceAxis); base = line->getBasePoint(); dir = line->getDirection(); // Check that axis is perpendicular with sketch plane! if (sketchplane.Axis().Direction().Angle(gp_Dir(dir.x, dir.y, dir.z))< Precision::Angular()) throw Base::Exception("Rotation axis must not be perpendicular with the sketch plane"); return; } if (pcReferenceAxis->getTypeId().isDerivedFrom(App::Line::getClassTypeId())) { const App::Line* line = static_cast(pcReferenceAxis); base = Base::Vector3d(0,0,0); line->Placement.getValue().multVec (Base::Vector3d (1,0,0), dir); // Check that axis is perpendicular with sketch plane! if (sketchplane.Axis().Direction().Angle(gp_Dir(dir.x, dir.y, dir.z)) < Precision::Angular()) throw Base::Exception("Rotation axis must not be perpendicular with the sketch plane"); return; } if (pcReferenceAxis == sketch){ bool hasValidAxis=false; Base::Axis axis; if (subReferenceAxis[0] == "V_Axis") { hasValidAxis = true; axis = sketch->getAxis(Part::Part2DObject::V_Axis); } else if (subReferenceAxis[0] == "H_Axis") { hasValidAxis = true; axis = sketch->getAxis(Part::Part2DObject::H_Axis); } else if (subReferenceAxis[0].size() > 4 && subReferenceAxis[0].substr(0,4) == "Axis") { int AxId = std::atoi(subReferenceAxis[0].substr(4,4000).c_str()); if (AxId >= 0 && AxId < sketch->getAxisCount()) { hasValidAxis = true; axis = sketch->getAxis(AxId); } } if (hasValidAxis) { axis *= SketchPlm; base=axis.getBase(); dir=axis.getDirection(); return; } //else - an edge of the sketch was selected as an axis } if (pcReferenceAxis->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) { if (subReferenceAxis.empty()) throw Base::Exception("No rotation axis reference specified"); const Part::Feature* refFeature = static_cast(pcReferenceAxis); Part::TopoShape refShape = refFeature->Shape.getShape(); TopoDS_Shape ref = refShape.getSubShape(subReferenceAxis[0].c_str()); if (ref.ShapeType() == TopAbs_EDGE) { TopoDS_Edge refEdge = TopoDS::Edge(ref); if (refEdge.IsNull()) throw Base::Exception("Failed to extract rotation edge"); BRepAdaptor_Curve adapt(refEdge); if (adapt.GetType() != GeomAbs_Line) throw Base::Exception("Rotation edge must be a straight line"); gp_Pnt b = adapt.Line().Location(); base = Base::Vector3d(b.X(), b.Y(), b.Z()); gp_Dir d = adapt.Line().Direction(); dir = Base::Vector3d(d.X(), d.Y(), d.Z()); // Check that axis is co-planar with sketch plane! // Check that axis is perpendicular with sketch plane! if (sketchplane.Axis().Direction().Angle(d) < Precision::Angular()) throw Base::Exception("Rotation axis must not be perpendicular with the sketch plane"); return; } else { throw Base::Exception("Rotation reference must be an edge"); } } throw Base::Exception("Rotation axis reference is invalid"); } TopoDS_Shape ProfileBased::refineShapeIfActive(const TopoDS_Shape& oldShape) const { Base::Reference hGrp = App::GetApplication().GetUserParameter() .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/PartDesign"); if (hGrp->GetBool("RefineModel", false)) { Part::BRepBuilderAPI_RefineModel mkRefine(oldShape); TopoDS_Shape resShape = mkRefine.Shape(); return resShape; } return oldShape; } Base::Vector3d ProfileBased::getProfileNormal() const { Base::Vector3d SketchVector(0,0,1); auto obj = getVerifiedObject(true); if(!obj) return SketchVector; // get the Sketch plane if(obj->isDerivedFrom(Part::Part2DObject::getClassTypeId())) { Base::Placement SketchPos = obj->Placement.getValue(); Base::Rotation SketchOrientation = SketchPos.getRotation(); SketchOrientation.multVec(SketchVector,SketchVector); } else { TopoDS_Shape shape = getVerifiedFace(true); if(shape.ShapeType() == TopAbs_FACE) { BRepAdaptor_Surface adapt(TopoDS::Face(shape)); double u = adapt.FirstUParameter() + (adapt.LastUParameter() - adapt.FirstUParameter())/2.; double v = adapt.FirstVParameter() + (adapt.LastVParameter() - adapt.FirstVParameter())/2.; BRepLProp_SLProps prop(adapt,u,v,2,Precision::Confusion()); if(prop.IsNormalDefined()) { gp_Pnt pnt; gp_Vec vec; // handles the orientation state of the shape BRepGProp_Face(TopoDS::Face(shape)).Normal(u,v,pnt,vec); SketchVector = Base::Vector3d(vec.X(), vec.Y(), vec.Z()); } } } return SketchVector; } void ProfileBased::Restore(Base::XMLReader& reader) { reader.readElement("Properties"); int Cnt = reader.getAttributeAsInteger("Count"); for (int i=0 ;i vec; // read my element reader.readElement("Link"); // get the value of my attribute std::string name = reader.getAttribute("value"); if (name != "") { App::Document* document = getDocument(); DocumentObject* object = document ? document->getObject(name.c_str()) : 0; Profile.setValue(object, vec); } else { Profile.setValue(0, vec); } } else if (prop && strcmp(prop->getTypeId().getName(), TypeName) == 0) prop->Restore(reader); } catch (const Base::XMLParseException&) { throw; // re-throw } catch (const Base::Exception &e) { Base::Console().Error("%s\n", e.what()); } catch (const std::exception &e) { Base::Console().Error("%s\n", e.what()); } catch (const char* e) { Base::Console().Error("%s\n", e); } reader.readEndElement("Property"); } reader.readEndElement("Properties"); }