1 /****************************************************************************
2 **
3 ** This file is part of the LibreCAD project, a 2D CAD program
4 **
5 ** Copyright (C) 2018 A. Stebich (librecad@mail.lordofbikes.de)
6 ** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl)
7 ** Copyright (C) 2001-2003 RibbonSoft. All rights reserved.
8 **
9 **
10 ** This file may be distributed and/or modified under the terms of the
11 ** GNU General Public License version 2 as published by the Free Software
12 ** Foundation and appearing in the file gpl-2.0.txt included in the
13 ** packaging of this file.
14 **
15 ** This program is distributed in the hope that it will be useful,
16 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ** GNU General Public License for more details.
19 **
20 ** You should have received a copy of the GNU General Public License
21 ** along with this program; if not, write to the Free Software
22 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 **
24 ** This copyright notice MUST APPEAR in all copies of the script!
25 **
26 **********************************************************************/
27 
28 #include<iostream>
29 #include<cmath>
30 #include "rs_dimangular.h"
31 #include "rs_math.h"
32 
33 #include "rs_constructionline.h"
34 #include "rs_arc.h"
35 #include "rs_line.h"
36 #include "rs_graphic.h"
37 #include "rs_information.h"
38 #include "rs_solid.h"
39 #include "rs_mtext.h"
40 #include "rs_debug.h"
41 
RS_DimAngularData()42 RS_DimAngularData::RS_DimAngularData():
43     definitionPoint1( false),
44     definitionPoint2( false),
45     definitionPoint3( false),
46     definitionPoint4( false)
47 {
48 }
49 
RS_DimAngularData(const RS_DimAngularData & ed)50 RS_DimAngularData::RS_DimAngularData(const RS_DimAngularData &ed):
51     definitionPoint1( ed.definitionPoint1),
52     definitionPoint2( ed.definitionPoint2),
53     definitionPoint3( ed.definitionPoint3),
54     definitionPoint4( ed.definitionPoint4)
55 {
56 }
57 
58 /**
59  * Constructor with initialisation.
60  *
61  * @param definitionPoint Definition point of the angular dimension.
62  * @param leader Leader length.
63  */
RS_DimAngularData(const RS_Vector & _definitionPoint1,const RS_Vector & _definitionPoint2,const RS_Vector & _definitionPoint3,const RS_Vector & _definitionPoint4)64 RS_DimAngularData::RS_DimAngularData(const RS_Vector& _definitionPoint1,
65                                      const RS_Vector& _definitionPoint2,
66                                      const RS_Vector& _definitionPoint3,
67                                      const RS_Vector& _definitionPoint4):
68     definitionPoint1( _definitionPoint1),
69     definitionPoint2( _definitionPoint2),
70     definitionPoint3( _definitionPoint3),
71     definitionPoint4( _definitionPoint4)
72 {
73 }
74 
75 /**
76  * Constructor with initialisation.
77  *
78  * @param dimscale  general scale (DIMSCALE)
79  * @param dimexo  distance from entities (DIMEXO)
80  * @param dimexe  extension line extension (DIMEXE)
81  * @param dimtxt  text height (DIMTXT)
82  * @param dimgap  text distance to line (DIMGAP)
83  * @param arrowSize  arrow length
84  */
LC_DimAngularVars(const double _dimscale,const double _dimexo,const double _dimexe,const double _dimtxt,const double _dimgap,const double _arrowSize)85 LC_DimAngularVars::LC_DimAngularVars(const double _dimscale,
86                                      const double _dimexo,
87                                      const double _dimexe,
88                                      const double _dimtxt,
89                                      const double _dimgap,
90                                      const double _arrowSize) :
91     dimscale( _dimscale),
92     dimexo( _dimexo * _dimscale),
93     dimexe( _dimexe * _dimscale),
94     dimtxt( _dimtxt * _dimscale),
95     dimgap( _dimgap * _dimscale),
96     arrowSize( _arrowSize * _dimscale)
97 {
98 }
99 
LC_DimAngularVars(const LC_DimAngularVars & av)100 LC_DimAngularVars::LC_DimAngularVars(const LC_DimAngularVars& av) :
101     dimscale( av.dimscale),
102     dimexo( av.dimexo),
103     dimexe( av.dimexe),
104     dimtxt( av.dimtxt),
105     dimgap( av.dimgap),
106     arrowSize( av.arrowSize)
107 {
108 }
109 
110 /**
111  * Constructor.
112  *
113  * @para parent Parent Entity Container.
114  * @para d Common dimension geometrical data.
115  * @para ed Extended geometrical data for angular dimension.
116  */
RS_DimAngular(RS_EntityContainer * parent,const RS_DimensionData & d,const RS_DimAngularData & ed)117 RS_DimAngular::RS_DimAngular(RS_EntityContainer* parent,
118                              const RS_DimensionData& d,
119                              const RS_DimAngularData& ed) :
120     RS_Dimension( parent, d),
121     edata( ed)
122 {
123     calcDimension();
124     calculateBorders();
125 }
126 
clone() const127 RS_Entity* RS_DimAngular::clone() const
128 {
129     RS_DimAngular *d {new RS_DimAngular(*this)};
130 
131     d->setOwner( isOwner());
132     d->initId();
133     d->detach();
134 
135     return d;
136 }
137 
138 /**
139  * @return Automatically created label for the default
140  * measurement of this dimension.
141  */
getMeasuredLabel()142 QString RS_DimAngular::getMeasuredLabel()
143 {
144     int dimaunit {getGraphicVariableInt( QStringLiteral( "$DIMAUNIT"), 0)};
145     int dimadec {getGraphicVariableInt( QStringLiteral( "$DIMADEC"), 0)};
146     int dimazin {getGraphicVariableInt( QStringLiteral( "$DIMAZIN"), 0)};
147     RS2::AngleFormat format {RS_Units::numberToAngleFormat( dimaunit)};
148     QString strLabel( RS_Units::formatAngle( dimAngle, format, dimadec));
149 
150     if (RS2::DegreesMinutesSeconds != format
151         && RS2::Surveyors != format) {
152         strLabel = stripZerosAngle( strLabel, dimazin);
153     }
154 
155     //verify if units are decimal and comma separator
156     if (RS2::DegreesMinutesSeconds != dimaunit) {
157         if (',' == getGraphicVariableInt( QStringLiteral( "$DIMDSEP"), 0)) {
158             strLabel.replace( QChar('.'), QChar(','));
159         }
160     }
161 
162     return strLabel;
163 }
164 
165 /**
166  * @return Center of the measured dimension.
167  */
getCenter() const168 RS_Vector RS_DimAngular::getCenter() const
169 {
170     return dimCenter;
171 }
172 
173 /**
174  * @brief Add an extension line if necessary
175  *
176  * @param dimLine  dimension definition line including extension offset
177  * @param dimPoint  point where the arc meets the definition line
178  * @param dirStart  unit vector defining the lines starting point direction
179  * @param dirEnd  unit vector defining the lines ending point direction
180  * @param av  DXF variables with offset and extension line length
181  * @param pen  pen to draw the extension line
182  */
extensionLine(const RS_ConstructionLine & dimLine,const RS_Vector & dimPoint,const RS_Vector & dirStart,const RS_Vector & dirEnd,const LC_DimAngularVars & av,const RS_Pen & pen)183 void RS_DimAngular::extensionLine(const RS_ConstructionLine& dimLine,
184                                   const RS_Vector& dimPoint,
185                                   const RS_Vector& dirStart,
186                                   const RS_Vector& dirEnd,
187                                   const LC_DimAngularVars& av,
188                                   const RS_Pen& pen)
189 {
190     double diffLine {RS_Vector::posInLine( dimLine.getStartpoint(), dimLine.getEndpoint(), dimPoint)};
191     double diffCenter {RS_Vector::posInLine( dimLine.getStartpoint(), dimCenter, dimPoint)};
192 
193     if( 0.0 <= diffLine && 1.0 >= diffLine) {
194         // dimension ends on entity, nothing to extend
195         return;
196     }
197 
198     if( 0.0 > diffLine && 0.0 > diffCenter) {
199         RS_Line* line {new RS_Line( this,
200                                     dimLine.getStartpoint(),
201                                     dimPoint - dirStart * av.exe())};
202 
203         line->setPen( pen);
204         line->setLayer( nullptr);
205         addEntity( line);
206     }
207     else if( 1.0 < diffLine && 0.0 < diffCenter) {
208         RS_Line* line {new RS_Line( this,
209                                     dimLine.getEndpoint(),
210                                     dimPoint - dirEnd * av.exe())};
211 
212         line->setPen( pen);
213         line->setLayer( nullptr);
214         addEntity( line);
215     }
216     else if( 0.0 > diffLine && 1.0 < diffCenter) {
217         RS_Line* line {new RS_Line( this,
218                                     dimCenter - dirStart * av.exo(),
219                                     dimPoint + dirEnd * av.exe())};
220 
221         line->setPen( pen);
222         line->setLayer( nullptr);
223         addEntity( line);
224     }
225 }
226 
227 /**
228  * @brief Add an arrow to the dimension arc
229  *
230  * @param point  arc endpoint, the arrow tip
231  * @param angle  the angle from center to the arc endpoint
232  * @param direction  this holds the sign for the arrow endpoint direction
233  * @param outsideArrow  when the arc becomes too small, arrows are placed outside
234  * @param av  DXF variables with offset and extension line length
235  * @param pen  pen to draw the extension line
236  */
arrow(const RS_Vector & point,const double angle,const double direction,const bool outsideArrows,const LC_DimAngularVars & av,const RS_Pen & pen)237 void RS_DimAngular::arrow(const RS_Vector& point,
238                           const double angle,
239                           const double direction,
240                           const bool outsideArrows,
241                           const LC_DimAngularVars& av,
242                           const RS_Pen& pen)
243 {
244     double arrowAngle {0.0};
245 
246     if (outsideArrows) {
247         // for outside arrows use tangent angle on endpoints
248         // because for small radius the arrows looked inclined
249         arrowAngle = angle + std::copysign( M_PI_2, direction);
250     }
251     else {
252         // compute the angle from center to the endpoint of the arrow on the arc
253         double endAngle {0.0};
254         if (RS_TOLERANCE_ANGLE < dimRadius) {
255             endAngle = av.arrow() / dimRadius;
256         }
257 
258         // compute the endpoint of the arrow on the arc
259         RS_Vector arrowEnd;
260         arrowEnd.setPolar( dimRadius, angle + std::copysign( endAngle, direction));
261         arrowEnd += dimCenter;
262         arrowAngle = arrowEnd.angleTo( point);
263     }
264 
265     RS_SolidData sd;
266     RS_Solid* arrow;
267 
268     arrow = new RS_Solid( this, sd);
269     arrow->shapeArrow( point, arrowAngle, av.arrow());
270     arrow->setPen( pen);
271     arrow->setLayer( nullptr);
272     addEntity( arrow);
273 
274 }
275 
276 /**
277  * Updates the sub entities of this dimension. Called when the
278  * dimension or the position, alignment, .. changes.
279  *
280  * @param autoText Automatically reposition the text label
281  */
updateDim(bool autoText)282 void RS_DimAngular::updateDim(bool autoText /*= false*/)
283 {
284     Q_UNUSED( autoText)
285     RS_DEBUG->print("RS_DimAngular::update");
286 
287     clear();
288 
289     if (isUndone()) {
290         return;
291     }
292 
293     if ( ! dimCenter.valid) {
294         return;
295     }
296 
297     LC_DimAngularVars   av( getGeneralScale(),
298                             getExtensionLineOffset(),
299                             getExtensionLineExtension(),
300                             getTextHeight(),
301                             getDimensionLineGap(),
302                             getArrowSize());
303 
304     // create new lines with offsets for extension lines
305     RS_ConstructionLine line1( nullptr,
306                                RS_ConstructionLineData( dimLine1.getStartpoint() - dimDir1s * av.exo(),
307                                                         dimLine1.getEndpoint() - dimDir1e * av.exo()));
308     RS_ConstructionLine line2( nullptr,
309                                RS_ConstructionLineData( dimLine2.getStartpoint() - dimDir2s * av.exo(),
310                                                         dimLine2.getEndpoint() - dimDir2e * av.exo()));
311 
312     RS_Vector p1 {dimCenter + dimDir1e * dimRadius};
313     RS_Vector p2 {dimCenter + dimDir2e * dimRadius};
314     RS_Pen pen( getExtensionLineColor(), getExtensionLineWidth(), RS2::LineByBlock);
315 
316     extensionLine( line1, p1, dimDir1s, dimDir1e, av, pen);
317     extensionLine( line2, p2, dimDir2s, dimDir2e, av, pen);
318 
319     // Create dimension line (arc)
320     RS_Arc* arc {new RS_Arc( this, RS_ArcData( dimCenter, dimRadius, dimAngleL1, dimAngleL2, false))};
321     pen.setWidth( getDimensionLineWidth());
322     pen.setColor( getDimensionLineColor());
323     arc->setPen( pen);
324     arc->setLayer( nullptr);
325     addEntity( arc);
326 
327     // do we have to put the arrows outside of the arc?
328     bool outsideArrows {arc->getLength() < 3.0 * av.arrow()};
329 
330     arrow( p1, dimAngleL1, +1.0, outsideArrows, av, pen);
331     arrow( p2, dimAngleL2, -1.0, outsideArrows, av, pen);
332 
333     // text label
334     RS_MTextData textData;
335     RS_Vector textPos {arc->getMiddlePoint()};
336 
337     RS_Vector distV;
338     double textAngle {0.0};
339     double angle1 {textPos.angleTo( dimCenter) - M_PI_2};
340 
341     // rotate text so it's readable from the bottom or right (ISO)
342     // quadrant 1 & 4
343     if (angle1 > M_PI_2 * 3.0 + 0.001
344         || angle1 < M_PI_2 + 0.001) {
345         distV.setPolar( av.gap(), angle1 + M_PI_2);
346         textAngle = angle1;
347     }
348     // quadrant 2 & 3
349     else {
350         distV.setPolar( av.gap(), angle1 - M_PI_2);
351         textAngle = angle1 + M_PI;
352     }
353 
354     // move text away from dimension line:
355     textPos += distV;
356 
357     textData = RS_MTextData( textPos,
358                              av.txt(), 30.0,
359                              RS_MTextData::VABottom,
360                              RS_MTextData::HACenter,
361                              RS_MTextData::LeftToRight,
362                              RS_MTextData::Exact,
363                              1.0,
364                              getLabel(),
365                              getTextStyle(),
366                              textAngle);
367 
368     RS_MText* text {new RS_MText( this, textData)};
369 
370     // move text to the side:
371     text->setPen( RS_Pen( getTextColor(), RS2::WidthByBlock, RS2::SolidLine));
372     text->setLayer( nullptr);
373     addEntity( text);
374 
375     calculateBorders();
376 }
377 
update()378 void RS_DimAngular::update()
379 {
380     calcDimension();
381     RS_Dimension::update();
382 }
383 
move(const RS_Vector & offset)384 void RS_DimAngular::move(const RS_Vector& offset)
385 {
386     RS_Dimension::move( offset);
387 
388     edata.definitionPoint1.move( offset);
389     edata.definitionPoint2.move( offset);
390     edata.definitionPoint3.move( offset);
391     edata.definitionPoint4.move( offset);
392     update();
393 }
394 
rotate(const RS_Vector & center,const double & angle)395 void RS_DimAngular::rotate(const RS_Vector& center, const double& angle)
396 {
397     rotate( center, RS_Vector( angle));
398 }
399 
rotate(const RS_Vector & center,const RS_Vector & angleVector)400 void RS_DimAngular::rotate(const RS_Vector& center, const RS_Vector& angleVector)
401 {
402     RS_Dimension::rotate( center, angleVector);
403 
404     edata.definitionPoint1.rotate( center, angleVector);
405     edata.definitionPoint2.rotate( center, angleVector);
406     edata.definitionPoint3.rotate( center, angleVector);
407     edata.definitionPoint4.rotate( center, angleVector);
408     update();
409 }
410 
scale(const RS_Vector & center,const RS_Vector & factor)411 void RS_DimAngular::scale(const RS_Vector& center, const RS_Vector& factor)
412 {
413     RS_Dimension::scale( center, factor);
414 
415     edata.definitionPoint1.scale( center, factor);
416     edata.definitionPoint2.scale( center, factor);
417     edata.definitionPoint3.scale( center, factor);
418     edata.definitionPoint4.scale( center, factor);
419     update();
420 }
421 
mirror(const RS_Vector & axisPoint1,const RS_Vector & axisPoint2)422 void RS_DimAngular::mirror(const RS_Vector& axisPoint1, const RS_Vector& axisPoint2)
423 {
424     RS_Dimension::mirror( axisPoint1, axisPoint2);
425 
426     edata.definitionPoint1.mirror( axisPoint1, axisPoint2);
427     edata.definitionPoint2.mirror( axisPoint1, axisPoint2);
428     edata.definitionPoint3.mirror( axisPoint1, axisPoint2);
429     edata.definitionPoint4.mirror( axisPoint1, axisPoint2);
430     update();
431 }
432 
433 /**
434  * @brief Compute all static values for dimension.
435  *
436  * From DXF reference the lines are P2-P1 and P-P3.
437  * The dimension is drawn from line1 (P2-P1) to line2 (P-P3) in CCW direction.
438  */
calcDimension(void)439 void RS_DimAngular::calcDimension(void)
440 {
441     // get unit vectors for definition points
442     dimDir1s = RS_Vector::polar( 1.0, RS_Math::correctAngle( edata.definitionPoint2.angleTo( edata.definitionPoint1)));
443     dimDir1e = RS_Vector::polar( 1.0, RS_Math::correctAngle( edata.definitionPoint1.angleTo( edata.definitionPoint2)));
444     dimDir2s = RS_Vector::polar( 1.0, RS_Math::correctAngle( data.definitionPoint.angleTo( edata.definitionPoint3)));
445     dimDir2e = RS_Vector::polar( 1.0, RS_Math::correctAngle( edata.definitionPoint3.angleTo( data.definitionPoint)));
446 
447     // create the two dimension definition lines
448     dimLine1 = RS_ConstructionLine( nullptr,
449                                     RS_ConstructionLineData( edata.definitionPoint2,
450                                                              edata.definitionPoint1));
451     dimLine2 = RS_ConstructionLine( nullptr,
452                                     RS_ConstructionLineData( data.definitionPoint,
453                                                              edata.definitionPoint3));
454 
455     RS_VectorSolutions vs {RS_Information::getIntersection( &dimLine1, &dimLine2, false)};
456     dimCenter = vs.get(0);
457     dimRadius = dimCenter.distanceTo( edata.definitionPoint4);
458     dimDirRad = RS_Vector::polar( 1.0, RS_Math::correctAngle( dimCenter.angleTo( edata.definitionPoint4)));
459 
460     fixDimension();
461 
462     dimAngleL1 = dimLine1.getDirection2();
463     dimAngleL2 = dimLine2.getDirection2();
464 
465     dimAngle = RS_Math::correctAngle( dimLine2.getDirection1() - dimLine1.getDirection1());
466 }
467 
468 /**
469  * @brief check the dimension and fix non conform values from foreign CAD systems
470  *
471  * check if the radius definition point is on the arc,
472  * from line1 to line2 in counter clockwise direction
473  * LibreCAD takes care on correct orientation and line order in RS_ActionDimAngular
474  * but angular dimensions, created in other CAD software, may fail and must be fixed here
475  */
fixDimension(void)476 void RS_DimAngular::fixDimension(void)
477 {
478     if( ! RS_Math::isAngleBetween( dimDirRad.angle(), dimDir2s.angle(), dimDir1s.angle(), false)) {
479         double distance0 {data.definitionPoint.distanceTo( dimCenter)};
480         double distance1 {edata.definitionPoint1.distanceTo( dimCenter)};
481         double distance2 {edata.definitionPoint2.distanceTo( dimCenter)};
482         double distance3 {edata.definitionPoint3.distanceTo( dimCenter)};
483         double  angle0 {0.0};
484         double  angle1 {0.0};
485         double  angle2 {0.0};
486         double  angle3 {0.0};
487         if( RS_TOLERANCE >= distance0) {
488             angle3 = (edata.definitionPoint3 - dimCenter).angle();
489             angle0 = angle3;
490         }
491         else if( RS_TOLERANCE >= distance3) {
492             angle0 = (data.definitionPoint - dimCenter).angle();
493             angle3 = angle0;
494         }
495         else {
496             angle0 = (data.definitionPoint - dimCenter).angle();
497             angle3 = (edata.definitionPoint3 - dimCenter).angle();
498         }
499 
500         if( RS_TOLERANCE >= distance1) {
501             angle2 = (edata.definitionPoint2- dimCenter).angle();
502             angle1 = angle2;
503         }
504         else if( RS_TOLERANCE >= distance2) {
505             angle1 = (edata.definitionPoint1 - dimCenter).angle();
506             angle2 = angle1;
507         }
508         else {
509             angle1 = (edata.definitionPoint1 - dimCenter).angle();
510             angle2 = (edata.definitionPoint2 - dimCenter).angle();
511         }
512 
513         if( angle2 == angle1
514             && distance2 < distance1
515             && angle0 == angle3
516             && distance0 < distance3) {
517             // revert both lines
518             dimLine1 = RS_ConstructionLine( nullptr,
519                                             RS_ConstructionLineData( dimLine1.getEndpoint(),
520                                                                      dimLine1.getStartpoint()));
521             dimLine2 = RS_ConstructionLine( nullptr,
522                                             RS_ConstructionLineData( dimLine2.getEndpoint(),
523                                                                      dimLine2.getStartpoint()));
524 
525             // and their unit vectors
526             RS_Vector swapDir {dimDir1s};
527             dimDir1s = dimDir1e;
528             dimDir1e = swapDir;
529 
530             swapDir = dimDir2s;
531             dimDir2s = dimDir2e;
532             dimDir2e = swapDir;
533         }
534 
535         // check again, as the previous revert may have made this condition false
536         if( ! RS_Math::isAngleBetween( dimDirRad.angle(), dimDir2s.angle(), dimDir1s.angle(), false)) {
537             // swap the lines
538             RS_ConstructionLine swapLine {dimLine1};
539             dimLine1 = dimLine2;
540             dimLine2 = swapLine;
541 
542             // and their unit vectors
543             RS_Vector swapDir {dimDir1s};
544             dimDir1s = dimDir2s;
545             dimDir2s = swapDir;
546 
547             swapDir = dimDir1e;
548             dimDir1e = dimDir2e;
549             dimDir2e = swapDir;
550         }
551     }
552 }
553 
554 /**
555  * Dumps the point's data to stdout.
556  */
operator <<(std::ostream & os,const RS_DimAngular & d)557 std::ostream& operator << (std::ostream& os, const RS_DimAngular& d)
558 {
559     os << " DimAngular: "
560        << d.getData() << std::endl
561        << d.getEData() << std::endl;
562 
563     return os;
564 }
565 
operator <<(std::ostream & os,const RS_DimAngularData & dd)566 std::ostream& operator << (std::ostream& os, const RS_DimAngularData& dd)
567 {
568     os << "(" << dd.definitionPoint1
569        << "," << dd.definitionPoint2
570        << "," << dd.definitionPoint3
571        << "," << dd.definitionPoint3
572        << ")";
573 
574     return os;
575 }
576