/*************************************************************************** * Copyright (c) 2010 Jürgen Riegel * * * * 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 * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ # include # include # include # include # include # include #endif // #ifndef _PreComp_ /// Here the FreeCAD includes sorted by Base,App,Gui...... #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DrawSketchHandler.h" #include "ViewProviderSketch.h" #include "CommandConstraints.h" using namespace SketcherGui; using namespace Sketcher; //************************************************************************** // Construction/Destruction DrawSketchHandler::DrawSketchHandler() : sketchgui(0) { } DrawSketchHandler::~DrawSketchHandler() { } void DrawSketchHandler::quit(void) { assert(sketchgui); sketchgui->drawEdit(std::vector()); resetPositionText(); unsetCursor(); sketchgui->purgeHandler(); } //************************************************************************** // Helpers int DrawSketchHandler::getHighestVertexIndex(void) { return sketchgui->getSketchObject()->getHighestVertexIndex(); } int DrawSketchHandler::getHighestCurveIndex(void) { return sketchgui->getSketchObject()->getHighestCurveIndex(); } void DrawSketchHandler::setCursor(const QPixmap &p,int x,int y) { Gui::MDIView* view = Gui::getMainWindow()->activeWindow(); if (view && view->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) { Gui::View3DInventorViewer* viewer = static_cast(view)->getViewer(); oldCursor = viewer->getWidget()->cursor(); QCursor cursor(p, x, y); actCursor = cursor; viewer->getWidget()->setCursor(cursor); } } void DrawSketchHandler::applyCursor(void) { applyCursor(actCursor); } void DrawSketchHandler::applyCursor(QCursor &newCursor) { Gui::MDIView* view = Gui::getMainWindow()->activeWindow(); if (view && view->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) { Gui::View3DInventorViewer* viewer = static_cast(view)->getViewer(); viewer->getWidget()->setCursor(newCursor); } } void DrawSketchHandler::unsetCursor(void) { Gui::MDIView* view = Gui::getMainWindow()->activeWindow(); if (view && view->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) { Gui::View3DInventorViewer* viewer = static_cast(view)->getViewer(); viewer->getWidget()->setCursor(oldCursor); } } int DrawSketchHandler::seekAutoConstraint(std::vector &suggestedConstraints, const Base::Vector2D& Pos, const Base::Vector2D& Dir, AutoConstraint::TargetType type) { suggestedConstraints.clear(); if (!sketchgui->Autoconstraints.getValue()) return 0; // If Autoconstraints property is not set quit Base::Vector3d hitShapeDir = Base::Vector3d(0,0,0); // direction of hit shape (if it is a line, the direction of the line) // Get Preselection int preSelPnt = sketchgui->getPreselectPoint(); int preSelCrv = sketchgui->getPreselectCurve(); int preSelCrs = sketchgui->getPreselectCross(); int GeoId = Constraint::GeoUndef; Sketcher::PointPos PosId = Sketcher::none; if (preSelPnt != -1) sketchgui->getSketchObject()->getGeoVertexIndex(preSelPnt, GeoId, PosId); else if (preSelCrv != -1){ GeoId = preSelCrv; const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId); if(geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()){ const Part::GeomLineSegment *line = static_cast(geom); hitShapeDir= line->getEndPoint()-line->getStartPoint(); } } else if (preSelCrs == 0) { // root point GeoId = -1; PosId = Sketcher::start; } else if (preSelCrs == 1){ // x axis GeoId = -1; hitShapeDir = Base::Vector3d(1,0,0); } else if (preSelCrs == 2){ // y axis GeoId = -2; hitShapeDir = Base::Vector3d(0,1,0); } if (GeoId != Constraint::GeoUndef) { // Currently only considers objects in current Sketcher AutoConstraint constr; constr.Type = Sketcher::None; constr.GeoId = GeoId; constr.PosId = PosId; if (type == AutoConstraint::VERTEX && PosId != Sketcher::none) constr.Type = Sketcher::Coincident; else if (type == AutoConstraint::CURVE && PosId != Sketcher::none) constr.Type = Sketcher::PointOnObject; else if (type == AutoConstraint::VERTEX && PosId == Sketcher::none) constr.Type = Sketcher::PointOnObject; else if (type == AutoConstraint::CURVE && PosId == Sketcher::none) constr.Type = Sketcher::Tangent; if(constr.Type == Sketcher::Tangent && Dir.Length() > 1e-8 && hitShapeDir.Length() > 1e-8) { // We are hitting a line and have hitting vector information Base::Vector3d dir3d = Base::Vector3d(Dir.fX,Dir.fY,0); double cosangle=dir3d.Normalize()*hitShapeDir.Normalize(); // the angle between the line and the hitting direction are over around 6 degrees (it is substantially parallel) // or if it is an sketch axis (that can not move to accomodate to the shape), then only if it is around 6 degrees with the normal (around 84 degrees) if (fabs(cosangle) < 0.995f || ((GeoId==-1 || GeoId==-2) && fabs(cosangle) < 0.1)) suggestedConstraints.push_back(constr); return suggestedConstraints.size(); } if (constr.Type != Sketcher::None) suggestedConstraints.push_back(constr); } if (Dir.Length() < 1e-8 || type == AutoConstraint::CURVE) // Direction not set so return; return suggestedConstraints.size(); // Suggest vertical and horizontal constraints // Number of Degree of deviation from horizontal or vertical lines const double angleDev = 2; const double angleDevRad = angleDev * M_PI / 180.; AutoConstraint constr; constr.Type = Sketcher::None; constr.GeoId = Constraint::GeoUndef; constr.PosId = Sketcher::none; double angle = std::abs(atan2(Dir.fY, Dir.fX)); if (angle < angleDevRad || (M_PI - angle) < angleDevRad ) // Suggest horizontal constraint constr.Type = Sketcher::Horizontal; else if (std::abs(angle - M_PI_2) < angleDevRad) // Suggest vertical constraint constr.Type = Sketcher::Vertical; if (constr.Type != Sketcher::None) suggestedConstraints.push_back(constr); // Find if there are tangent constraints (currently arcs and circles) int tangId = Constraint::GeoUndef; // Do not consider if distance is more than that. // Decrease this value when a candidate is found. double tangDeviation = 0.1 * sketchgui->getScaleFactor(); // Get geometry list const std::vector geomlist = sketchgui->getSketchObject()->getCompleteGeometry(); Base::Vector3d tmpPos(Pos.fX, Pos.fY, 0.f); // Current cursor point Base::Vector3d tmpDir(Dir.fX, Dir.fY, 0.f); // Direction of line Base::Vector3d tmpStart(Pos.fX-Dir.fX, Pos.fY-Dir.fY, 0.f); // Start point // Iterate through geometry int i = 0; for (std::vector::const_iterator it=geomlist.begin(); it != geomlist.end(); ++it, i++) { if ((*it)->getTypeId() == Part::GeomCircle::getClassTypeId()) { const Part::GeomCircle *circle = dynamic_cast((*it)); Base::Vector3d center = circle->getCenter(); double radius = circle->getRadius(); // ignore if no touch (use dot product) if(tmpDir * (center-tmpPos) > 0 || tmpDir * (center-tmpStart) < 0) continue; Base::Vector3d projPnt(0.f, 0.f, 0.f); projPnt = projPnt.ProjToLine(center - tmpPos, tmpDir); double projDist = std::abs(projPnt.Length() - radius); // Find if nearest if (projDist < tangDeviation) { tangId = i; tangDeviation = projDist; } } else if ((*it)->getTypeId() == Part::GeomEllipse::getClassTypeId()) { const Part::GeomEllipse *ellipse = dynamic_cast((*it)); Base::Vector3d center = ellipse->getCenter(); double a = ellipse->getMajorRadius(); double b = ellipse->getMinorRadius(); Base::Vector3d majdir = ellipse->getMajorAxisDir(); double cf = sqrt(a*a - b*b); Base::Vector3d focus1P = center + cf * majdir; Base::Vector3d focus2P = center - cf * majdir; Base::Vector3d norm = Base::Vector3d(Dir.fY,-Dir.fX).Normalize(); double distancetoline = norm*(tmpPos - focus1P); // distance focus1 to line Base::Vector3d focus1PMirrored = focus1P + 2*distancetoline*norm; // mirror of focus1 with respect to the line double error = fabs((focus1PMirrored-focus2P).Length() - 2*a); if ( error< tangDeviation) { tangId = i; tangDeviation = error; } } else if ((*it)->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { const Part::GeomArcOfCircle *arc = dynamic_cast((*it)); Base::Vector3d center = arc->getCenter(); double radius = arc->getRadius(); // ignore if no touch (use dot product) if(tmpDir * (center-tmpPos) > 0 || tmpDir * (center-tmpStart) < 0) continue; Base::Vector3d projPnt(0.f, 0.f, 0.f); projPnt = projPnt.ProjToLine(center - tmpPos, tmpDir); double projDist = std::abs(projPnt.Length() - radius); if (projDist < tangDeviation) { double startAngle, endAngle; arc->getRange(startAngle, endAngle, /*emulateCCW=*/true); double angle = atan2(projPnt.y, projPnt.x); while(angle < startAngle) angle += 2*D_PI; // Bring it to range of arc // if the point is on correct side of arc if (angle <= endAngle) { // Now need to check only one side tangId = i; tangDeviation = projDist; } } } else if ((*it)->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId()) { const Part::GeomArcOfEllipse *aoe = dynamic_cast((*it)); Base::Vector3d center = aoe->getCenter(); double a = aoe->getMajorRadius(); double b = aoe->getMinorRadius(); Base::Vector3d majdir = aoe->getMajorAxisDir(); double cf = sqrt(a*a - b*b); Base::Vector3d focus1P = center + cf * majdir; Base::Vector3d focus2P = center - cf * majdir; Base::Vector3d norm = Base::Vector3d(Dir.fY,-Dir.fX).Normalize(); double distancetoline = norm*(tmpPos - focus1P); // distance focus1 to line Base::Vector3d focus1PMirrored = focus1P + 2*distancetoline*norm; // mirror of focus1 with respect to the line double error = fabs((focus1PMirrored-focus2P).Length() - 2*a); if ( error< tangDeviation ) { tangId = i; tangDeviation = error; } if (error < tangDeviation) { double startAngle, endAngle; aoe->getRange(startAngle, endAngle, /*emulateCCW=*/true); double angle = Base::fmod( atan2(-aoe->getMajorRadius()*((tmpPos.x-center.x)*majdir.y-(tmpPos.y-center.y)*majdir.x), aoe->getMinorRadius()*((tmpPos.x-center.x)*majdir.x+(tmpPos.y-center.y)*majdir.y) )- startAngle, 2.f*M_PI); while(angle < startAngle) angle += 2*D_PI; // Bring it to range of arc // if the point is on correct side of arc if (angle <= endAngle) { // Now need to check only one side tangId = i; tangDeviation = error; } } } } if (tangId != Constraint::GeoUndef) { if (tangId > getHighestCurveIndex()) // external Geometry tangId = getHighestCurveIndex() - tangId; // Suggest vertical constraint constr.Type = Tangent; constr.GeoId = tangId; constr.PosId = Sketcher::none; suggestedConstraints.push_back(constr); } return suggestedConstraints.size(); } void DrawSketchHandler::createAutoConstraints(const std::vector &autoConstrs, int geoId1, Sketcher::PointPos posId1) { if (!sketchgui->Autoconstraints.getValue()) return; // If Autoconstraints property is not set quit if (autoConstrs.size() > 0) { // Open the Command Gui::Command::openCommand("Add auto constraints"); // Iterate through constraints std::vector::const_iterator it = autoConstrs.begin(); for (; it != autoConstrs.end(); ++it) { switch (it->Type) { case Sketcher::Coincident: { if (posId1 == Sketcher::none) continue; // If the auto constraint has a point create a coincident otherwise it is an edge on a point Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Coincident',%i,%i,%i,%i)) " ,sketchgui->getObject()->getNameInDocument() ,geoId1, posId1, it->GeoId, it->PosId ); } break; case Sketcher::PointOnObject: { int geoId2 = it->GeoId; Sketcher::PointPos posId2 = it->PosId; if (posId1 == Sketcher::none) { // Auto constraining an edge so swap parameters std::swap(geoId1,geoId2); std::swap(posId1,posId2); } Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('PointOnObject',%i,%i,%i)) " ,sketchgui->getObject()->getNameInDocument() ,geoId1, posId1, geoId2 ); } break; case Sketcher::Horizontal: { bool start_external; bool mid_external; bool end_external; dynamic_cast((sketchgui->getObject()))->isCoincidentWithExternalGeometry(geoId1, start_external, mid_external, end_external); if( !(start_external && end_external) ) { Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Horizontal',%i)) " ,sketchgui->getObject()->getNameInDocument() ,geoId1 ); } } break; case Sketcher::Vertical: { bool start_external; bool mid_external; bool end_external; dynamic_cast((sketchgui->getObject()))->isCoincidentWithExternalGeometry(geoId1, start_external, mid_external, end_external); if( !(start_external && end_external) ) { Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Vertical',%i)) " ,sketchgui->getObject()->getNameInDocument() ,geoId1 ); } } break; case Sketcher::Tangent: { Sketcher::SketchObject* Obj = dynamic_cast(sketchgui->getObject()); const Part::Geometry *geom1 = Obj->getGeometry(geoId1); const Part::Geometry *geom2 = Obj->getGeometry(it->GeoId); int geoId2 = it->GeoId; // ellipse tangency support using construction elements (lines) if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomEllipse::getClassTypeId() )){ if(geom1->getTypeId() != Part::GeomEllipse::getClassTypeId()) std::swap(geoId1,geoId2); // geoId1 is the ellipse geom1 = Obj->getGeometry(geoId1); geom2 = Obj->getGeometry(geoId2); if( geom2->getTypeId() == Part::GeomEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomCircle::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() ) { // in all these cases an intermediate element is needed makeTangentToEllipseviaNewPoint(Obj,geom1,geom2,geoId1,geoId2); return; } } // arc of ellipse tangency support using external elements if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() )){ if(geom1->getTypeId() != Part::GeomArcOfEllipse::getClassTypeId()) std::swap(geoId1,geoId2); // geoId1 is the arc of ellipse geom1 = Obj->getGeometry(geoId1); geom2 = Obj->getGeometry(geoId2); if( geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() || geom2->getTypeId() == Part::GeomCircle::getClassTypeId() || geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId() ) { // in all these cases an intermediate element is needed makeTangentToArcOfEllipseviaNewPoint(Obj,geom1,geom2,geoId1,geoId2); return; } } Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.addConstraint(Sketcher.Constraint('Tangent',%i, %i)) " ,sketchgui->getObject()->getNameInDocument() ,geoId1, it->GeoId ); } break; default: break; } Gui::Command::commitCommand(); //Gui::Command::updateActive(); // There is already an recompute in each command creation, this is redundant. } } } void DrawSketchHandler::renderSuggestConstraintsCursor(std::vector &suggestedConstraints) { // Auto Constrait icon size in px int iconSize = 16; // Create a pixmap that will contain icon and each autoconstraint icon QPixmap baseIcon = actCursor.pixmap(); QPixmap newIcon(baseIcon.width() + suggestedConstraints.size() * iconSize, baseIcon.height()); newIcon.fill(Qt::transparent); QPainter qp; qp.begin(&newIcon); qp.drawPixmap(0,0, baseIcon); // Iterate through AutoConstraints type and add icons to the cursor pixmap std::vector::iterator it=suggestedConstraints.begin(); int i = 0; for (; it != suggestedConstraints.end(); ++it, i++) { QString iconType; switch (it->Type) { case Horizontal: iconType = QString::fromLatin1("Constraint_Horizontal"); break; case Vertical: iconType = QString::fromLatin1("Constraint_Vertical"); break; case Coincident: iconType = QString::fromLatin1("Constraint_PointOnPoint"); break; case PointOnObject: iconType = QString::fromLatin1("Constraint_PointOnObject"); break; case Tangent: iconType = QString::fromLatin1("Constraint_Tangent"); break; default: break; } if (!iconType.isEmpty()) { QPixmap icon = Gui::BitmapFactory().pixmap(iconType.toLatin1()).scaledToWidth(iconSize); qp.drawPixmap(QPoint(baseIcon.width() + i * iconSize, baseIcon.height() - iconSize), icon); } } qp.end(); // Finish painting // Create the new cursor with the icon. QPoint p=actCursor.hotSpot(); QCursor newCursor(newIcon, p.x(), p.y()); applyCursor(newCursor); } void DrawSketchHandler::setPositionText(const Base::Vector2D &Pos, const SbString &text) { sketchgui->setPositionText(Pos, text); } void DrawSketchHandler::setPositionText(const Base::Vector2D &Pos) { sketchgui->setPositionText(Pos); } void DrawSketchHandler::resetPositionText(void) { sketchgui->resetPositionText(); }