/**************************************************************************** ** ** This file is part of the LibreCAD project, a 2D CAD program ** ** Copyright (C) 2021 Melwyn Francis Carlo ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file gpl-2.0.txt included in the ** packaging of this file. ** ** This program 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ** ** This copyright notice MUST APPEAR in all copies of the script! ** **********************************************************************/ #include "lc_dimarc.h" #include #include "lc_rect.h" #include "rs_arc.h" #include "rs_debug.h" #include "rs_graphic.h" #include "rs_line.h" #include "rs_solid.h" #include "rs_units.h" namespace{ // trunc a floating point to multiplier of the giving unitLength void truncF(double& length, double unitLength) { length -= std::fmod(length, unitLength); } // trunc a point to a 2D grid with spacing of the given unit length. void truncF(RS_Vector& point, double unitLength) { truncF(point.x, unitLength); truncF(point.y, unitLength); } } LC_DimArcData::LC_DimArcData(const LC_DimArcData& other) :radius(other.radius), arcLength(other.arcLength), centre(other.centre), endAngle(other.endAngle), startAngle(other.startAngle) { } LC_DimArcData::LC_DimArcData(double input_radius, double input_arcLength, const RS_Vector& input_centre, const RS_Vector& input_endAngle, const RS_Vector& input_startAngle) :radius(input_radius), arcLength(input_arcLength), centre(input_centre), endAngle(input_endAngle), startAngle(input_startAngle) { } LC_DimArc::LC_DimArc(const LC_DimArc& other): RS_Dimension(other), m_dimArcData{other.m_dimArcData} { } LC_DimArc::LC_DimArc(RS_EntityContainer* parent, const RS_DimensionData& input_commonDimData, const LC_DimArcData& input_dimArcData) :RS_Dimension(parent, input_commonDimData), m_dimArcData(input_dimArcData) { LC_DimArc::update(); } RS_Entity* LC_DimArc::clone() const { LC_DimArc* clone = new LC_DimArc(*this); return clone; } QString LC_DimArc::getMeasuredLabel() { RS_Graphic* currentGraphic = getGraphic(); QString measuredLabel; if (currentGraphic) { const int dimlunit{getGraphicVariableInt(QStringLiteral("$DIMLUNIT"), 2)}; const int dimdec{getGraphicVariableInt(QStringLiteral("$DIMDEC"), 4)}; const int dimzin{getGraphicVariableInt(QStringLiteral("$DIMZIN"), 1)}; RS2::LinearFormat format = currentGraphic->convertLinearFormatDXF2LC(dimlunit); measuredLabel = RS_Units::formatLinear(m_dimArcData.arcLength, getGraphicUnit(), format, dimdec); if (format == RS2::Decimal) measuredLabel = stripZerosLinear(measuredLabel, dimzin); if ((format == RS2::Decimal) || (format == RS2::ArchitecturalMetric)) { if (getGraphicVariableInt("$DIMDSEP", 0) == 44) measuredLabel.replace(QChar('.'), QChar(',')); } } else { measuredLabel = QString("%1").arg(m_dimArcData.arcLength); } return measuredLabel; } void LC_DimArc::arrow(const RS_Vector& point, const double angle, const double direction, const RS_Pen& pen) { if ((getTickSize() * getGeneralScale()) < 0.01) { double endAngle{0.0}; if (m_dimArcData.radius > RS_TOLERANCE_ANGLE) endAngle = getArrowSize() / m_dimArcData.radius; const RS_Vector arrowEnd = RS_Vector::polar(m_dimArcData.radius, angle + std::copysign(endAngle, direction)) + m_dimArcData.centre; const double arrowAngle{arrowEnd.angleTo(point)}; RS_SolidData dummyVar; RS_Solid* arrow = new RS_Solid(this, dummyVar); arrow->shapeArrow(point, arrowAngle, getArrowSize() * getGeneralScale()); arrow->setPen(pen); arrow->setLayer(nullptr); addEntity(arrow); } else { const double deg45 = M_PI_2 / 2.0; const double midAngle = (m_dimArcData.startAngle.angle() + m_dimArcData.endAngle.angle()) / 2.0; const RS_Vector tickVector = RS_Vector::polar(getTickSize() * getGeneralScale(), midAngle - deg45); auto* tick = new RS_Line(this, point - tickVector, point + tickVector); tick->setPen(pen); tick->setLayer(nullptr); addEntity(tick); } } void LC_DimArc::doUpdateDim() { RS_DEBUG->print("LC_DimArc::update"); clear(); if (isUndone()) return; if (!m_dimArcData.centre.valid) return; calcDimension(); RS_Pen pen(getExtensionLineColor(), getExtensionLineWidth(), RS2::LineByBlock); pen.setWidth(getDimensionLineWidth()); pen.setColor(getDimensionLineColor()); extLine1->setPen(pen); extLine2->setPen(pen); extLine1->setLayer(nullptr); extLine2->setLayer(nullptr); addEntity(extLine1); addEntity(extLine2); RS_Arc* refArc{ new RS_Arc(this,RS_ArcData(m_dimArcData.centre, m_dimArcData.radius, m_dimArcData.startAngle.angle(), m_dimArcData.endAngle.angle(), false) ) }; arrow(arrowStartPoint, m_dimArcData.startAngle.angle(), +1.0, pen); arrow(arrowEndPoint, m_dimArcData.endAngle.angle(), -1.0, pen); double textAngle{0.0}; RS_Vector textPos{refArc->getMiddlePoint()}; const double textAngle_preliminary{std::trunc((textPos.angleTo(m_dimArcData.centre) - M_PI) * 1.0E+10) * 1.0E-10}; if (!this->getInsideHorizontalText()) { RS_Vector textPosOffset; constexpr double deg360{M_PI * 2.0}; constexpr double degTolerance{1.0E-3}; /* With regards to Quadrants #1 and #2 */ if (((textAngle_preliminary >= -degTolerance) && (textAngle_preliminary <= (M_PI + degTolerance))) || ((textAngle_preliminary <= -(M_PI - degTolerance)) && (textAngle_preliminary >= -(deg360 + degTolerance)))) { textPosOffset.setPolar(getDimensionLineGap() * getGeneralScale(), textAngle_preliminary); textAngle = textAngle_preliminary + M_PI + M_PI_2; } /* With regards to Quadrants #3 and #4 */ else { textPosOffset.setPolar(getDimensionLineGap() * getGeneralScale(), textAngle_preliminary + M_PI); textAngle = textAngle_preliminary + M_PI_2; } } QString dimLabel{getLabel()}; bool ok = false; dimLabel.toDouble(&ok); if (ok) dimLabel.prepend("∩ "); RS_MTextData textData { RS_MTextData(textPos, getTextHeight() * getGeneralScale(), 30.0, RS_MTextData::VABottom, RS_MTextData::HACenter, RS_MTextData::LeftToRight, RS_MTextData::Exact, 1.0, dimLabel, QString("unicode"), textAngle) }; auto text = std::make_unique(this, textData); text->setPen(RS_Pen(getTextColor(), RS2::WidthByBlock, RS2::SolidLine)); text->setLayer(nullptr); addEntity(text.get()); double halfWidth_plusGap = (text->getUsedTextWidth() / 2.0) + getDimensionLineGap() * getGeneralScale(); double halfHeight_plusGap = (getTextHeight() / 2.0) + getDimensionLineGap() * getGeneralScale(); text->move(-RS_Vector::polar(getTextHeight() / 2.0, textAngle + M_PI_2)); /* Text rectangle's corners : top left, top right, bottom right, bottom left. */ RS_Vector textRectCorners[4] = { RS_Vector(false), RS_Vector(textPos + RS_Vector(+halfWidth_plusGap, +halfHeight_plusGap)), RS_Vector(false), RS_Vector(textPos + RS_Vector(-halfWidth_plusGap, -halfHeight_plusGap)) }; RS_Vector cornerTopRight{textRectCorners[1]}; RS_Vector cornerBottomLeft{textRectCorners[3]}; LC_Rect textRect{cornerBottomLeft, cornerTopRight}; textRectCorners[0] = textRect.upperLeftCorner(); textRectCorners[2] = textRect.lowerRightCorner(); LC_Rect textRectRotated{}; for (RS_Vector& corner : textRectCorners) { corner.rotate(textPos, text->getAngle()); truncF(corner, 1.0E-4); textRectRotated = textRectRotated.merge(corner); } text.release(); if (RS_DEBUG->getLevel() == RS_Debug::D_INFORMATIONAL) { std::cout << std::endl << " LC_DimArc::updateEntity: Text position / angle : " << textPos << " / " << text->getAngle() << std::endl; std::cout << std::endl << " LC_DimArc::updateEntity: Reference arc middle point : " << refArc->getMiddlePoint() << std::endl; std::cout << std::endl << " LC_DimArc::updateEntity: DimArc-1 start point : " << dimArc1->getStartpoint() << std::endl; std::cout << std::endl << " LC_DimArc::updateEntity: DimArc-2 start point : " << dimArc2->getStartpoint() << std::endl; std::cout << std::endl << " LC_DimArc::updateEntity: Text rectangle corners : " << textRectCorners[0] << ", " << textRectCorners[1] << ", " << textRectCorners[2] << ", " << textRectCorners[3] << std::endl; } //TODO: the current algorithm to find dimArc1/dimArc2 angles could be // costly. constexpr double deltaOffset{1.0E-2}; while (!textRectRotated.inArea(dimArc1->getEndpoint()) && (dimArc1->getAngle2() < RS_MAXDOUBLE) && (dimArc1->getAngle2() > RS_MINDOUBLE)) { dimArc1->setAngle2(dimArc1->getAngle2() + deltaOffset); } while (!textRectRotated.inArea(dimArc2->getStartpoint()) && (dimArc2->getAngle1() < RS_MAXDOUBLE) && (dimArc2->getAngle1() > RS_MINDOUBLE)) { dimArc2->setAngle1(dimArc2->getAngle1() - deltaOffset); } dimArc1->setPen(pen); dimArc2->setPen(pen); dimArc1->setLayer(nullptr); dimArc2->setLayer(nullptr); addEntity(dimArc1); addEntity(dimArc2); calculateBorders(); } void LC_DimArc::update() { RS_Dimension::update(); LC_DimArc::updateDim(); } void LC_DimArc::move(const RS_Vector& offset) { RS_Dimension::move(offset); m_dimArcData.centre.move(offset); update(); } void LC_DimArc::rotate(const RS_Vector& center, double angle) { rotate(center, RS_Vector(angle)); update(); } void LC_DimArc::rotate(const RS_Vector& center, const RS_Vector& angleVector) { RS_Dimension::rotate(center, angleVector); m_dimArcData.centre.rotate(center, angleVector); const double arcDeltaAngle{m_dimArcData.startAngle.angleTo(m_dimArcData.endAngle)}; m_dimArcData.startAngle = RS_Vector(m_dimGenericData.definitionPoint.angleTo(m_dimArcData.centre) - M_PI); m_dimArcData.endAngle = m_dimArcData.startAngle; m_dimArcData.endAngle.rotate(arcDeltaAngle); update(); } void LC_DimArc::scale(const RS_Vector& center, const RS_Vector& factor) { const double adjustedFactor = factor.x < factor.y ? factor.x : factor.y; const RS_Vector adjustedFactorVector(adjustedFactor, adjustedFactor); RS_Dimension::scale(center, adjustedFactorVector); m_dimArcData.centre.scale(center, adjustedFactorVector); m_dimArcData.radius *= adjustedFactor; update(); } void LC_DimArc::mirror(const RS_Vector& axisPoint1, const RS_Vector& axisPoint2) { RS_Dimension::mirror(axisPoint1, axisPoint2); m_dimArcData.centre.mirror(axisPoint1, axisPoint2); /* // Just another way of accomplishing the operation below this comment. dimStartPoint.mirror (axisPoint1, axisPoint2); dimEndPoint.mirror (axisPoint1, axisPoint2); dimArcData.startAngle = RS_Vector((dimStartPoint - dimArcData.centre).angle() - M_PI); dimArcData.endAngle = RS_Vector((dimEndPoint - dimArcData.centre).angle() - M_PI); */ const RS_Vector originPoint(0.0, 0.0); const RS_Vector deltaAxisPoints = axisPoint2 - axisPoint1; /* The minus one (-1) value denotes that mirroring changes direction (and hence, sign). */ m_dimArcData.startAngle.setPolar(-1.0, m_dimArcData.startAngle.angle()); m_dimArcData.endAngle.setPolar(-1.0, m_dimArcData.endAngle.angle()); m_dimArcData.startAngle.mirror(originPoint, deltaAxisPoints); m_dimArcData.endAngle.mirror(originPoint, deltaAxisPoints); update(); } RS_Vector LC_DimArc::truncateVector(const RS_Vector input_vector) { return RS_Vector(std::trunc(input_vector.x * 1.0E+10) * 1.0E-10, std::trunc(input_vector.y * 1.0E+10) * 1.0E-10, 0.0); } void LC_DimArc::calcDimension() { const double endAngle = m_dimArcData.endAngle.angle(); const double startAngle = m_dimArcData.startAngle.angle(); dimArc1 = new RS_Arc(this, RS_ArcData(m_dimArcData.centre, m_dimArcData.radius, startAngle, startAngle, false)); dimArc2 = new RS_Arc(this, RS_ArcData(m_dimArcData.centre, m_dimArcData.radius, endAngle, endAngle, false)); RS_Vector entityStartPoint = truncateVector(m_dimGenericData.definitionPoint); const double entityRadius = m_dimArcData.centre.distanceTo(entityStartPoint); RS_Vector entityEndPoint = truncateVector(m_dimArcData.centre + RS_Vector(m_dimArcData.endAngle).scale(entityRadius)); dimStartPoint = m_dimArcData.centre + RS_Vector(m_dimArcData.startAngle).scale(m_dimArcData.radius); dimEndPoint = m_dimArcData.centre + RS_Vector(m_dimArcData.endAngle).scale(m_dimArcData.radius); arrowStartPoint = dimStartPoint; arrowEndPoint = dimEndPoint; entityStartPoint += RS_Vector::polar(getExtensionLineOffset(), entityStartPoint.angleTo(dimStartPoint)); entityEndPoint += RS_Vector::polar(getExtensionLineOffset(), entityEndPoint.angleTo(dimEndPoint)); dimStartPoint += RS_Vector::polar(getExtensionLineExtension(), entityStartPoint.angleTo(dimStartPoint)); dimEndPoint += RS_Vector::polar(getExtensionLineExtension(), entityEndPoint.angleTo(dimEndPoint)); extLine1 = new RS_Line(this, entityStartPoint, dimStartPoint); extLine2 = new RS_Line(this, entityEndPoint, dimEndPoint); /* RS_DEBUG->setLevel(RS_Debug::D_INFORMATIONAL); */ RS_DEBUG->print(RS_Debug::D_INFORMATIONAL, "\n LC_DimArc::calcDimension: Start / end angles : %lf / %lf\n", startAngle, endAngle); RS_DEBUG->print(RS_Debug::D_INFORMATIONAL, "\n LC_DimArc::calcDimension: Dimension / entity radii : %lf / %lf\n", m_dimArcData.radius, entityRadius); if (RS_DEBUG->getLevel() == RS_Debug::D_INFORMATIONAL) { std::cout << std::endl << " LC_DimArc::calcDimension: Start Points : " << entityStartPoint << " to " << dimStartPoint << std::endl; std::cout << std::endl << " LC_DimArc::calcDimension: End Points : " << entityEndPoint << " to " << dimEndPoint << std::endl; } } std::ostream& operator <<(std::ostream& os, const LC_DimArc& input_dimArc) { os << " DimArc Information : \n" << input_dimArc.getData() << std::endl << std::endl; return os; } std::ostream& operator <<(std::ostream& os, const LC_DimArcData& input_dimArcData) { os << " {\n\tCentre : " << input_dimArcData.centre << "\n\tRadius : " << input_dimArcData.radius << "\n\tStart Angle : " << input_dimArcData.startAngle << "\n\tEnd Angle : " << input_dimArcData.endAngle << "\n}" << std::endl << std::endl; return os; }