[Sketcher] Helper functions for deleting geometry

Some tests probably needed
This commit is contained in:
Ajinkya Dahale
2024-06-29 11:29:23 +05:30
parent 2249c99b33
commit bc2b35da4e

View File

@@ -1770,19 +1770,11 @@ int SketchObject::delGeometry(int GeoId, bool deleteinternalgeo)
const std::vector<Constraint*>& constraints = this->Constraints.getValues();
std::vector<Constraint*> newConstraints;
newConstraints.reserve(constraints.size());
for (auto cstr : constraints) {
if (cstr->First == GeoId || cstr->Second == GeoId || cstr->Third == GeoId)
continue;
if (cstr->First > GeoId || cstr->Second > GeoId || cstr->Third > GeoId) {
cstr = cstr->clone();
if (cstr->First > GeoId)
cstr->First -= 1;
if (cstr->Second > GeoId)
cstr->Second -= 1;
if (cstr->Third > GeoId)
cstr->Third -= 1;
for (const auto& constr : constraints) {
auto newConstr = getConstraintAfterDeletingGeo(constr, GeoId);
if (newConstr) {
newConstraints.push_back(newConstr.release());
}
newConstraints.push_back(cstr);
}
// Block acceptGeometry in OnChanged to avoid unnecessary checks and updates
@@ -1891,32 +1883,22 @@ int SketchObject::delGeometriesExclusiveList(const std::vector<int>& GeoIds)
// Copy the original constraints
std::vector<Constraint*> constraints;
for (const auto ptr : this->Constraints.getValues())
for (const auto& ptr : this->Constraints.getValues()) {
constraints.push_back(ptr->clone());
std::vector<Constraint*> filteredConstraints(0);
for (auto itGeo = sGeoIds.rbegin(); itGeo != sGeoIds.rend(); ++itGeo) {
int GeoId = *itGeo;
for (const auto& constr : constraints) {
Constraint* copiedConstr(constr);
if (constr->First == GeoId ||
constr->Second == GeoId ||
constr->Third == GeoId) {
delete copiedConstr;
continue;
}
if (copiedConstr->First > GeoId)
copiedConstr->First -= 1;
if (copiedConstr->Second > GeoId)
copiedConstr->Second -= 1;
if (copiedConstr->Third > GeoId)
copiedConstr->Third -= 1;
filteredConstraints.push_back(copiedConstr);
}
constraints = filteredConstraints;
filteredConstraints.clear();
}
for (auto itGeo = sGeoIds.rbegin(); itGeo != sGeoIds.rend(); ++itGeo) {
const int GeoId = *itGeo;
for (auto& constr : constraints) {
changeConstraintAfterDeletingGeo(constr, GeoId);
}
}
constraints.erase(std::remove_if(constraints.begin(),
constraints.end(),
[](const auto& constr) {
return constr->Type == ConstraintType::None;
}),
constraints.end());
// Block acceptGeometry in OnChanged to avoid unnecessary checks and updates
{
@@ -3476,6 +3458,62 @@ void SketchObject::addConstraint(Sketcher::ConstraintType constrType, int firstG
this->addConstraint(std::move(newConstr));
}
std::unique_ptr<Constraint>
SketchObject::getConstraintAfterDeletingGeo(const Constraint* constr,
const int deletedGeoId) const
{
if (!constr) {
return nullptr;
}
// TODO: While this is not incorrect, it recreates all constraints regardless of whether or not we need to.
auto newConstr = std::unique_ptr<Constraint>(constr->clone());
changeConstraintAfterDeletingGeo(newConstr.get(), deletedGeoId);
if (newConstr->Type == ConstraintType::None) {
return nullptr;
}
return newConstr;
}
void SketchObject::changeConstraintAfterDeletingGeo(Constraint* constr,
const int deletedGeoId) const
{
if (!constr) {
return;
}
if (constr->First == deletedGeoId ||
constr->Second == deletedGeoId ||
constr->Third == deletedGeoId) {
constr->Type = ConstraintType::None;
return;
}
int step = 1;
std::function<bool (const int&)> needsUpdate = [&deletedGeoId](const int& givenId) -> bool {
return givenId > deletedGeoId;
};
if (deletedGeoId < 0) {
step = -1;
needsUpdate = [&deletedGeoId](const int& givenId) -> bool {
return givenId < deletedGeoId && givenId != GeoEnum::GeoUndef;
};
}
if (needsUpdate(constr->First)) {
constr->First -= step;
}
if (needsUpdate(constr->Second)) {
constr->Second -= step;
}
if (needsUpdate(constr->Third)) {
constr->Third -= step;
}
}
bool SketchObject::seekTrimPoints(int GeoId, const Base::Vector3d& point, int& GeoId1,
Base::Vector3d& intersect1, int& GeoId2,
Base::Vector3d& intersect2)
@@ -3528,31 +3566,47 @@ int SketchObject::trim(int GeoId, const Base::Vector3d& point)
return ((point1 - point2).Length() < 500 * Precision::Confusion()) ;
};
auto isPointAtPosition = [this, &arePointsWithinPrecision]
(int GeoId1, PointPos pos1, Base::Vector3d point) {
Base::Vector3d pp = getPoint(GeoId1, pos1);
auto isPointAtPosition =
[this, &arePointsWithinPrecision](int GeoId1, PointPos pos1, Base::Vector3d point) {
Base::Vector3d pp = getPoint(GeoId1, pos1);
return arePointsWithinPrecision(point, pp);
};
return arePointsWithinPrecision(point, pp);
};
#if 0
// Checks whether preexisting constraints must be converted to new constraints.
// Preexisting point on object constraints get converted to coincidents, unless an end-to-end
// tangency is more relevant. returns by reference:
// - The type of constraint that should be used to constraint GeoId1 and GeoId
// - The element of GeoId1 to which the constraint should be applied.
// - The type of constraint that should be used to constraint GeoId1 and GeoId
// - The element of GeoId1 to which the constraint should be applied.
auto transformPreexistingConstraint = [this, isPointAtPosition](int GeoId,
int GeoId1,
Base::Vector3d point1,
Constraint* constr) {
// TODO: Move code currently later in this method (that does as per the following description) here.
/* It is possible that the trimming entity has both a PointOnObject constraint to the
* trimmed entity, and a simple Tangent constraint to the trimmed entity. In this case we
* want to change to a single end-to-end tangency, i.e we want to ensure that constrType1 is
* set to Sketcher::Tangent, that the secondPos1 is captured from the PointOnObject, and
* also make sure that the PointOnObject constraint is deleted. The below loop ensures this,
* also in case the ordering of the constraints is first Tangent and then PointOnObject. */
// if (!(constr->Type == Sketcher::Tangent
// || constr->Type == Sketcher::Perpendicular)) {
// return;
// }
// if (constr->First == GeoId1 && constr->Second == GeoId) {
// constr->Type = constr->Type;
// if (secondPos == Sketcher::PointPos::none)
// secondPos = constr->FirstPos;
// delete_list.push_back(constrId);
// }
// else if (constr->First == GeoId && constr->Second == GeoId1) {
// constr->Type = constr->Type;
// if (secondPos == Sketcher::PointPos::none)
// secondPos = constr->SecondPos;
// delete_list.push_back(constrId);
// }
};
#endif
// makes an equality constraint between GeoId1 and GeoId2
auto constrainAsEqual = [this](int GeoId1, int GeoId2) {
auto newConstr = std::make_unique<Sketcher::Constraint>();
@@ -3627,40 +3681,133 @@ int SketchObject::trim(int GeoId, const Base::Vector3d& point)
double lastParam = geoAsCurve->getLastParameter();
double pointParam, point1Param, point2Param;
if (!getIntersectionParameters(
geo, point, pointParam, point1, point1Param, point2, point2Param)) {
geo, point, pointParam, point1, point1Param, point2, point2Param))
return -1;
}
bool ok = false;
bool startPointRemains = false;
bool endPointRemains = false;
std::vector<std::pair<double, double> > paramsOfNewGeos;
std::vector<int> newIds;
std::vector<Part::Geometry*> newGeos;
std::vector<const Part::Geometry*> newGeosAsConsts;
bool oldGeoIsConstruction = GeometryFacade::getConstruction(static_cast<const Part::GeomCurve*>(geo));
if (isClosedCurve(geo)) {
startPointRemains = false;
endPointRemains = false;
if (GeoId1 != GeoEnum::GeoUndef && GeoId2 != GeoEnum::GeoUndef) {
paramsOfNewGeos.emplace_back(point2Param, point1Param);
auto setUpNewGeoLimitsFromNonPeriodic =
[&]() {
if (GeoId1 != GeoEnum::GeoUndef) {
startPointRemains = true;
paramsOfNewGeos.emplace_back(firstParam, point1Param);
}
if (GeoId2 != GeoEnum::GeoUndef) {
endPointRemains = true;
paramsOfNewGeos.emplace_back(point2Param, lastParam);
}
};
auto setUpNewGeoLimitsFromPeriodic =
[&]() {
startPointRemains = false;
endPointRemains = false;
if (GeoId1 != GeoEnum::GeoUndef && GeoId2 != GeoEnum::GeoUndef) {
paramsOfNewGeos.emplace_back(point2Param, point1Param);
}
};
auto createArcGeos = [&](const Part::GeomTrimmedCurve* curve) {
for (auto& [u1, u2] : paramsOfNewGeos) {
auto newArc = std::unique_ptr<Part::GeomTrimmedCurve>(
static_cast<Part::GeomTrimmedCurve*>(curve->copy()));
newArc->setRange(u1, u2);
newGeos.push_back(newArc.release());
}
return true;
};
auto createArcGeosForCircle = [&](const Part::GeomCircle* curve) {
for (auto& [u1, u2] : paramsOfNewGeos) {
auto newArc = std::make_unique<Part::GeomArcOfCircle>(
Handle(Geom_Circle)::DownCast(curve->handle()->Copy()));
newArc->setRange(u1, u2, false);
newGeos.push_back(newArc.release());
// int newId(GeoEnum::GeoUndef);
// newId = addGeometry(std::move(newArc));
// if (newId < 0) {
// return false;
// }
// newIds.push_back(newId);
// setConstruction(newId, GeometryFacade::getConstruction(curve));
// exposeInternalGeometry(newId);
}
return true;
};
auto createArcGeosForEllipse = [&](const Part::GeomEllipse* curve) {
for (auto& [u1, u2] : paramsOfNewGeos) {
auto newArc = std::make_unique<Part::GeomArcOfEllipse>(
Handle(Geom_Ellipse)::DownCast(curve->handle()->Copy()));
newArc->setRange(u1, u2, false);
newGeos.push_back(newArc.release());
}
return true;
};
auto createArcGeosForBSplineCurve = [&](const Part::GeomBSplineCurve* curve) {
for (auto& [u1, u2] : paramsOfNewGeos) {
auto newBsp = std::unique_ptr<Part::GeomBSplineCurve>(
static_cast<Part::GeomBSplineCurve*>(curve->copy()));
newBsp->Trim(u1, u2);
newGeos.push_back(newBsp.release());
}
return true;
};
if (geo->is<Part::GeomLineSegment>()) {
setUpNewGeoLimitsFromNonPeriodic();
ok = createArcGeos(static_cast<const Part::GeomTrimmedCurve*>(geo));
}
else {
if (GeoId1 != GeoEnum::GeoUndef) {
startPointRemains = true;
paramsOfNewGeos.emplace_back(firstParam, point1Param);
else if (geo->is<Part::GeomCircle>()) {
setUpNewGeoLimitsFromPeriodic();
ok = createArcGeosForCircle(static_cast<const Part::GeomCircle*>(geo));
}
else if (geo->is<Part::GeomEllipse>()) {
setUpNewGeoLimitsFromPeriodic();
ok = createArcGeosForEllipse(static_cast<const Part::GeomEllipse*>(geo));
}
else if (geo->is<Part::GeomArcOfCircle>()) {
setUpNewGeoLimitsFromNonPeriodic();
ok = createArcGeos(static_cast<const Part::GeomTrimmedCurve*>(geo));
}
else if (geo->isDerivedFrom<Part::GeomArcOfConic>()) {
setUpNewGeoLimitsFromNonPeriodic();
ok = createArcGeos(static_cast<const Part::GeomTrimmedCurve*>(geo));
}
else if (geo->is<Part::GeomBSplineCurve>()) {
const Part::GeomBSplineCurve* bsp = static_cast<const Part::GeomBSplineCurve*>(geo);
// what to do for periodic b-splines?
if (bsp->isPeriodic()) {
setUpNewGeoLimitsFromPeriodic();
}
if (GeoId2 != GeoEnum::GeoUndef) {
endPointRemains = true;
paramsOfNewGeos.emplace_back(point2Param, lastParam);
else {
setUpNewGeoLimitsFromNonPeriodic();
}
ok = createArcGeosForBSplineCurve(bsp);
}
for (auto& [u1, u2] : paramsOfNewGeos) {
auto newGeo = geoAsCurve->createArc(u1, u2);
assert(newGeo);
newGeos.push_back(newGeo);
newGeosAsConsts.push_back(newGeo);
if (!ok) {
delGeometries(newIds);
return -1;
}
switch (newGeos.size()) {
@@ -3685,15 +3832,16 @@ int SketchObject::trim(int GeoId, const Base::Vector3d& point)
}
}
for (auto& newGeo : newGeos) {
newGeosAsConsts.push_back(newGeo);
}
// Now that we have the new curves, change constraints as needed
// Some are covered with `deriveConstraintsForPieces`, others are specific to trim
// FIXME: We are using non-smart pointers since that's what's needed in `addConstraints`.
std::vector<Constraint*> newConstraints;
// A geoId we expect to never exist during this operation (but still not `GeoEnum::GeoUndef`).
// We use it because newIds.front() is supposed to be `GeoId`, but we will delete constraints
// on it down the line, even if we may need it later.
// Note that this will cause some malformed constraints until they are transferred back.
// A geoId we expect to never exist during this operation. We use it because newIds.front() is supposed to be `GeoId`, but we will delete constraints on it down the line, even if we will need it later. As a result, note that this will cause some malformed constraints until they are transferred back.
int nonexistentGeoId = getHighestCurveIndex() + newIds.size();
newIds.front() = nonexistentGeoId;
@@ -3731,94 +3879,79 @@ int SketchObject::trim(int GeoId, const Base::Vector3d& point)
const auto& allConstraints = this->Constraints.getValues();
bool isPoint1ConstrainedOnGeoId1 = false;
bool isPoint2ConstrainedOnGeoId2 = false;
bool isGeoId1CoincidentOnPoint1 = false;
bool isGeoId2CoincidentOnPoint2 = false;
// Workaround to avoid https://github.com/FreeCAD/FreeCAD/issues/15484.
// We will delete its internal geometry first and then replace GeoId with one of the curves.
// Of course, this will change `newIds`, and that's why we offset them.
std::set< int, std::greater<> > geoIdsToBeDeleted;
std::vector<int> geoIdsToBeDeleted;
// geoIdsToBeDeleted.push_back(GeoId);
if (hasInternalGeometry(geo)) {
for (const auto& oldConstrId: idsOfOldConstraints) {
if (allConstraints[oldConstrId]->Type == InternalAlignment) {
geoIdsToBeDeleted.push_back(allConstraints[oldConstrId]->First);
}
}
// NOTE: Assuming no duplication here.
// If there are redundants for some pathological reason, use std::unique.
std::sort(geoIdsToBeDeleted.begin(), geoIdsToBeDeleted.end(), std::greater<>());
// keep constraints on internal geometries so they are deleted
// when the old curve is deleted
}
for (const auto& oldConstrId: idsOfOldConstraints) {
// trim-specific changes first
const Constraint* con = allConstraints[oldConstrId];
bool constraintWasModified = false;
bool newConstraintCreated = deriveConstraintsForPieces(GeoId, newIds, newGeosAsConsts, con, newConstraints);
// trim-specific changes once general changes are done
switch (con->Type) {
case PointOnObject: {
// we might want to transform this (and the new point-on-object constraints) into a coincidence
if (con->First == GeoId1 && con->Second == GeoId
&& isPointAtPosition(con->First, con->FirstPos, point1)) {
// This concerns the start portion of the trim
Constraint* newConstr = con->copy();
newConstr->Type = Sketcher::Coincident;
newConstr->Second = newIds.front();
newConstr->SecondPos = PointPos::end;
newConstraints.push_back(newConstr);
isPoint1ConstrainedOnGeoId1 = true;
constraintWasModified = true;
if (!newConstraintCreated) {
break;
}
else if (con->First == GeoId2 && con->Second == GeoId
&& isPointAtPosition(con->First, con->FirstPos, point2)) {
// This concerns the end portion of the trim
Constraint* newConstr = con->copy();
Constraint* newConstr = newConstraints.back();
// we might want to transform this (and the new point-on-object constraints) into a coincidence
if (newConstr->Second == newIds.front() &&
isPointAtPosition(newConstr->First, newConstr->FirstPos, point1)) {
// This concerns the start portion of the trim
newConstr->Type = Sketcher::Coincident;
newConstr->SecondPos = PointPos::end;
if (newConstr->First == GeoId1) {
isGeoId1CoincidentOnPoint1 = true;
}
}
if (newConstr->Second == newIds.back() &&
isPointAtPosition(newConstr->First, newConstr->FirstPos, point2)) {
// This concerns the end portion of the trim
newConstr->Type = Sketcher::Coincident;
newConstr->Second = newIds.back();
newConstr->SecondPos = PointPos::start;
newConstraints.push_back(newConstr);
isPoint2ConstrainedOnGeoId2 = true;
constraintWasModified = true;
if (newConstr->First == GeoId2) {
isGeoId2CoincidentOnPoint2 = true;
}
}
break;
}
case Tangent:
case Perpendicular: {
// These may have to be turned into endpoint-to-endpoint or endpoint-to-edge
// TODO: could there be tangent/perpendicular constraints not involving the trim that are modified below?
if (con->involvesGeoId(GeoId1) && con->involvesGeoIdAndPosId(GeoId, PointPos::none)) {
Constraint* newConstr = con->copy();
newConstr->substituteIndexAndPos(GeoId, PointPos::none, newIds.front(), PointPos::end);
newConstraints.push_back(newConstr);
isPoint1ConstrainedOnGeoId1 = true;
constraintWasModified = true;
// TODO Tangent may have to be turned into endpoint-to-endpoint
// we might want to transform this into a coincidence
if (!newConstraintCreated) {
break;
}
if (con->involvesGeoId(GeoId2) && con->involvesGeoIdAndPosId(GeoId, PointPos::none)) {
Constraint* newConstr = con->copy();
newConstr->substituteIndexAndPos(GeoId, PointPos::none, newIds.back(), PointPos::start);
newConstraints.push_back(newConstr);
isPoint2ConstrainedOnGeoId2 = true;
constraintWasModified = true;
}
if (constraintWasModified) {
// make sure the first position is a point
if (newConstraints.back()->FirstPos == PointPos::none) {
std::swap(newConstraints.back()->First, newConstraints.back()->Second);
std::swap(newConstraints.back()->FirstPos, newConstraints.back()->SecondPos);
}
// there is no need for the third point if it exists
newConstraints.back()->Third = GeoEnum::GeoUndef;
newConstraints.back()->ThirdPos = PointPos::none;
}
break;
}
case InternalAlignment: {
geoIdsToBeDeleted.insert(con->First);
break;
}
default:
break;
}
if (!constraintWasModified) {
deriveConstraintsForPieces(GeoId, newIds, newGeosAsConsts, con, newConstraints);
}
}
// Add point-on-object/coincidence constraints with the newly exposed points.
// This will need to account for the constraints that were already converted
// to coincident or end-to-end tangency/perpendicularity.
// This will need to account for the constraints that were already converted to coincident or end-to-end tangency/perpendicularity.
// TODO: Tangent/perpendicular not yet covered
if (GeoId1 != GeoEnum::GeoUndef && !isPoint1ConstrainedOnGeoId1) {
if (GeoId1 != GeoEnum::GeoUndef && !isGeoId1CoincidentOnPoint1) {
Constraint* newConstr = new Constraint();
newConstr->First = newIds.front();
newConstr->FirstPos = PointPos::end;
@@ -3839,7 +3972,7 @@ int SketchObject::trim(int GeoId, const Base::Vector3d& point)
newConstraints.push_back(newConstr);
}
if (GeoId2 != GeoEnum::GeoUndef && !isPoint2ConstrainedOnGeoId2) {
if (GeoId2 != GeoEnum::GeoUndef && !isGeoId2CoincidentOnPoint2) {
Constraint* newConstr = new Constraint();
newConstr->First = newIds.back();
newConstr->FirstPos = PointPos::start;
@@ -3905,49 +4038,6 @@ int SketchObject::trim(int GeoId, const Base::Vector3d& point)
return 0;
}
std::unique_ptr<Constraint>
SketchObject::getConstraintAfterDeletingGeo(const Constraint* constr,
const int deletedGeoId) const
{
if (constr->First == deletedGeoId ||
constr->Second == deletedGeoId ||
constr->Third == deletedGeoId) {
return nullptr;
}
// TODO: While this is not incorrect, it recreates all constraints regardless of whether or not we need to.
auto newConstr = std::unique_ptr<Constraint>(constr->clone());
changeConstraintAfterDeletingGeo(newConstr.get(), deletedGeoId);
return newConstr;
}
void SketchObject::changeConstraintAfterDeletingGeo(Constraint* constr,
const int deletedGeoId) const
{
int step = 1;
std::function<bool (const int&)> needsUpdate = [&deletedGeoId](const int& givenId) -> bool {
return givenId > deletedGeoId;
};
if (deletedGeoId < 0) {
step = -1;
needsUpdate = [&deletedGeoId](const int& givenId) -> bool {
return givenId < deletedGeoId && givenId != GeoEnum::GeoUndef;
};
}
if (needsUpdate(constr->First)) {
constr->First -= step;
}
if (needsUpdate(constr->Second)) {
constr->Second -= step;
}
if (needsUpdate(constr->Third)) {
constr->Third -= step;
}
}
bool SketchObject::deriveConstraintsForPieces(const int oldId,
const std::vector<int>& newIds,
const Constraint* con,
@@ -4940,7 +5030,6 @@ int SketchObject::addSymmetric(const std::vector<int>& geoIdList, int refGeoId,
return Geometry.getSize() - 1;
}
std::vector<Part::Geometry*> SketchObject::getSymmetric(const std::vector<int>& geoIdList,
std::map<int, int>& geoIdMap,
std::map<int, bool>& isStartEndInverted,
@@ -5867,7 +5956,6 @@ int SketchObject::addCopy(const std::vector<int>& geoIdList, const Base::Vector3
return Geometry.getSize() - 1;
}
int SketchObject::removeAxesAlignment(const std::vector<int>& geoIdList)
{
// no need to check input data validity as this is an sketchobject managed operation.
@@ -8308,12 +8396,12 @@ void SketchObject::validateExternalLinks()
refSubShape = refShape.getSubShape(SubElement.c_str());
}
}
catch ( Base::IndexError& indexError) {
catch (Base::IndexError& indexError) {
removeBadLink = true;
Base::Console().Warning(
this->getFullLabel(), (indexError.getMessage() + "\n").c_str());
}
catch ( Base::ValueError& valueError ) {
catch (Base::ValueError& valueError) {
removeBadLink = true;
Base::Console().Warning(
this->getFullLabel(), (valueError.getMessage() + "\n").c_str());
@@ -8321,7 +8409,7 @@ void SketchObject::validateExternalLinks()
catch (Standard_Failure&) {
removeBadLink = true;
}
if ( removeBadLink ) {
if (removeBadLink) {
rebuild = true;
Objects.erase(Objects.begin() + i);
SubElements.erase(SubElements.begin() + i);
@@ -8329,19 +8417,10 @@ void SketchObject::validateExternalLinks()
const std::vector<Constraint*>& constraints = Constraints.getValues();
std::vector<Constraint*> newConstraints(0);
int GeoId = GeoEnum::RefExt - i;
for (std::vector<Constraint*>::const_iterator it = constraints.begin();
it != constraints.end();
++it) {
if ((*it)->First != GeoId && (*it)->Second != GeoId && (*it)->Third != GeoId) {
Constraint* copiedConstr = (*it)->clone();
if (copiedConstr->First < GeoId && copiedConstr->First != GeoEnum::GeoUndef)
copiedConstr->First += 1;
if (copiedConstr->Second < GeoId && copiedConstr->Second != GeoEnum::GeoUndef)
copiedConstr->Second += 1;
if (copiedConstr->Third < GeoId && copiedConstr->Third != GeoEnum::GeoUndef)
copiedConstr->Third += 1;
newConstraints.push_back(copiedConstr);
for (const auto& constr : constraints) {
auto newConstr = getConstraintAfterDeletingGeo(constr, GeoId);
if (newConstr) {
newConstraints.push_back(newConstr.release());
}
}