/*************************************************************************** * 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 #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 "FeatureSketchBased.h" #include "DatumLine.h" #include "DatumPlane.h" #include "Mod/Part/App/Geometry.h" FC_LOG_LEVEL_INIT("PartDesign", true, true); using namespace PartDesign; PROPERTY_SOURCE(PartDesign::ProfileBased, PartDesign::FeatureAddSub) ProfileBased::ProfileBased() { ADD_PROPERTY_TYPE(Profile, (nullptr), "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, (nullptr), "Side1", (App::PropertyType)(App::Prop_None), "Face where feature will end" ); ADD_PROPERTY_TYPE( UpToShape, (nullptr), "Side1", (App::PropertyType)(App::Prop_None), "Shape where feature will end" ); ADD_PROPERTY_TYPE( UpToFace2, (nullptr), "Side2", (App::PropertyType)(App::Prop_None), "Face where feature will end" ); ADD_PROPERTY_TYPE( UpToShape2, (nullptr), "Side2", (App::PropertyType)(App::Prop_None), "Shape where feature will end" ); ADD_PROPERTY_TYPE( AllowMultiFace, (false), "SketchBased", App::Prop_None, "Allow multiple faces in profile" ); } short ProfileBased::mustExecute() const { if (Profile.isTouched() || Midplane.isTouched() || Reversed.isTouched() || UpToFace.isTouched() || UpToFace2.isTouched() || UpToShape.isTouched() || UpToShape2.isTouched()) { return 1; } return PartDesign::FeatureAddSub::mustExecute(); } void ProfileBased::setupObject() { AllowMultiFace.setValue(true); } void ProfileBased::positionByPrevious() { 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->AttachmentSupport.getValue(); if (support && support->isDerivedFrom()) { 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->isDerivedFrom()) { err = "Linked object is not a Sketch or Part2DObject"; result = nullptr; } } if (!silent && err) { throw Base::RuntimeError(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->isDerivedFrom()) { err = "Linked object is not a Sketch, Part2DObject or Feature"; } } if (!silent && err) { throw Base::RuntimeError(err); } return static_cast(result); } TopoShape ProfileBased::getTopoShapeVerifiedFace( bool silent, [[maybe_unused]] bool doFit, // TODO: Remove parameter bool allowOpen, const App::DocumentObject* profile, const std::vector& _subs ) const { auto obj = profile ? profile : Profile.getValue(); if (!obj || !obj->getNameInDocument()) { if (silent) { return TopoShape(); } throw Base::ValueError("No profile linked"); } const auto& subs = profile ? _subs : Profile.getSubValues(); try { TopoShape shape; if (AllowMultiFace.getValue()) { if (subs.empty()) { shape = Part::Feature::getTopoShape( obj, Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform ); } else { std::vector shapes; for (auto& sub : subs) { auto subshape = Part::Feature::getTopoShape( obj, Part::ShapeOption::NeedSubElement | Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform, sub.c_str() ); if (subshape.isNull()) { if (silent) { return {}; } FC_THROWM( Base::CADKernelError, "Sub shape not found: " << obj->getFullName() << "." << sub ); } shapes.push_back(subshape); } shape.makeElementCompound(shapes); } } else { std::string sub; if (!obj->isDerivedFrom()) { if (!subs.empty()) { sub = subs[0]; } } shape = Part::Feature::getTopoShape( obj, (sub.empty() ? Part::ShapeOption::NoFlag : Part::ShapeOption::NeedSubElement) | Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform, sub.c_str() ); } if (shape.isNull()) { if (silent) { return shape; } throw Base::CADKernelError("Linked shape object is empty"); } TopoShape openshape; if (!shape.hasSubShape(TopAbs_FACE)) { try { if (!shape.hasSubShape(TopAbs_WIRE)) { shape = shape.makeElementWires(); } if (shape.hasSubShape(TopAbs_WIRE)) { shape.Hasher = getDocument()->getStringHasher(); if (allowOpen) { std::vector openwires; std::vector wires; for (auto& wire : shape.getSubTopoShapes(TopAbs_WIRE)) { if (!wire.isClosed()) { openwires.push_back(wire); } else { wires.push_back(wire); } } if (openwires.size()) { openshape.makeElementCompound( openwires, nullptr, TopoShape ::SingleShapeCompoundCreationPolicy::returnShape ); if (wires.empty()) { shape = TopoShape(); } else { shape.makeElementCompound( wires, nullptr, TopoShape ::SingleShapeCompoundCreationPolicy::returnShape ); } } } if (!shape.isNull()) { if (AllowMultiFace.getValue()) { shape = shape.makeElementFace(); // default to use FaceMakerBullseye } else { shape = shape.makeElementFace(nullptr, "Part::FaceMakerCheese"); } } } } catch (const Base::Exception&) { if (silent) { return TopoShape(); } throw; } catch (const Standard_Failure&) { if (silent) { return TopoShape(); } throw; } } int count = shape.countSubShapes(TopAbs_FACE); if (!count && !allowOpen) { if (silent) { return TopoShape(); } throw Base::CADKernelError("Cannot make face from profile"); } if (!openshape.isNull()) { if (shape.isNull()) { shape = openshape; } else { shape.makeElementCompound({shape, openshape}); } } if (count > 1) { if (AllowMultiFace.getValue() || obj->isDerivedFrom()) { return shape; } FC_WARN("Found more than one face from profile"); } if (!openshape.isNull()) { return shape; } if (count) { return shape.getSubTopoShape(TopAbs_FACE, 1); } return shape; } catch (Standard_Failure&) { if (silent) { return TopoShape(); } throw; } } // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const { App::DocumentObject* result = Profile.getValue(); const char* err = nullptr; std::string _err; if (!result) { err = "No profile linked"; } else if (AllowMultiFace.getValue()) { try { auto shape = getProfileShape(); if (shape.isNull()) { err = "Linked shape object is empty"; } else { auto faces = shape.getSubTopoShapes(TopAbs_FACE); if (faces.empty()) { if (!shape.hasSubShape(TopAbs_WIRE)) { shape = shape.makeWires(); } if (shape.hasSubShape(TopAbs_WIRE)) { shape = shape.makeFace(nullptr, "Part::FaceMakerBullseye"); } else { err = "Cannot make face from profile"; } } else if (faces.size() == 1) { shape = faces.front(); } else { shape = TopoShape().makeCompound(faces); } } if (!err) { return shape.getShape(); } } catch (Standard_Failure& e) { _err = e.GetMessageString(); err = _err.c_str(); } } else { if (result->isDerivedFrom()) { auto wires = getProfileWires(); return Part::FaceMakerCheese::makeFace(wires); } else if (result->isDerivedFrom()) { 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::RuntimeError(err); } return TopoDS_Face(); } TopoShape ProfileBased::getProfileShape(Part::ShapeOptions subShapeOptions) const { TopoShape shape; const auto& subs = Profile.getSubValues(); auto profile = Profile.getValue(); if (subs.empty()) { shape = Part::Feature::getTopoShape( profile, Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform ); } else { std::vector shapes; for (auto& sub : subs) { shapes.push_back(Part::Feature::getTopoShape(profile, subShapeOptions, sub.c_str())); } shape = TopoShape(shape.Tag).makeElementCompound(shapes); } if (shape.isNull()) { throw Part::NullShapeException("Linked shape object is empty"); } return shape; } // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. std::vector ProfileBased::getProfileWires() const { std::vector result; if (!Profile.getValue() || !Profile.getValue()->isDerivedFrom()) { throw Base::TypeError("No valid profile linked"); } TopoDS_Shape shape; if (Profile.getValue()->isDerivedFrom()) { shape = Profile.getValue()->Shape.getValue(); } else { if (Profile.getSubValues().empty()) { throw Base::ValueError("No valid subelement linked in Part::Feature"); } shape = Profile.getValue()->Shape.getShape().getSubShape( Profile.getSubValues().front().c_str() ); } if (shape.IsNull()) { throw Base::ValueError("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::ValueError("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::ValueError("Linked shape object is not a wire"); } return result; } std::vector ProfileBased::getTopoShapeProfileWires() const { // shape copy 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 mostly happens when re-computing the // shape but sometimes also for the first time auto shape = getProfileShape().makeElementCopy(); if (shape.hasSubShape(TopAbs_WIRE)) { return shape.getSubTopoShapes(TopAbs_WIRE); } auto wires = shape.makeElementWires().getSubTopoShapes(TopAbs_WIRE); if (wires.empty()) { throw Part::NullShapeException("Linked shape object is not a wire"); } return wires; } // 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! // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. const TopoDS_Face ProfileBased::getSupportFace() const { const Part::Part2DObject* sketch = getVerifiedSketch(true); if (sketch) { return getSupportFace(sketch); } return getSupportFace(Profile); } TopoDS_Face ProfileBased::getSupportFace(const Part::Part2DObject* sketch) const { if (sketch && sketch->MapMode.getValue() == Attacher::mmFlatFace && sketch->AttachmentSupport.getValue()) { const auto& AttachmentSupport = sketch->AttachmentSupport; App::DocumentObject* ref = AttachmentSupport.getValue(); Part::Feature* part = dynamic_cast(ref); if (part) { const std::vector& sub = AttachmentSupport.getSubValues(); assert(sub.size() == 1); if (sub.at(0).empty()) { // This seems to happen when sketch is on a datum plane return TopoDS::Face(Feature::makeShapeFromPlane(sketch)); } // get the selected sub shape (a Face) const Part::TopoShape& shape = part->Shape.getShape(); if (shape.getShape().IsNull()) { throw Base::ValueError("Sketch support shape is empty!"); } TopoDS_Shape sh = shape.getSubShape(sub[0].c_str()); if (sh.IsNull()) { throw Base::ValueError("Null shape in SketchBased::getSupportFace()!"); } const TopoDS_Face face = TopoDS::Face(sh); if (face.IsNull()) { throw Base::ValueError("Null face in SketchBased::getSupportFace()!"); } BRepAdaptor_Surface adapt(face); if (adapt.GetType() != GeomAbs_Plane) { throw Base::TypeError("No planar face in SketchBased::getSupportFace()!"); } return face; } } return TopoDS::Face(Feature::makeShapeFromPlane(sketch)); } TopoDS_Face ProfileBased::getSupportFace(const App::PropertyLinkSub& link) const { App::DocumentObject* result = link.getValue(); if (!result) { throw Base::RuntimeError("No support linked"); } TopoDS_Face face; getFaceFromLinkSub(face, link); return face; } TopoShape ProfileBased::getTopoShapeSupportFace() const { TopoShape shape; const Part::Part2DObject* sketch = getVerifiedSketch(true); if (!sketch) { shape = getTopoShapeVerifiedFace(true); } else if (sketch->MapMode.getValue() == Attacher::mmFlatFace && sketch->AttachmentSupport.getValue()) { const auto& Support = sketch->AttachmentSupport; App::DocumentObject* ref = Support.getValue(); shape = Part::Feature::getTopoShape( ref, Part::ShapeOption::NeedSubElement | Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform, Support.getSubValues().empty() ? "" : Support.getSubValues()[0].c_str() ); } if (!shape.isNull()) { if (shape.shapeType(true) != TopAbs_FACE) { if (!shape.hasSubShape(TopAbs_FACE)) { throw Base::ValueError("Null face in SketchBased::getSupportFace()!"); } shape = shape.getSubTopoShape(TopAbs_FACE, 1); } gp_Pln pln; if (!shape.findPlane(pln)) { throw Base::TypeError("No planar face in SketchBased::getSupportFace()!"); } return shape; } if (!sketch) { throw Base::RuntimeError("No planar support"); } return Feature::makeShapeFromPlane(sketch); } int ProfileBased::getSketchAxisCount() 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()) { 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->AttachmentSupport.getValue(); if (spt) { if (spt->isDerivedFrom()) { 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::RuntimeError(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() != nullptr); } FeatureAddSub::onChanged(prop); } void ProfileBased::getUpToFaceFromLinkSub(TopoShape& upToFace, const App::PropertyLinkSub& refFace) { App::DocumentObject* ref = refFace.getValue(); if (!ref) { throw Base::ValueError("SketchBased: No face selected"); } if (ref->isDerivedFrom()) { upToFace = makeShapeFromPlane(ref); return; } const auto& subs = refFace.getSubValues(); upToFace = Part::Feature::getTopoShape( ref, Part::ShapeOption::NeedSubElement | Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform, subs.empty() ? nullptr : subs[0].c_str() ); if (!upToFace.hasSubShape(TopAbs_FACE)) { throw Base::ValueError("SketchBased: Up to face: Failed to extract face"); } } int ProfileBased::getUpToShapeFromLinkSubList( TopoShape& upToShape, const App::PropertyLinkSubList& refShape ) { int ret = 0; auto subSets = refShape.getSubListValues(); std::vector faceList; for (auto& subSet : subSets) { auto ref = subSet.first; if (ref->isDerivedFrom()) { faceList.push_back(makeTopoShapeFromPlane(ref)); ret++; } else { if (!ref->isDerivedFrom()) { throw Base::TypeError("SketchBased: Must be face of a feature"); } auto subStrings = subSet.second; if (subStrings.empty() || subStrings[0].empty()) { TopoShape baseShape = Part::Feature::getTopoShape( ref, Part::ShapeOption::NeedSubElement | Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform ); for (auto face : baseShape.getSubTopoShapes(TopAbs_FACE)) { faceList.push_back(face); ret++; } } else { for (auto& subString : subStrings) { auto shape = Part::Feature::getShape( ref, Part::ShapeOption::NeedSubElement | Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform, subString.c_str() ); TopoShape face = shape; face = face.makeElementFace(); if (face.isNull()) { throw Base::ValueError("SketchBased: Failed to extract face"); } faceList.push_back(face); ret++; } } } } if (ret == 0) { return 0; } if (ret == 1) { upToShape = faceList[0]; return 1; } // create a unique shell with all selected faces upToShape = upToShape.makeElementCompound(faceList); return ret; } void ProfileBased::getFaceFromLinkSub(TopoDS_Face& upToFace, const App::PropertyLinkSub& refFace) { App::DocumentObject* ref = refFace.getValue(); std::vector subStrings = refFace.getSubValues(); if (!ref) { throw Base::ValueError("SketchBased: No face selected"); } if (ref->isDerivedFrom()) { upToFace = TopoDS::Face(makeShapeFromPlane(ref)); return; } else if (ref->isDerivedFrom()) { Part::Datum* datum = static_cast(ref); upToFace = TopoDS::Face(datum->getShape()); return; } if (!ref->isDerivedFrom()) { throw Base::TypeError("SketchBased: Must be face of a feature"); } Part::TopoShape baseShape = static_cast(ref)->Shape.getShape(); // Allow an empty sub here - example is a sketch reference (no sub) that creates a face. if (subStrings.empty()) { throw Base::ValueError("SketchBased: No face selected"); } // TODO: Check for multiple UpToFaces? upToFace = TopoDS::Face(baseShape.getSubShape(subStrings[0].c_str())); if (upToFace.IsNull()) { throw Base::ValueError("SketchBased: Failed to extract face"); } } void ProfileBased::getUpToFace( TopoShape& upToFace, const TopoShape& support, const TopoShape& sketchshape, const std::string& method, gp_Dir& dir ) { if ((method == "UpToLast") || (method == "UpToFirst")) { std::vector cfaces = Part::findAllFacesCutBy(support, sketchshape, dir); if (cfaces.empty()) { throw Base::ValueError("SketchBased: 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); } else if (Part::findAllFacesCutBy(upToFace, sketchshape, dir).empty()) { dir = -dir; } if (upToFace.shapeType(true) != TopAbs_FACE) { if (!upToFace.hasSubShape(TopAbs_FACE)) { throw Base::ValueError("SketchBased: Up to face: No face found"); } upToFace = upToFace.getSubTopoShape(TopAbs_FACE, 1); } TopoDS_Face face = TopoDS::Face(upToFace.getShape()); // Check that the upToFace does not intersect the sketch face and // is not parallel to the extrusion direction BRepAdaptor_Surface adapt(face); if (adapt.GetType() == GeomAbs_Plane) { if (dir.IsNormal(adapt.Plane().Axis().Direction(), Precision::Confusion())) { throw Base::ValueError( "SketchBased: Up to face: Must not be parallel to extrusion direction!" ); } } // We must measure from sketchshape, not supportface, here BRepExtrema_DistShapeShape distSS(sketchshape.getShape(), face); if (distSS.Value() < Precision::Confusion()) { throw Base::ValueError("SketchBased: Up to face: Must not intersect sketch!"); } } void ProfileBased::addOffsetToFace(TopoShape& upToFace, const gp_Dir& dir, double offset) { // Move the face in the extrusion direction // TODO: For non-planar faces, we could consider offsetting the surface if (fabs(offset) > Precision::Confusion()) { gp_Trsf mov; mov.SetTranslation(offset * gp_Vec(dir)); TopLoc_Location loc(mov); upToFace.move(loc); } } double ProfileBased::getThroughAllLength() const { TopoShape profileshape; TopoShape base; profileshape = getTopoShapeVerifiedFace(true); base = getBaseTopoShape(); Bnd_Box box; BRepBndLib::Add(base.getShape(), box); if (!profileshape.isNull()) { BRepBndLib::Add(profileshape.getShape(), box); } box.SetGap(0.0); // The diagonal of the bounding box, plus 1% extra to eliminate risk of // co-planar issues, gives a length that is guaranteed to go through all. // The result is multiplied by 2 for the guarantee to work also for the midplane option. return 2.02 * sqrt(box.SquareExtent()); } 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()); } bool ProfileBased::checkLineCrossesFace(const gp_Lin& line, const TopoDS_Face& face) { 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 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 noticeable 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); 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 noticeable 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; } void ProfileBased::remapSupportShape(const TopoDS_Shape& newShape) { (void)newShape; // Realthunder: with the new topological naming, I don't think this function // is necessary. A missing element will cause an explicitly error, and the // user will be force to manually select the element. Various editors, such // as dress up editors, can perform element guessing when activated. } namespace PartDesign { struct gp_Pnt_Less { 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 } }; } // namespace PartDesign 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) { // Do nothing here } 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; } double ProfileBased::getReversedAngle(const Base::Vector3d& b, const Base::Vector3d& v) const { try { Part::Feature* obj = getVerifiedObject(); TopoShape sketchshape = getTopoShapeVerifiedFace(); // get centre of gravity of the sketch face GProp_GProps props; BRepGProp::SurfaceProperties(sketchshape.getShape(), 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() ? 1 : 0; } } void ProfileBased::getAxis( const App::DocumentObject* pcReferenceAxis, const std::vector& subReferenceAxis, Base::Vector3d& base, Base::Vector3d& dir, ProfileBased::ForbiddenAxis checkAxis ) const { auto verifyAxisFunc = [](ProfileBased::ForbiddenAxis checkAxis, const gp_Pln& sketchplane, const gp_Dir& dir) { switch (checkAxis) { case ForbiddenAxis::NotPerpendicularWithNormal: // If perpendicular to the normal then it's parallel to the plane if (sketchplane.Axis().Direction().IsNormal(dir, Precision::Angular())) { throw Base::ValueError("Axis must not be parallel to the sketch plane"); } break; case ForbiddenAxis::NotParallelWithNormal: // If parallel with the normal then it's perpendicular to the plane if (sketchplane.Axis().Direction().IsParallel(dir, Precision::Angular())) { throw Base::ValueError("Axis must not be perpendicular to the sketch plane"); } break; default: break; } }; auto getAxisFromEdge = [](const TopoDS_Edge& refEdge, Base::Vector3d& base, Base::Vector3d& dir) { if (refEdge.IsNull()) { throw Base::ValueError("Failed to extract rotation edge"); } BRepAdaptor_Curve adapt(refEdge); gp_Pnt b; gp_Dir d; if (adapt.GetType() == GeomAbs_Line) { b = adapt.Line().Location(); d = adapt.Line().Direction(); } else if (adapt.GetType() == GeomAbs_Circle) { b = adapt.Circle().Location(); d = adapt.Circle().Axis().Direction(); } else { throw Base::TypeError("Edge must be a straight line, circle or arc of circle"); } base = Base::Vector3d(b.X(), b.Y(), b.Z()); dir = Base::Vector3d(d.X(), d.Y(), d.Z()); }; dir = Base::Vector3d(0, 0, 0); // If unchanged signals that no valid axis was found if (!pcReferenceAxis || subReferenceAxis.empty()) { return; } App::DocumentObject* profile = Profile.getValue(); gp_Pln sketchplane; if (profile->isDerivedFrom()) { Part::Part2DObject* sketch = getVerifiedSketch(); Base::Placement SketchPlm = sketch->Placement.getValue(); Base::Vector3d SketchVector = Base::Vector3d(0, 0, 1); Base::Rotation SketchOrientation = SketchPlm.getRotation(); SketchOrientation.multVec(SketchVector, SketchVector); Base::Vector3d SketchPos = SketchPlm.getPosition(); sketchplane = gp_Pln( gp_Pnt(SketchPos.x, SketchPos.y, SketchPos.z), gp_Dir(SketchVector.x, SketchVector.y, SketchVector.z) ); if (pcReferenceAxis == profile) { 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] == "N_Axis") { hasValidAxis = true; axis = sketch->getAxis(Part::Part2DObject::N_Axis); } else if (subReferenceAxis[0].compare(0, 4, "Axis") == 0) { 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 } } else if (profile->isDerivedFrom()) { Base::Placement SketchPlm = getVerifiedObject()->Placement.getValue(); Base::Vector3d SketchVector = getProfileNormal(); Base::Vector3d SketchPos = SketchPlm.getPosition(); sketchplane = gp_Pln( gp_Pnt(SketchPos.x, SketchPos.y, SketchPos.z), gp_Dir(SketchVector.x, SketchVector.y, SketchVector.z) ); } // get reference axis if (pcReferenceAxis->isDerivedFrom()) { const PartDesign::Line* line = static_cast(pcReferenceAxis); base = line->getBasePoint(); dir = line->getDirection(); verifyAxisFunc(checkAxis, sketchplane, gp_Dir(dir.x, dir.y, dir.z)); return; } if (pcReferenceAxis->isDerivedFrom()) { auto* line = static_cast(pcReferenceAxis); base = line->getBasePoint(); dir = line->getDirection(); verifyAxisFunc(checkAxis, sketchplane, gp_Dir(dir.x, dir.y, dir.z)); return; } if (pcReferenceAxis->isDerivedFrom()) { if (subReferenceAxis.empty()) { throw Base::ValueError("No rotation axis reference specified"); } const Part::Feature* refFeature = static_cast(pcReferenceAxis); Part::TopoShape refShape = refFeature->Shape.getShape(); TopoDS_Shape ref; try { // if an exception is raised then convert it into a FreeCAD-specific exception ref = refShape.getSubShape(subReferenceAxis[0].c_str()); } catch (const Standard_Failure& e) { throw Base::RuntimeError(e.GetMessageString()); } if (ref.ShapeType() == TopAbs_EDGE) { getAxisFromEdge(TopoDS::Edge(ref), base, dir); verifyAxisFunc(checkAxis, sketchplane, gp_Dir(dir.x, dir.y, dir.z)); return; } } throw Base::TypeError("Unsupported geometry type to get reference axis"); } 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()) { Base::Placement SketchPos = obj->Placement.getValue(); Base::Rotation SketchOrientation = SketchPos.getRotation(); SketchOrientation.multVec(SketchVector, SketchVector); return SketchVector; } // For newer version, do not do fitting, as it may flip the face normal for // some reason. TopoShape shape = getTopoShapeVerifiedFace(true, true, true); //, _ProfileBasedVersion.getValue() //<= 0); if (shape.isNull()) { return SketchVector; } gp_Pln pln; if (shape.findPlane(pln)) { gp_Dir dir = pln.Axis().Direction(); return Base::Vector3d(dir.X(), dir.Y(), dir.Z()); } if (shape.hasSubShape(TopAbs_EDGE)) { // Find the first planar face that contains the edge, and return the plane normal TopoShape objShape = Part::Feature::getTopoShape( obj, Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform ); for (int idx : objShape.findAncestors(shape.getSubShape(TopAbs_EDGE, 1), TopAbs_FACE)) { if (objShape.getSubTopoShape(TopAbs_FACE, idx).findPlane(pln)) { gp_Dir dir = pln.Axis().Direction(); return Base::Vector3d(dir.X(), dir.Y(), dir.Z()); } } } // If no planar face, try to use the normal of the center of the first face. if (shape.hasSubShape(TopAbs_FACE)) { TopoDS_Face face = TopoDS::Face(shape.getSubShape(TopAbs_FACE, 1)); BRepAdaptor_Surface adapt(face); 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(face).Normal(u, v, pnt, vec); return Base::Vector3d(vec.X(), vec.Y(), vec.Z()); } } if (!shape.hasSubShape(TopAbs_EDGE)) { return SketchVector; } // If the shape is a line, then return an arbitrary direction that is perpendicular to the line auto geom = Part::Geometry::fromShape(shape.getSubShape(TopAbs_EDGE, 1), true); auto geomLine = freecad_cast(geom.get()); if (geomLine) { Base::Vector3d dir = geomLine->getDir(); double x = std::fabs(dir.x); double y = std::fabs(dir.y); double z = std::fabs(dir.z); if (x > y && x > z && x > 1e-7) { if (y + z < 1e-7) { return Base::Vector3d(0, 0, 1); } dir.x = -(dir.z + dir.y) / dir.x; } else if (y > x && y > z && y > 1e-7) { if (x + z < 1e-7) { return Base::Vector3d(0, 0, 1); } dir.y = -(dir.z + dir.x) / dir.y; } else if (z > 1e-7) { if (x + y < 1e-7) { return Base::Vector3d(1, 0, 0); } dir.z = -(dir.x + dir.y) / dir.z; } else { return SketchVector; } return dir.Normalize(); } return SketchVector; } void ProfileBased::Restore(Base::XMLReader& reader) { PartDesign::FeatureAddSub::Restore(reader); } void ProfileBased::handleChangedPropertyName( Base::XMLReader& reader, const char* TypeName, const char* PropName ) { // check if we load the old sketch property if ((strcmp("Sketch", PropName) == 0) && (strcmp("App::PropertyLink", TypeName) == 0)) { std::vector vec; // read my element reader.readElement("Link"); // get the value of my attribute std::string name = reader.getAttribute("value"); if (!name.empty()) { App::Document* document = getDocument(); DocumentObject* object = document ? document->getObject(name.c_str()) : nullptr; Profile.setValue(object, vec); } else { Profile.setValue(nullptr, vec); } } else { PartDesign::FeatureAddSub::handleChangedPropertyName(reader, TypeName, PropName); } }