From e4213fc10f4149e7debf3fffc9dfbc7a4c9c7e11 Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Fri, 16 Feb 2024 15:28:30 +0100 Subject: [PATCH] Sketcher: Symmetry tool rework. --- src/Mod/Sketcher/App/SketchObject.cpp | 660 ++++++++---------- src/Mod/Sketcher/App/SketchObject.h | 11 +- src/Mod/Sketcher/Gui/CMakeLists.txt | 1 + src/Mod/Sketcher/Gui/CommandSketcherTools.cpp | 187 +---- .../Sketcher/Gui/DrawSketchHandlerSymmetry.h | 291 ++++++++ src/Mod/Sketcher/Gui/Resources/Sketcher.qrc | 1 + .../Sketcher_Pointer_Create_Symmetry.svg | 67 ++ 7 files changed, 676 insertions(+), 542 deletions(-) create mode 100644 src/Mod/Sketcher/Gui/DrawSketchHandlerSymmetry.h create mode 100644 src/Mod/Sketcher/Gui/Resources/icons/pointers/Sketcher_Pointer_Create_Symmetry.svg diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index ecb6c9abe6..6452b8607f 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -4227,105 +4227,298 @@ bool SketchObject::isCarbonCopyAllowed(App::Document* pDoc, App::DocumentObject* } int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, - Sketcher::PointPos refPosId /*=Sketcher::PointPos::none*/) + Sketcher::PointPos refPosId /*=Sketcher::PointPos::none*/, + bool addSymmetryConstraints /*= false*/) { // no need to check input data validity as this is an sketchobject managed operation. Base::StateLocker lock(managedoperation, true); - const std::vector& geovals = getInternalGeometry(); - std::vector newgeoVals(geovals); - const std::vector& constrvals = this->Constraints.getValues(); std::vector newconstrVals(constrvals); - newgeoVals.reserve(geovals.size() + geoIdList.size()); - - int cgeoid = getHighestCurveIndex() + 1; - - std::map geoIdMap; - std::map isStartEndInverted; - // Find out if reference is aligned with V or H axis, // if so we can keep Vertical and Horizontal constraints in the mirrored geometry. + bool refIsLine = refPosId == Sketcher::PointPos::none; bool refIsAxisAligned = false; - if (refGeoId == Sketcher::GeoEnum::VAxis || refGeoId == Sketcher::GeoEnum::HAxis) { + if (refGeoId == Sketcher::GeoEnum::VAxis || refGeoId == Sketcher::GeoEnum::HAxis || !refIsLine) { refIsAxisAligned = true; } else { - for (std::vector::const_iterator it = constrvals.begin(); - it != constrvals.end(); - ++it) { - Constraint* constr = *(it); + for (auto* constr : constrvals) { if (constr->First == refGeoId - && (constr->Type == Sketcher::Vertical || constr->Type == Sketcher::Horizontal)) + && (constr->Type == Sketcher::Vertical || constr->Type == Sketcher::Horizontal)){ refIsAxisAligned = true; + } } } - // reference is a line - if (refPosId == Sketcher::PointPos::none) { - const Part::Geometry* georef = getGeometry(refGeoId); - if (georef->getTypeId() != Part::GeomLineSegment::getClassTypeId()) { - Base::Console().Error("Reference for symmetric is neither a point nor a line.\n"); - return -1; + // add the geometry + std::map geoIdMap; + std::map isStartEndInverted; + std::vector newgeoVals(getInternalGeometry()); + std::vector symmetricVals = getSymmetric(geoIdList, geoIdMap, isStartEndInverted, refGeoId, refPosId); + newgeoVals.insert(newgeoVals.end(), symmetricVals.begin(), symmetricVals.end()); + + // Block acceptGeometry in OnChanged to avoid unnecessary checks and updates + { + Base::StateLocker lock(internaltransaction, true); + Geometry.setValues(std::move(newgeoVals)); + + + for (auto* constr : constrvals) { + // we look in the map, because we might have skipped internal alignment geometry + auto fit = geoIdMap.find(constr->First); + + if (fit != geoIdMap.end()) {// if First of constraint is in geoIdList + if (addSymmetryConstraints && constr->Type != Sketcher::InternalAlignment) { + // if we are making symmetric constraints, then we don't want to copy all constraints + continue; + } + + if (constr->Second == GeoEnum::GeoUndef /*&& constr->Third == GeoEnum::GeoUndef*/) { + if (refIsAxisAligned) { + // in this case we want to keep the Vertical, Horizontal constraints + // DistanceX ,and DistanceY constraints should also be possible to keep in + // this case, but keeping them causes segfault, not sure why. + + if (constr->Type != Sketcher::DistanceX + && constr->Type != Sketcher::DistanceY) { + Constraint* constNew = constr->copy(); + constNew->First = fit->second; + newconstrVals.push_back(constNew); + } + } + else if (constr->Type != Sketcher::DistanceX + && constr->Type != Sketcher::DistanceY + && constr->Type != Sketcher::Vertical + && constr->Type != Sketcher::Horizontal) { + // this includes all non-directional single GeoId constraints, as radius, + // diameter, weight,... + + Constraint* constNew = constr->copy(); + constNew->First = fit->second; + newconstrVals.push_back(constNew); + } + } + else {// other geoids intervene in this constraint + + auto sit = geoIdMap.find(constr->Second); + + if (sit != geoIdMap.end()) {// Second is also in the list + + if (constr->Third == GeoEnum::GeoUndef) { + if (constr->Type == Sketcher::Coincident + || constr->Type == Sketcher::Perpendicular + || constr->Type == Sketcher::Parallel + || constr->Type == Sketcher::Tangent + || constr->Type == Sketcher::Distance + || constr->Type == Sketcher::Equal || constr->Type == Sketcher::Angle + || constr->Type == Sketcher::PointOnObject + || constr->Type == Sketcher::InternalAlignment) { + Constraint* constNew = constr->copy(); + + constNew->First = fit->second; + constNew->Second = sit->second; + if (isStartEndInverted[constr->First]) { + if (constr->FirstPos == Sketcher::PointPos::start) + constNew->FirstPos = Sketcher::PointPos::end; + else if (constr->FirstPos == Sketcher::PointPos::end) + constNew->FirstPos = Sketcher::PointPos::start; + } + if (isStartEndInverted[constr->Second]) { + if (constr->SecondPos == Sketcher::PointPos::start) + constNew->SecondPos = Sketcher::PointPos::end; + else if (constr->SecondPos == Sketcher::PointPos::end) + constNew->SecondPos = Sketcher::PointPos::start; + } + + if (constNew->Type == Tangent || constNew->Type == Perpendicular) + AutoLockTangencyAndPerpty(constNew, true); + + if ((constr->Type == Sketcher::Angle) + && (refPosId == Sketcher::PointPos::none)) { + constNew->setValue(-constr->getValue()); + } + + newconstrVals.push_back(constNew); + } + } + else {// three GeoIds intervene in constraint + auto tit = geoIdMap.find(constr->Third); + + if (tit != geoIdMap.end()) {// Third is also in the list + Constraint* constNew = constr->copy(); + constNew->First = fit->second; + constNew->Second = sit->second; + constNew->Third = tit->second; + if (isStartEndInverted[constr->First]) { + if (constr->FirstPos == Sketcher::PointPos::start) + constNew->FirstPos = Sketcher::PointPos::end; + else if (constr->FirstPos == Sketcher::PointPos::end) + constNew->FirstPos = Sketcher::PointPos::start; + } + if (isStartEndInverted[constr->Second]) { + if (constr->SecondPos == Sketcher::PointPos::start) + constNew->SecondPos = Sketcher::PointPos::end; + else if (constr->SecondPos == Sketcher::PointPos::end) + constNew->SecondPos = Sketcher::PointPos::start; + } + if (isStartEndInverted[constr->Third]) { + if (constr->ThirdPos == Sketcher::PointPos::start) + constNew->ThirdPos = Sketcher::PointPos::end; + else if (constr->ThirdPos == Sketcher::PointPos::end) + constNew->ThirdPos = Sketcher::PointPos::start; + } + newconstrVals.push_back(constNew); + } + } + } + } + } } - const Part::GeomLineSegment* refGeoLine = static_cast(georef); + if (addSymmetryConstraints) { + auto createSymConstr = [&] + (int first, int second, Sketcher::PointPos firstPos, Sketcher::PointPos secondPos) { + auto symConstr = new Constraint(); + symConstr->Type = Symmetric; + symConstr->First = first; + symConstr->Second = second; + symConstr->Third = refGeoId; + symConstr->FirstPos = firstPos; + symConstr->SecondPos = secondPos; + symConstr->ThirdPos = refPosId; + newconstrVals.push_back(symConstr); + }; + auto createEqualityConstr = [&] + (int first, int second) { + auto symConstr = new Constraint(); + symConstr->Type = Equal; + symConstr->First = first; + symConstr->Second = second; + newconstrVals.push_back(symConstr); + }; + + for (auto geoIdPair : geoIdMap) { + int geoId1 = geoIdPair.first; + int geoId2 = geoIdPair.second; + const Part::Geometry* geo = getGeometry(geoId1); + + if (geo->is()) { + auto gf = GeometryFacade::getFacade(geo); + if (!gf->isInternalAligned()) { + // Note internal aligned lines (ellipse, parabola, hyperbola) are causing redundant constraint. + createSymConstr(geoId1, geoId2, PointPos::start, isStartEndInverted[geoId1] ? PointPos::end : PointPos::start); + createSymConstr(geoId1, geoId2, PointPos::end, isStartEndInverted[geoId1] ? PointPos::start : PointPos::end); + } + } + else if (geo->is() || geo->is()) { + createEqualityConstr(geoId1, geoId2); + createSymConstr(geoId1, geoId2, PointPos::mid, PointPos::mid); + } + else if (geo->is() + || geo->is() + || geo->is() + || geo->is()) { + createEqualityConstr(geoId1, geoId2); + createSymConstr(geoId1, geoId2, PointPos::start, isStartEndInverted[geoId1] ? PointPos::end : PointPos::start); + createSymConstr(geoId1, geoId2, PointPos::end, isStartEndInverted[geoId1] ? PointPos::start : PointPos::end); + } + else if (geo->is()) { + auto gf = GeometryFacade::getFacade(geo); + if (!gf->isInternalAligned()) { + createSymConstr(geoId1, geoId2, PointPos::start, PointPos::start); + } + } + // Note bspline has symmetric by the internal aligned circles. + } + } + + if (newconstrVals.size() > constrvals.size()){ + Constraints.setValues(std::move(newconstrVals)); + } + } + + // we delayed update, so trigger it now. + // Update geometry indices and rebuild vertexindex now via onChanged, so that + // ViewProvider::UpdateData is triggered. + Geometry.touch(); + + return Geometry.getSize() - 1; +} + + +std::vector SketchObject::getSymmetric(const std::vector& geoIdList, + std::map& geoIdMap, + std::map& isStartEndInverted, + int refGeoId, + Sketcher::PointPos refPosId) +{ + std::vector symmetricVals; + bool refIsLine = refPosId == Sketcher::PointPos::none; + int cgeoid = getHighestCurveIndex() + 1; + + auto shouldCopyGeometry = [&](auto* geo, int geoId) -> bool { + auto gf = GeometryFacade::getFacade(geo); + if (gf->isInternalAligned()) { + // only add if the corresponding geometry it defines is also in the list. + int definedGeo = GeoEnum::GeoUndef; + for (auto c : Constraints.getValues()) { + if (c->Type == Sketcher::InternalAlignment && c->First == geoId) { + definedGeo = c->Second; + break; + } + } + // Return true if definedGeo is in geoIdList, false otherwise + return std::find(geoIdList.begin(), geoIdList.end(), definedGeo) != geoIdList.end(); + } + // Return true if not internal aligned, indicating it should always be copied + return true; + }; + + if (refIsLine) { + const Part::Geometry* georef = getGeometry(refGeoId); + if (!georef->is()) { + Base::Console().Error("Reference for symmetric is neither a point nor a line.\n"); + return {}; + } + + auto* refGeoLine = static_cast(georef); // line Base::Vector3d refstart = refGeoLine->getStartPoint(); Base::Vector3d vectline = refGeoLine->getEndPoint() - refstart; - for (std::vector::const_iterator it = geoIdList.begin(); it != geoIdList.end(); ++it) { - const Part::Geometry* geo = getGeometry(*it); + for (auto geoId : geoIdList) { + const Part::Geometry* geo = getGeometry(geoId); Part::Geometry* geosym; - auto gf = GeometryFacade::getFacade(geo); - - if (gf->isInternalAligned()) { - // only add this geometry if the corresponding geometry it defines is also in the - // list. - int definedGeo = GeoEnum::GeoUndef; - - for (auto c : Constraints.getValues()) { - if (c->Type == Sketcher::InternalAlignment && c->First == *it) { - definedGeo = c->Second; - break; - } - } - - if (std::find(geoIdList.begin(), geoIdList.end(), definedGeo) != geoIdList.end()) - geosym = geo->copy(); - else { - // we should not mirror internal alignment geometry, unless the element they - // define is also mirrored - continue; - } - } - else { - geosym = geo->copy(); + if (!shouldCopyGeometry(geo, geoId)) { + continue; } + geosym = geo->copy(); + // Handle Geometry if (geosym->is()) { - Part::GeomLineSegment* geosymline = static_cast(geosym); + auto* geosymline = static_cast(geosym); Base::Vector3d sp = geosymline->getStartPoint(); Base::Vector3d ep = geosymline->getEndPoint(); geosymline->setPoints( sp + 2.0 * (sp.Perpendicular(refGeoLine->getStartPoint(), vectline) - sp), ep + 2.0 * (ep.Perpendicular(refGeoLine->getStartPoint(), vectline) - ep)); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomCircle* geosymcircle = static_cast(geosym); + auto* geosymcircle = static_cast(geosym); Base::Vector3d cp = geosymcircle->getCenter(); geosymcircle->setCenter( cp + 2.0 * (cp.Perpendicular(refGeoLine->getStartPoint(), vectline) - cp)); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomArcOfCircle* geoaoc = static_cast(geosym); + auto* geoaoc = static_cast(geosym); Base::Vector3d sp = geoaoc->getStartPoint(true); Base::Vector3d ep = geoaoc->getEndPoint(true); Base::Vector3d cp = geoaoc->getCenter(); @@ -4342,10 +4535,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geoaoc->setCenter(scp); geoaoc->setRange(theta1, theta2, true); - isStartEndInverted.insert(std::make_pair(*it, true)); + isStartEndInverted.insert(std::make_pair(geoId, true)); } else if (geosym->is()) { - Part::GeomEllipse* geosymellipse = static_cast(geosym); + auto* geosymellipse = static_cast(geosym); Base::Vector3d cp = geosymellipse->getCenter(); Base::Vector3d majdir = geosymellipse->getMajorAxisDir(); @@ -4362,10 +4555,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geosymellipse->setMajorAxisDir(sf1 - scp); geosymellipse->setCenter(scp); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomArcOfEllipse* geosymaoe = static_cast(geosym); + auto* geosymaoe = static_cast(geosym); Base::Vector3d cp = geosymaoe->getCenter(); Base::Vector3d majdir = geosymaoe->getMajorAxisDir(); @@ -4394,11 +4587,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, } geosymaoe->setRange(theta1, theta2, true); - isStartEndInverted.insert(std::make_pair(*it, true)); + isStartEndInverted.insert(std::make_pair(geoId, true)); } else if (geosym->is()) { - Part::GeomArcOfHyperbola* geosymaoe = - static_cast(geosym); + auto* geosymaoe = static_cast(geosym); Base::Vector3d cp = geosymaoe->getCenter(); Base::Vector3d majdir = geosymaoe->getMajorAxisDir(); @@ -4423,10 +4615,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, std::swap(theta1, theta2); geosymaoe->setRange(theta1, theta2, true); - isStartEndInverted.insert(std::make_pair(*it, true)); + isStartEndInverted.insert(std::make_pair(geoId, true)); } else if (geosym->is()) { - Part::GeomArcOfParabola* geosymaoe = static_cast(geosym); + auto* geosymaoe = static_cast(geosym); Base::Vector3d cp = geosymaoe->getCenter(); // double df= geosymaoe->getFocal(); @@ -4447,45 +4639,41 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, std::swap(theta1, theta2); geosymaoe->setRange(theta1, theta2, true); - isStartEndInverted.insert(std::make_pair(*it, true)); + isStartEndInverted.insert(std::make_pair(geoId, true)); } else if (geosym->is()) { - Part::GeomBSplineCurve* geosymbsp = static_cast(geosym); + auto* geosymbsp = static_cast(geosym); std::vector poles = geosymbsp->getPoles(); - for (std::vector::iterator jt = poles.begin(); jt != poles.end(); - ++jt) { - - (*jt) = (*jt) - + 2.0 - * ((*jt).Perpendicular(refGeoLine->getStartPoint(), vectline) - (*jt)); + for (auto& pole : poles) { + pole = pole + + 2.0 * (pole.Perpendicular(refGeoLine->getStartPoint(), vectline) - pole); } geosymbsp->setPoles(poles); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomPoint* geosympoint = static_cast(geosym); + auto* geosympoint = static_cast(geosym); Base::Vector3d cp = geosympoint->getPoint(); geosympoint->setPoint( cp + 2.0 * (cp.Perpendicular(refGeoLine->getStartPoint(), vectline) - cp)); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else { Base::Console().Error("Unsupported Geometry!! Just copying it.\n"); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } - newgeoVals.push_back(geosym); - geoIdMap.insert(std::make_pair(*it, cgeoid)); + symmetricVals.push_back(geosym); + geoIdMap.insert(std::make_pair(geoId, cgeoid)); cgeoid++; } } else {// reference is a point - refIsAxisAligned = true; Vector3d refpoint; const Part::Geometry* georef = getGeometry(refGeoId); @@ -4496,160 +4684,43 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, refpoint = Vector3d(0, 0, 0); } else { - switch (refPosId) { - case Sketcher::PointPos::start: - if (georef->is()) { - const Part::GeomLineSegment* geosymline = - static_cast(georef); - refpoint = geosymline->getStartPoint(); - } - else if (georef->is()) { - const Part::GeomArcOfCircle* geoaoc = - static_cast(georef); - refpoint = geoaoc->getStartPoint(true); - } - else if (georef->is()) { - const Part::GeomArcOfEllipse* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getStartPoint(true); - } - else if (georef->is()) { - const Part::GeomArcOfHyperbola* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getStartPoint(true); - } - else if (georef->is()) { - const Part::GeomArcOfParabola* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getStartPoint(true); - } - else if (georef->is()) { - const Part::GeomBSplineCurve* geosymbsp = - static_cast(georef); - refpoint = geosymbsp->getStartPoint(); - } - break; - case Sketcher::PointPos::end: - if (georef->is()) { - const Part::GeomLineSegment* geosymline = - static_cast(georef); - refpoint = geosymline->getEndPoint(); - } - else if (georef->is()) { - const Part::GeomArcOfCircle* geoaoc = - static_cast(georef); - refpoint = geoaoc->getEndPoint(true); - } - else if (georef->is()) { - const Part::GeomArcOfEllipse* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getEndPoint(true); - } - else if (georef->is()) { - const Part::GeomArcOfHyperbola* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getEndPoint(true); - } - else if (georef->is()) { - const Part::GeomArcOfParabola* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getEndPoint(true); - } - else if (georef->is()) { - const Part::GeomBSplineCurve* geosymbsp = - static_cast(georef); - refpoint = geosymbsp->getEndPoint(); - } - break; - case Sketcher::PointPos::mid: - if (georef->is()) { - const Part::GeomCircle* geosymcircle = - static_cast(georef); - refpoint = geosymcircle->getCenter(); - } - else if (georef->is()) { - const Part::GeomArcOfCircle* geoaoc = - static_cast(georef); - refpoint = geoaoc->getCenter(); - } - else if (georef->is()) { - const Part::GeomEllipse* geosymellipse = - static_cast(georef); - refpoint = geosymellipse->getCenter(); - } - else if (georef->is()) { - const Part::GeomArcOfEllipse* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getCenter(); - } - else if (georef->is()) { - const Part::GeomArcOfHyperbola* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getCenter(); - } - else if (georef->is()) { - const Part::GeomArcOfParabola* geosymaoe = - static_cast(georef); - refpoint = geosymaoe->getCenter(); - } - break; - default: - Base::Console().Error("Wrong PointPosId.\n"); - return -1; + if (refPosId == Sketcher::PointPos::none) { + Base::Console().Error("Wrong PointPosId.\n"); + return {}; } + refpoint = getPoint(georef, refPosId); } - for (std::vector::const_iterator it = geoIdList.begin(); it != geoIdList.end(); ++it) { - const Part::Geometry* geo = getGeometry(*it); - + for (auto geoId : geoIdList) { + const Part::Geometry* geo = getGeometry(geoId); Part::Geometry* geosym; - auto gf = GeometryFacade::getFacade(geo); - - if (gf->isInternalAligned()) { - // only add this geometry if the corresponding geometry it defines is also in the - // list. - int definedGeo = GeoEnum::GeoUndef; - - for (auto c : Constraints.getValues()) { - if (c->Type == Sketcher::InternalAlignment && c->First == *it) { - definedGeo = c->Second; - break; - } - } - - if (std::find(geoIdList.begin(), geoIdList.end(), definedGeo) != geoIdList.end()) - geosym = geo->copy(); - else { - // we should not mirror internal alignment geometry, unless the element they - // define is also mirrored - continue; - } - } - else { - geosym = geo->copy(); + if (!shouldCopyGeometry(geo, geoId)) { + continue; } + geosym = geo->copy(); + // Handle Geometry if (geosym->is()) { - Part::GeomLineSegment* geosymline = static_cast(geosym); + auto* geosymline = static_cast(geosym); Base::Vector3d sp = geosymline->getStartPoint(); Base::Vector3d ep = geosymline->getEndPoint(); Base::Vector3d ssp = sp + 2.0 * (refpoint - sp); Base::Vector3d sep = ep + 2.0 * (refpoint - ep); geosymline->setPoints(ssp, sep); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomCircle* geosymcircle = static_cast(geosym); + auto* geosymcircle = static_cast(geosym); Base::Vector3d cp = geosymcircle->getCenter(); geosymcircle->setCenter(cp + 2.0 * (refpoint - cp)); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomArcOfCircle* geoaoc = static_cast(geosym); + auto* geoaoc = static_cast(geosym); Base::Vector3d sp = geoaoc->getStartPoint(true); Base::Vector3d ep = geoaoc->getEndPoint(true); Base::Vector3d cp = geoaoc->getCenter(); @@ -4663,10 +4734,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geoaoc->setCenter(scp); geoaoc->setRange(theta1, theta2, true); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomEllipse* geosymellipse = static_cast(geosym); + auto* geosymellipse = static_cast(geosym); Base::Vector3d cp = geosymellipse->getCenter(); Base::Vector3d majdir = geosymellipse->getMajorAxisDir(); @@ -4681,10 +4752,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geosymellipse->setMajorAxisDir(sf1 - scp); geosymellipse->setCenter(scp); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomArcOfEllipse* geosymaoe = static_cast(geosym); + auto* geosymaoe = static_cast(geosym); Base::Vector3d cp = geosymaoe->getCenter(); Base::Vector3d majdir = geosymaoe->getMajorAxisDir(); @@ -4699,11 +4770,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geosymaoe->setMajorAxisDir(sf1 - scp); geosymaoe->setCenter(scp); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomArcOfHyperbola* geosymaoe = - static_cast(geosym); + auto* geosymaoe = static_cast(geosym); Base::Vector3d cp = geosymaoe->getCenter(); Base::Vector3d majdir = geosymaoe->getMajorAxisDir(); @@ -4718,10 +4788,10 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geosymaoe->setMajorAxisDir(sf1 - scp); geosymaoe->setCenter(scp); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomArcOfParabola* geosymaoe = static_cast(geosym); + auto* geosymaoe = static_cast(geosym); Base::Vector3d cp = geosymaoe->getCenter(); /*double df= geosymaoe->getFocal();*/ @@ -4733,167 +4803,39 @@ int SketchObject::addSymmetric(const std::vector& geoIdList, int refGeoId, geosymaoe->setXAxisDir(sf1 - scp); geosymaoe->setCenter(scp); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomBSplineCurve* geosymbsp = static_cast(geosym); + auto* geosymbsp = static_cast(geosym); std::vector poles = geosymbsp->getPoles(); - for (std::vector::iterator it = poles.begin(); it != poles.end(); - ++it) { - (*it) = (*it) + 2.0 * (refpoint - (*it)); + for (auto& pole : poles) { + pole = pole + 2.0 * (refpoint - pole); } geosymbsp->setPoles(poles); - // isStartEndInverted.insert(std::make_pair(*it, false)); + // isStartEndInverted.insert(std::make_pair(geoId, false)); } else if (geosym->is()) { - Part::GeomPoint* geosympoint = static_cast(geosym); + auto* geosympoint = static_cast(geosym); Base::Vector3d cp = geosympoint->getPoint(); geosympoint->setPoint(cp + 2.0 * (refpoint - cp)); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } else { Base::Console().Error("Unsupported Geometry!! Just copying it.\n"); - isStartEndInverted.insert(std::make_pair(*it, false)); + isStartEndInverted.insert(std::make_pair(geoId, false)); } - newgeoVals.push_back(geosym); - geoIdMap.insert(std::make_pair(*it, cgeoid)); + symmetricVals.push_back(geosym); + geoIdMap.insert(std::make_pair(geoId, cgeoid)); cgeoid++; } } - - // add the geometry - - // Block acceptGeometry in OnChanged to avoid unnecessary checks and updates - { - Base::StateLocker lock(internaltransaction, true); - Geometry.setValues(std::move(newgeoVals)); - - for (std::vector::const_iterator it = constrvals.begin(); - it != constrvals.end(); - ++it) { - // we look in the map, because we might have skipped internal alignment geometry - auto fit = geoIdMap.find((*it)->First); - - if (fit != geoIdMap.end()) {// if First of constraint is in geoIdList - - if ((*it)->Second == GeoEnum::GeoUndef /*&& (*it)->Third == GeoEnum::GeoUndef*/) { - if (refIsAxisAligned) { - // in this case we want to keep the Vertical, Horizontal constraints - // DistanceX ,and DistanceY constraints should also be possible to keep in - // this case, but keeping them causes segfault, not sure why. - - if ((*it)->Type != Sketcher::DistanceX - && (*it)->Type != Sketcher::DistanceY) { - Constraint* constNew = (*it)->copy(); - constNew->First = fit->second; - newconstrVals.push_back(constNew); - } - } - else if ((*it)->Type != Sketcher::DistanceX - && (*it)->Type != Sketcher::DistanceY - && (*it)->Type != Sketcher::Vertical - && (*it)->Type != Sketcher::Horizontal) { - // this includes all non-directional single GeoId constraints, as radius, - // diameter, weight,... - - Constraint* constNew = (*it)->copy(); - constNew->First = fit->second; - newconstrVals.push_back(constNew); - } - } - else {// other geoids intervene in this constraint - - auto sit = geoIdMap.find((*it)->Second); - - if (sit != geoIdMap.end()) {// Second is also in the list - - if ((*it)->Third == GeoEnum::GeoUndef) { - if ((*it)->Type == Sketcher::Coincident - || (*it)->Type == Sketcher::Perpendicular - || (*it)->Type == Sketcher::Parallel - || (*it)->Type == Sketcher::Tangent - || (*it)->Type == Sketcher::Distance - || (*it)->Type == Sketcher::Equal || (*it)->Type == Sketcher::Angle - || (*it)->Type == Sketcher::PointOnObject - || (*it)->Type == Sketcher::InternalAlignment) { - Constraint* constNew = (*it)->copy(); - - constNew->First = fit->second; - constNew->Second = sit->second; - if (isStartEndInverted[(*it)->First]) { - if ((*it)->FirstPos == Sketcher::PointPos::start) - constNew->FirstPos = Sketcher::PointPos::end; - else if ((*it)->FirstPos == Sketcher::PointPos::end) - constNew->FirstPos = Sketcher::PointPos::start; - } - if (isStartEndInverted[(*it)->Second]) { - if ((*it)->SecondPos == Sketcher::PointPos::start) - constNew->SecondPos = Sketcher::PointPos::end; - else if ((*it)->SecondPos == Sketcher::PointPos::end) - constNew->SecondPos = Sketcher::PointPos::start; - } - - if (constNew->Type == Tangent || constNew->Type == Perpendicular) - AutoLockTangencyAndPerpty(constNew, true); - - if (((*it)->Type == Sketcher::Angle) - && (refPosId == Sketcher::PointPos::none)) { - constNew->setValue(-(*it)->getValue()); - } - - newconstrVals.push_back(constNew); - } - } - else {// three GeoIds intervene in constraint - auto tit = geoIdMap.find((*it)->Third); - - if (tit != geoIdMap.end()) {// Third is also in the list - Constraint* constNew = (*it)->copy(); - constNew->First = fit->second; - constNew->Second = sit->second; - constNew->Third = tit->second; - if (isStartEndInverted[(*it)->First]) { - if ((*it)->FirstPos == Sketcher::PointPos::start) - constNew->FirstPos = Sketcher::PointPos::end; - else if ((*it)->FirstPos == Sketcher::PointPos::end) - constNew->FirstPos = Sketcher::PointPos::start; - } - if (isStartEndInverted[(*it)->Second]) { - if ((*it)->SecondPos == Sketcher::PointPos::start) - constNew->SecondPos = Sketcher::PointPos::end; - else if ((*it)->SecondPos == Sketcher::PointPos::end) - constNew->SecondPos = Sketcher::PointPos::start; - } - if (isStartEndInverted[(*it)->Third]) { - if ((*it)->ThirdPos == Sketcher::PointPos::start) - constNew->ThirdPos = Sketcher::PointPos::end; - else if ((*it)->ThirdPos == Sketcher::PointPos::end) - constNew->ThirdPos = Sketcher::PointPos::start; - } - newconstrVals.push_back(constNew); - } - } - } - } - } - } - - if (newconstrVals.size() > constrvals.size()) - Constraints.setValues(std::move(newconstrVals)); - } - - // we delayed update, so trigger it now. - // Update geometry indices and rebuild vertexindex now via onChanged, so that - // ViewProvider::UpdateData is triggered. - Geometry.touch(); - - return Geometry.getSize() - 1; + return symmetricVals; } int SketchObject::addCopy(const std::vector& geoIdList, const Base::Vector3d& displacement, diff --git a/src/Mod/Sketcher/App/SketchObject.h b/src/Mod/Sketcher/App/SketchObject.h index 5d748bdadb..1d64977edc 100644 --- a/src/Mod/Sketcher/App/SketchObject.h +++ b/src/Mod/Sketcher/App/SketchObject.h @@ -368,7 +368,16 @@ public: /// adds symmetric geometric elements with respect to the refGeoId (line or point) int addSymmetric(const std::vector& geoIdList, int refGeoId, - Sketcher::PointPos refPosId = Sketcher::PointPos::none); + Sketcher::PointPos refPosId = Sketcher::PointPos::none, + bool addSymmetryConstraints = false); + // get the symmetric geometries of the geoIdList + std::vector + getSymmetric(const std::vector& geoIdList, + std::map& geoIdMap, + std::map& isStartEndInverted, + int refGeoId, + Sketcher::PointPos refPosId = Sketcher::PointPos::none); + /// with default parameters adds a copy of the geometric elements displaced by the displacement /// vector. It creates an array of csize elements in the direction of the displacement vector by /// rsize elements in the direction perpendicular to the displacement vector, wherein the diff --git a/src/Mod/Sketcher/Gui/CMakeLists.txt b/src/Mod/Sketcher/Gui/CMakeLists.txt index 1b1c42cd93..1c500f3b9a 100644 --- a/src/Mod/Sketcher/Gui/CMakeLists.txt +++ b/src/Mod/Sketcher/Gui/CMakeLists.txt @@ -80,6 +80,7 @@ SET(SketcherGui_SRCS DrawSketchHandlerOffset.h DrawSketchHandlerRotate.h DrawSketchHandlerScale.h + DrawSketchHandlerSymmetry.h CommandCreateGeo.cpp CommandConstraints.h CommandConstraints.cpp diff --git a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp index b3d56aa851..93de8d79d8 100644 --- a/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp +++ b/src/Mod/Sketcher/Gui/CommandSketcherTools.cpp @@ -58,6 +58,7 @@ #include "DrawSketchHandlerOffset.h" #include "DrawSketchHandlerRotate.h" #include "DrawSketchHandlerScale.h" +#include "DrawSketchHandlerSymmetry.h" // Hint: this is to prevent to re-format big parts of the file. Remove it later again. // clang-format off @@ -1092,7 +1093,7 @@ CmdSketcherSymmetry::CmdSketcherSymmetry() sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Symmetry"); sToolTipText = - QT_TR_NOOP("Creates symmetric geometry with respect to the last selected line or point"); + QT_TR_NOOP("Creates symmetric of selected geometry. After starting the tool select the reference line or point."); sWhatsThis = "Sketcher_Symmetry"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Symmetry"; @@ -1103,190 +1104,12 @@ CmdSketcherSymmetry::CmdSketcherSymmetry() void CmdSketcherSymmetry::activated(int iMsg) { Q_UNUSED(iMsg); + std::vector listOfGeoIds = getListOfSelectedGeoIds(true); - // Cancel any in-progress operation - Gui::Document* doc = Gui::Application::Instance->activeDocument(); - SketcherGui::ReleaseHandler(doc); - - // get the selection - std::vector selection; - selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId()); - - // only one sketch with its subelements are allowed to be selected - if (selection.size() != 1) { - Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(), - QObject::tr("Wrong selection"), - QObject::tr("Select elements from a single sketch.")); - return; + if (!listOfGeoIds.empty()) { + ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerSymmetry(listOfGeoIds)); } - - // get the needed lists and objects - const std::vector& SubNames = selection[0].getSubNames(); - if (SubNames.empty()) { - Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(), - QObject::tr("Wrong selection"), - QObject::tr("Select elements from a single sketch.")); - - return; - } - - Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); getSelection().clearSelection(); - - int LastGeoId = 0; - Sketcher::PointPos LastPointPos = Sketcher::PointPos::none; - const Part::Geometry* LastGeo; - using GeoType = enum { invalid = -1, line = 0, point = 1 }; - - GeoType lastgeotype = invalid; - - // create python command with list of elements - std::stringstream stream; - int geoids = 0; - - for (std::vector::const_iterator it = SubNames.begin(); it != SubNames.end(); - ++it) { - // only handle non-external edges - if ((it->size() > 4 && it->substr(0, 4) == "Edge") - || (it->size() > 12 && it->substr(0, 12) == "ExternalEdge")) { - - if (it->substr(0, 4) == "Edge") { - LastGeoId = std::atoi(it->substr(4, 4000).c_str()) - 1; - LastPointPos = Sketcher::PointPos::none; - } - else { - LastGeoId = -std::atoi(it->substr(12, 4000).c_str()) - 2; - LastPointPos = Sketcher::PointPos::none; - } - - // reference can be external or non-external - LastGeo = Obj->getGeometry(LastGeoId); - // Only for supported types - if (LastGeo->is()) - lastgeotype = line; - else - lastgeotype = invalid; - - // lines to make symmetric (only non-external) - if (LastGeoId >= 0) { - geoids++; - stream << LastGeoId << ","; - } - } - else if (it->size() > 6 && it->substr(0, 6) == "Vertex") { - // only if it is a GeomPoint - int VtId = std::atoi(it->substr(6, 4000).c_str()) - 1; - int GeoId; - Sketcher::PointPos PosId; - Obj->getGeoVertexIndex(VtId, GeoId, PosId); - - if (Obj->getGeometry(GeoId)->is()) { - LastGeoId = GeoId; - LastPointPos = Sketcher::PointPos::start; - lastgeotype = point; - - // points to make symmetric - if (LastGeoId >= 0) { - geoids++; - stream << LastGeoId << ","; - } - } - } - } - - bool lastvertexoraxis = false; - // check if last selected element is a Vertex, not being a GeomPoint - if (SubNames.rbegin()->size() > 6 && SubNames.rbegin()->substr(0, 6) == "Vertex") { - int VtId = std::atoi(SubNames.rbegin()->substr(6, 4000).c_str()) - 1; - int GeoId; - Sketcher::PointPos PosId; - Obj->getGeoVertexIndex(VtId, GeoId, PosId); - if (Obj->getGeometry(GeoId)->getTypeId() != Part::GeomPoint::getClassTypeId()) { - LastGeoId = GeoId; - LastPointPos = PosId; - lastgeotype = point; - lastvertexoraxis = true; - } - } - // check if last selected element is horizontal axis - else if (SubNames.rbegin()->size() == 6 && SubNames.rbegin()->substr(0, 6) == "H_Axis") { - LastGeoId = Sketcher::GeoEnum::HAxis; - LastPointPos = Sketcher::PointPos::none; - lastgeotype = line; - lastvertexoraxis = true; - } - // check if last selected element is vertical axis - else if (SubNames.rbegin()->size() == 6 && SubNames.rbegin()->substr(0, 6) == "V_Axis") { - LastGeoId = Sketcher::GeoEnum::VAxis; - LastPointPos = Sketcher::PointPos::none; - lastgeotype = line; - lastvertexoraxis = true; - } - // check if last selected element is the root point - else if (SubNames.rbegin()->size() == 9 && SubNames.rbegin()->substr(0, 9) == "RootPoint") { - LastGeoId = Sketcher::GeoEnum::RtPnt; - LastPointPos = Sketcher::PointPos::start; - lastgeotype = point; - lastvertexoraxis = true; - } - - if (geoids == 0 || (geoids == 1 && LastGeoId >= 0 && !lastvertexoraxis)) { - Gui::TranslatedUserWarning(Obj, - QObject::tr("Wrong selection"), - QObject::tr("A symmetric construction requires " - "at least two geometric elements, " - "the last geometric element being the reference " - "for the symmetry construction.")); - return; - } - - if (lastgeotype == invalid) { - Gui::TranslatedUserWarning(Obj, - QObject::tr("Wrong selection"), - QObject::tr("The last element must be a point " - "or a line serving as reference " - "for the symmetry construction.")); - - return; - } - - std::string geoIdList = stream.str(); - - // missing cases: - // 1- Last element is an edge, and is V or H axis - // 2- Last element is a point GeomPoint - // 3- Last element is a point (Vertex) - - if (LastGeoId >= 0 && !lastvertexoraxis) { - // if LastGeoId was added remove the last element - int index = geoIdList.rfind(','); - index = geoIdList.rfind(',', index - 1); - geoIdList.resize(index); - } - else { - int index = geoIdList.rfind(','); - geoIdList.resize(index); - } - - geoIdList.insert(0, 1, '['); - geoIdList.append(1, ']'); - - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create symmetric geometry")); - - try { - Gui::cmdAppObjectArgs(Obj, - "addSymmetric(%s, %d, %d)", - geoIdList.c_str(), - LastGeoId, - static_cast(LastPointPos)); - Gui::Command::commitCommand(); - } - catch (const Base::Exception& e) { - Gui::NotifyUserError( - Obj, QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"), e.what()); - Gui::Command::abortCommand(); - } - tryAutoRecomputeIfNotSolve(Obj); } bool CmdSketcherSymmetry::isActive() diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerSymmetry.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerSymmetry.h new file mode 100644 index 0000000000..b151a77056 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerSymmetry.h @@ -0,0 +1,291 @@ +/*************************************************************************** + * Copyright (c) 2022 Boyer Pierre-Louis * + * * + * 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 * + * * + ***************************************************************************/ + + +#ifndef SKETCHERGUI_DrawSketchHandlerSymmetry_H +#define SKETCHERGUI_DrawSketchHandlerSymmetry_H + +#include + +#include +#include +#include +#include + +#include +#include + +#include "DrawSketchDefaultWidgetController.h" +#include "DrawSketchControllableHandler.h" + +#include "GeometryCreationMode.h" +#include "Utils.h" + +using namespace Sketcher; + +namespace SketcherGui +{ + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerSymmetry; + +using DSHSymmetryController = + DrawSketchDefaultWidgetController, + /*WidgetParametersT =*/WidgetParameters<0>, + /*WidgetCheckboxesT =*/WidgetCheckboxes<2>, + /*WidgetComboboxesT =*/WidgetComboboxes<0>>; + +using DSHSymmetryControllerBase = DSHSymmetryController::ControllerBase; + +using DrawSketchHandlerSymmetryBase = DrawSketchControllableHandler; + +class DrawSketchHandlerSymmetry: public DrawSketchHandlerSymmetryBase +{ + friend DSHSymmetryController; + friend DSHSymmetryControllerBase; + +public: + explicit DrawSketchHandlerSymmetry(std::vector listOfGeoIds) + : listOfGeoIds(listOfGeoIds) + , refGeoId(Sketcher::GeoEnum::GeoUndef) + , refPosId(Sketcher::PointPos::none) + , deleteOriginal(false) + , createSymConstraints(false) + {} + + DrawSketchHandlerSymmetry(const DrawSketchHandlerSymmetry&) = delete; + DrawSketchHandlerSymmetry(DrawSketchHandlerSymmetry&&) = delete; + DrawSketchHandlerSymmetry& operator=(const DrawSketchHandlerSymmetry&) = delete; + DrawSketchHandlerSymmetry& operator=(DrawSketchHandlerSymmetry&&) = delete; + + ~DrawSketchHandlerSymmetry() override = default; + +private: + void updateDataAndDrawToPosition(Base::Vector2d onSketchPos) override + { + switch (state()) { + case SelectMode::SeekFirst: { + int VtId = getPreselectPoint(); + int CrvId = getPreselectCurve(); + int CrsId = getPreselectCross(); + + if (VtId >= 0) { // Vertex + SketchObject* Obj = sketchgui->getSketchObject(); + Obj->getGeoVertexIndex(VtId, refGeoId, refPosId); + } + else if (CrsId == 0) { // RootPoint + refGeoId = Sketcher::GeoEnum::RtPnt; + refPosId = Sketcher::PointPos::start; + } + else if (CrsId == 1) { // H_Axis + refGeoId = Sketcher::GeoEnum::HAxis; + refPosId = Sketcher::PointPos::none; + } + else if (CrsId == 2) { // V_Axis + refGeoId = Sketcher::GeoEnum::VAxis; + refPosId = Sketcher::PointPos::none; + } + else if (CrvId >= 0 || CrvId <= Sketcher::GeoEnum::RefExt) { // Curves + refGeoId = CrvId; + refPosId = Sketcher::PointPos::none; + } + else { + refGeoId = Sketcher::GeoEnum::GeoUndef; + refPosId = Sketcher::PointPos::none; + } + + + CreateAndDrawShapeGeometry(); + } break; + default: + break; + } + } + + void executeCommands() override + { + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Symmetry geometries")); + + SketchObject* Obj = sketchgui->getSketchObject(); + createSymConstraints = !deleteOriginal && createSymConstraints; + Obj->addSymmetric(listOfGeoIds, refGeoId, refPosId, createSymConstraints); + + if (deleteOriginal) { + deleteOriginalGeos(); + } + tryAutoRecomputeIfNotSolve(Obj); + + Gui::Command::commitCommand(); + } + catch (const Base::Exception& e) { + e.ReportException(); + Gui::NotifyError(sketchgui, + QT_TRANSLATE_NOOP("Notifications", "Error"), + QT_TRANSLATE_NOOP("Notifications", "Failed to create symmetry")); + + Gui::Command::abortCommand(); + THROWM(Base::RuntimeError, + QT_TRANSLATE_NOOP( + "Notifications", + "Tool execution aborted") "\n") // This prevents constraints from being + // applied on non existing geometry + } + } + + void createAutoConstraints() override + { + // none + } + + std::string getToolName() const override + { + return "DSH_Symmetry"; + } + + QString getCrosshairCursorSVGName() const override + { + return QString::fromLatin1("Sketcher_Pointer_Create_Symmetry"); + } + + std::unique_ptr createWidget() const override + { + return std::make_unique(); + } + + bool isWidgetVisible() const override + { + return true; + }; + + QPixmap getToolIcon() const override + { + return Gui::BitmapFactory().pixmap("Sketcher_Symmetry"); + } + + QString getToolWidgetText() const override + { + return QString(QObject::tr("Symmetry parameters")); + } + + void activated() override + { + DrawSketchDefaultHandler::activated(); + continuousMode = false; + } + + bool canGoToNextMode() override + { + if (state() == SelectMode::SeekFirst && refGeoId == Sketcher::GeoEnum::GeoUndef) { + // Prevent validation if no reference selected. + return false; + } + return true; + } + +private: + std::vector listOfGeoIds; + int refGeoId; + Sketcher::PointPos refPosId; + bool deleteOriginal, createSymConstraints; + + void deleteOriginalGeos() + { + std::stringstream stream; + for (size_t j = 0; j < listOfGeoIds.size() - 1; j++) { + stream << listOfGeoIds[j] << ","; + } + stream << listOfGeoIds[listOfGeoIds.size() - 1]; + try { + Gui::cmdAppObjectArgs(sketchgui->getObject(), + "delGeometries([%s])", + stream.str().c_str()); + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + } + } + + void createShape(bool onlyeditoutline) override + { + SketchObject* Obj = sketchgui->getSketchObject(); + + ShapeGeometry.clear(); + + if (refGeoId == Sketcher::GeoEnum::GeoUndef) { + return; + } + + if (onlyeditoutline) { + std::map dummy1; + std::map dummy2; + std::vector symGeos = + Obj->getSymmetric(listOfGeoIds, dummy1, dummy2, refGeoId, refPosId); + + for (auto* geo : symGeos) { + ShapeGeometry.emplace_back(std::move(std::unique_ptr(geo))); + } + } + } +}; + +template<> +void DSHSymmetryController::configureToolWidget() +{ + if (!init) { // Code to be executed only upon initialisation + toolWidget->setCheckboxLabel(WCheckbox::FirstBox, + QApplication::translate("TaskSketcherTool_c1_symmetry", + "Delete original geometries (U)")); + toolWidget->setCheckboxLabel(WCheckbox::SecondBox, + QApplication::translate("TaskSketcherTool_c2_symmetry", + "Create Symmetry Constraints (J)")); + } +} + +template<> +void DSHSymmetryController::adaptDrawingToCheckboxChange(int checkboxindex, bool value) +{ + switch (checkboxindex) { + case WCheckbox::FirstBox: { + handler->deleteOriginal = value; + if (value && toolWidget->getCheckboxChecked(WCheckbox::SecondBox)) { + toolWidget->setCheckboxChecked(WCheckbox::SecondBox, false); + } + } break; + case WCheckbox::SecondBox: { + handler->createSymConstraints = value; + if (value && toolWidget->getCheckboxChecked(WCheckbox::FirstBox)) { + toolWidget->setCheckboxChecked(WCheckbox::FirstBox, false); + } + } break; + } +} + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerSymmetry_H diff --git a/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc b/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc index 6572d92280..621488dec2 100644 --- a/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc +++ b/src/Mod/Sketcher/Gui/Resources/Sketcher.qrc @@ -246,6 +246,7 @@ icons/pointers/Sketcher_Pointer_Create_Offset.svg icons/pointers/Sketcher_Pointer_Create_Rotate.svg icons/pointers/Sketcher_Pointer_Create_Scale.svg + icons/pointers/Sketcher_Pointer_Create_Symmetry.svg icons/pointers/Sketcher_Pointer_Extension.svg icons/pointers/Sketcher_Pointer_External.svg icons/pointers/Sketcher_Pointer_Heptagon.svg diff --git a/src/Mod/Sketcher/Gui/Resources/icons/pointers/Sketcher_Pointer_Create_Symmetry.svg b/src/Mod/Sketcher/Gui/Resources/icons/pointers/Sketcher_Pointer_Create_Symmetry.svg new file mode 100644 index 0000000000..6b8026d8d4 --- /dev/null +++ b/src/Mod/Sketcher/Gui/Resources/icons/pointers/Sketcher_Pointer_Create_Symmetry.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + +