/**************************************************************************** ** ** This file is part of the LibreCAD project, a 2D CAD program ** ** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl) ** Copyright (C) 2001-2003 RibbonSoft. All rights reserved. ** ** ** 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 #include #include #include "rs_information.h" #include "rs_line.h" #include "rs_dimension.h" #include "rs_solid.h" #include "rs_units.h" #include "rs_math.h" #include "rs_filterdxfrw.h" //for int <-> rs_color conversion #include "rs_debug.h" RS_DimensionData::RS_DimensionData(): definitionPoint(false), middleOfText(false), valign(RS_MTextData::VABottom), halign(RS_MTextData::HALeft), lineSpacingStyle(RS_MTextData::Exact), lineSpacingFactor(0.0), text(""), style(""), angle(0.0) {} /** * Constructor with initialisation. * * @param definitionPoint Definition point. * @param middleOfText Middle point of dimension text. * @param valign Vertical alignment. * @param halign Horizontal alignment. * @param lineSpacingStyle Line spacing style. * @param lineSpacingFactor Line spacing factor. * @param text Text string entered explicitly by user or null * or "<>" for the actual measurement or " " (one blank space). * for suppressing the text. * @param style Dimension style name. * @param angle Rotation angle of dimension text away from * default orientation. */ RS_DimensionData::RS_DimensionData(const RS_Vector& _definitionPoint, const RS_Vector& _middleOfText, RS_MTextData::VAlign _valign, RS_MTextData::HAlign _halign, RS_MTextData::MTextLineSpacingStyle _lineSpacingStyle, double _lineSpacingFactor, QString _text, QString _style, double _angle): definitionPoint(_definitionPoint) ,middleOfText(_middleOfText) ,valign(_valign) ,halign(_halign) ,lineSpacingStyle(_lineSpacingStyle) ,lineSpacingFactor(_lineSpacingFactor) ,text(_text) ,style(_style) ,angle(_angle) { } std::ostream& operator << (std::ostream& os, const RS_DimensionData& dd) { os << "(" << dd.definitionPoint<<',' < is replaced by the measurement) else { ret = data.text; ret = ret.replace(QString("<>"), getMeasuredLabel()); } return ret; } /** * Sets a new text for the label. */ void RS_Dimension::setLabel(const QString& l) { data.text = l; } /** * Find intersections between a line and an EntityContainer. Solutions are * sorted along the line before returning. * * @param infiniteLine Treat the line as infinitely long in both directions. */ RS_VectorSolutions RS_Dimension::getIntersectionsLineContainer( const RS_Line* l, const RS_EntityContainer* c, bool infiniteLine) { RS_VectorSolutions solutions_initial; RS_VectorSolutions solutions_filtered; const double tol = 1.0e-4; // Find all intersections, including those beyond limits of container // entities. for(RS_Entity* e: *c) { solutions_initial.push_back( RS_Information::getIntersection(l, e, false) ); } // Filter solutions based on whether they are actually on any entities. for(const RS_Vector& vp: solutions_initial){ for(RS_Entity* e: *c) { if (e->isConstruction(true) || e->isPointOnEntity(vp, tol)) { // the intersection is at least on the container, now check the line: if (infiniteLine) { // The line is treated as infinitely long so we don't need to // check if the intersection is on the line. solutions_filtered.push_back(vp); break; } else if (l->isConstruction(true) || l->isPointOnEntity(vp, tol)) { solutions_filtered.push_back(vp); break; } } } } /** * We cannot sort the solutions in place because getVector() returns a * const vector, so first construct a copy: */ std::vector solutions_sorted(solutions_filtered.getVector()); std::sort(solutions_sorted.begin(), solutions_sorted.end(), [l](const RS_Vector& lhs, const RS_Vector& rhs) { return l->getProjectionValueAlongLine(lhs) < l->getProjectionValueAlongLine(rhs); }); return RS_VectorSolutions(solutions_sorted); } /** * Creates a horizontal-text dimensioning line (line with one, two or no arrows * and "inside horizontal" text). * * @param forceAutoText Automatically reposition the text label. */ void RS_Dimension::updateCreateHorizontalTextDimensionLine(const RS_Vector& p1, const RS_Vector& p2, bool arrow1, bool arrow2, bool forceAutoText) { // general scale (DIMSCALE) double dimscale = getGeneralScale(); // text height (DIMTXT) double dimtxt = getTextHeight()*dimscale; // text distance to line (DIMGAP) double dimgap = getDimensionLineGap()*dimscale; // length of dimension line: double distance = p1.distanceTo(p2); // arrow size: double arrowSize = getArrowSize()*dimscale; // arrow angles: double arrowAngle1, arrowAngle2; RS_Pen pen(getDimensionLineColor(), getDimensionLineWidth(), RS2::LineByBlock); // Create dimension line: RS_Line* dimensionLine {new RS_Line{this, p1, p2}}; RS_Line* dimensionLineInside1 {nullptr}; RS_Line* dimensionLineInside2 {nullptr}; RS_Line* dimensionLineOutside1 {nullptr}; RS_Line* dimensionLineOutside2 {nullptr}; dimensionLine->setPen(pen); dimensionLine->setLayer(nullptr); // Text label: RS_MTextData textData; RS_Vector textPos; double textAngle = 0.0; bool autoText = !data.middleOfText.valid || forceAutoText; if (autoText) { textPos = dimensionLine->getMiddlePoint(); //// the next update should still be able to adjust this //// auto text position. leave it invalid data.middleOfText = textPos; } else { textPos = data.middleOfText; } textData = RS_MTextData(textPos, dimtxt, 30.0, RS_MTextData::VAMiddle, RS_MTextData::HACenter, RS_MTextData::LeftToRight, RS_MTextData::Exact, 1.0, getLabel(), getTextStyle(), textAngle); RS_MText* text = new RS_MText(this, textData); text->setPen(RS_Pen(getTextColor(), RS2::WidthByBlock, RS2::SolidLine)); text->setLayer(nullptr); // evaluate intersection between dim line and text double textIntersectionLength = 0.0; double w = text->getUsedTextWidth()/2+dimgap; double h = text->getUsedTextHeight()/2+dimgap; // textCorner variables correspond to the corners of the text bounding box // if the text were to be positioned in the center of the dimensionLine. RS_Vector textCorner1 = dimensionLine->getMiddlePoint() - RS_Vector{w, h}; RS_Vector textCorner2 = dimensionLine->getMiddlePoint() + RS_Vector{w, h}; RS_EntityContainer c; c.addRectangle(textCorner1, textCorner2); RS_VectorSolutions sol1 = getIntersectionsLineContainer( dimensionLine, &c, true // treat line as infinitely long in both directions ); textIntersectionLength = sol1.get(0).distanceTo(sol1.get(1)); // determine if we should use outside arrows bool outsideArrows = (textIntersectionLength+3*arrowSize) > distance; // add arrows if (outsideArrows==false) { arrowAngle1 = dimensionLine->getAngle2(); arrowAngle2 = dimensionLine->getAngle1(); } else { arrowAngle1 = dimensionLine->getAngle1(); arrowAngle2 = dimensionLine->getAngle2(); // extend dimension line outside arrows RS_Vector dir = RS_Vector::polar(arrowSize*2, dimensionLine->getAngle1()); dimensionLineOutside1 = new RS_Line{this, p1 - dir, p1}; dimensionLineOutside2 = new RS_Line{this, p2 + dir, p2}; // move text to the side if it won't fit either RS_Vector distH; if (textIntersectionLength>distance && autoText) { distH.setPolar(textIntersectionLength/2.0+arrowSize*2+distance/2.0, arrowAngle1); text->move(distH); textPos = text->getInsertionPoint(); data.middleOfText = textPos; } } double dimtsz=getTickSize()*dimscale; bool displayArrows = dimtsz < 0.01; if(displayArrows) { //display arrow // Arrows: RS_SolidData sd; RS_Solid* arrow; if (arrow1) { // arrow 1 arrow = new RS_Solid(this, sd); arrow->shapeArrow(p1, arrowAngle1, arrowSize); arrow->setPen(pen); arrow->setLayer(nullptr); addEntity(arrow); } if (arrow2) { // arrow 2: arrow = new RS_Solid(this, sd); arrow->shapeArrow(p2, arrowAngle2, arrowSize); arrow->setPen(pen); arrow->setLayer(nullptr); addEntity(arrow); } } else { //display ticks // Arrows: RS_Line* tick; RS_Vector tickVector = RS_Vector::polar(dimtsz,arrowAngle1 + M_PI*0.25); //tick is 45 degree away if (arrow1) { // tick 1 tick = new RS_Line(this, p1-tickVector, p1+tickVector); tick->setPen(pen); tick->setLayer(nullptr); addEntity(tick); } if (arrow2) { // tick 2: tick = new RS_Line(this, p2-tickVector, p2+tickVector); tick->setPen(pen); tick->setLayer(nullptr); addEntity(tick); } } // calculate split dimension lines bool splitDimensionLine = false; if (!outsideArrows) { w = text->getUsedTextWidth()/2+dimgap; h = text->getUsedTextHeight()/2+dimgap; RS_Vector s1 = text->getInsertionPoint() - RS_Vector{w, h}; RS_Vector s2 = text->getInsertionPoint() + RS_Vector{w, h}; c = RS_EntityContainer(); c.addRectangle(s1, s2); sol1 = getIntersectionsLineContainer(dimensionLine, &c); if (sol1.size()>1) { // the text bounding box intersects dimensionLine on two sides splitDimensionLine = true; s1 = sol1.get(0); s2 = sol1.get(1); } else if (sol1.size()==1) { // the text bounding box intersects dimensionLine on one side splitDimensionLine = true; if (RS_Information::isPointInsideContour(p1, &c)) { // the dimension line begins inside the text bounds s1 = p1; s2 = sol1.get(0); } else { // the dimension line ends inside the text bounds s1 = sol1.get(0); s2 = p2; } } else { // the text bounding box does not intersect with dimensionLine, but we // should still check if dimensionLine endpoints are completely inside // the bounding box. if (RS_Information::isPointInsideContour(p1, &c)) { splitDimensionLine = true; s1 = p1; s2 = p2; } } if (splitDimensionLine) { dimensionLineInside1 = new RS_Line{this, p1, s1}; dimensionLineInside2 = new RS_Line{this, s2, p2}; } } // finally, add the dimension line(s) and text to the drawing if (outsideArrows && dimensionLineOutside1) { addEntity(dimensionLineOutside1); addEntity(dimensionLineOutside2); } else if (splitDimensionLine && dimensionLineInside1) { addEntity(dimensionLineInside1); addEntity(dimensionLineInside2); } else { addEntity(dimensionLine); } addEntity(text); } /** * Creates an aligned-text dimensioning line (line with one, two or no arrows * and aligned text). * * @param forceAutoText Automatically reposition the text label. */ void RS_Dimension::updateCreateAlignedTextDimensionLine(const RS_Vector& p1, const RS_Vector& p2, bool arrow1, bool arrow2, bool forceAutoText) { // general scale (DIMSCALE) double dimscale = getGeneralScale(); // text height (DIMTXT) double dimtxt = getTextHeight()*dimscale; // text distance to line (DIMGAP) double dimgap = getDimensionLineGap()*dimscale; // length of dimension line: double distance = p1.distanceTo(p2); // arrow size: double arrowSize = getArrowSize()*dimscale; // do we have to put the arrows outside of the line? bool outsideArrows = (distancesetPen(pen); dimensionLine->setLayer(nullptr); addEntity(dimensionLine); // Text label: RS_MTextData textData; RS_Vector textPos; double dimAngle1 = dimensionLine->getAngle1(); bool corrected = false; double textAngle = RS_Math::makeAngleReadable(dimAngle1, true, &corrected); if (data.middleOfText.valid && !forceAutoText) { textPos = data.middleOfText; } else { textPos = dimensionLine->getMiddlePoint(); // rotate text so it's readable from the bottom or right (ISO) // quadrant 1 & 4 double const a = corrected?-M_PI_2:M_PI_2; RS_Vector distV = RS_Vector::polar(dimgap + dimtxt/2.0, dimAngle1+a); // move text away from dimension line: textPos+=distV; //// the next update should still be able to adjust this //// auto text position. leave it invalid data.middleOfText = textPos; } textData = RS_MTextData(textPos, dimtxt, 30.0, RS_MTextData::VAMiddle, RS_MTextData::HACenter, RS_MTextData::LeftToRight, RS_MTextData::Exact, 1.0, getLabel(), getTextStyle(), textAngle); RS_MText* text = new RS_MText(this, textData); text->setPen(RS_Pen(getTextColor(), RS2::WidthByBlock, RS2::SolidLine)); text->setLayer(nullptr); // move text to the side: RS_Vector distH; if (text->getUsedTextWidth()>distance) { distH.setPolar(text->getUsedTextWidth()/2.0 +distance/2.0+dimgap, textAngle); text->move(distH); } addEntity(text); // add arrows if (outsideArrows==false) { arrowAngle1 = dimensionLine->getAngle2(); arrowAngle2 = dimensionLine->getAngle1(); } else { arrowAngle1 = dimensionLine->getAngle1(); arrowAngle2 = dimensionLine->getAngle2(); // extend dimension line outside arrows RS_Vector dir = RS_Vector::polar(arrowSize*2, arrowAngle2); dimensionLine->setStartpoint(p1 + dir); dimensionLine->setEndpoint(p2 - dir); } double dimtsz=getTickSize()*dimscale; if(dimtsz < 0.01) { //display arrow // Arrows: RS_SolidData sd; RS_Solid* arrow; if (arrow1) { // arrow 1 arrow = new RS_Solid(this, sd); arrow->shapeArrow(p1, arrowAngle1, arrowSize); arrow->setPen(pen); arrow->setLayer(nullptr); addEntity(arrow); } if (arrow2) { // arrow 2: arrow = new RS_Solid(this, sd); arrow->shapeArrow(p2, arrowAngle2, arrowSize); arrow->setPen(pen); arrow->setLayer(nullptr); addEntity(arrow); } }else{ //display ticks // Arrows: RS_Line* tick; RS_Vector tickVector = RS_Vector::polar(dimtsz,arrowAngle1 + M_PI*0.25); //tick is 45 degree away if (arrow1) { // tick 1 tick = new RS_Line(this, p1-tickVector, p1+tickVector); tick->setPen(pen); tick->setLayer(nullptr); addEntity(tick); } if (arrow2) { // tick 2: tick = new RS_Line(this, p2-tickVector, p2+tickVector); tick->setPen(pen); tick->setLayer(nullptr); addEntity(tick); } } } /** * Creates a dimensioning line (line with one, two or no arrows and a text). * * @param forceAutoText Automatically reposition the text label. */ void RS_Dimension::updateCreateDimensionLine(const RS_Vector& p1, const RS_Vector& p2, bool arrow1, bool arrow2, bool forceAutoText) { if (getInsideHorizontalText()) updateCreateHorizontalTextDimensionLine(p1, p2, arrow1, arrow2, forceAutoText); else updateCreateAlignedTextDimensionLine(p1, p2, arrow1, arrow2, forceAutoText); } /** * @return general factor for linear dimensions. */ double RS_Dimension::getGeneralFactor() { return getGraphicVariable("$DIMLFAC", 1.0, 40); } /** * @return general scale for dimensions. */ double RS_Dimension::getGeneralScale() { return getGraphicVariable("$DIMSCALE", 1.0, 40); } /** * @return arrow size in drawing units. */ double RS_Dimension::getArrowSize() { return getGraphicVariable("$DIMASZ", 2.5, 40); } /** * @return tick size in drawing units. */ double RS_Dimension::getTickSize() { return getGraphicVariable("$DIMTSZ", 0., 40); } /** * @return extension line overlength in drawing units. */ double RS_Dimension::getExtensionLineExtension() { return getGraphicVariable("$DIMEXE", 1.25, 40); } /** * @return extension line offset from entities in drawing units. */ double RS_Dimension::getExtensionLineOffset() { return getGraphicVariable("$DIMEXO", 0.625, 40); } /** * @return extension line gap to text in drawing units. */ double RS_Dimension::getDimensionLineGap() { return getGraphicVariable("$DIMGAP", 0.625, 40); } /** * @return Dimension labels text height. */ double RS_Dimension::getTextHeight() { return getGraphicVariable("$DIMTXT", 2.5, 40); } /** * @return Dimension labels alignment text true= horizontal, false= aligned. */ bool RS_Dimension::getInsideHorizontalText() { int v = getGraphicVariableInt("$DIMTIH", 1); if (v>0) { addGraphicVariable("$DIMTIH", 1, 70); getGraphicVariableInt("$DIMTIH", 1); return true; } return false; } /** * @return Dimension fixed length for extension lines true= fixed, false= not fixed. */ bool RS_Dimension::getFixedLengthOn() { int v = getGraphicVariableInt("$DIMFXLON", 0); if (v == 1) { addGraphicVariable("$DIMFXLON", 1, 70); getGraphicVariableInt("$DIMFXLON", 0); return true; } return false; } /** * @return Dimension fixed length for extension lines. */ double RS_Dimension::getFixedLength() { return getGraphicVariable("$DIMFXL", 1.0, 40); } /** * @return extension line Width. */ RS2::LineWidth RS_Dimension::getExtensionLineWidth() { return RS2::intToLineWidth( getGraphicVariableInt("$DIMLWE", -2) ); //default -2 (RS2::WidthByBlock) } /** * @return dimension line Width. */ RS2::LineWidth RS_Dimension::getDimensionLineWidth() { return RS2::intToLineWidth( getGraphicVariableInt("$DIMLWD", -2) ); //default -2 (RS2::WidthByBlock) } /** * @return dimension line Color. */ RS_Color RS_Dimension::getDimensionLineColor() { return RS_FilterDXFRW::numberToColor(getGraphicVariableInt("$DIMCLRD", 0)); } /** * @return extension line Color. */ RS_Color RS_Dimension::getExtensionLineColor() { return RS_FilterDXFRW::numberToColor(getGraphicVariableInt("$DIMCLRE", 0)); } /** * @return dimension text Color. */ RS_Color RS_Dimension::getTextColor() { return RS_FilterDXFRW::numberToColor(getGraphicVariableInt("$DIMCLRT", 0)); } /** * @return text style for dimensions. */ QString RS_Dimension::getTextStyle() { return getGraphicVariableString("$DIMTXSTY", "standard"); } /** * @return the given graphic variable or the default value given in mm * converted to the graphic unit. * If the variable is not found it is added with the given default * value converted to the local unit. */ double RS_Dimension::getGraphicVariable(const QString& key, double defMM, int code) { double v = getGraphicVariableDouble(key, RS_MINDOUBLE); if (v<=RS_MINDOUBLE) { addGraphicVariable( key, RS_Units::convert(defMM, RS2::Millimeter, getGraphicUnit()), code); v = getGraphicVariableDouble(key, 1.0); } return v; } /** * Removes zeros from angle string. * * @param angle The string representing angle. * @param zeros Zeros suppression (0 none, 1 suppress leading, 2 suppress trailing, 3 both) * Decimal separator are '.' * * @ret String with the formatted angle. */ QString RS_Dimension::stripZerosAngle(QString angle, int zeros){ if (zeros == 0) //do nothing return angle; if (zeros & 2 && (angle.contains(QString('.')) || angle.contains(QString(',')))) { int end = angle.size() - 1; QChar format = angle[end--]; //stores & skip format char while (end > 0 && angle[end] == QChar('0')) // locate first 0 from end end--; if (angle[end] == QChar('.')) end--; angle.truncate(end+1); angle.append(format); } if (zeros & 1){ if (angle[0] == QChar('0') && angle[1] == QChar('.')) angle = angle.remove(0, 1); } return angle; } /** * Removes zeros from linear string. * * @param linear The string representing linear measure. * @param zeros Zeros suppression (see dimzin) * * @ret String with the formatted linear measure. */ QString RS_Dimension::stripZerosLinear(QString linear, int zeros){ //do nothing if (zeros == 1) return linear; // return at least 1 character in string int ls = linear.size(); if (ls <= 1) { return linear; } // if removing of trailing zeroes is needed if (zeros & 8 && (linear.contains(QString('.')) || linear.contains(QString(',')))) { // search index int i = ls - 1; // locate first 0 in row from right while (i > 0 && linear[i] == QChar('0')) { i--; } // strip decimal point if ((linear[i] == QChar('.') || linear[i] == QChar(',')) && i > 0) i--; // strip zeros. Leave at least one character at the beginning linear = linear.remove(i+1, ls-i); } // if removing of initial zeroes is needed if (zeros & 4) { int i = 0; // locate last 0 in row from left while (i < ls-1 && linear[i] == QChar('0')) { i++; } linear = linear.remove(0, i); } return linear; } void RS_Dimension::move(const RS_Vector& offset) { data.definitionPoint.move(offset); data.middleOfText.move(offset); } void RS_Dimension::rotate(const RS_Vector& center, const double& angle) { RS_Vector angleVector(angle); data.definitionPoint.rotate(center, angleVector); data.middleOfText.rotate(center, angleVector); data.angle = RS_Math::correctAngle(data.angle+angle); } void RS_Dimension::rotate(const RS_Vector& center, const RS_Vector& angleVector) { data.definitionPoint.rotate(center, angleVector); data.middleOfText.rotate(center, angleVector); data.angle = RS_Math::correctAngle(data.angle+angleVector.angle()); } void RS_Dimension::scale(const RS_Vector& center, const RS_Vector& factor) { data.definitionPoint.scale(center, factor); data.middleOfText.scale(center, factor); } void RS_Dimension::mirror(const RS_Vector& axisPoint1, const RS_Vector& axisPoint2) { data.definitionPoint.mirror(axisPoint1, axisPoint2); data.middleOfText.mirror(axisPoint1, axisPoint2); } // EOF