fixes #19582 Sketcher - cannot project BSpline from another sketch (#19700)

Also fixes some other types of external geo

---------

Co-authored-by: Andrew Shkolik <andrew.shkolik@selerix.com>
Co-authored-by: realthunder <realthunder.dev@gmail.com>
This commit is contained in:
Andrew
2025-02-24 10:17:24 -06:00
committed by GitHub
parent df2c6637e6
commit 0aed23ca81
3 changed files with 251 additions and 60 deletions

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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<std::unique_ptr<Part::Geometry>>& 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<TopoDS_Shape> projectShape(const TopoDS_Shape& inShape, const gp_Ax3
return res;
}
}
void SketchObject::rebuildExternalGeometry(std::optional<ExternalToAdd> extToAdd)