Sketcher: Symmetry tool rework.
This commit is contained in:
committed by
Yorik van Havre
parent
3defef03c6
commit
e4213fc10f
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -80,6 +80,7 @@ SET(SketcherGui_SRCS
|
||||
DrawSketchHandlerOffset.h
|
||||
DrawSketchHandlerRotate.h
|
||||
DrawSketchHandlerScale.h
|
||||
DrawSketchHandlerSymmetry.h
|
||||
CommandCreateGeo.cpp
|
||||
CommandConstraints.h
|
||||
CommandConstraints.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<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()
|
||||
|
||||
291
src/Mod/Sketcher/Gui/DrawSketchHandlerSymmetry.h
Normal file
291
src/Mod/Sketcher/Gui/DrawSketchHandlerSymmetry.h
Normal 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
|
||||
@@ -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>
|
||||
|
||||
@@ -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 |
Reference in New Issue
Block a user