/*************************************************************************** * 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 #endif #include #include #include #include #include "FeatureSketchBased.h" #include "DatumLine.h" #include "DatumPlane.h" 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), "SketchBased", (App::PropertyType)(App::Prop_None), "Face 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()) 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->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::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->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) err = "Linked object is not a Sketch, Part2DObject or Feature"; } if (!silent && err) { throw Base::RuntimeError(err); } return static_cast(result); } Part::TopoShape ProfileBased::getProfileShape() const { auto shape = getTopoShape(Profile.getValue()); if (!shape.isNull() && !Profile.getSubValues().empty()) { std::vector shapes; for (auto& sub : Profile.getSubValues(true)) shapes.emplace_back(shape.getSubShape(sub.c_str())); shape = Part::TopoShape().makeCompound(shapes); } return shape; } 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->getTypeId().isDerivedFrom(Part::Part2DObject::getClassTypeId())) { auto wires = getProfileWires(); return Part::FaceMakerCheese::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::RuntimeError(err); } return TopoDS_Face(); } std::vector ProfileBased::getProfileWires() const { std::vector result; if (!Profile.getValue() || !Profile.getValue()->isDerivedFrom(Part::Feature::getClassTypeId())) throw Base::TypeError("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::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; } // 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(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->Support.getValue()) { const auto& Support = sketch->Support; App::DocumentObject* ref = Support.getValue(); Part::Feature* part = dynamic_cast(ref); if (part) { const std::vector& sub = Support.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; } 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(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::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::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->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::TypeError("SketchBased: Must be face of a feature"); Part::TopoShape baseShape = static_cast(ref)->Shape.getShape(); if (subStrings.empty() || subStrings[0].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(TopoDS_Face& upToFace, const TopoDS_Shape& support, const TopoDS_Shape& sketchshape, const std::string& method, const gp_Dir& dir) { if ((method == "UpToLast") || (method == "UpToFirst")) { // Check for valid support object if (support.IsNull()) throw Base::ValueError("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::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); } // 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; } } // It must also be checked that all projected inner wires of the upToFace // lie outside the sketch shape. If this is not the case then the sketch // shape is not completely covered by the upToFace. See #0003141 if (!remove_limits) { TopoDS_Wire outerWire = ShapeAnalysis::OuterWire(upToFace); for (Ex.Init(upToFace, TopAbs_WIRE); Ex.More(); Ex.Next()) { if (!outerWire.IsSame(Ex.Current())) { BRepProj_Projection proj(TopoDS::Wire(Ex.Current()), sketchshape, -dir); if (proj.More()) { 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); // use the placement of the adapter, not of the upToFace loc = TopLoc_Location(adapt.Trsf()); BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface(), Precision::Confusion()); if (!mkFace.IsDone()) throw Base::ValueError("SketchBased: Up To Face: Failed to create unlimited face"); upToFace = TopoDS::Face(mkFace.Shape()); upToFace.Location(loc); } } // Check that the upToFace is either not parallel to the extrusion direction // and that upToFace is not too near if (upToFace.IsNull()) throw Base::ValueError("SketchBased: The UpTo-Face is null!"); BRepAdaptor_Surface upToFaceSurface(TopoDS::Face(upToFace)); BRepExtrema_DistShapeShape distSS(sketchshape, upToFace); if (upToFaceSurface.GetType() == GeomAbs_Plane) { // Check that the upToFace is not parallel to the extrusion direction if (dir.IsNormal(upToFaceSurface.Plane().Axis().Direction(), Precision::Confusion())) throw Base::ValueError( "SketchBased: The UpTo-Face must not be parallel to the extrusion direction!"); // Check the distance if the upToFace is normal to the extrusion direction if (dir.IsParallel(upToFaceSurface.Plane().Axis().Direction(), Precision::Confusion())) if (distSS.Value() < Precision::Confusion()) throw Base::ValueError("SketchBased: The UpTo-Face is too close to the sketch"); } } void ProfileBased::addOffsetToFace(TopoDS_Face& 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()) { BRepAdaptor_Surface upToFaceSurface(TopoDS::Face(upToFace)); if (upToFaceSurface.GetType() == GeomAbs_Plane) { gp_Trsf mov; mov.SetTranslation(offset * gp_Vec(dir)); TopLoc_Location loc(mov); upToFace.Move(loc); // When using the face with BRepFeat_MakePrism::Perform(const TopoDS_Shape& Until) // then the algorithm expects that the 'NaturalRestriction' flag is set in order // to work as expected (see generatePrism()) BRep_Builder builder; builder.NaturalRestriction(upToFace, Standard_True); } else { throw Base::TypeError("SketchBased: Up to Face: Offset not supported yet for non-planar faces"); } } } double ProfileBased::getThroughAllLength() const { TopoDS_Shape profileshape; TopoDS_Shape base; profileshape = getVerifiedFace(); base = getBaseShape(); Bnd_Box box; BRepBndLib::Add(base, box); BRepBndLib::Add(profileshape, 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) { #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 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; #else // This is not as easy as it looks, because a distance of zero might be OK if // the axis touches the sketchshape in a linear edge or a vertex // Note: This algorithm doesn't 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::RuntimeError("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 every 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 (intersector.SquareDistance(i) < Precision::Confusion()) { 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 (auto ref : refs) { std::vector props; ref->getPropertyList(props); for (auto prop : props) { if (!prop->isDerivedFrom(App::PropertyLinkSub::getClassTypeId())) continue; App::PropertyLinkSub* link = static_cast(prop); if (link->getValue() != this) continue; std::vector subValues = link->getSubValues(); std::vector newSubValues; for (auto & subValue : subValues) { std::string shapetype; if (subValue.compare(0, 4, "Face") == 0) { shapetype = "Face"; } else if (subValue.compare(0, 4, "Edge") == 0) { shapetype = "Edge"; } else if (subValue.compare(0, 6, "Vertex") == 0) { shapetype = "Vertex"; } else { newSubValues.push_back(subValue); continue; } bool success = false; TopoDS_Shape element; try { element = shape.getSubShape(subValue.c_str()); } catch (Standard_Failure&) { // This shape doesn't even exist, so no chance to do some tests newSubValues.push_back(subValue); continue; } try { // as very first test check if old face and new face are parallel planes TopoDS_Shape newElement = Part::TopoShape(newShape).getSubShape(subValue.c_str()); if (isParallelPlane(element, newElement)) { newSubValues.push_back(subValue); success = true; } } catch (Standard_Failure&) { } // try an exact matching if (!success) { for (int i = 1; i < faceMap.Extent(); i++) { if (isQuasiEqual(element, faceMap.FindKey(i))) { std::stringstream str; str << shapetype << i; newSubValues.push_back(str.str()); success = true; break; } } } // if an exact matching fails then try to compare only the geometries if (!success) { for (int i = 1; i < faceMap.Extent(); i++) { if (isEqualGeometry(element, faceMap.FindKey(i))) { std::stringstream str; str << shapetype << i; newSubValues.push_back(str.str()); success = true; break; } } } // the new shape couldn't be found so keep the old sub-name if (!success) newSubValues.push_back(subValue); } link->setValue(this, newSubValues); } } } 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 } }; } 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(); 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() ? 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) return; App::DocumentObject* profile = Profile.getValue(); gp_Pln sketchplane; if (profile->getTypeId().isDerivedFrom(Part::Part2DObject::getClassTypeId())) { 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->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) { 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->getTypeId().isDerivedFrom(PartDesign::Line::getClassTypeId())) { 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->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); verifyAxisFunc(checkAxis, sketchplane, gp_Dir(dir.x, dir.y, dir.z)); return; } if (pcReferenceAxis->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) { 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(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.IsNull()) return SketchVector; // the shape can be a single face or a compound of faces, only consider the first face TopExp_Explorer ex(shape, TopAbs_FACE); if (ex.More()) { TopoDS_Face face = TopoDS::Face(ex.Current()); 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); SketchVector = Base::Vector3d(vec.X(), vec.Y(), vec.Z()); } } } 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); } }