diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index 2259e3798b..8c01f76b64 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -1136,6 +1136,30 @@ Base::BoundBox3d TopoShape::getBoundBox() const return box; } +Base::BoundBox3d TopoShape::getBoundBoxOptimal() const +{ + Base::BoundBox3d box; + try { + // If the shape is empty an exception may be thrown + Bnd_Box bounds; + BRepBndLib::AddOptimal(_Shape, bounds, false, false); + bounds.SetGap(0.0); + Standard_Real xMin, yMin, zMin, xMax, yMax, zMax; + bounds.Get(xMin, yMin, zMin, xMax, yMax, zMax); + + box.MinX = xMin; + box.MaxX = xMax; + box.MinY = yMin; + box.MaxY = yMax; + box.MinZ = zMin; + box.MaxZ = zMax; + } + catch (Standard_Failure&) { + } + + return box; +} + namespace { bool getShapeProperties(const TopoDS_Shape& shape, GProp_GProps& prop) { diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index ae94e6cea8..421d68cd4e 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -316,6 +316,8 @@ public: Base::Matrix4D getTransform() const override; /// Bound box from the CasCade shape Base::BoundBox3d getBoundBox() const override; + /// More precise bound box from the CasCade shape + Base::BoundBox3d getBoundBoxOptimal() const; bool getCenterOfGravity(Base::Vector3d& center) const override; static void convertTogpTrsf(const Base::Matrix4D& mtrx, gp_Trsf& trsf); static void convertToMatrix(const gp_Trsf& trsf, Base::Matrix4D& mtrx); diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index e642f757ba..e881eee5e4 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -8320,6 +8320,89 @@ void SketchObject::validateExternalLinks() } namespace { + +void adjustParameterRange(const TopoDS_Edge &edge, + Handle(Geom_Plane) gPlane, + const gp_Trsf &mov, + Handle(Geom_Curve) curve, + double &firstParameter, + double &lastParameter) +{ + // This function is to deal with the ambiguity of trimming a periodic + // curve, e.g. given two points on a circle, whether to get the upper or + // lower arc. Because projection orientation may swap the first and last + // parameter of the original curve. + // + // We project the middel point of the original curve to the projected curve + // to decide whether to flip the parameters. + + Handle(Geom_Curve) origCurve = BRepAdaptor_Curve(edge).Curve().Curve(); + + // GeomAPI_ProjectPointOnCurve will project a point to an untransformed + // curve, so make sure to obtain the point on an untransformed edge. + auto e = edge.Located(TopLoc_Location()); + + gp_Pnt firstPoint = BRep_Tool::Pnt(TopExp::FirstVertex(TopoDS::Edge(e))); + double f = GeomAPI_ProjectPointOnCurve(firstPoint, origCurve).LowerDistanceParameter(); + + gp_Pnt lastPoint = BRep_Tool::Pnt(TopExp::LastVertex(TopoDS::Edge(e))); + double l = GeomAPI_ProjectPointOnCurve(lastPoint, origCurve).LowerDistanceParameter(); + + auto adjustPeriodic = [](Handle(Geom_Curve) curve, double &f, double &l) { + // Copied from Geom_TrimmedCurve::setTrim() + if (curve->IsPeriodic()) { + Standard_Real Udeb = curve->FirstParameter(); + Standard_Real Ufin = curve->LastParameter(); + // set f in the range Udeb , Ufin + // set l in the range f , f + Period() + ElCLib::AdjustPeriodic(Udeb, Ufin, + std::min(std::abs(f-l)/2,Precision::PConfusion()), + f, l); + } + }; + + // Adjust for periodic curve to deal with orientation + adjustPeriodic(origCurve, f, l); + + // Obtain the middle parameter in order to get the mid point of the arc + double m = (l - f) * 0.5 + f; + GeomLProp_CLProps prop(origCurve,m,0,Precision::Confusion()); + gp_Pnt midPoint = prop.Value(); + + // Transform all three points to the world coordinate + auto trsf = edge.Location().Transformation(); + midPoint.Transform(trsf); + firstPoint.Transform(trsf); + lastPoint.Transform(trsf); + + // Project the points to the sketch plane. Note the coordinates are still + // in world coordinate system. + gp_Pnt pm = GeomAPI_ProjectPointOnSurf(midPoint, gPlane).NearestPoint(); + gp_Pnt pf = GeomAPI_ProjectPointOnSurf(firstPoint, gPlane).NearestPoint(); + gp_Pnt pl = GeomAPI_ProjectPointOnSurf(lastPoint, gPlane).NearestPoint(); + + // Transform the projected points to sketch plane local coordinates + pm.Transform(mov); + pf.Transform(mov); + pl.Transform(mov); + + // Obtain the corresponding parameters for those points in the projected curve + double f2 = GeomAPI_ProjectPointOnCurve(pf, curve).LowerDistanceParameter(); + double l2 = GeomAPI_ProjectPointOnCurve(pl, curve).LowerDistanceParameter(); + double m2 = GeomAPI_ProjectPointOnCurve(pm, curve).LowerDistanceParameter(); + + firstParameter = f2; + lastParameter = l2; + + adjustPeriodic(curve, f2, l2); + adjustPeriodic(curve, f2, m2); + // If the middle point is out of range, it means we need to choose the + // other half of the arc. + if (m2 > l2){ + std::swap(firstParameter, lastParameter); + } +} + void processEdge2(TopoDS_Edge& projEdge, std::vector>& geos) { BRepAdaptor_Curve projCurve(projEdge); @@ -8484,11 +8567,15 @@ void processEdge(const TopoDS_Edge& edge, else if (curve.GetType() == GeomAbs_Circle) { gp_Dir vec1 = sketchPlane.Axis().Direction(); gp_Dir vec2 = curve.Circle().Axis().Direction(); + + // start point of arc of circle + gp_Pnt beg = curve.Value(curve.FirstParameter()); + // end point of arc of circle + gp_Pnt end = curve.Value(curve.LastParameter()); + if (vec1.IsParallel(vec2, Precision::Confusion())) { gp_Circ circle = curve.Circle(); gp_Pnt cnt = circle.Location(); - gp_Pnt beg = curve.Value(curve.FirstParameter()); - gp_Pnt end = curve.Value(curve.LastParameter()); GeomAPI_ProjectPointOnSurf proj(cnt, gPlane); cnt = proj.NearestPoint(); @@ -8516,15 +8603,12 @@ void processEdge(const TopoDS_Edge& edge, } else { // creates an ellipse or a segment - - gp_Dir vec1 = sketchPlane.Axis().Direction(); - gp_Dir vec2 = curve.Circle().Axis().Direction(); gp_Circ origCircle = curve.Circle(); - if (vec1.IsNormal( - vec2, Precision::Angular())) {// circle's normal vector in plane: - // projection is a line - // define center by projection + if (vec1.IsNormal(vec2, Precision::Angular())) { + // circle's normal vector in plane: + // projection is a line + // define center by projection gp_Pnt cnt = origCircle.Location(); GeomAPI_ProjectPointOnSurf proj(cnt, gPlane); cnt = proj.NearestPoint(); @@ -8540,12 +8624,6 @@ void processEdge(const TopoDS_Edge& edge, ligne.D0(origCircle.Radius(), P2); if (!curve.IsClosed()) {// arc of circle - - // start point of arc of circle - gp_Pnt pntF = curve.Value(curve.FirstParameter()); - // end point of arc of circle - gp_Pnt pntL = curve.Value(curve.LastParameter()); - double alpha = dirOrientation.AngleWithRef(curve.Circle().XAxis().Direction(), curve.Circle().Axis().Direction()); @@ -8567,17 +8645,17 @@ void processEdge(const TopoDS_Edge& edge, if (startAngle <= 0.0) { if (endAngle <= 0.0) { - P1 = ProjPointOnPlane_XYZ(pntF, sketchPlane); - P2 = ProjPointOnPlane_XYZ(pntL, sketchPlane); + P1 = ProjPointOnPlane_XYZ(beg, sketchPlane); + P2 = ProjPointOnPlane_XYZ(end, sketchPlane); } else { if (endAngle <= fabs(startAngle)) { // P2 = P2 already defined - P1 = ProjPointOnPlane_XYZ(pntF, sketchPlane); + P1 = ProjPointOnPlane_XYZ(beg, sketchPlane); } else if (endAngle < M_PI) { // P2 = P2, already defined - P1 = ProjPointOnPlane_XYZ(pntL, sketchPlane); + P1 = ProjPointOnPlane_XYZ(end, sketchPlane); } else { // P1 = P1, already defined @@ -8587,15 +8665,15 @@ void processEdge(const TopoDS_Edge& edge, } else if (startAngle < M_PI) { if (endAngle < M_PI) { - P1 = ProjPointOnPlane_XYZ(pntF, sketchPlane); - P2 = ProjPointOnPlane_XYZ(pntL, sketchPlane); + P1 = ProjPointOnPlane_XYZ(beg, sketchPlane); + P2 = ProjPointOnPlane_XYZ(end, sketchPlane); } else if (endAngle < 2.0 * M_PI - startAngle) { - P2 = ProjPointOnPlane_XYZ(pntF, sketchPlane); + P2 = ProjPointOnPlane_XYZ(beg, sketchPlane); // P1 = P1, already defined } else if (endAngle < 2.0 * M_PI) { - P2 = ProjPointOnPlane_XYZ(pntL, sketchPlane); + P2 = ProjPointOnPlane_XYZ(end, sketchPlane); // P1 = P1, already defined } else { @@ -8605,16 +8683,16 @@ void processEdge(const TopoDS_Edge& edge, } else { if (endAngle < 2 * M_PI) { - P1 = ProjPointOnPlane_XYZ(pntF, sketchPlane); - P2 = ProjPointOnPlane_XYZ(pntL, sketchPlane); + P1 = ProjPointOnPlane_XYZ(beg, sketchPlane); + P2 = ProjPointOnPlane_XYZ(end, sketchPlane); } else if (endAngle < 4 * M_PI - startAngle) { - P1 = ProjPointOnPlane_XYZ(pntF, sketchPlane); + P1 = ProjPointOnPlane_XYZ(beg, sketchPlane); // P2 = P2, already defined } else if (endAngle < 3 * M_PI) { // P1 = P1, already defined - P2 = ProjPointOnPlane_XYZ(pntL, sketchPlane); + P2 = ProjPointOnPlane_XYZ(end, sketchPlane); } else { // P1 = P1, already defined @@ -8632,7 +8710,7 @@ void processEdge(const TopoDS_Edge& edge, GeometryFacade::setConstruction(projectedSegment, true); geos.emplace_back(projectedSegment); } - else {// general case, full circle + else {// general case, full circle or arc of circle gp_Pnt cnt = origCircle.Location(); GeomAPI_ProjectPointOnSurf proj(cnt, gPlane); // projection of circle center on sketch plane, 3D space @@ -8664,12 +8742,33 @@ void processEdge(const TopoDS_Edge& edge, // NB: force normal of ellipse to be normal of sketch's plane. gp_Ax2 refFrameEllipse( gp_Pnt(gp_XYZ(p[0], p[1], p[2])), gp_Vec(0, 0, 1), vecMajorAxis); - Handle(Geom_Ellipse) curve = - new Geom_Ellipse(refFrameEllipse, origCircle.Radius(), minorRadius); - Part::GeomEllipse* ellipse = new Part::GeomEllipse(); - ellipse->setHandle(curve); - GeometryFacade::setConstruction(ellipse, true); - geos.emplace_back(ellipse); + + gp_Elips elipsDest; + elipsDest.SetPosition(refFrameEllipse); + elipsDest.SetMajorRadius(origCircle.Radius()); + elipsDest.SetMinorRadius(minorRadius); + + Handle(Geom_Ellipse) projCurve = new Geom_Ellipse(elipsDest); + + if (beg.SquareDistance(end) < Precision::Confusion()) { + // projection is an ellipse + auto* ellipse = new Part::GeomEllipse(); + ellipse->setHandle(projCurve); + GeometryFacade::setConstruction(ellipse, true); + geos.emplace_back(ellipse); + } + else { + // projection is an arc of ellipse + auto* aoe = new Part::GeomArcOfEllipse(); + double firstParam, lastParam; + // ajust the parameter range to get the correct arc + adjustParameterRange(edge, gPlane, mov, projCurve, firstParam, lastParam); + + Handle(Geom_TrimmedCurve) trimmedCurve = new Geom_TrimmedCurve(projCurve, firstParam, lastParam); + aoe->setHandle(trimmedCurve); + GeometryFacade::setConstruction(aoe, true); + geos.emplace_back(aoe); + } } } } @@ -8738,18 +8837,18 @@ void processEdge(const TopoDS_Edge& edge, // projection is a circle if ((RDest - rDest) < (double)Precision::Confusion()) { - Handle(Geom_Circle) curve2 = new Geom_Circle(destCurveAx2, 0.5 * (rDest + RDest)); + Handle(Geom_Circle) projCurve = new Geom_Circle(destCurveAx2, 0.5 * (rDest + RDest)); if (P1.SquareDistance(P2) < Precision::Confusion()) { auto* circle = new Part::GeomCircle(); - circle->setHandle(curve2); + circle->setHandle(projCurve); GeometryFacade::setConstruction(circle, true); geos.emplace_back(circle); } else { auto* arc = new Part::GeomArcOfCircle(); - Handle(Geom_TrimmedCurve) tCurve = new Geom_TrimmedCurve(curve2, - curve.FirstParameter(), - curve.LastParameter()); + double firstParam, lastParam; + adjustParameterRange(edge, gPlane, mov, projCurve, firstParam, lastParam); + Handle(Geom_TrimmedCurve) tCurve = new Geom_TrimmedCurve(projCurve, firstParam, lastParam); arc->setHandle(tCurve); GeometryFacade::setConstruction(arc, true); geos.emplace_back(arc); @@ -8774,20 +8873,20 @@ void processEdge(const TopoDS_Edge& edge, elipsDest.SetMajorRadius(destAxisMajor.Magnitude()); elipsDest.SetMinorRadius(destAxisMinor.Magnitude()); + Handle(Geom_Ellipse) projCurve = new Geom_Ellipse(elipsDest); if (P1.SquareDistance(P2) < Precision::Confusion()) { - Handle(Geom_Ellipse) curve = new Geom_Ellipse(elipsDest); auto* ellipse = new Part::GeomEllipse(); - ellipse->setHandle(curve); + ellipse->setHandle(projCurve); GeometryFacade::setConstruction(ellipse, true); geos.emplace_back(ellipse); } else { auto* aoe = new Part::GeomArcOfEllipse(); - Handle(Geom_Curve) curve2 = new Geom_Ellipse(elipsDest); - Handle(Geom_TrimmedCurve) tCurve = new Geom_TrimmedCurve(curve2, - curve.FirstParameter(), - curve.LastParameter()); + double firstParam, lastParam; + adjustParameterRange(edge, gPlane, mov, projCurve, firstParam, lastParam); + + Handle(Geom_TrimmedCurve) tCurve = new Geom_TrimmedCurve(projCurve, firstParam, lastParam); aoe->setHandle(tCurve); GeometryFacade::setConstruction(aoe, true); geos.emplace_back(aoe); @@ -8796,23 +8895,88 @@ void processEdge(const TopoDS_Edge& edge, } } else { - try { - BRepOffsetAPI_NormalProjection mkProj(aProjFace); - mkProj.Add(edge); - mkProj.Build(); - const TopoDS_Shape& projShape = mkProj.Projection(); - if (!projShape.IsNull()) { - TopExp_Explorer xp; - for (xp.Init(projShape, TopAbs_EDGE); xp.More(); xp.Next()) { - TopoDS_Edge projEdge = TopoDS::Edge(xp.Current()); - TopLoc_Location loc(mov); - projEdge.Location(loc); - processEdge2(projEdge, geos); - } + gp_Pln plane; + auto shape = Part::TopoShape(edge); + bool planar = shape.findPlane(plane); + + // Check if the edge is planar and plane is perpendicular to the projection plane + if (planar && plane.Axis().IsNormal(sketchPlane.Axis(), Precision::Angular())) { + // Project an edge to a line. Only works if the edge is planar and its plane is + // perpendicular to the projection plane. OCC has trouble handling + // BSpline projection to a straight line. Although it does correctly projects + // the line including extreme bounds (not always a case), it will produce a BSpline with degree + // more than one. + // + // The work around here is to use an aligned bounding box of the edge to get + // the projection of the extremum points to construct the projected line. + + // First, transform the shape to the projection plane local coordinates. + shape.setPlacement(invPlm * shape.getPlacement()); + + // Align the z axis of the edge plane to the y axis of the projection + // plane, so that the extreme bound will be a line in the x axis direction + // of the projection plane. + double angle = plane.Axis().Direction().Angle(sketchPlane.YAxis().Direction()); + + gp_Trsf trsf; + if (fabs(angle) > Precision::Angular()) { + trsf.SetRotation(gp_Ax1(gp_Pnt(), gp_Dir(0, 0, 1)), angle); + shape.move(trsf); + } + + // Make a copy to work around OCC circular edge transformation bug + shape = shape.makeElementCopy(); + + // Obtain the bounding box (precise version!) and move the extreme points back + // to the original location + auto bbox = shape.getBoundBoxOptimal(); + if (!bbox.IsValid()){ + throw Base::CADKernelError("Invalid bounding box"); + } + + gp_Pnt p1(bbox.MinX, bbox.MinY, 0); + gp_Pnt p2(bbox.MaxX, bbox.MaxY, 0); + if (fabs(angle) > Precision::Angular()) { + trsf.SetRotation(gp_Ax1(gp_Pnt(), gp_Dir(0, 0, 1)), -angle); + p1.Transform(trsf); + p2.Transform(trsf); + } + + Base::Vector3d P1(p1.X(), p1.Y(), 0); + Base::Vector3d P2(p2.X(), p2.Y(), 0); + + // check for degenerated case when the line is collapsed to a point + if (p1.SquareDistance(p2) < Precision::SquareConfusion()) { + Part::GeomPoint* point = new Part::GeomPoint((P1 + P2) / 2); + GeometryFacade::setConstruction(point, true); + geos.emplace_back(point); + } + else { + auto* projectedSegment = new Part::GeomLineSegment(); + projectedSegment->setPoints(P1, P2); + GeometryFacade::setConstruction(projectedSegment, true); + geos.emplace_back(projectedSegment); } } - catch (Standard_Failure& e) { - throw Base::CADKernelError(e.GetMessageString()); + else { + try { + BRepOffsetAPI_NormalProjection mkProj(aProjFace); + mkProj.Add(edge); + mkProj.Build(); + const TopoDS_Shape& projShape = mkProj.Projection(); + if (!projShape.IsNull()) { + TopExp_Explorer xp; + for (xp.Init(projShape, TopAbs_EDGE); xp.More(); xp.Next()) { + TopoDS_Edge projEdge = TopoDS::Edge(xp.Current()); + TopLoc_Location loc(mov); + projEdge.Location(loc); + processEdge2(projEdge, geos); + } + } + } + catch (Standard_Failure& e) { + throw Base::CADKernelError(e.GetMessageString()); + } } } } @@ -8896,6 +9060,7 @@ std::vector projectShape(const TopoDS_Shape& inShape, const gp_Ax3 return res; } + } void SketchObject::rebuildExternalGeometry(std::optional extToAdd)