/*************************************************************************** * Copyright (c) 2013 Luke Parry * * * * 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 # include # include # include # include # include # include # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "Rez.h" #include "ZVALUE.h" #include "QGCustomLabel.h" #include "QGCustomBorder.h" #include "QGCustomText.h" #include "QGICaption.h" #include "QGCustomImage.h" #include "QGIArrow.h" #include "QGIDimLines.h" #include "QGIViewDimension.h" #include "ViewProviderDimension.h" #include "DrawGuiUtil.h" #ifndef M_2PI #define M_2PI ((M_PI) * 2.0) #endif //TODO: hide the Qt coord system (+y down). using namespace TechDraw; using namespace TechDrawGui; enum SnapMode{ NoSnap, VerticalSnap, HorizontalSnap }; QGIDatumLabel::QGIDatumLabel() { posX = 0; posY = 0; setCacheMode(QGraphicsItem::NoCache); setFlag(ItemSendsGeometryChanges, true); setFlag(ItemIsMovable, true); setFlag(ItemIsSelectable, true); setAcceptHoverEvents(true); m_dimText = new QGCustomText(); m_dimText->setParentItem(this); m_tolText = new QGCustomText(); m_tolText->setParentItem(this); m_ctrl = false; hasHover = false; } QVariant QGIDatumLabel::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == ItemSelectedHasChanged && scene()) { if(isSelected()) { Q_EMIT selected(true); setPrettySel(); } else { Q_EMIT selected(false); setPrettyNormal(); } update(); } else if(change == ItemPositionHasChanged && scene()) { setLabelCenter(); Q_EMIT dragging(m_ctrl); } return QGraphicsItem::itemChange(change, value); } void QGIDatumLabel::mousePressEvent(QGraphicsSceneMouseEvent * event) { if(event->modifiers() & Qt::ControlModifier) { m_ctrl = true; } if(scene() && this == scene()->mouseGrabberItem()) { Q_EMIT dragFinished(); } QGraphicsItem::mousePressEvent(event); } void QGIDatumLabel::mouseMoveEvent(QGraphicsSceneMouseEvent * event) { QGraphicsItem::mouseMoveEvent(event); } void QGIDatumLabel::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) { m_ctrl = false; if(scene() && this == scene()->mouseGrabberItem()) { Q_EMIT dragFinished(); } QGraphicsItem::mouseReleaseEvent(event); } void QGIDatumLabel::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { Q_EMIT hover(true); hasHover = true; if (!isSelected()) { setPrettyPre(); } QGraphicsItem::hoverEnterEvent(event); } void QGIDatumLabel::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { QGIView *view = dynamic_cast (parentItem()); assert(view != 0); Q_UNUSED(view); Q_EMIT hover(false); hasHover = false; if (!isSelected()) { setPrettyNormal(); } QGraphicsItem::hoverLeaveEvent(event); } QRectF QGIDatumLabel::boundingRect() const { return childrenBoundingRect(); } void QGIDatumLabel::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(widget); Q_UNUSED(painter); QStyleOptionGraphicsItem myOption(*option); myOption.state &= ~QStyle::State_Selected; //painter->drawRect(boundingRect()); //good for debugging } void QGIDatumLabel::setPosFromCenter(const double &xCenter, const double &yCenter) { //set label's Qt position(top,left) given boundingRect center point setPos(xCenter - m_dimText->boundingRect().width() / 2., yCenter - m_dimText->boundingRect().height() / 2.); //set tolerance position QRectF labelBox = m_dimText->boundingRect(); double right = labelBox.right(); double top = labelBox.top(); m_tolText->setPos(right,top); } void QGIDatumLabel::setLabelCenter() { //save label's bRect center (posX,posY) given Qt position (top,left) posX = x() + m_dimText->boundingRect().width() / 2.; posY = y() + m_dimText->boundingRect().height() / 2.; } void QGIDatumLabel::setFont(QFont f) { m_dimText->setFont(f); QFont tFont(f); double fontSize = f.pixelSize(); double tolAdj = getTolAdjust(); tFont.setPixelSize((int) (fontSize * tolAdj)); m_tolText->setFont(tFont); } void QGIDatumLabel::setDimString(QString t) { prepareGeometryChange(); m_dimText->setPlainText(t); } void QGIDatumLabel::setDimString(QString t, qreal maxWidth) { prepareGeometryChange(); m_dimText->setPlainText(t); m_dimText->setTextWidth(maxWidth); } void QGIDatumLabel::setTolString() { prepareGeometryChange(); QGIViewDimension* qgivd = dynamic_cast(parentItem()); if( qgivd == nullptr ) { return; //tarfu } const auto dim( dynamic_cast(qgivd->getViewObject()) ); if( dim == nullptr ) { return; } else if (!dim->hasTolerance()) { return; } double overTol = dim->OverTolerance.getValue(); double underTol = dim->UnderTolerance.getValue(); int precision = getPrecision(); QString overFormat = QString::number(overTol,'f', precision); QString underFormat = QString::number(underTol,'f',precision); QString html = QString::fromUtf8("
%1
%2
"); html = html.arg(overFormat).arg(underFormat); m_tolText->setHtml(html); return; } int QGIDatumLabel::getPrecision(void) { int precision; bool global = false; Base::Reference hGrp = App::GetApplication().GetUserParameter() .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Dimensions"); global = hGrp->GetBool("UseGlobalDecimals", true); if (global) { precision = Base::UnitsApi::getDecimals(); } else { precision = hGrp->GetInt("AltDecimals", 2); } return precision; } double QGIDatumLabel::getTolAdjust(void) { double adjust; Base::Reference hGrp = App::GetApplication().GetUserParameter() .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Dimensions"); adjust = hGrp->GetFloat("TolSizeAdjust", 0.50); return adjust; } void QGIDatumLabel::setPrettySel(void) { m_dimText->setPrettySel(); m_tolText->setPrettySel(); } void QGIDatumLabel::setPrettyPre(void) { m_dimText->setPrettyPre(); m_tolText->setPrettyPre(); } void QGIDatumLabel::setPrettyNormal(void) { m_dimText->setPrettyNormal(); m_tolText->setPrettyNormal(); } void QGIDatumLabel::setColor(QColor c) { m_colNormal = c; m_dimText->setColor(m_colNormal); m_tolText->setColor(m_colNormal); } //************************************************************** QGIViewDimension::QGIViewDimension() : hasHover(false), m_lineWidth(0.0), m_obtuse(false) { setHandlesChildEvents(false); setFlag(QGraphicsItem::ItemIsMovable, false); setFlag(QGraphicsItem::ItemIsSelectable, false); // setAcceptHoverEvents(true); setAcceptHoverEvents(false); setCacheMode(QGraphicsItem::NoCache); datumLabel = new QGIDatumLabel(); addToGroup(datumLabel); datumLabel->setColor(getNormalColor()); datumLabel->setPrettyNormal(); dimLines = new QGIDimLines(); addToGroup(dimLines); aHead1 = new QGIArrow(); addToGroup(aHead1); aHead2 = new QGIArrow(); addToGroup(aHead2); datumLabel->setZValue(ZVALUE::DIMENSION); dimLines->setZValue(ZVALUE::DIMENSION); aHead1->setZValue(ZVALUE::DIMENSION); aHead2->setZValue(ZVALUE::DIMENSION); //centerMark = new QGICMark(); //addToGroup(centerMark); // connecting the needed slots and signals QObject::connect( datumLabel, SIGNAL(dragging(bool)), this , SLOT (datumLabelDragged(bool))); QObject::connect( datumLabel, SIGNAL(dragFinished()), this , SLOT (datumLabelDragFinished())); QObject::connect( datumLabel, SIGNAL(selected(bool)), this , SLOT (select(bool))); QObject::connect( datumLabel, SIGNAL(hover(bool)), this , SLOT (hover(bool))); dimLines->setStyle(Qt::SolidLine); setZValue(ZVALUE::DIMENSION); //note: this won't paint dimensions over another View if it stacks //above this Dimension's parent view. need Layers? m_label->hide(); m_border->hide(); m_caption->hide(); m_lock->hide(); setPrettyNormal(); } QVariant QGIViewDimension::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == ItemSelectedHasChanged && scene()) { if(isSelected()) { setSelected(false); datumLabel->setSelected(true); } else { datumLabel->setSelected(false); } draw(); } return QGIView::itemChange(change, value); } void QGIViewDimension::select(bool state) { // Base::Console().Message("QGIVD::select(%d)\n", state); if (state) { setPrettySel(); } else { setPrettyNormal(); } draw(); } //surrogate for hover enter (true), hover leave (false) events void QGIViewDimension::hover(bool state) { hasHover = state; if (state) { if (datumLabel->isSelected()) { setPrettySel(); } else { setPrettyPre(); //have hover, not selected -> preselect } } else { if (datumLabel->isSelected()) { setPrettySel(); } else { setPrettyNormal(); } } draw(); } void QGIViewDimension::setViewPartFeature(TechDraw::DrawViewDimension *obj) { if(obj == 0) return; setViewFeature(static_cast(obj)); // Set the QGIGroup Properties based on the DrawView float x = Rez::guiX(obj->X.getValue()); float y = Rez::guiX(-obj->Y.getValue()); datumLabel->setPosFromCenter(x, y); updateDim(); draw(); } void QGIViewDimension::updateView(bool update) { Q_UNUSED(update); auto dim( dynamic_cast(getViewObject()) ); if( dim == nullptr ) return; auto vp = static_cast(getViewProvider(getViewObject())); if ( vp == nullptr ) { return; } if (update|| dim->X.isTouched() || dim->Y.isTouched()) { float x = Rez::guiX(dim->X.getValue()); float y = Rez::guiX(dim->Y.getValue()); datumLabel->setPosFromCenter(x,-y); updateDim(); } else if(vp->Fontsize.isTouched() || vp->Font.isTouched()) { updateDim(); } else if (vp->LineWidth.isTouched()) { //never happens!! m_lineWidth = vp->LineWidth.getValue(); updateDim(); } else { updateDim(); } draw(); } void QGIViewDimension::updateDim(bool obtuse) { (void) obtuse; const auto dim( dynamic_cast(getViewObject()) ); if( dim == nullptr ) { return; } auto vp = static_cast(getViewProvider(getViewObject())); if ( vp == nullptr ) { return; } QString labelText = QString::fromUtf8(dim->getFormatedValue(m_obtuse).c_str()); QFont font = datumLabel->getFont(); font.setFamily(QString::fromUtf8(vp->Font.getValue())); font.setPixelSize(calculateFontPixelSize(vp->Fontsize.getValue())); datumLabel->setFont(font); prepareGeometryChange(); datumLabel->setDimString(labelText); datumLabel->setTolString(); datumLabel->setPosFromCenter(datumLabel->X(),datumLabel->Y()); } //this is for formatting and finding centers, not display QString QGIViewDimension::getLabelText(void) { QString result; QString first = datumLabel->getDimText()->toPlainText(); QString second = datumLabel->getTolText()->toPlainText(); result = first + second; return result; } void QGIViewDimension::datumLabelDragged(bool ctrl) { Q_UNUSED(ctrl); draw(); } void QGIViewDimension::datumLabelDragFinished() { auto dim( dynamic_cast(getViewObject()) ); if( dim == nullptr ) { return; } double x = Rez::appX(datumLabel->X()), y = Rez::appX(datumLabel->Y()); Gui::Command::openCommand("Drag Dimension"); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.X = %f", dim->getNameInDocument(), x); Gui::Command::doCommand(Gui::Command::Doc,"App.ActiveDocument.%s.Y = %f", dim->getNameInDocument(), -y); Gui::Command::commitCommand(); } void QGIViewDimension::draw() { if (!isVisible()) { return; } datumLabel->show(); show(); TechDraw::DrawViewDimension *dim = dynamic_cast(getViewObject()); if((!dim) || //nothing to draw, don't try (!dim->isDerivedFrom(TechDraw::DrawViewDimension::getClassTypeId())) || (!dim->has2DReferences()) ) { datumLabel->hide(); hide(); return; } const TechDraw::DrawViewPart *refObj = dim->getViewPart(); if(!refObj->hasGeometry()) { //nothing to draw yet (restoring) datumLabel->hide(); hide(); return; } auto vp = static_cast(getViewProvider(getViewObject())); if ( vp == nullptr ) { return; } m_colNormal = getNormalColor(); datumLabel->setColor(m_colNormal); m_lineWidth = Rez::guiX(vp->LineWidth.getValue()); float margin = Rez::guiX(5.f); QString labelText = getLabelText(); Base::Vector3d lblCenter(datumLabel->X(), datumLabel->Y(), 0); //already Qt gui coords const char *dimType = dim->Type.getValueAsString(); datumLabel->show(); show(); if (strcmp(dimType, "Distance") == 0 || strcmp(dimType, "DistanceX") == 0 || strcmp(dimType, "DistanceY") == 0) { Base::Vector3d stdUp(0.0,1.0,0.0); Base::Vector3d stdLeft(-1.0,0.0,0.0); pointPair pts = dim->getLinearPoints(); Base::Vector3d startDist, endDist, midDist; //start/end/mid points of distance line startDist = Rez::guiX(pts.first); endDist = Rez::guiX(pts.second); if (startDist.y < endDist.y) { //always measure bottom to top Base::Vector3d temp = startDist; startDist = endDist; endDist = temp; } Base::Vector3d vecDist = (endDist - startDist); // +/- aligned method // dimension text legible from bottom or right // text outside arrows (not between) iso convention (asme convention) // text to left of vertical dims // text above horizontal dims Base::Vector3d dirDist, normDist; //direction/normal vectors of distance line Base::Vector3d dirExt; Base::Vector3d dirDim, normDim; Base::Vector3d dirIso; dirDist = vecDist; dirDist.Normalize(); normDist = Base::Vector3d (-dirDist.y,dirDist.x, 0); //normal to distance direction //toward dimension line??? how to tell? if (strcmp(dimType, "Distance") == 0 ) { //distance and dimension lines parallel dirDim = dirDist; normDim = normDist; } else if (strcmp(dimType, "DistanceX") == 0 ) { //distance and dimension lines not (necessarily) parallel dirDim = Base::Vector3d ( ((endDist.x - startDist.x >= FLT_EPSILON) ? 1 : -1) , 0, 0); normDim = Base::Vector3d (-dirDim.y,dirDim.x, 0); } else if (strcmp(dimType, "DistanceY") == 0 ) { //distance and dimension lines not (necessarily) parallel dirDim = Base::Vector3d (0, 1, 0); normDim = Base::Vector3d (-1, 0, 0); } Base::Vector3d adjustDir = dirDim; //adjust line lengths for arrowheads //for ortho drawing extension lines are para to normDim, perp to dirDist dirExt = normDim; //dirExt is para or anti-parallel to real extension direction dirIso = normDim; if (refObj->isIso()) { //is this dimension an iso dimension? ie points +/-isoX,+/-isoY,+/-isoZ dirIso = findIsoDir(dirDist); dirExt = findIsoExt(dirIso); } dirExt.Normalize(); // Get magnitude of angle between dimension line and horizontal // to determine rotation of dimension text ("aligned" convention. alt is "unidirectional") //note qt y axis is reversed! and angles are CW! double textRotAngle = atan2(-dirDim.y,dirDim.x); if (textRotAngle < 0.0) { textRotAngle = 2 * M_PI + textRotAngle; //map to +ve textRotAngle } //orient text right side up double angleFiddle = M_PI / 10.0; // 18 => 10*, 12 => 15*, 10 => 18*, ... if ((textRotAngle > M_PI_2 + angleFiddle) && // > 100CW (textRotAngle <= M_PI)) { // < 180CW -> Q2 textRotAngle += M_PI; // flip CW } else if ((textRotAngle > M_PI) && // > 180CW (textRotAngle <= 1.5*M_PI - angleFiddle)) { // < 260CW -> Q3 textRotAngle -= M_PI; // flip CCW } Base::Vector3d textNorm = normDim; if (strcmp(dimType, "DistanceX") == 0 ) { textNorm = stdUp; //always above } else if (strcmp(dimType, "DistanceY") == 0 ) { textNorm = stdLeft; //left of dimLine } else if (std::abs(dirDist.x) < FLT_EPSILON) { //this is horizontal dim line textNorm = stdLeft; //left of dimLine } else if (std::abs(dirDist.y) < FLT_EPSILON) { //this is vertical dim line textNorm = stdLeft; //left of dimLine } // +/- pos of startDist vs endDist for vert/horiz Dims // distStartDelta sb zero for normal dims float distStartDelta = vecDist.Dot(normDim); // component of distance vector in dim line direction Base::Vector3d startDistAdj = startDist + normDim * distStartDelta; double textOffset = getDefaultTextVerticalOffset(); QPointF qFigure = boundingRect().center(); Base::Vector3d figureCenter(qFigure.x(),qFigure.y(),0.0); QRectF mappedRect = mapRectFromItem(datumLabel, datumLabel->boundingRect()); lblCenter = Base::Vector3d(mappedRect.center().x(), mappedRect.center().y(), 0.0); //find actual extension direction vector Base::Vector3d startIntercept = DrawUtil::Intersect2d(startDist, dirExt, lblCenter,dirDim); Base::Vector3d dirExtActual = (startIntercept - startDist); dirExtActual.Normalize(); midDist = (startDistAdj + endDist) / 2.0; //middle point of distance line //fauxCenter is where the getDimText() would be if it was on the dimLine Base::Vector3d fauxCenter; if (strcmp(dimType, "Distance") == 0 ) { //oblique line textRotAngle = -textRotAngle; // flip text 180* fauxCenter = lblCenter + textOffset * dirExtActual; double slope; if (DrawUtil::fpCompare(dirDist.x, 0.0)) { slope = std::numeric_limits::max(); //vertical line } else { slope = fabs(dirDist.y / dirDist.x); } double deg = atan(slope) * 180.0 / M_PI; if (deg > (90.0 - (angleFiddle * 180.0/M_PI))) { //mostly vertical +/- 10* //dim text reads bottom to top on left side of dim line if (lblCenter.x > fauxCenter.x) { //label is to right of dimline fauxCenter = lblCenter - textOffset*dirExtActual; //move dim line closer to figure } } else { //mostly horizontal //dim text reads left to right above dim line if (lblCenter.y > fauxCenter.y) { //label is below dimline fauxCenter = lblCenter - textOffset*dirExtActual; //move dim line closer to figure } } } else if (strcmp(dimType, "DistanceX") == 0 ) { fauxCenter = lblCenter + textOffset * textNorm; } else if (strcmp(dimType, "DistanceY") == 0 ) { textRotAngle = - textRotAngle; fauxCenter = lblCenter - textOffset * textNorm; } //intersection of extension lines and dimension line startIntercept = DrawUtil::Intersect2d(startDist, dirExt, fauxCenter,dirDim); Base::Vector3d endIntercept = DrawUtil::Intersect2d(endDist, dirExt, fauxCenter,dirDim); dirExtActual = (startIntercept - startDist); dirExtActual.Normalize(); margin = Rez::guiX(2.f); float scaler = 1.; Base::Vector3d extStartEnd = startIntercept + dirExtActual * (margin * scaler); Base::Vector3d extEndEnd = endIntercept + dirExtActual * (margin * scaler); //case 1: inner placement: text between extensions & fits. arros point out from inside (default) //case 2: inner placement2: text too big to fit. arrows point in from outside //case 3: outer placement: text is outside extensions. arrows point in, 1 arrow tracks getDimText() Base::Vector3d dim1Tip = startIntercept; Base::Vector3d dim1Tail = fauxCenter; Base::Vector3d dim2Tip = endIntercept; Base::Vector3d dim2Tail = fauxCenter; Base::Vector3d a1Dir = -dirDim; Base::Vector3d a2Dir = dirDim; if (strcmp(dimType, "DistanceY") == 0 ) { adjustDir = -adjustDir; a1Dir = Base::Vector3d(0,1,0); a2Dir = Base::Vector3d(0,-1,0); } double dimSpan = (extEndEnd - extStartEnd).Length(); //distance between extension lines double fauxToDim1 = (fauxCenter - dim1Tip).Length(); //label to arrow #1 double fauxToDim2 = (fauxCenter - dim2Tip).Length(); //label to end #2 double tailLength = Rez::guiX(10.f) * scaler; //case2 - innerPlacement && text > span double lblWidth = datumLabel->boundingRect().width(); if ((DrawUtil::isBetween(fauxCenter, dim1Tip, dim2Tip)) && (lblWidth > dimSpan) ) { adjustDir = -adjustDir; if (strcmp(dimType, "DistanceY") == 0 ) { a1Dir = Base::Vector3d(0,-1,0); a2Dir = Base::Vector3d(0,1,0); dim1Tail = dim1Tip + dirDim * tailLength; dim2Tail = dim2Tip - dirDim * tailLength; } else { dim1Tail = dim1Tip - tailLength * dirDim; dim2Tail = dim2Tip + tailLength * dirDim; a1Dir = dirDim; a2Dir = -dirDim; } } if (!DrawUtil::isBetween(fauxCenter, dim1Tip, dim2Tip)) { //case3 - outerPlacement adjustDir = -adjustDir; if (strcmp(dimType, "DistanceY") == 0 ) { a1Dir = Base::Vector3d(0,-1,0); a2Dir = Base::Vector3d(0,1,0); } else { a1Dir = dirDim; a2Dir = -dirDim; } if (fauxToDim1 < fauxToDim2) { dim1Tail = fauxCenter; dim2Tail = dim2Tip - tailLength*a2Dir; } else { dim1Tail = dim1Tip - tailLength*a1Dir; dim2Tail = fauxCenter; } } // Extension lines QPainterPath path; Base::Vector3d gap = startDist + dirExtActual * (margin * scaler); //space ext line a bit path.moveTo(gap.x, gap.y); path.lineTo(extStartEnd.x, extStartEnd.y); gap = endDist + dirExtActual * (margin * scaler); path.moveTo(gap.x, gap.y); path.lineTo(extEndEnd.x, extEndEnd.y); //Dimension lines (arrow shafts) adjustDir.Normalize(); double dimLineAdjust = Rez::guiX(QGIArrow::getOverlapAdjust(QGIArrow::getPrefArrowStyle(), QGIArrow::getPrefArrowSize())); Base::Vector3d adjustedTip = dim1Tip + adjustDir * dimLineAdjust; path.moveTo(adjustedTip.x, adjustedTip.y); path.lineTo(dim1Tail.x, dim1Tail.y); adjustedTip = dim2Tip - adjustDir * dimLineAdjust; path.moveTo(adjustedTip.x, adjustedTip.y); path.lineTo(dim2Tail.x, dim2Tail.y); dimLines->setPath(path); // Note Bounding Box size is not the same width or height as text (only used for finding center) double bbX = datumLabel->boundingRect().width(); double bbY = datumLabel->boundingRect().height(); datumLabel->setTransformOriginPoint(bbX / 2, bbY /2); double angleOption = 0.0; //put lblText angle adjustments here datumLabel->setRotation((textRotAngle * 180 / M_PI) + angleOption); if (strcmp(dimType, "DistanceY") == 0 ) { datumLabel->setRotation(-90.0 + angleOption); } aHead1->setDirMode(true); aHead2->setDirMode(true); if (vp->FlipArrowheads.getValue()) { aHead1->setDirection(a1Dir * -1.0); aHead2->setDirection(a2Dir * -1.0); } else { aHead1->setDirection(a1Dir); aHead2->setDirection(a2Dir); } aHead1->setStyle(QGIArrow::getPrefArrowStyle()); aHead1->setSize(QGIArrow::getPrefArrowSize()); aHead1->draw(); aHead2->setStyle(QGIArrow::getPrefArrowStyle()); aHead2->setSize(QGIArrow::getPrefArrowSize()); aHead2->draw(); aHead1->setPos(dim1Tip.x, dim1Tip.y); aHead2->setPos(dim2Tip.x, dim2Tip.y); aHead1->setDirMode(false); aHead2->setDirMode(false); } else if(strcmp(dimType, "Diameter") == 0) { // terminology: Dimension Text, Dimension Line(s), Extension Lines, Arrowheads Base::Vector3d arrow1Tip, arrow2Tip; Base::Vector3d arrow1Tail, arrow2Tail; Base::Vector3d arrow1Dir,arrow2Dir; double radius; Base::Vector3d pointOnCurve,curveCenter; Base::Vector3d startExt1,endExt1,startExt2,endExt2; arcPoints pts = dim->getArcPoints(); bool isArc = pts.isArc; radius = Rez::guiX(pts.radius); curveCenter = Rez::guiX(pts.center); pointOnCurve = Rez::guiX(pts.onCurve.first); Base::Vector3d startDist,endDist,dirDim; startDist = Rez::guiX(pts.onCurve.first); endDist = Rez::guiX(pts.onCurve.second); dirDim = endDist - startDist; if (fabs(dirDim.Length()) < Precision::Confusion()) { dirDim = Base::Vector3d(1.0,0.0,0.0); } dirDim.Normalize(); Base::Vector3d adjustDir = dirDim; //adjust line lengths for arrowheads Base::Vector3d fauxCenter = lblCenter; //default arrow endpoints double dimLineAdjust = Rez::guiX(QGIArrow::getOverlapAdjust(QGIArrow::getPrefArrowStyle(), QGIArrow::getPrefArrowSize())); arrow1Tip = curveCenter - dirDim * radius; Base::Vector3d adjustedTip1 = arrow1Tip - adjustDir * dimLineAdjust; arrow2Tip = curveCenter + dirDim * radius; Base::Vector3d adjustedTip2 = arrow2Tip + adjustDir * dimLineAdjust; arrow1Tail = curveCenter; arrow2Tail = curveCenter; double gapMargin = Rez::guiX(4.f); margin = Rez::guiX(2.f); float scaler = 1.; double tip = (margin * scaler); double gap = (gapMargin * scaler); //sb % of radius? //offset of dimLine from getDimText() double horizOffset = getDefaultTextHorizontalOffset(lblCenter.x > curveCenter.x ? -1.0 : +1.0); double vertOffset = getDefaultTextVerticalOffset(); bool outerPlacement = false; if ((lblCenter-curveCenter).Length() > radius) { //label is outside circle outerPlacement = true; } // // Note Bounding Box size is not the same width or height as text (only used for finding center) float bbX = datumLabel->boundingRect().width(); float bbY = datumLabel->boundingRect().height(); datumLabel->setTransformOriginPoint(bbX / 2, bbY /2); int posMode = NoSnap; bool isLeader = false; QPainterPath path; if(outerPlacement) { Base::Vector3d v = (lblCenter - curveCenter); double angle = atan2(v.y, v.x); double tolerance = 15.0; //deg tolerance *= M_PI / 180; if( (angle > -tolerance && angle < tolerance) || //angle = 0 or 180 (+/- 15) (angle > (M_PI - tolerance) || angle < (-M_PI + tolerance)) ) { //dim line is Horizontal posMode = HorizontalSnap; } else if( (angle < ( M_PI / 2. + tolerance) && angle > ( M_PI / 2. - tolerance)) || //angle 90/270 (angle < (-M_PI / 2. + tolerance) && angle > (-M_PI / 2. - tolerance)) ) { //Vertical posMode = VerticalSnap; } //NOTE: VerticalSnap and Horizontal snap are confusing names. // VerticalSnap => dim line is horizontal, dim text is directly above/below center // HorizontalSnap => dim line is vertical, dim text is directly left/right of center. if(posMode == VerticalSnap) { QRectF mappedRect = mapRectFromItem(datumLabel, datumLabel->boundingRect()); lblCenter = Base::Vector3d(mappedRect.center().x(), mappedRect.center().y(), 0.0); if (lblCenter.y > curveCenter.y) { fauxCenter = Base::Vector3d(lblCenter.x,lblCenter.y + vertOffset,lblCenter.z); } else { fauxCenter = Base::Vector3d(lblCenter.x,lblCenter.y + vertOffset,lblCenter.z); tip = -tip; gap = -gap; } arrow1Tip.x = curveCenter.x - radius; //to left, on circle cl arrow1Tip.y = fauxCenter.y; arrow1Tail.x = curveCenter.x; arrow1Tail.y = arrow1Tip.y; arrow1Dir = (arrow1Tip - arrow1Tail).Normalize(); adjustedTip1.x = arrow1Tip.x + dimLineAdjust; adjustedTip1.y = arrow1Tip.y; path.moveTo(arrow1Tail.x, arrow1Tail.y); path.lineTo(adjustedTip1.x, adjustedTip1.y); arrow2Tip.x = curveCenter.x + radius; arrow2Tip.y = fauxCenter.y; arrow2Tail.x = curveCenter.x; arrow2Tail.y = arrow2Tip.y; arrow2Dir = (arrow2Tip - arrow2Tail).Normalize(); adjustedTip2.x = arrow2Tip.x - dimLineAdjust; adjustedTip2.y = arrow2Tip.y; path.moveTo(arrow2Tail.x, arrow2Tail.y); path.lineTo(adjustedTip2.x, adjustedTip2.y); startExt1.x = curveCenter.x - radius; startExt1.y = curveCenter.y + gap; endExt1.x = startExt1.x; endExt1.y = fauxCenter.y + tip; startExt2.x = curveCenter.x + radius; startExt2.y = curveCenter.y + gap; endExt2.x = startExt2.x; endExt2.y = fauxCenter.y + tip; path.moveTo(startExt1.x, startExt1.y); path.lineTo(endExt1.x, endExt1.y); path.moveTo(startExt2.x, startExt2.y); path.lineTo(endExt2.x, endExt2.y); datumLabel->setRotation(0.0); } else if(posMode == HorizontalSnap) { QRectF mappedRect = mapRectFromItem(datumLabel, datumLabel->boundingRect()); lblCenter = Base::Vector3d(mappedRect.center().x(), mappedRect.center().y(), 0.0); if (lblCenter.x > curveCenter.x) { //label right // fauxCenter = Base::Vector3d(lblCenter.x - horizOffset,lblCenter.y,lblCenter.z); //uniform convention fauxCenter = Base::Vector3d(lblCenter.x + vertOffset,lblCenter.y,lblCenter.z); //aligned convention } else { //label left tip = -tip; gap = -gap; // fauxCenter = Base::Vector3d(lblCenter.x + horizOffset,lblCenter.y,lblCenter.z); //uniform fauxCenter = Base::Vector3d(lblCenter.x + vertOffset,lblCenter.y,lblCenter.z); //aligned } arrow1Tip.x = fauxCenter.x; arrow1Tip.y = curveCenter.y - radius; arrow1Tail.x = arrow1Tip.x; arrow1Tail.y = curveCenter.y; arrow1Dir = (arrow1Tip - arrow1Tail).Normalize(); adjustedTip1.x = arrow1Tip.x; adjustedTip1.y = arrow1Tip.y + dimLineAdjust; path.moveTo(arrow1Tail.x, arrow1Tail.y); path.lineTo(adjustedTip1.x, adjustedTip1.y); arrow2Tip.x = fauxCenter.x; arrow2Tip.y = curveCenter.y + radius; arrow2Tail.x = arrow2Tip.x; arrow2Tail.y = curveCenter.y; arrow2Dir = (arrow2Tip - arrow2Tail).Normalize(); adjustedTip2.x = arrow2Tip.x; adjustedTip2.y = arrow2Tip.y - dimLineAdjust; path.moveTo(arrow2Tail.x, arrow2Tail.y); path.lineTo(adjustedTip2.x, adjustedTip2.y); startExt1.x = curveCenter.x + gap; startExt1.y = curveCenter.y - radius; endExt1.x = fauxCenter.x + tip; endExt1.y = startExt1.y; startExt2.x = curveCenter.x + gap; startExt2.y = curveCenter.y + radius; endExt2.x = fauxCenter.x + tip; endExt2.y = startExt2.y; path.moveTo(startExt1.x, startExt1.y); path.lineTo(endExt1.x, endExt1.y); path.moveTo(startExt2.x, startExt2.y); path.lineTo(endExt2.x, endExt2.y); datumLabel->setRotation(-90.0); //aligned convention // datumLabel->setRotation(0.0); //unidirectional convention } else { //outer placement, NoSnap (leader type) QRectF mappedRect = mapRectFromItem(datumLabel, datumLabel->boundingRect()); lblCenter = Base::Vector3d(mappedRect.center().x(), mappedRect.center().y(), 0.0); isLeader = true; arrow1Tail = lblCenter; arrow1Tail.x += horizOffset; Base::Vector3d kinkPoint = arrow1Tail; kinkPoint.x += (lblCenter.x < curveCenter.x) ? margin : - margin; arrow1Tip = curveCenter + (kinkPoint - curveCenter).Normalize() * radius; adjustedTip1 = arrow1Tip + (kinkPoint - curveCenter).Normalize() * dimLineAdjust; path.moveTo(arrow1Tail.x, arrow1Tail.y); path.lineTo(kinkPoint.x, kinkPoint.y); path.lineTo(adjustedTip1.x, adjustedTip1.y); arrow1Dir = (arrow1Tip - kinkPoint).Normalize(); datumLabel->setRotation(0.); } } else { //NOT outerplacement ie dimLines are inside circle double vertOffset = getDefaultTextVerticalOffset(); Base::Vector3d dirLabel = lblCenter - curveCenter; if (dirLabel.Length() < Precision::Confusion()) { dirLabel = Base::Vector3d(-1.0, 0.0, 0.0); } double labelAngle = 0.0; if (vertOffset < dirLabel.Length()) { double lineShiftAngle = asin(vertOffset/dirLabel.Length()); labelAngle = atan2(dirLabel.y, dirLabel.x); if (labelAngle > 0.25*M_PI) { labelAngle -= M_PI + lineShiftAngle; } else if (labelAngle < -0.75*M_PI) { labelAngle += M_PI - lineShiftAngle; } else { labelAngle += lineShiftAngle; } } // Set the angle of the dimension text datumLabel->setRotation(labelAngle*180/M_PI); dirDim = Base::Vector3d(cos(labelAngle), sin(labelAngle), 0.0); arrow1Tip = curveCenter - dirDim * radius; adjustedTip1 = arrow1Tip + dirDim * dimLineAdjust; arrow1Tail = curveCenter; arrow1Dir = (arrow1Tip - arrow1Tail).Normalize(); arrow2Tip = curveCenter + dirDim * radius; adjustedTip2 = arrow2Tip - dirDim * dimLineAdjust; arrow2Tail = curveCenter; arrow2Dir = (arrow2Tip - arrow2Tail).Normalize(); path.moveTo(arrow1Tail.x, arrow1Tail.y); path.lineTo(adjustedTip1.x, adjustedTip1.y); path.moveTo(arrow2Tail.x, arrow2Tail.y); path.lineTo(adjustedTip2.x, adjustedTip2.y); } aHead1->setStyle(QGIArrow::getPrefArrowStyle()); aHead1->setSize(QGIArrow::getPrefArrowSize()); if (!isLeader) { aHead2->setStyle(QGIArrow::getPrefArrowStyle()); aHead2->setSize(QGIArrow::getPrefArrowSize()); } //handle partial arc weird cases //TODO: does anybody dimension Arcs with Diameter? doesn't Radius make more sense? Base::Vector3d dLineStart; Base::Vector3d kinkPoint; margin = Rez::guiX(5.f); double kinkLength = Rez::guiX(5.0); //sb % of horizontal dist(lblCenter,curveCenter)??? QPainterPath arcPath; if (isArc) { arrow1Tail = lblCenter; arrow1Tail.x += horizOffset; Base::Vector3d kinkPoint = arrow1Tail; kinkPoint.x += (lblCenter.x < curveCenter.x) ? margin : - margin; if (lblCenter.x > curveCenter.x) { // label to right of vert c/l dLineStart = Base::Vector3d(lblCenter.x + horizOffset,lblCenter.y,lblCenter.z); kinkPoint = Base::Vector3d(dLineStart.x - kinkLength,dLineStart.y,dLineStart.z); } else { tip = -tip; gap = -gap; dLineStart = Base::Vector3d(lblCenter.x + horizOffset,lblCenter.y,lblCenter.z); kinkPoint = Base::Vector3d(dLineStart.x + kinkLength,dLineStart.y,dLineStart.z); } dirDim = (kinkPoint - curveCenter).Normalize(); pointOnCurve = curveCenter + (dirDim * radius); pointOnCurve = Rez::guiX(pts.midArc); adjustedTip1 = pointOnCurve + (dirDim * dimLineAdjust); arcPath.moveTo(dLineStart.x,dLineStart.y); arcPath.lineTo(kinkPoint.x,kinkPoint.y); arcPath.lineTo(adjustedTip1.x,adjustedTip1.y); arrow1Dir = (arrow1Tip - kinkPoint).Normalize(); datumLabel->setRotation(0.); } dimLines->setPath(path); if (isArc) { dimLines->setPath(arcPath); aHead1->setPos(pointOnCurve.x, pointOnCurve.y); aHead1->setDirMode(true); aHead1->setDirection(arrow1Dir); aHead1->draw(); aHead1->show(); aHead2->hide(); } else if(outerPlacement) { if(posMode > NoSnap) { // Horizontal or Vertical snap aHead1->setPos(arrow1Tip.x, arrow1Tip.y); aHead1->setDirMode(true); aHead1->setDirection(arrow1Dir); aHead1->draw(); aHead1->show(); aHead2->setPos(arrow2Tip.x, arrow2Tip.y); aHead2->setDirMode(true); aHead2->setDirection(arrow2Dir); aHead2->draw(); aHead2->show(); } else { //leader stye aHead1->setPos(arrow1Tip.x, arrow1Tip.y); aHead1->setDirMode(true); aHead1->setDirection(arrow1Dir); aHead1->draw(); aHead1->show(); aHead2->hide(); //only 1 arrowhead for NoSnap + outerplacement (ie a leader) } } else { //inner placement aHead1->setPos(arrow1Tip.x, arrow1Tip.y); aHead1->setDirMode(true); aHead1->setDirection(arrow1Dir); aHead1->draw(); aHead1->show(); aHead2->setPos(arrow2Tip.x, arrow2Tip.y); aHead2->setDirMode(true); aHead2->setDirection(arrow2Dir); aHead2->draw(); aHead2->show(); } // code for centerMark being attribute of Dim instead of View // if (dim->CentreLines.getValue()) { // curveCenterMark->setPos(curveCenter.x,curveCenter.y); // centerMark->show(); // dim->getViewPart()->addVertex(curveCenter,true); // } } else if(strcmp(dimType, "Radius") == 0) { drawRadius(dim, vp); } else if( (strcmp(dimType, "Angle") == 0) || (strcmp(dimType, "Angle3Pt") == 0)) { anglePoints pts = dim->getAnglePoints(); Base::Vector3d X(1.0,0.0,0.0); Base::Vector3d vertex = Rez::guiX(pts.vertex); Base::Vector3d legEnd0 = Rez::guiX(pts.ends.first); Base::Vector3d legEnd1 = Rez::guiX(pts.ends.second); Base::Vector3d dir0 = legEnd0 - vertex; Base::Vector3d dir1 = legEnd1 - vertex; Base::Vector3d d0 = dir0; d0.Normalize(); Base::Vector3d d1 = dir1; d1.Normalize(); Base::Vector3d leg0 = dir0; Base::Vector3d leg1 = dir1; // Qt y coordinates are flipped dir0.y *= -1.; dir1.y *= -1.; double insideAngle = dir0.GetAngle(dir1); // [0,PI] double outsideAngle = 2*M_PI - insideAngle; // [PI,2PI] Base::Vector3d tempCross = d0.Cross(d1); double insideDir = tempCross.z; bool ccwInner = true; if (insideDir > 0.0) { ccwInner = false; } //TODO: figure out the math for adjusting the arc so the tip doesn't overlap the arrowhead. // double dimLineAdjust = Rez::guiX(QGIArrow::getOverlapAdjust(QGIArrow::getPrefArrowStyle(), // QGIArrow::getPrefArrowSize())); QRectF mappedRect = mapRectFromItem(datumLabel, datumLabel->boundingRect()); lblCenter = Base::Vector3d(mappedRect.center().x(), mappedRect.center().y(), 0.0); Base::Vector3d labelVec = (lblCenter - vertex); //dir from label to vertex double textOffset = getDefaultTextVerticalOffset(); double radius = labelVec.Length() - textOffset; QRectF arcRect(vertex.x - radius, vertex.y - radius, 2. * radius, 2. * radius); Base::Vector3d ar0Pos = vertex + d0 * radius; // Base::Vector3d adjustedTip0 = ar0Pos - d0 * dimLineAdjust; Base::Vector3d ar1Pos = vertex + d1 * radius; // Base::Vector3d adjustedTip1 = ar1Pos - d1 * dimLineAdjust; double startangle = atan2(dir0.y,dir0.x); if (startangle < 0) { startangle += 2.0 * M_PI; } double endangle = atan2(dir1.y,dir1.x); if (endangle < 0) { endangle += 2.0 * M_PI; } Base::Vector3d startExt0 = legEnd0; Base::Vector3d startExt1 = legEnd1; // add an offset from the ends double offsetFudge = 5.0; startExt0 += d0 * offsetFudge; startExt1 += d1 * offsetFudge; // Draw the path QPainterPath path; // Only draw extension lines if outside arc double extFudge = 5.0; if(radius > (startExt0-vertex).Length()) { path.moveTo(startExt0.x, startExt0.y); Base::Vector3d endExt0 = ar0Pos + d0*Rez::guiX(extFudge); path.lineTo(endExt0.x, endExt0.y); } if(radius > (startExt1-vertex).Length()) { path.moveTo(startExt1.x, startExt1.y); Base::Vector3d endExt1 = ar1Pos + d1*Rez::guiX(extFudge); path.lineTo(endExt1.x, endExt1.y); } // Treat zero as positive to be consistent for horizontal lines if(std::abs(startangle) < FLT_EPSILON) startangle = 0; if(std::abs(endangle) < FLT_EPSILON) endangle = 0; //https://stackoverflow.com/questions/13640931/how-to-determine-if-a-vector-is-between-two-other-vectors bool isOutside = true; if ( ((d0.Cross(labelVec)).Dot(d0.Cross(d1)) >= 0.0) && ((d1.Cross(labelVec)).Dot(d1.Cross(d0)) >= 0.0) ) { isOutside = false; } //dim line (arc) calculation is very different here. //TODO: make arc a bit shorter to not overlap arrowheads. ends +/- x degrees. path.arcMoveTo(arcRect, startangle * 180 / M_PI); double actualSweep = 0.0; m_obtuse = false; if(isOutside) { m_obtuse = true; if (ccwInner) { //inner is ccw so outer is cw and sweep is -ve actualSweep = -outsideAngle; } else { //inner is cw so outer is ccw and sweep is +ve actualSweep = outsideAngle; } } else { if (ccwInner) { //inner is ccw and sweep is +ve actualSweep = insideAngle; } else { //inner is cw and sweep is -ve actualSweep = -insideAngle; } } path.arcTo(arcRect, startangle * 180 / M_PI, actualSweep*180.0/M_PI); dimLines->setPath(path); //NOTE: arrowheads are dirMode(false) aHead1->setFlipped(true); aHead1->setStyle(QGIArrow::getPrefArrowStyle()); aHead1->setSize(QGIArrow::getPrefArrowSize()); aHead1->draw(); aHead2->setStyle(QGIArrow::getPrefArrowStyle()); aHead2->setSize(QGIArrow::getPrefArrowSize()); aHead2->draw(); aHead1->setPos(ar0Pos.x,ar0Pos.y ); aHead2->setPos(ar1Pos.x,ar1Pos.y ); Base::Vector3d norm1 = leg0; Base::Vector3d norm2 = leg1; Base::Vector3d avg = (norm1 + norm2) / 2.; //midline of legs norm1 = norm1.ProjectToLine(avg, norm1); norm2 = norm2.ProjectToLine(avg, norm2); float ar0angle = atan2(-norm1.y, -norm1.x) * 180 / M_PI; float ar1angle = atan2(norm2.y, norm2.x) * 180 / M_PI; if(isOutside) { aHead1->setRotation(ar0angle + 180.); aHead2->setRotation(ar1angle + 180.); } else { aHead1->setRotation(ar0angle); aHead2->setRotation(ar1angle); } // Set the angle of the dimension text Base::Vector3d labelNorm(-labelVec.y, labelVec.x, 0.); double lAngle = atan2(labelNorm.y, labelNorm.x); if (lAngle < 0.0) { lAngle = 2 * M_PI + lAngle; //map to +ve lAngle } //orient text right side up double angleFiddle = M_PI / 10.0; // 18 => 10*, 12 => 15*, 10 => 18*, ... if ((lAngle > M_PI_2 + angleFiddle) && // > 100CW (lAngle <= M_PI)) { // < 180CW -> Q2 lAngle += M_PI; // flip CW } else if ((lAngle > M_PI) && // > 180CW (lAngle <= 1.5*M_PI - angleFiddle)) { // < 260CW -> Q3 lAngle -= M_PI; // flip CCW } // //if label is more/less vertical, make it vertical // if (lAngle > M_PI_2+M_PI/12) { // label norm angle > 90 + 15 = 105 // lAngle -= M_PI; // lAngle - 180 Flip // } else if (lAngle <= -M_PI_2+M_PI/12) { // < -90 + 15 = - 85 // lAngle += M_PI; // langle + 180 Flip // } float bbX = datumLabel->boundingRect().width(); float bbY = datumLabel->boundingRect().height(); datumLabel->setTransformOriginPoint(bbX / 2., bbY /2.); datumLabel->setRotation(lAngle * 180 / M_PI); } //endif Distance/Diameter/Radius/Angle //this is already handled in select() and hover() // // redraw the Dimension and the parent View // if (datumLabel->hasHover && !datumLabel->isSelected()) { // setPrettyPre(); // } else if (datumLabel->isSelected()) { // setPrettySel(); // } else { // setPrettyNormal(); // } update(); if (parentItem()) { //TODO: parent redraw still required with new frame/label?? parentItem()->update(); } else { Base::Console().Log("INFO - QGIVD::draw - no parent to update\n"); } } double QGIViewDimension::getIsoStandardLinePlacement(double labelAngle) { // According to ISO 129-1 Standard Figure 23, the bordering angle is 2/3 PI, resp. -1/3 PI // As Qt Y axis points downwards, all signs are flipped return labelAngle > +M_PI/3.0 || labelAngle < -2.0*M_PI/3.0 ? -1.0 : +1.0; } double QGIViewDimension::computeLineAndLabelAngles(Base::Vector2d lineTarget, Base::Vector2d labelCenter, double lineLabelDistance, double &lineAngle, double &labelAngle) { // By default horizontal line and no label rotation lineAngle = 0.0; labelAngle = 0.0; Base::Vector2d rawDirection(labelCenter - lineTarget); double rawDistance = rawDirection.Length(); if (rawDistance < Precision::Confusion()) { // Almost single point, can't tell return 0.0; } double rawAngle = atan2(rawDirection.y, rawDirection.x); lineAngle = rawAngle; // If we are too close to the line origin, no further adjustments if (lineLabelDistance >= rawDistance) { return 0.0; } // Rotate the line by angle between the label rectangle center and label bottom side center double devAngle = getIsoStandardLinePlacement(rawAngle)*asin(lineLabelDistance/rawDistance); lineAngle = addAngles(lineAngle, devAngle); labelAngle = devAngle > 0.0 ? lineAngle : addAngles(lineAngle, M_PI); return devAngle; } bool QGIViewDimension::computeLineRectangleExitPoint(const QRectF &rectangle, Base::Vector2d targetPoint, Base::Vector2d &exitPoint) { if (targetPoint.x > rectangle.left() && targetPoint.x < rectangle.right() && targetPoint.y > rectangle.top() && targetPoint.y < rectangle.bottom()) { // Target point is inside the rectangle - no crossing at all return false; } Base::Vector2d lineOrigin(rectangle.center().x(), rectangle.center().y()); Base::Vector2d direction = targetPoint - lineOrigin; if (fabs(direction.y) >= Precision::Confusion()) { // The line is not parallel with X axis exitPoint.y = direction.y < 0 ? rectangle.top() : rectangle.bottom(); exitPoint.x = lineOrigin.x + direction.x*(exitPoint.y - lineOrigin.y)/direction.y; if (exitPoint.x >= rectangle.left() && exitPoint.x <= rectangle.right()) { return true; } } if (fabs(direction.x) >= Precision::Confusion()) { // The line is not parallel with Y axis exitPoint.x = direction.x < 0 ? rectangle.left() : rectangle.right(); exitPoint.y = lineOrigin.y + direction.y*(exitPoint.x - lineOrigin.x)/direction.x; if (exitPoint.y >= rectangle.top() && exitPoint.y <= rectangle.bottom()) { return true; } } return false; } Base::Vector2d QGIViewDimension::computeLineOriginPoint(Base::Vector2d lineTarget, double projectedLabelDistance, double lineAngle, double labelWidth, double direction) const { return lineTarget + (projectedLabelDistance + direction*(0.5*labelWidth + getDefaultReferenceLineOverhang())) *Base::Vector2d(cos(lineAngle), sin(lineAngle)); } Base::Vector2d QGIViewDimension::getIsoJointPoint(Base::Vector2d labelCenter, double width, double dir) const { return Base::Vector2d(labelCenter.x + dir*(width*0.5 + getDefaultReferenceLineOverhang()), labelCenter.y + getDefaultTextVerticalOffset()); } Base::Vector2d QGIViewDimension::getAsmeJointPoint(Base::Vector2d labelCenter, double width, double dir) const { return Base::Vector2d(labelCenter.x + dir*(width*0.5 + getDefaultHorizontalLeaderLength()), labelCenter.y + TextOffsetFudge); } void QGIViewDimension::drawRadius(TechDraw::DrawViewDimension *dimension, ViewProviderDimension *viewProvider) const { // Preferred terminology according to ISO 129-1 for Radius: // Dimensional Value, Leader Line, Reference Line, Terminator QPainterPath radiusPath; datumLabel->setRotation(0.0); aHead1->setRotation(0.0); aHead1->setFlipped(false); QRectF mappedRect = mapRectFromItem(datumLabel, datumLabel->boundingRect()); Base::Vector2d labelCenter = Base::Vector2d(mappedRect.center().x(), mappedRect.center().y()); arcPoints curvePoints = dimension->getArcPoints(); Base::Vector2d curveCenter = Rez::guiX(curvePoints.center, true); double mappedRadius = Rez::guiX(curvePoints.radius); double centerDistance = (labelCenter - curveCenter).Length(); double arcStartAngle; double arcEndAngle; bool arcClockwise; if (curvePoints.isArc) { arcStartAngle = atan2(curvePoints.arcEnds.first.y - curvePoints.center.y, curvePoints.arcEnds.first.x - curvePoints.center.x); arcEndAngle = atan2(curvePoints.arcEnds.second.y - curvePoints.center.y, curvePoints.arcEnds.second.x - curvePoints.center.x); arcClockwise = !curvePoints.arcCW; } else { // A circle arc covers the whole plane arcStartAngle = -M_PI; arcEndAngle = +M_PI; arcClockwise = false; } double labelAngle = 0.0; Base::Vector2d arcPoint; double lineAngle; int standardStyle = viewProvider->StandardAndStyle.getValue(); if (standardStyle == ViewProviderDimension::STD_STYLE_ISO_LEVELLED || standardStyle == ViewProviderDimension::STD_STYLE_ASME_REGULAR) { // The dimensional value text must stay horizontal Base::Vector2d leftJoint, rightJoint; if (standardStyle == ViewProviderDimension::STD_STYLE_ISO_LEVELLED) { leftJoint = getIsoJointPoint(labelCenter, mappedRect.width(), -1.0); rightJoint = getIsoJointPoint(labelCenter, mappedRect.width(), +1.0); } else { leftJoint = getAsmeJointPoint(labelCenter, mappedRect.width(), -1.0); rightJoint = getAsmeJointPoint(labelCenter, mappedRect.width(), +1.0); } double leftAngle = atan2(leftJoint.y - curveCenter.y, leftJoint.x - curveCenter.x); double rightAngle = atan2(rightJoint.y - curveCenter.y, rightJoint.x - curveCenter.x); int leftPosition = classifyPointToArcPosition((leftJoint - curveCenter).Length(), leftAngle, mappedRadius, arcStartAngle, arcEndAngle, arcClockwise); int rightPosition = classifyPointToArcPosition((rightJoint - curveCenter).Length(), rightAngle, mappedRadius, arcStartAngle, arcEndAngle, arcClockwise); Base::Vector2d originPoint; Base::Vector2d jointPoint; Base::Vector2d targetPoint; if (leftPosition <= OPPOSITE_SECTOR || rightPosition <= OPPOSITE_SECTOR) { // At least from one of the reference line sides can run the leader line // perpendicularly to the arc, i.e. in direction to the center if (leftPosition <= OPPOSITE_SECTOR && rightPosition <= OPPOSITE_SECTOR) { // Both are acceptable, so choose the more convenient one double leftBend = leftPosition == INNER_SECTOR ? M_PI - fabs(leftAngle) : fabs(leftAngle); double rightBend = rightPosition == INNER_SECTOR ? fabs(rightAngle) : M_PI - fabs(rightAngle); // If right leader line bends less or does not cross the dimensional value, // use it by marking left point as outlayer if (leftBend <= M_PI_2 || rightBend <= M_PI_2 || standardStyle == ViewProviderDimension::STD_STYLE_ASME_REGULAR) { // Either at least one line does not cross the text, or it is an ASME connection // (vertically centered), which behaves the same going up or down if (rightBend < leftBend) { leftPosition = COMPLEMENT_SECTOR; } } else { // ISO connection, but crosses the value - try to find the one pointing down (if exists) bool leftDown = leftPosition == INNER_SECTOR ? leftAngle > 0.0 : leftAngle < 0.0; bool rightDown = rightPosition == INNER_SECTOR ? rightAngle > 0.0 : rightAngle < 0.0; if (leftDown == rightDown) { // Both lines go downwards or upwards if (rightBend < leftBend) { leftPosition = COMPLEMENT_SECTOR; } } else if (rightDown) { leftPosition = COMPLEMENT_SECTOR; } } } int resultPosition; if (leftPosition <= OPPOSITE_SECTOR) { if (standardStyle == ViewProviderDimension::STD_STYLE_ASME_REGULAR) { originPoint = Base::Vector2d(labelCenter.x + getDefaultTextHorizontalOffset(-1.0), labelCenter.y + TextOffsetFudge); } else { originPoint = rightJoint; } jointPoint = leftJoint; lineAngle = leftAngle; resultPosition = leftPosition; } else { if (standardStyle == ViewProviderDimension::STD_STYLE_ASME_REGULAR) { originPoint = Base::Vector2d(labelCenter.x + getDefaultTextHorizontalOffset(+1.0), labelCenter.y + TextOffsetFudge); } else { originPoint = leftJoint; } jointPoint = rightJoint; lineAngle = rightAngle; resultPosition = rightPosition; } switch (resultPosition) { case INNER_SECTOR: arcPoint = curveCenter + mappedRadius*Base::Vector2d(cos(lineAngle), sin(lineAngle)); targetPoint = arcPoint; break; case OUTER_SECTOR: arcPoint = curveCenter + mappedRadius*Base::Vector2d(cos(lineAngle), sin(lineAngle)); // If desired, extend the target point to the center if (viewProvider->ExtendToCenter.getValue()) { targetPoint = curveCenter; if (standardStyle == ViewProviderDimension::STD_STYLE_ISO_LEVELLED) { aHead1->flip(); } } else { targetPoint = arcPoint; aHead1->flip(); } break; case OPPOSITE_SECTOR: arcPoint = curveCenter - mappedRadius*Base::Vector2d(cos(lineAngle), sin(lineAngle)); targetPoint = arcPoint; aHead1->flip(); break; } } else { // Both joint points lay outside the vertical angles arcPoint = Rez::guiX(curvePoints.midArc, true); if (labelCenter.x >= arcPoint.x) { // Place the dimensional value right if (standardStyle == ViewProviderDimension::STD_STYLE_ASME_REGULAR) { originPoint = Base::Vector2d(mappedRect.left(), labelCenter.y); } else { originPoint = rightJoint; } jointPoint = leftJoint; } else { // Place the dimensional value left if (standardStyle == ViewProviderDimension::STD_STYLE_ASME_REGULAR) { originPoint = Base::Vector2d(mappedRect.right(), labelCenter.y); } else { originPoint = leftJoint; } jointPoint = rightJoint; } targetPoint = arcPoint; lineAngle = atan2(targetPoint.y - jointPoint.y, targetPoint.x - jointPoint.x); } radiusPath.moveTo(originPoint.x, originPoint.y); radiusPath.lineTo(jointPoint.x, jointPoint.y); radiusPath.lineTo(targetPoint.x, targetPoint.y); } else if (standardStyle == ViewProviderDimension::STD_STYLE_ISO_ORIENTED) { // We may rotate the label so no reference line is needed double devAngle = computeLineAndLabelAngles(curveCenter, labelCenter, getDefaultTextVerticalOffset(), lineAngle, labelAngle); // Correct the label center distance projected on the leader line centerDistance *= cos(devAngle); Base::Vector2d originPoint; Base::Vector2d targetPoint; switch (classifyPointToArcPosition(centerDistance, lineAngle, mappedRadius, arcStartAngle, arcEndAngle, arcClockwise)) { case INNER_SECTOR: { // The label is placed within the arc sector angle, there's always point // on the arc where the leader line can cross it perpendicularly arcPoint = curveCenter + mappedRadius*Base::Vector2d(cos(lineAngle), sin(lineAngle)); if (viewProvider->ExtendToCenter.getValue()) { // Start in the very center originPoint = curveCenter; } else { // Start on the label side closer to the center originPoint = computeLineOriginPoint(curveCenter, centerDistance, lineAngle, mappedRect.width(), -1.0); } targetPoint = arcPoint; break; } case OUTER_SECTOR: { // Same situation as when on the inner side of sector arcPoint = curveCenter + mappedRadius*Base::Vector2d(cos(lineAngle), sin(lineAngle)); aHead1->flip(); originPoint = computeLineOriginPoint(curveCenter, centerDistance, lineAngle, mappedRect.width(), +1.0); // If leader line shall not be extended to the center, start on the arc projection targetPoint = viewProvider->ExtendToCenter.getValue() ? curveCenter : arcPoint; break; } case OPPOSITE_SECTOR: { // If the label is placed within the vertically opposite angle of the arc sector, // the leader line passing through the arc center can mark a point on the arc arcPoint = curveCenter - mappedRadius*Base::Vector2d(cos(lineAngle), sin(lineAngle)); aHead1->flip(); originPoint = computeLineOriginPoint(curveCenter, centerDistance, lineAngle, mappedRect.width(), +1.0); targetPoint = arcPoint; break; } default: { // Label outside both arc wedges arcPoint = Rez::guiX(curvePoints.midArc, true); aHead1->flip(); devAngle = computeLineAndLabelAngles(arcPoint, labelCenter, getDefaultTextVerticalOffset(), lineAngle, labelAngle); centerDistance = (labelCenter - arcPoint).Length()*cos(devAngle); originPoint = computeLineOriginPoint(arcPoint, centerDistance, lineAngle, mappedRect.width(), +1.0); targetPoint = arcPoint; break; } } // Draw only the leader line from start point to end point radiusPath.moveTo(originPoint.x, originPoint.y); radiusPath.lineTo(targetPoint.x, targetPoint.y); } else if (standardStyle == ViewProviderDimension::STD_STYLE_ASME_INLINED) { // Text must remain horizontal, but it may split the leader line Base::Vector2d lineDirection(labelCenter - curveCenter); lineAngle = atan2(lineDirection.y, lineDirection.x); Base::Vector2d exitPoint; switch (classifyPointToArcPosition(centerDistance, lineAngle, mappedRadius, arcStartAngle, arcEndAngle, arcClockwise)) { case INNER_SECTOR: { // The label is placed within the arc sector angle, there's always point // on the arc where the leader line can cross it perpendicularly arcPoint = curveCenter + mappedRadius*Base::Vector2d(cos(lineAngle), sin(lineAngle)); if (computeLineRectangleExitPoint(mappedRect, arcPoint, exitPoint)) { radiusPath.moveTo(exitPoint.x, exitPoint.y); radiusPath.lineTo(arcPoint.x, arcPoint.y); } if (viewProvider->ExtendToCenter.getValue() && computeLineRectangleExitPoint(mappedRect, curveCenter, exitPoint)) { radiusPath.moveTo(exitPoint.x, exitPoint.y); radiusPath.lineTo(curveCenter.x, curveCenter.y); } break; } case OUTER_SECTOR: { // Same situation as when on the inner side of sector arcPoint = curveCenter + mappedRadius*Base::Vector2d(cos(lineAngle), sin(lineAngle)); Base::Vector2d targetPoint(viewProvider->ExtendToCenter.getValue() ? curveCenter : arcPoint); if (computeLineRectangleExitPoint(mappedRect, targetPoint, exitPoint)) { radiusPath.moveTo(exitPoint.x, exitPoint.y); radiusPath.lineTo(targetPoint.x, targetPoint.y); } if (!viewProvider->ExtendToCenter.getValue()) { aHead1->flip(); } break; } case OPPOSITE_SECTOR: { // If the label is placed within the vertically opposite angle of the arc sector, // the leader line passing through the arc center can mark a point on the arc arcPoint = curveCenter - mappedRadius*Base::Vector2d(cos(lineAngle), sin(lineAngle)); if (computeLineRectangleExitPoint(mappedRect, arcPoint, exitPoint)) { radiusPath.moveTo(exitPoint.x, exitPoint.y); radiusPath.lineTo(arcPoint.x, arcPoint.y); } aHead1->flip(); break; } default: { // Label outside both arc wedges arcPoint = Rez::guiX(curvePoints.midArc, true); lineDirection = labelCenter - arcPoint; lineAngle = atan2(lineDirection.y, lineDirection.x); if (computeLineRectangleExitPoint(mappedRect, arcPoint, exitPoint)) { radiusPath.moveTo(exitPoint.x, exitPoint.y); radiusPath.lineTo(arcPoint.x, arcPoint.y); } aHead1->flip(); break; } } } else { Base::Console().Error("QGIVD::drawRadius - this Standard&Style is not supported: %d\n", standardStyle); return; } datumLabel->setTransformOriginPoint(datumLabel->boundingRect().width()*0.5, datumLabel->boundingRect().height()*0.5); datumLabel->setRotation(labelAngle*180.0/M_PI); dimLines->setPath(radiusPath); aHead1->setPos(arcPoint.x, arcPoint.y); aHead1->setDirMode(true); aHead1->setDirection(lineAngle); if (viewProvider->FlipArrowheads.getValue()) { aHead1->flip(); } aHead1->setStyle(QGIArrow::getPrefArrowStyle()); aHead1->setSize(QGIArrow::getPrefArrowSize()); aHead1->draw(); aHead1->show(); aHead2->hide(); } QColor QGIViewDimension::getNormalColor() { Base::Reference hGrp = App::GetApplication().GetUserParameter() .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Dimensions"); App::Color fcColor; fcColor.setPackedValue(hGrp->GetUnsigned("Color", 0x00000000)); m_colNormal = fcColor.asValue(); auto dim( dynamic_cast(getViewObject()) ); if( dim == nullptr ) return m_colNormal; auto vp = static_cast(getViewProvider(getViewObject())); if ( vp == nullptr ) { return m_colNormal; } m_colNormal = vp->Color.getValue().asValue(); return m_colNormal; } //! find the closest isometric axis given an ortho vector Base::Vector3d QGIViewDimension::findIsoDir(Base::Vector3d ortho) { std::vector isoDirs = { Base::Vector3d(0.866,0.5,0.0), //iso X Base::Vector3d(-0.866,-0.5,0.0), //iso -X Base::Vector3d(-0.866,0.5,0.0), //iso -Y? Base::Vector3d(0.866,-0.5,0.0), //iso +Y? Base::Vector3d(0.0,-1.0,0.0), //iso -Z Base::Vector3d(0.0,1.0,0.0) }; //iso Z std::vector angles; for (auto& iso: isoDirs) { angles.push_back(ortho.GetAngle(iso)); } int idx = 0; double min = angles[0]; for (int i = 1; i < 6; i++) { if (angles[i] < min) { idx = i; min = angles[i]; } } return isoDirs[idx]; } //! find the iso extension direction corresponding to an iso dist direction Base::Vector3d QGIViewDimension::findIsoExt(Base::Vector3d dir) { Base::Vector3d dirExt(1,0,0); Base::Vector3d isoX(0.866,0.5,0.0); //iso X Base::Vector3d isoXr(-0.866,-0.5,0.0); //iso -X Base::Vector3d isoY(-0.866,0.5,0.0); //iso -Y? Base::Vector3d isoYr(0.866,-0.5,0.0); //iso +Y? Base::Vector3d isoZ(0.0,1.0,0.0); //iso Z Base::Vector3d isoZr(0.0,-1.0,0.0); //iso -Z if (dir.IsEqual(isoX, FLT_EPSILON)) { dirExt = isoY; } else if (dir.IsEqual(-isoX, FLT_EPSILON)) { dirExt = -isoY; } else if (dir.IsEqual(isoY, FLT_EPSILON)) { dirExt = isoZ; } else if (dir.IsEqual(-isoY, FLT_EPSILON)) { dirExt = -isoZ; } else if (dir.IsEqual(isoZ, FLT_EPSILON)) { dirExt = isoX; } else if (dir.IsEqual(-isoZ, FLT_EPSILON)) { dirExt = -isoX; } else { //tarfu Base::Console().Message("QGIVD::findIsoExt - %s - input is not iso axis\n", getViewObject()->getNameInDocument()); } return dirExt; } void QGIViewDimension::setPrettyPre(void) { aHead1->setPrettyPre(); aHead2->setPrettyPre(); dimLines->setPrettyPre(); } void QGIViewDimension::setPrettySel(void) { aHead1->setPrettySel(); aHead2->setPrettySel(); dimLines->setPrettySel(); } void QGIViewDimension::setPrettyNormal(void) { aHead1->setPrettyNormal(); aHead2->setPrettyNormal(); dimLines->setPrettyNormal(); } void QGIViewDimension::drawBorder(void) { //Dimensions have no border! // Base::Console().Message("TRACE - QGIViewDimension::drawBorder - doing nothing!\n"); } const double QGIViewDimension::TextOffsetFudge = 2.0; double QGIViewDimension::getDefaultTextHorizontalOffset(double direction) const { return direction*(datumLabel->boundingRect().width()*0.5 + TextOffsetFudge*2.0); } double QGIViewDimension::getDefaultTextVerticalOffset() const { TechDraw::DrawViewDimension *dim = dynamic_cast(getViewObject()); ViewProviderDimension *vp = static_cast(getViewProvider(getViewObject())); double textMult = 1.0; if (dim->hasTolerance()) { textMult += datumLabel->getTolAdjust(); } return textMult*Rez::guiX(vp->Fontsize.getValue()) + TextOffsetFudge; } double QGIViewDimension::getDefaultReferenceLineOverhang() const { return 2.0*TextOffsetFudge; } double QGIViewDimension::getDefaultHorizontalLeaderLength() const { QFontMetrics fontMetrics(datumLabel->getFont()); return 1.5*fontMetrics.width(QChar::fromLatin1('M')); } bool QGIViewDimension::angleWithinSector(double testAngle, double startAngle, double endAngle, bool clockwise) { if (clockwise) { std::swap(startAngle, endAngle); } if (endAngle < startAngle) { endAngle += M_2PI; } if (testAngle < startAngle) { testAngle += M_2PI; } return testAngle <= endAngle; } double QGIViewDimension::addAngles(double angle1, double angle2) { angle1 += angle2; if (angle2 >= 0.0) { if (angle1 > +M_PI) angle1 -= M_2PI; return angle1; } else { if (angle1 < -M_PI) angle1 += M_2PI; return angle1; } } int QGIViewDimension::classifyPointToArcPosition(double pointDistance, double pointAngle, double radius, double startAngle, double endAngle, bool clockwise) { if (angleWithinSector(pointAngle, startAngle, endAngle, clockwise)) { return pointDistance > radius ? OUTER_SECTOR : INNER_SECTOR; } if (angleWithinSector(addAngles(pointAngle, M_PI), startAngle, endAngle, clockwise)) { return OPPOSITE_SECTOR; } return COMPLEMENT_SECTOR; } //frame, border, caption are never shown in QGIVD, so shouldn't be in bRect QRectF QGIViewDimension::boundingRect() const { QRectF labelRect = mapFromItem(datumLabel, datumLabel->boundingRect()).boundingRect(); QRectF linesRect = mapFromItem(dimLines, dimLines->boundingRect()).boundingRect(); QRectF aHead1Rect = mapFromItem(aHead1, aHead1->boundingRect()).boundingRect(); QRectF aHead2Rect = mapFromItem(aHead2, aHead2->boundingRect()).boundingRect(); QRectF result(labelRect); result = result.united(linesRect); result = result.united(aHead1Rect); result = result.united(aHead2Rect); return result; } void QGIViewDimension::paint ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) { QStyleOptionGraphicsItem myOption(*option); myOption.state &= ~QStyle::State_Selected; QPaintDevice* hw = painter->device(); QSvgGenerator* svg = dynamic_cast(hw); setPens(); //double arrowSaveWidth = aHead1->getWidth(); if (svg) { setSvgPens(); } else { setPens(); } // painter->drawRect(boundingRect()); //good for debugging // QGIView::paint (painter, &myOption, widget); QGraphicsItemGroup::paint(painter, &myOption, widget); setPens(); } void QGIViewDimension::setSvgPens(void) { double svgLineFactor = 3.0; //magic number. should be a setting somewhere. dimLines->setWidth(m_lineWidth/svgLineFactor); aHead1->setWidth(aHead1->getWidth()/svgLineFactor); aHead2->setWidth(aHead2->getWidth()/svgLineFactor); } void QGIViewDimension::setPens(void) { dimLines->setWidth(m_lineWidth); aHead1->setWidth(m_lineWidth); aHead2->setWidth(m_lineWidth); } #include