Sketcher: Symmetry tool rework.

This commit is contained in:
PaddleStroke
2024-02-16 15:28:30 +01:00
committed by Yorik van Havre
parent 3defef03c6
commit e4213fc10f
7 changed files with 676 additions and 542 deletions

View File

@@ -4227,105 +4227,298 @@ bool SketchObject::isCarbonCopyAllowed(App::Document* pDoc, App::DocumentObject*
}
int SketchObject::addSymmetric(const std::vector<int>& 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<Part::Geometry*>& geovals = getInternalGeometry();
std::vector<Part::Geometry*> newgeoVals(geovals);
const std::vector<Constraint*>& constrvals = this->Constraints.getValues();
std::vector<Constraint*> newconstrVals(constrvals);
newgeoVals.reserve(geovals.size() + geoIdList.size());
int cgeoid = getHighestCurveIndex() + 1;
std::map<int, int> geoIdMap;
std::map<int, bool> 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<Constraint*>::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<int, int> geoIdMap;
std::map<int, bool> isStartEndInverted;
std::vector<Part::Geometry*> newgeoVals(getInternalGeometry());
std::vector<Part::Geometry*> 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<const Part::GeomLineSegment*>(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<Part::GeomLineSegment>()) {
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<Part::GeomCircle>() || geo->is<Part::GeomEllipse>()) {
createEqualityConstr(geoId1, geoId2);
createSymConstr(geoId1, geoId2, PointPos::mid, PointPos::mid);
}
else if (geo->is<Part::GeomArcOfCircle>()
|| geo->is<Part::GeomArcOfEllipse>()
|| geo->is<Part::GeomArcOfHyperbola>()
|| geo->is<Part::GeomArcOfParabola>()) {
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<Part::GeomPoint>()) {
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<Part::Geometry*> SketchObject::getSymmetric(const std::vector<int>& geoIdList,
std::map<int, int>& geoIdMap,
std::map<int, bool>& isStartEndInverted,
int refGeoId,
Sketcher::PointPos refPosId)
{
std::vector<Part::Geometry*> 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<Part::GeomLineSegment>()) {
Base::Console().Error("Reference for symmetric is neither a point nor a line.\n");
return {};
}
auto* refGeoLine = static_cast<const Part::GeomLineSegment*>(georef);
// line
Base::Vector3d refstart = refGeoLine->getStartPoint();
Base::Vector3d vectline = refGeoLine->getEndPoint() - refstart;
for (std::vector<int>::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>()) {
Part::GeomLineSegment* geosymline = static_cast<Part::GeomLineSegment*>(geosym);
auto* geosymline = static_cast<Part::GeomLineSegment*>(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>()) {
Part::GeomCircle* geosymcircle = static_cast<Part::GeomCircle*>(geosym);
auto* geosymcircle = static_cast<Part::GeomCircle*>(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>()) {
Part::GeomArcOfCircle* geoaoc = static_cast<Part::GeomArcOfCircle*>(geosym);
auto* geoaoc = static_cast<Part::GeomArcOfCircle*>(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<int>& 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>()) {
Part::GeomEllipse* geosymellipse = static_cast<Part::GeomEllipse*>(geosym);
auto* geosymellipse = static_cast<Part::GeomEllipse*>(geosym);
Base::Vector3d cp = geosymellipse->getCenter();
Base::Vector3d majdir = geosymellipse->getMajorAxisDir();
@@ -4362,10 +4555,10 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomArcOfEllipse* geosymaoe = static_cast<Part::GeomArcOfEllipse*>(geosym);
auto* geosymaoe = static_cast<Part::GeomArcOfEllipse*>(geosym);
Base::Vector3d cp = geosymaoe->getCenter();
Base::Vector3d majdir = geosymaoe->getMajorAxisDir();
@@ -4394,11 +4587,10 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomArcOfHyperbola* geosymaoe =
static_cast<Part::GeomArcOfHyperbola*>(geosym);
auto* geosymaoe = static_cast<Part::GeomArcOfHyperbola*>(geosym);
Base::Vector3d cp = geosymaoe->getCenter();
Base::Vector3d majdir = geosymaoe->getMajorAxisDir();
@@ -4423,10 +4615,10 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomArcOfParabola* geosymaoe = static_cast<Part::GeomArcOfParabola*>(geosym);
auto* geosymaoe = static_cast<Part::GeomArcOfParabola*>(geosym);
Base::Vector3d cp = geosymaoe->getCenter();
// double df= geosymaoe->getFocal();
@@ -4447,45 +4639,41 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomBSplineCurve* geosymbsp = static_cast<Part::GeomBSplineCurve*>(geosym);
auto* geosymbsp = static_cast<Part::GeomBSplineCurve*>(geosym);
std::vector<Base::Vector3d> poles = geosymbsp->getPoles();
for (std::vector<Base::Vector3d>::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>()) {
Part::GeomPoint* geosympoint = static_cast<Part::GeomPoint*>(geosym);
auto* geosympoint = static_cast<Part::GeomPoint*>(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<int>& geoIdList, int refGeoId,
refpoint = Vector3d(0, 0, 0);
}
else {
switch (refPosId) {
case Sketcher::PointPos::start:
if (georef->is<Part::GeomLineSegment>()) {
const Part::GeomLineSegment* geosymline =
static_cast<const Part::GeomLineSegment*>(georef);
refpoint = geosymline->getStartPoint();
}
else if (georef->is<Part::GeomArcOfCircle>()) {
const Part::GeomArcOfCircle* geoaoc =
static_cast<const Part::GeomArcOfCircle*>(georef);
refpoint = geoaoc->getStartPoint(true);
}
else if (georef->is<Part::GeomArcOfEllipse>()) {
const Part::GeomArcOfEllipse* geosymaoe =
static_cast<const Part::GeomArcOfEllipse*>(georef);
refpoint = geosymaoe->getStartPoint(true);
}
else if (georef->is<Part::GeomArcOfHyperbola>()) {
const Part::GeomArcOfHyperbola* geosymaoe =
static_cast<const Part::GeomArcOfHyperbola*>(georef);
refpoint = geosymaoe->getStartPoint(true);
}
else if (georef->is<Part::GeomArcOfParabola>()) {
const Part::GeomArcOfParabola* geosymaoe =
static_cast<const Part::GeomArcOfParabola*>(georef);
refpoint = geosymaoe->getStartPoint(true);
}
else if (georef->is<Part::GeomBSplineCurve>()) {
const Part::GeomBSplineCurve* geosymbsp =
static_cast<const Part::GeomBSplineCurve*>(georef);
refpoint = geosymbsp->getStartPoint();
}
break;
case Sketcher::PointPos::end:
if (georef->is<Part::GeomLineSegment>()) {
const Part::GeomLineSegment* geosymline =
static_cast<const Part::GeomLineSegment*>(georef);
refpoint = geosymline->getEndPoint();
}
else if (georef->is<Part::GeomArcOfCircle>()) {
const Part::GeomArcOfCircle* geoaoc =
static_cast<const Part::GeomArcOfCircle*>(georef);
refpoint = geoaoc->getEndPoint(true);
}
else if (georef->is<Part::GeomArcOfEllipse>()) {
const Part::GeomArcOfEllipse* geosymaoe =
static_cast<const Part::GeomArcOfEllipse*>(georef);
refpoint = geosymaoe->getEndPoint(true);
}
else if (georef->is<Part::GeomArcOfHyperbola>()) {
const Part::GeomArcOfHyperbola* geosymaoe =
static_cast<const Part::GeomArcOfHyperbola*>(georef);
refpoint = geosymaoe->getEndPoint(true);
}
else if (georef->is<Part::GeomArcOfParabola>()) {
const Part::GeomArcOfParabola* geosymaoe =
static_cast<const Part::GeomArcOfParabola*>(georef);
refpoint = geosymaoe->getEndPoint(true);
}
else if (georef->is<Part::GeomBSplineCurve>()) {
const Part::GeomBSplineCurve* geosymbsp =
static_cast<const Part::GeomBSplineCurve*>(georef);
refpoint = geosymbsp->getEndPoint();
}
break;
case Sketcher::PointPos::mid:
if (georef->is<Part::GeomCircle>()) {
const Part::GeomCircle* geosymcircle =
static_cast<const Part::GeomCircle*>(georef);
refpoint = geosymcircle->getCenter();
}
else if (georef->is<Part::GeomArcOfCircle>()) {
const Part::GeomArcOfCircle* geoaoc =
static_cast<const Part::GeomArcOfCircle*>(georef);
refpoint = geoaoc->getCenter();
}
else if (georef->is<Part::GeomEllipse>()) {
const Part::GeomEllipse* geosymellipse =
static_cast<const Part::GeomEllipse*>(georef);
refpoint = geosymellipse->getCenter();
}
else if (georef->is<Part::GeomArcOfEllipse>()) {
const Part::GeomArcOfEllipse* geosymaoe =
static_cast<const Part::GeomArcOfEllipse*>(georef);
refpoint = geosymaoe->getCenter();
}
else if (georef->is<Part::GeomArcOfHyperbola>()) {
const Part::GeomArcOfHyperbola* geosymaoe =
static_cast<const Part::GeomArcOfHyperbola*>(georef);
refpoint = geosymaoe->getCenter();
}
else if (georef->is<Part::GeomArcOfParabola>()) {
const Part::GeomArcOfParabola* geosymaoe =
static_cast<const Part::GeomArcOfParabola*>(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<int>::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>()) {
Part::GeomLineSegment* geosymline = static_cast<Part::GeomLineSegment*>(geosym);
auto* geosymline = static_cast<Part::GeomLineSegment*>(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>()) {
Part::GeomCircle* geosymcircle = static_cast<Part::GeomCircle*>(geosym);
auto* geosymcircle = static_cast<Part::GeomCircle*>(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>()) {
Part::GeomArcOfCircle* geoaoc = static_cast<Part::GeomArcOfCircle*>(geosym);
auto* geoaoc = static_cast<Part::GeomArcOfCircle*>(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<int>& 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>()) {
Part::GeomEllipse* geosymellipse = static_cast<Part::GeomEllipse*>(geosym);
auto* geosymellipse = static_cast<Part::GeomEllipse*>(geosym);
Base::Vector3d cp = geosymellipse->getCenter();
Base::Vector3d majdir = geosymellipse->getMajorAxisDir();
@@ -4681,10 +4752,10 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomArcOfEllipse* geosymaoe = static_cast<Part::GeomArcOfEllipse*>(geosym);
auto* geosymaoe = static_cast<Part::GeomArcOfEllipse*>(geosym);
Base::Vector3d cp = geosymaoe->getCenter();
Base::Vector3d majdir = geosymaoe->getMajorAxisDir();
@@ -4699,11 +4770,10 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomArcOfHyperbola* geosymaoe =
static_cast<Part::GeomArcOfHyperbola*>(geosym);
auto* geosymaoe = static_cast<Part::GeomArcOfHyperbola*>(geosym);
Base::Vector3d cp = geosymaoe->getCenter();
Base::Vector3d majdir = geosymaoe->getMajorAxisDir();
@@ -4718,10 +4788,10 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomArcOfParabola* geosymaoe = static_cast<Part::GeomArcOfParabola*>(geosym);
auto* geosymaoe = static_cast<Part::GeomArcOfParabola*>(geosym);
Base::Vector3d cp = geosymaoe->getCenter();
/*double df= geosymaoe->getFocal();*/
@@ -4733,167 +4803,39 @@ int SketchObject::addSymmetric(const std::vector<int>& 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>()) {
Part::GeomBSplineCurve* geosymbsp = static_cast<Part::GeomBSplineCurve*>(geosym);
auto* geosymbsp = static_cast<Part::GeomBSplineCurve*>(geosym);
std::vector<Base::Vector3d> poles = geosymbsp->getPoles();
for (std::vector<Base::Vector3d>::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>()) {
Part::GeomPoint* geosympoint = static_cast<Part::GeomPoint*>(geosym);
auto* geosympoint = static_cast<Part::GeomPoint*>(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<Constraint*>::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<int>& geoIdList, const Base::Vector3d& displacement,

View File

@@ -368,7 +368,16 @@ public:
/// adds symmetric geometric elements with respect to the refGeoId (line or point)
int addSymmetric(const std::vector<int>& 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<Part::Geometry*>
getSymmetric(const std::vector<int>& geoIdList,
std::map<int, int>& geoIdMap,
std::map<int, bool>& 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

View File

@@ -80,6 +80,7 @@ SET(SketcherGui_SRCS
DrawSketchHandlerOffset.h
DrawSketchHandlerRotate.h
DrawSketchHandlerScale.h
DrawSketchHandlerSymmetry.h
CommandCreateGeo.cpp
CommandConstraints.h
CommandConstraints.cpp

View File

@@ -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<int> listOfGeoIds = getListOfSelectedGeoIds(true);
// Cancel any in-progress operation
Gui::Document* doc = Gui::Application::Instance->activeDocument();
SketcherGui::ReleaseHandler(doc);
// get the selection
std::vector<Gui::SelectionObject> 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<std::string>& 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<Sketcher::SketchObject*>(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<std::string>::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<Part::GeomLineSegment>())
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<Part::GeomPoint>()) {
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<int>(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()

View File

@@ -0,0 +1,291 @@
/***************************************************************************
* Copyright (c) 2022 Boyer Pierre-Louis <pierrelouis.boyer@gmail.com> *
* *
* 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 <QApplication>
#include <Gui/BitmapFactory.h>
#include <Gui/Notifications.h>
#include <Gui/Command.h>
#include <Gui/CommandT.h>
#include <Mod/Sketcher/App/GeometryFacade.h>
#include <Mod/Sketcher/App/SketchObject.h>
#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<DrawSketchHandlerSymmetry,
StateMachines::OneSeekEnd,
/*PAutoConstraintSize =*/0,
/*OnViewParametersT =*/OnViewParameters<0>,
/*WidgetParametersT =*/WidgetParameters<0>,
/*WidgetCheckboxesT =*/WidgetCheckboxes<2>,
/*WidgetComboboxesT =*/WidgetComboboxes<0>>;
using DSHSymmetryControllerBase = DSHSymmetryController::ControllerBase;
using DrawSketchHandlerSymmetryBase = DrawSketchControllableHandler<DSHSymmetryController>;
class DrawSketchHandlerSymmetry: public DrawSketchHandlerSymmetryBase
{
friend DSHSymmetryController;
friend DSHSymmetryControllerBase;
public:
explicit DrawSketchHandlerSymmetry(std::vector<int> 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<QWidget> createWidget() const override
{
return std::make_unique<SketcherToolDefaultWidget>();
}
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<int> 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<int, int> dummy1;
std::map<int, bool> dummy2;
std::vector<Part::Geometry*> symGeos =
Obj->getSymmetric(listOfGeoIds, dummy1, dummy2, refGeoId, refPosId);
for (auto* geo : symGeos) {
ShapeGeometry.emplace_back(std::move(std::unique_ptr<Part::Geometry>(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

View File

@@ -246,6 +246,7 @@
<file>icons/pointers/Sketcher_Pointer_Create_Offset.svg</file>
<file>icons/pointers/Sketcher_Pointer_Create_Rotate.svg</file>
<file>icons/pointers/Sketcher_Pointer_Create_Scale.svg</file>
<file>icons/pointers/Sketcher_Pointer_Create_Symmetry.svg</file>
<file>icons/pointers/Sketcher_Pointer_Extension.svg</file>
<file>icons/pointers/Sketcher_Pointer_External.svg</file>
<file>icons/pointers/Sketcher_Pointer_Heptagon.svg</file>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
height="64"
width="64"
id="svg12"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs16">
<marker
style="overflow:visible"
id="Arrow1Lstart"
refX="0.0"
refY="0.0"
orient="auto">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4362" />
</marker>
</defs>
<g
id="crosshair"
style="stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:miter">
<path
d="m16,3v9m0,8v9m-13-13h9m8,0h9"
id="path9" />
</g>
<path
style="fill:none;stroke:#cc0000;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 33.612274,29.732474 v 0 c 0,0 -6.759671,4.19863 -6.731131,13.462262 0.02854,9.263631 6.731131,13.58246 6.731131,13.58246"
id="path1221" />
<path
style="fill:none;stroke:#cc0000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:2, 2;stroke-dashoffset:0;stroke-opacity:1"
d="M 43.348375,32.497046 V 55.455009"
id="path1256" />
<path
style="fill:none;stroke:#cc0000;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 52.363237,56.837294 v 0 c 0,0 6.759671,-4.19863 6.731131,-13.462262 -0.02854,-9.263631 -6.731131,-13.58246 -6.731131,-13.58246"
id="path1221-9" />
<circle
cx="33.612274"
cy="29.732473"
r="4"
id="circle6"
style="fill:none;stroke:#cc0000;stroke-width:2" />
<circle
cx="33.612274"
cy="56.777195"
r="4"
id="circle6-4"
style="fill:none;stroke:#cc0000;stroke-width:2" />
<circle
cx="52.363235"
cy="29.792572"
r="4"
id="circle6-4-0"
style="fill:none;stroke:#cc0000;stroke-width:2" />
<circle
cx="52.363235"
cy="56.837296"
r="4"
id="circle6-4-0-2"
style="fill:none;stroke:#cc0000;stroke-width:2" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB