Sketcher: add reverse mapping correction to Carbon Copy (#25745)

* Sketcher: add reverse mapping correction to Carbon Copy

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Replace M_PI with std::numbers::pi

* Replace vector initialization with Base::Vector3d::UnitX/UnitY, apply suggestions from code review

Co-authored-by: Kacper Donat <kadet1090@gmail.com>

* Fix guard clause logic, apply suggestions from code review

* Replace std::numbers::pi with pi via `using std::numbers`

* Fix issues reported by the linter

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Kacper Donat <kadet1090@gmail.com>
This commit is contained in:
marbocub
2025-12-22 23:00:49 +09:00
committed by GitHub
parent 46066e1cca
commit 176ef6da4e
7 changed files with 368 additions and 6 deletions

View File

@@ -7064,6 +7064,8 @@ bool SketchObject::insertBSplineKnot(int GeoId, double param, int multiplicity)
int SketchObject::carbonCopy(App::DocumentObject* pObj, bool construction)
{
using std::numbers::pi;
// no need to check input data validity as this is an sketchobject managed operation.
Base::StateLocker lock(managedoperation, true);
@@ -7096,6 +7098,11 @@ int SketchObject::carbonCopy(App::DocumentObject* pObj, bool construction)
newVals.reserve(vals.size() + svals.size());
newcVals.reserve(cvals.size() + scvals.size());
const Base::Vector3d& origin = this->Placement.getValue().getPosition();
const Base::Rotation& rotation = this->Placement.getValue().getRotation();
const Base::Vector3d axisH = rotation.multVec(Base::Vector3d::UnitX);
const Base::Vector3d axisV = rotation.multVec(Base::Vector3d::UnitY);
std::map<int, int> extMap;
if (psObj->ExternalGeo.getSize() > 1) {
int i = -1;
@@ -7185,8 +7192,26 @@ int SketchObject::carbonCopy(App::DocumentObject* pObj, bool construction)
solverNeedsUpdate = true;
}
auto applyGeometryFlipCorrection = [xinv, yinv, origin, axisV, axisH]
(Part::Geometry* geoNew) {
if (!xinv && !yinv) {
return;
}
if (xinv) {
geoNew->mirror(origin, axisV);
}
if (yinv) {
geoNew->mirror(origin, axisH);
}
};
for (std::vector<Part::Geometry*>::const_iterator it = svals.begin(); it != svals.end(); ++it) {
Part::Geometry* geoNew = (*it)->copy();
if (xinv || yinv) {
// corrections for flipped geometry
applyGeometryFlipCorrection(geoNew);
}
generateId(geoNew);
if (construction && !geoNew->is<Part::GeomPoint>()) {
GeometryFacade::setConstruction(geoNew, true);
@@ -7194,6 +7219,65 @@ int SketchObject::carbonCopy(App::DocumentObject* pObj, bool construction)
newVals.push_back(geoNew);
}
auto applyConstraintFlipCorrection = [xinv, yinv]
(Sketcher::Constraint* newConstr) {
if (!xinv && !yinv) {
return;
}
// DistanceX, DistanceY
if ((xinv && newConstr->Type == Sketcher::DistanceX) ||
(yinv && newConstr->Type == Sketcher::DistanceY)) {
if (newConstr->First == newConstr->Second) {
std::swap(newConstr->FirstPos, newConstr->SecondPos);
} else{
newConstr->setValue(-newConstr->getValue());
}
}
// Angle
if (newConstr->Type == Sketcher::Angle) {
auto normalizeAngle = [](double angleDeg) {
while (angleDeg > pi) angleDeg -= pi * 2.0;
while (angleDeg <= -pi) angleDeg += pi * 2.0;
return angleDeg;
};
if (xinv && yinv) { // rotation 180 degrees around normal axis
if (newConstr->First ==-1 || newConstr->Second == -1
|| newConstr->First == -2 || newConstr->Second == -2
|| newConstr->Second == GeoEnum::GeoUndef) {
// angle to horizontal or vertical axis
newConstr->setValue(normalizeAngle(newConstr->getValue() + pi));
}
else {
// angle between two sketch entities
// do nothing
}
}
else if (xinv) { // rotation 180 degrees around vertical axis
if (newConstr->First == -1 || newConstr->Second == -1 || newConstr->Second == GeoEnum::GeoUndef) {
// angle to horizontal axis
newConstr->setValue(normalizeAngle(pi - newConstr->getValue()));
}
else {
// angle between two sketch entities or angle to vertical axis
newConstr->setValue(normalizeAngle(-newConstr->getValue()));
}
}
else if (yinv) { // rotation 180 degrees around horizontal axis
if (newConstr->First == -2 || newConstr->Second == -2) {
// angle to vertical axis
newConstr->setValue(normalizeAngle(pi - newConstr->getValue()));
}
else {
// angle between two sketch entities or angle to horizontal axis
newConstr->setValue(normalizeAngle(-newConstr->getValue()));
}
}
}
};
for (std::vector<Sketcher::Constraint*>::const_iterator it = scvals.begin(); it != scvals.end();
++it) {
Sketcher::Constraint* newConstr = (*it)->copy();
@@ -7211,6 +7295,11 @@ int SketchObject::carbonCopy(App::DocumentObject* pObj, bool construction)
if ((*it)->Third < -2 && (*it)->Third != GeoEnum::GeoUndef)
newConstr->Third -= (nextextgeoid - 2);
if (xinv || yinv) {
// corrections for flipped constraints
applyConstraintFlipCorrection(newConstr);
}
newcVals.push_back(newConstr);
}
@@ -7225,6 +7314,62 @@ int SketchObject::carbonCopy(App::DocumentObject* pObj, bool construction)
// ViewProvider::UpdateData is triggered.
Geometry.touch();
auto makeCorrectedExpressionString = [xinv, yinv]
(const Sketcher::Constraint* constr, const std::string expr)
-> std::string {
if (!xinv && !yinv) {
return expr;
}
// DistanceX, DistanceY
if ((xinv && constr->Type == Sketcher::DistanceX) ||
(yinv && constr->Type == Sketcher::DistanceY)) {
if (constr->First == constr->Second) {
return expr;
} else{
return "-(" + expr + ")";
}
}
// Angle
if (constr->Type == Sketcher::Angle) {
if (xinv && yinv) { // rotation 180 degrees around normal axis
if (constr->First ==-1 || constr->Second == -1
|| constr->First == -2 || constr->Second == -2
|| constr->Second == GeoEnum::GeoUndef) {
// angle to horizontal or vertical axis
return "(" + expr + ") + 180 deg";
}
else {
// angle between two sketch entities
// do nothing
return expr;
}
}
else if (xinv) { // rotation 180 degrees around vertical axis
if (constr->First == -1 || constr->Second == -1 || constr->Second == GeoEnum::GeoUndef) {
// angle to horizontal axis
return "180 deg - (" + expr + ")";
}
else {
// angle between two sketch entities or angle to vertical axis
return "-(" + expr + ")";
}
}
else if (yinv) { // rotation 180 degrees around horizontal axis
if (constr->First == -2 || constr->Second == -2) {
// angle to vertical axis
return "180 deg - (" + expr + ")";
}
else {
// angle between two sketch entities or angle to horizontal axis
return "-(" + expr + ")";
}
}
}
return expr;
};
int sourceid = 0;
for (std::vector<Sketcher::Constraint*>::const_iterator it = scvals.begin(); it != scvals.end();
++it, nextcid++, sourceid++) {
@@ -7235,19 +7380,22 @@ int SketchObject::carbonCopy(App::DocumentObject* pObj, bool construction)
App::ObjectIdentifier spath;
std::shared_ptr<App::Expression> expr;
std::string scname = (*it)->Name;
std::string sref;
if (App::ExpressionParser::isTokenAnIndentifier(scname)) {
spath = App::ObjectIdentifier(psObj->Constraints)
<< App::ObjectIdentifier::SimpleComponent(scname);
expr = std::shared_ptr<App::Expression>(App::Expression::parse(
this, spath.getDocumentObjectName().getString() + spath.toString()));
sref = spath.getDocumentObjectName().getString() + spath.toString();
}
else {
spath = psObj->Constraints.createPath(sourceid);
expr = std::shared_ptr<App::Expression>(
App::Expression::parse(this,
spath.getDocumentObjectName().getString()
+ std::string(1, '.') + spath.toString()));
sref = spath.getDocumentObjectName().getString()
+ std::string(1, '.') + spath.toString();
}
if (xinv || yinv) {
// corrections for flipped expressions
sref = makeCorrectedExpressionString((*it), sref);
}
expr = std::shared_ptr<App::Expression>(App::Expression::parse(this, sref));
setExpression(Constraints.createPath(nextcid), std::move(expr));
}
}