/*************************************************************************** * 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 "QGIArrow.h" #include "QGIDimLines.h" #include "QGIViewDimension.h" #include "ViewProviderDimension.h" #include "DrawGuiUtil.h" //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; } 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); 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); 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.pointSizeF(); double tolAdj = getTolAdjust(); tFont.setPointSizeF(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); 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); // toggleBorder(false); setZValue(ZVALUE::DIMENSION); //note: this won't paint dimensions over another View if it stacks //above this Dimension's parent view. need Layers? } 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::select(bool state) { setSelected(state); draw(); } void QGIViewDimension::hover(bool state) { hasHover = state; 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.setPointSizeF(calculateFontPointSizeF(this, 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()) { //should this be controlled by parent ViewPart? 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); 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) { // preferred terminology: Dimension Text, Dimension Line(s), Extension Lines, Arrowheads // radius gets 1 dimension line from the dimension text to a point on the curve Base::Vector3d pointOnCurve,curveCenter; double radius; arcPoints pts = dim->getArcPoints(); bool isArc = pts.isArc; radius = Rez::guiX(pts.radius); curveCenter = Rez::guiX(pts.center); pointOnCurve = Rez::guiX(pts.onCurve.first); QRectF mappedRect = mapRectFromItem(datumLabel, datumLabel->boundingRect()); lblCenter = Base::Vector3d(mappedRect.center().x(), mappedRect.center().y(), 0.0); // 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); datumLabel->setRotation(0.0); //label is always right side up & horizontal //if inside the arc (len(DimLine < radius)) arrow goes from center to edge away from label //if outside the arc arrow kinks, then goes to edge nearest label bool outerPlacement = false; if ((lblCenter - curveCenter).Length() > radius) { //label is outside circle outerPlacement = true; } Base::Vector3d dirDimLine = (lblCenter - curveCenter).Normalize(); if (fabs(dirDimLine.Length()) < (Precision::Confusion())) { dirDimLine = Base::Vector3d(-1.0,0.0,0.0); } // Base::Vector3d adjustDir = dirDimLine; //adjust line lengths for arrowheads double dimLineAdjust = Rez::guiX(QGIArrow::getOverlapAdjust(QGIArrow::getPrefArrowStyle(), QGIArrow::getPrefArrowSize())); Base::Vector3d dLineStart; Base::Vector3d dLineEnd; //?? radius draws line from text to curve?? (diam is curve to text!) Base::Vector3d kinkPoint; margin = Rez::guiX(5.f); //space around label double kinkLength = Rez::guiX(5.0); //sb % of horizontal dist(lblCenter,curveCenter)??? if (outerPlacement) { double offset = getDefaultTextHorizontalOffset(lblCenter.x > curveCenter.x); dLineStart.y = lblCenter.y; dLineStart.x = lblCenter.x + offset; //start at right or left of label kinkLength = (lblCenter.x < curveCenter.x) ? kinkLength : -kinkLength; kinkPoint.y = dLineStart.y; kinkPoint.x = dLineStart.x + kinkLength; pointOnCurve = curveCenter + (kinkPoint - curveCenter).Normalize() * radius; dLineEnd = pointOnCurve + (kinkPoint - curveCenter).Normalize() * dimLineAdjust; if ((kinkPoint - curveCenter).Length() < radius) { dirDimLine = (curveCenter - kinkPoint).Normalize(); } else { dirDimLine = (kinkPoint - curveCenter).Normalize(); } } else { dLineStart = curveCenter - dirDimLine * margin; //just beyond centerpoint pointOnCurve = curveCenter - dirDimLine * radius; dLineEnd = pointOnCurve + dirDimLine * dimLineAdjust; kinkPoint = dLineStart; //no kink } //handle partial arc weird cases if (isArc) { Base::Vector3d midPt = Rez::guiX(pts.midArc); Base::Vector3d startPt = Rez::guiX(pts.arcEnds.first); Base::Vector3d endPt = Rez::guiX(pts.arcEnds.second); if (outerPlacement && !dim->leaderIntersectsArc(Rez::appX(curveCenter),Rez::appX(kinkPoint))) { //keep pathological case within arc pointOnCurve = midPt; } else if (!outerPlacement) { if ((midPt - lblCenter).Length() > (midPt - curveCenter).Length()) { //label is farther than center dirDimLine = dirDimLine * -1; } dLineStart = curveCenter + dirDimLine * margin; pointOnCurve = curveCenter + dirDimLine * radius; dLineEnd = pointOnCurve - dirDimLine * dimLineAdjust; kinkPoint = dLineStart; if (!dim->leaderIntersectsArc(Rez::appX(dLineStart),Rez::appX(pointOnCurve))) { //keep pathological case within arc if ((pointOnCurve - endPt).Length() < (pointOnCurve - startPt).Length()) { if (!pts.arcCW ) { pointOnCurve = endPt; } else { pointOnCurve = startPt; } } else { if (!pts.arcCW ) { pointOnCurve = startPt; } else { pointOnCurve = endPt; } } dLineStart = curveCenter + (pointOnCurve - curveCenter).Normalize() * margin; dLineEnd = pointOnCurve - dirDimLine * dimLineAdjust; kinkPoint = dLineStart; } } } QPainterPath dLinePath; //radius dimension line path dLinePath.moveTo(dLineStart.x, dLineStart.y); dLinePath.lineTo(kinkPoint.x, kinkPoint.y); dLinePath.lineTo(dLineEnd.x, dLineEnd.y); dimLines->setPath(dLinePath); //NOTE: in this case aHead1->dirMode is false and Qt rotation is used to point arrowhead aHead1->setStyle(QGIArrow::getPrefArrowStyle()); aHead1->setSize(QGIArrow::getPrefArrowSize()); aHead1->draw(); Base::Vector3d ar1Pos = pointOnCurve; Base::Vector3d dirArrowLine = (pointOnCurve - kinkPoint).Normalize(); float arAngle = atan2(dirArrowLine.y, dirArrowLine.x) * 180 / M_PI; aHead1->setPos(ar1Pos.x, ar1Pos.y); aHead1->setRotation(arAngle); aHead1->show(); aHead2->hide(); // if (dim->CentreLines.getValue()) { // curveCenterMark->setPos(curveCenter.x,curveCenter.y); // centerMark->show(); // dim->getViewPart()->addVertex(curveCenter,true); // } } 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->flip(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 // redraw the Dimension and the parent View if (hasHover && !isSelected()) { aHead1->setPrettyPre(); aHead2->setPrettyPre(); dimLines->setPrettyPre(); } else if (isSelected()) { aHead1->setPrettySel(); aHead2->setPrettySel(); dimLines->setPrettySel(); } else { aHead1->setPrettyNormal(); aHead2->setPrettyNormal(); dimLines->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"); } } void QGIViewDimension::drawBorder(void) { //Dimensions have no border! // Base::Console().Message("TRACE - QGIViewDimension::drawBorder - doing nothing!\n"); } QVariant QGIViewDimension::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == ItemSelectedHasChanged && scene()) { if(isSelected()) { datumLabel->setSelected(true); } else { datumLabel->setSelected(false); } draw(); } return QGIView::itemChange(change, value); } 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); 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); } 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; } const double QGIViewDimension::TextOffsetFudge = 2.0; double QGIViewDimension::getDefaultTextHorizontalOffset(bool toLeft) const { double offset = datumLabel->boundingRect().width()*0.5 + TextOffsetFudge*2.0; // return toLeft ? -offset - TextOffsetFudge*5.0: offset; return toLeft ? -offset : offset; } 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; } QRectF QGIViewDimension::boundingRect() const { return childrenBoundingRect().adjusted(-3,-3,3,3); } #include