1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2004 Jean-Pierre Charras, jaen-pierre.charras@gipsa-lab.inpg.com
5  * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
23  */
24 
25 #ifndef PCB_DIMENSION_H
26 #define PCB_DIMENSION_H
27 
28 
29 #include <board_item.h>
30 #include <pcb_text.h>
31 #include <geometry/shape.h>
32 #include <geometry/circle.h>
33 
34 class LINE_READER;
35 class MSG_PANEL_ITEM;
36 
37 
38 /// How to display the units in a dimension's text
39 enum class DIM_UNITS_FORMAT
40 {
41     NO_SUFFIX,      // 1234.0
42     BARE_SUFFIX,    // 1234.0 mm
43     PAREN_SUFFIX    // 1234.0 (mm)
44 };
45 
46 /// Where to place the text on a dimension
47 enum class DIM_TEXT_POSITION
48 {
49     OUTSIDE,    ///< Text appears outside the dimension line (default)
50     INLINE,     ///< Text appears in line with the dimension line
51     MANUAL      ///< Text placement is manually set by the user
52 };
53 
54 /**
55  * Used for storing the units selection in the file because EDA_UNITS alone doesn't cut it
56  */
57 enum class DIM_UNITS_MODE
58 {
59     INCHES,
60     MILS,
61     MILLIMETRES,
62     AUTOMATIC
63 };
64 
65 /**
66  * Frame to show around dimension text
67  */
68 enum class DIM_TEXT_FRAME
69 {
70     NONE,
71     RECTANGLE,
72     CIRCLE,
73     ROUNDRECT
74 };
75 
76 /**
77  * Abstract dimension API
78  *
79  * Some notes about dimension nomenclature:
80  *
81  * - "feature points" are the points being measured by the dimension.  For an example, the start
82  *   and end points of a line to be measured.  These are the first points picked when drawing a
83  *   new dimension.  Dimensions can have one or more feature points: linear dimensions (the only
84  *   type supported in KiCad 5 and earlier) have two feature points; leader dimensions have one;
85  *   and ordinate dimensions can have in theory an unlimited number of feature points.
86  *
87  * - "feature lines" are lines that coincide with feature points.  Not all dimension types have
88  *   feature lines.  The actual start and end of feature lines is calculated from dimension style
89  *   properties (offset from feature point to start of feature line, height of crossbar, and height
90  *   of feature line past crossbar, for example in linear dimensions)
91  *
92  * - "crossbar" refers to the perpendicular line (usually with arrows at each end) between feature
93  *   lines on linear dimensions
94  */
95 class PCB_DIMENSION_BASE : public BOARD_ITEM
96 {
97 public:
98     PCB_DIMENSION_BASE( BOARD_ITEM* aParent, KICAD_T aType = PCB_DIMENSION_T );
99 
IsType(const KICAD_T aScanTypes[])100     bool IsType( const KICAD_T aScanTypes[] ) const override
101     {
102         if( BOARD_ITEM::IsType( aScanTypes ) )
103             return true;
104 
105         for( const KICAD_T* p = aScanTypes; *p != EOT; ++p )
106         {
107             if( *p == PCB_LOCATE_GRAPHIC_T )
108                 return true;
109         }
110 
111         return false;
112     }
113 
114     void SetParent( EDA_ITEM* aParent ) override;
115 
116     /**
117      * The dimension's origin is the first feature point for the dimension.  Every dimension has
118      * one or more feature points, so every dimension has at least an origin.
119      * @return the origin point of this dimension
120      */
GetStart()121     virtual const wxPoint& GetStart() const { return m_start; }
SetStart(const wxPoint & aPoint)122     virtual void SetStart( const wxPoint& aPoint ) { m_start = aPoint; }
123 
GetEnd()124     virtual const wxPoint& GetEnd() const { return m_end; }
SetEnd(const wxPoint & aPoint)125     virtual void SetEnd( const wxPoint& aPoint ) { m_end = aPoint; }
126 
GetPosition()127     wxPoint GetPosition() const override { return m_start; }
SetPosition(const wxPoint & aPos)128     void SetPosition( const wxPoint& aPos ) override { m_start = aPos; }
129 
GetOverrideTextEnabled()130     bool GetOverrideTextEnabled() const { return m_overrideTextEnabled; }
SetOverrideTextEnabled(bool aOverride)131     void SetOverrideTextEnabled( bool aOverride ) { m_overrideTextEnabled = aOverride; }
132 
GetOverrideText()133     wxString GetOverrideText() const { return m_valueString; }
SetOverrideText(const wxString & aValue)134     void SetOverrideText( const wxString& aValue ) { m_valueString = aValue; }
135 
GetMeasuredValue()136     int GetMeasuredValue() const { return m_measuredValue; }
137 
138     // KiCad normally calculates the measured value but some importers need to set it.
SetMeasuredValue(int aValue)139     void SetMeasuredValue( int aValue ) { m_measuredValue = aValue; }
140 
141     /**
142      * @return the dimension value, rendered with precision / zero suppression but no units, etc
143      */
144     wxString GetValueText() const;
145 
146     /**
147      * Update the dimension's cached text and geometry.
148      */
Update()149     void Update()
150     {
151         updateGeometry();
152         updateText();
153     }
154 
GetPrefix()155     wxString GetPrefix() const { return m_prefix; }
156     void SetPrefix( const wxString& aPrefix );
157 
GetSuffix()158     wxString GetSuffix() const { return m_suffix; }
159     void SetSuffix( const wxString& aSuffix );
160 
GetUnits(EDA_UNITS & aUnits)161     void GetUnits( EDA_UNITS& aUnits ) const { aUnits = m_units; }
162     void SetUnits( EDA_UNITS aUnits );
163 
164     DIM_UNITS_MODE GetUnitsMode() const;
165     void SetUnitsMode( DIM_UNITS_MODE aMode );
166 
167     void SetAutoUnits( bool aAuto = true ) { m_autoUnits = aAuto; }
168 
GetUnitsFormat()169     DIM_UNITS_FORMAT GetUnitsFormat() const { return m_unitsFormat; }
SetUnitsFormat(const DIM_UNITS_FORMAT aFormat)170     void SetUnitsFormat( const DIM_UNITS_FORMAT aFormat ) { m_unitsFormat = aFormat; }
171 
GetPrecision()172     int GetPrecision() const { return m_precision; }
SetPrecision(int aPrecision)173     void SetPrecision( int aPrecision ) { m_precision = aPrecision; }
174 
GetSuppressZeroes()175     bool GetSuppressZeroes() const { return m_suppressZeroes; }
SetSuppressZeroes(bool aSuppress)176     void SetSuppressZeroes( bool aSuppress ) { m_suppressZeroes = aSuppress; }
177 
GetKeepTextAligned()178     bool GetKeepTextAligned() const { return m_keepTextAligned; }
SetKeepTextAligned(bool aKeepAligned)179     void SetKeepTextAligned( bool aKeepAligned ) { m_keepTextAligned = aKeepAligned; }
180 
SetTextPositionMode(DIM_TEXT_POSITION aMode)181     void SetTextPositionMode( DIM_TEXT_POSITION aMode ) { m_textPosition = aMode; }
GetTextPositionMode()182     DIM_TEXT_POSITION GetTextPositionMode() const { return m_textPosition; }
183 
GetArrowLength()184     int GetArrowLength() const { return m_arrowLength; }
SetArrowLength(int aLength)185     void SetArrowLength( int aLength ) { m_arrowLength = aLength; }
186 
SetExtensionOffset(int aOffset)187     void SetExtensionOffset( int aOffset ) { m_extensionOffset = aOffset; }
GetExtensionOffset()188     int GetExtensionOffset() const { return m_extensionOffset; }
189 
GetLineThickness()190     int GetLineThickness() const        { return m_lineThickness; }
SetLineThickness(int aWidth)191     void SetLineThickness( int aWidth ) { m_lineThickness = aWidth; }
192 
193     void SetLayer( PCB_LAYER_ID aLayer ) override;
194 
SetTextSize(const wxSize & aTextSize)195     void SetTextSize( const wxSize& aTextSize )
196     {
197         m_text.SetTextSize( aTextSize );
198     }
199 
200     /**
201      * Set the override text - has no effect if m_overrideValue == false.
202      *
203      * @param aNewText is the text to use as the value.
204      */
205     void           SetText( const wxString& aNewText );
206 
207     /**
208      * Retrieve the value text or override text, not including prefix or suffix.
209      *
210      * @return the value portion of the dimension text (either overridden or not).
211      */
212     const wxString GetText() const;
213 
Text()214     PCB_TEXT& Text() { return m_text; }
Text()215     const PCB_TEXT& Text() const { return m_text; }
216 
217     /**
218      * @return a list of line segments that make up this dimension (for drawing, plotting, etc).
219      */
GetShapes()220     const std::vector<std::shared_ptr<SHAPE>>& GetShapes() const { return m_shapes; }
221 
222     // BOARD_ITEM overrides
223 
224     void Move( const wxPoint& offset ) override;
225     void Rotate( const wxPoint& aRotCentre, double aAngle ) override;
226     void Flip( const wxPoint& aCentre, bool aFlipLeftRight ) override;
227 
228     /**
229      * Mirror the dimension relative to a given horizontal axis.
230      *
231      * The text is not mirrored.  Only its position (and angle) is mirrored.  The layer is not
232      * changed.
233      *
234      * @param axis_pos is the vertical axis position to mirror around.
235      */
236     void Mirror( const wxPoint& axis_pos, bool aMirrorLeftRight = false );
237 
238     void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList ) override;
239 
240     bool HitTest( const wxPoint& aPosition, int aAccuracy ) const override;
241     bool HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy = 0 ) const override;
242 
243     const EDA_RECT GetBoundingBox() const override;
244 
245     std::shared_ptr<SHAPE> GetEffectiveShape( PCB_LAYER_ID aLayer ) const override;
246 
247     wxString GetSelectMenuText( EDA_UNITS aUnits ) const override;
248 
249     const BOX2I ViewBBox() const override;
250 
251     void TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, PCB_LAYER_ID aLayer,
252                                                int aClearance, int aError, ERROR_LOC aErrorLoc,
253                                                bool aIgnoreLineWidth ) const override;
254 
255 #if defined(DEBUG)
Show(int nestLevel,std::ostream & os)256     virtual void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); }
257 #endif
258 
259 protected:
260 
261     /**
262      * Update the cached geometry of the dimension after changing any of its properties.
263      */
264     virtual void updateGeometry() = 0;
265 
266     /**
267      * Update the text field value from the current geometry (called by updateGeometry normally).
268      */
269     virtual void updateText();
270 
271     template<typename ShapeType>
272     void addShape( const ShapeType& aShape );
273 
274     /**
275      * Find the intersection between a given segment and polygon outline.
276      *
277      * @param aPoly is the polygon to collide.
278      * @param aSeg is the segment to collide.
279      * @param aStart if true will start from aSeg.A, otherwise aSeg.B.
280      * @return a point on aSeg that collides with aPoly closest to the start, if one exists.
281      */
282     static OPT_VECTOR2I segPolyIntersection( const SHAPE_POLY_SET& aPoly, const SEG& aSeg,
283                                              bool aStart = true );
284     static OPT_VECTOR2I segCircleIntersection( CIRCLE& aCircle, SEG& aSeg, bool aStart = true );
285 
286     // Value format
287     bool              m_overrideTextEnabled;   ///< Manually specify the displayed measurement value
288     wxString          m_valueString;     ///< Displayed value when m_overrideValue = true
289     wxString          m_prefix;          ///< String prepended to the value
290     wxString          m_suffix;          ///< String appended to the value
291     EDA_UNITS         m_units;           ///< 0 = inches, 1 = mm
292     bool              m_autoUnits;       ///< If true, follow the currently selected UI units
293     DIM_UNITS_FORMAT  m_unitsFormat;     ///< How to render the units suffix
294     int               m_precision;       ///< Number of digits to display after decimal
295     bool              m_suppressZeroes;  ///< Suppress trailing zeroes
296 
297     // Geometry
298     int               m_lineThickness;    ///< Thickness used for all graphics in the dimension
299     int               m_arrowLength;      ///< Length of arrow shapes
300     int               m_extensionOffset;  ///< Distance from feature points to extension line start
301     DIM_TEXT_POSITION m_textPosition;     ///< How to position the text
302     bool              m_keepTextAligned;  ///< Calculate text orientation to match dimension
303 
304     // Internal
305     PCB_TEXT          m_text;             ///< The actual text object
306     int               m_measuredValue;    ///< value of PCB dimensions
307     wxPoint           m_start;
308     wxPoint           m_end;
309 
310     ///< Internal cache of drawn shapes
311     std::vector<std::shared_ptr<SHAPE>> m_shapes;
312 
313     static constexpr float s_arrowAngle = 27.5;
314 };
315 
316 
317 /**
318  * For better understanding of the points that make a dimension:
319  *
320  * Note: historically KiCad called extension lines "feature lines", and also note that what we
321  * call the "crossbar line" here is more commonly called the "dimension line"
322  *
323  *              Start (feature point 1)         End (feature point 2)
324  *                |                              |
325  *                |   <-- extension lines -->    |
326  *                |                              |
327  *                |  m_arrowG2F      m_arrowD2F  |
328  *                | /                          \ |
329  * Crossbar start |/_______crossbar line________\| Crossbar end
330  *                |\           m_text           /|
331  *                | \                          / |
332  *                |  m_arrowG1F      m_arrowD1F  |
333  *                |                              |
334  *                m_featureLineGF  m_featureLineDF
335  */
336 
337 /**
338  * An aligned dimension measures the distance between two feature points.  It has a crossbar
339  * (dimension line) that stays parallel with the vector between the feature points.
340  *
341  * The height (distance from features to crossbar) can be set directly, or set by manipulating the
342  * crossbar start or end point (with the point editor).
343  */
344 class PCB_DIM_ALIGNED : public PCB_DIMENSION_BASE
345 {
346 public:
347     PCB_DIM_ALIGNED( BOARD_ITEM* aParent, KICAD_T aType = PCB_DIM_ALIGNED_T );
348 
349     // Do not create a copy constructor & operator=.
350     // The ones generated by the compiler are adequate.
351 
352     ~PCB_DIM_ALIGNED() = default;
353 
ClassOf(const EDA_ITEM * aItem)354     static inline bool ClassOf( const EDA_ITEM* aItem )
355     {
356         return aItem && PCB_DIM_ALIGNED_T == aItem->Type();
357     }
358 
359     EDA_ITEM* Clone() const override;
360 
361     virtual void SwapData( BOARD_ITEM* aImage ) override;
362 
363     BITMAPS GetMenuImage() const override;
364 
GetCrossbarStart()365     const wxPoint& GetCrossbarStart() const { return m_crossBarStart; }
366 
GetCrossbarEnd()367     const wxPoint& GetCrossbarEnd() const { return m_crossBarEnd; }
368 
369     /**
370      * Set the distance from the feature points to the crossbar line.
371      *
372      * @param aHeight is the new height.
373      */
SetHeight(int aHeight)374     void SetHeight( int aHeight ) { m_height = aHeight; }
GetHeight()375     int GetHeight() const {  return m_height; }
376 
377     /**
378      * Update the stored height basing on points coordinates.
379      *
380      * @param aCrossbarStart is the start point of the crossbar.
381      */
382     void UpdateHeight( const wxPoint& aCrossbarStart, const wxPoint& aCrossbarEnd );
383 
SetExtensionHeight(int aHeight)384     void SetExtensionHeight( int aHeight ) { m_extensionHeight = aHeight; }
GetExtensionHeight()385     int GetExtensionHeight() const { return m_extensionHeight; }
386 
387     /**
388      * Return the angle of the crossbar.
389      *
390      * @return Angle of the crossbar line expressed in radians.
391      */
GetAngle()392     double GetAngle() const
393     {
394         wxPoint delta( m_end - m_start );
395 
396         return atan2( (double)delta.y, (double)delta.x );
397     }
398 
GetClass()399     wxString GetClass() const override
400     {
401         return wxT( "PCB_DIM_ALIGNED" );
402     }
403 
404     void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList ) override;
405 
406 protected:
407     void updateGeometry() override;
408 
409     void updateText() override;
410 
411     // Geometry
412     int          m_height;           ///< Perpendicular distance from features to crossbar
413     int          m_extensionHeight;  ///< Length of extension lines past the crossbar
414 
415     wxPoint      m_crossBarStart;    ///< Crossbar start control point
416     wxPoint      m_crossBarEnd;      ///< Crossbar end control point
417 
418 };
419 
420 
421 /**
422  * An orthogonal dimension is like an aligned dimension, but the extension lines are locked to the
423  * X or Y axes, and the measurement is only taken in the X or Y direction.
424  */
425 class PCB_DIM_ORTHOGONAL : public PCB_DIM_ALIGNED
426 {
427 public:
428     enum class DIR
429     {
430         HORIZONTAL, // Aligned with x-axis
431         VERTICAL    // Aligned with y-axis
432     };
433 
434     PCB_DIM_ORTHOGONAL( BOARD_ITEM* aParent );
435 
436     ~PCB_DIM_ORTHOGONAL() = default;
437 
ClassOf(const EDA_ITEM * aItem)438     static inline bool ClassOf( const EDA_ITEM* aItem )
439     {
440         return aItem && PCB_DIM_ORTHOGONAL_T == aItem->Type();
441     }
442 
443     EDA_ITEM* Clone() const override;
444 
445     void SwapData( BOARD_ITEM* aImage ) override;
446 
447     BITMAPS GetMenuImage() const override;
448 
449     /**
450      * Set the orientation of the dimension line (so, perpendicular to the feature lines).
451      *
452      * @param aOrientation is the orientation the dimension should take.
453      */
SetOrientation(DIR aOrientation)454     void SetOrientation( DIR aOrientation ) { m_orientation = aOrientation; }
GetOrientation()455     DIR GetOrientation() const { return m_orientation; }
456 
GetClass()457     wxString GetClass() const override
458     {
459         return wxT( "PCB_DIM_ORTHOGONAL" );
460     }
461     void     Rotate( const wxPoint& aRotCentre, double aAngle ) override;
462 
463 protected:
464     void updateGeometry() override;
465 
466     void updateText() override;
467 
468 private:
469     // Geometry
470     DIR      m_orientation;     ///< What axis to lock the dimension line to.
471 
472 };
473 
474 
475 /**
476  * A leader is a dimension-like object pointing to a specific point.
477  *
478  * A guide to the geometry of a leader:
479  *
480  *     a
481  *        _
482  *       |\
483  *          \
484  *            b---c TEXT
485  *
486  * Point (a) is m_start, point (b) is m_end, point (c) is the end of the "text line"
487  * The b-c line is drawn from b to the text center, and clipped on the text bounding box.
488  */
489 class PCB_DIM_LEADER : public PCB_DIMENSION_BASE
490 {
491 public:
492     PCB_DIM_LEADER( BOARD_ITEM* aParent );
493 
ClassOf(const EDA_ITEM * aItem)494     static inline bool ClassOf( const EDA_ITEM* aItem )
495     {
496         return aItem && PCB_DIM_LEADER_T == aItem->Type();
497     }
498 
499     EDA_ITEM* Clone() const override;
500 
501     virtual void SwapData( BOARD_ITEM* aImage ) override;
502 
503     BITMAPS GetMenuImage() const override;
504 
GetClass()505     wxString GetClass() const override
506     {
507         return wxT( "PCB_DIM_LEADER" );
508     }
509 
SetTextFrame(DIM_TEXT_FRAME aFrame)510     void SetTextFrame( DIM_TEXT_FRAME aFrame ) { m_textFrame = aFrame; }
GetTextFrame()511     DIM_TEXT_FRAME GetTextFrame() const { return m_textFrame; }
512 
513     void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList ) override;
514 
515 protected:
516     void updateGeometry() override;
517 
518 private:
519     DIM_TEXT_FRAME m_textFrame;
520 };
521 
522 
523 /**
524  * Mark the center of a circle or arc with a cross shape.
525  *
526  * The size and orientation of the cross is adjustable.
527  * m_start always marks the center being measured; m_end marks the end of one leg of the cross.
528  */
529 class PCB_DIM_CENTER : public PCB_DIMENSION_BASE
530 {
531 public:
532     PCB_DIM_CENTER( BOARD_ITEM* aParent );
533 
ClassOf(const EDA_ITEM * aItem)534     static inline bool ClassOf( const EDA_ITEM* aItem )
535     {
536         return aItem && PCB_DIM_CENTER_T == aItem->Type();
537     }
538 
539     EDA_ITEM* Clone() const override;
540 
541     virtual void SwapData( BOARD_ITEM* aImage ) override;
542 
543     BITMAPS GetMenuImage() const override;
544 
GetClass()545     wxString GetClass() const override
546     {
547         return wxT( "PCB_DIM_CENTER" );
548     }
549 
550     const EDA_RECT GetBoundingBox() const override;
551 
552     const BOX2I ViewBBox() const override;
553 
554 protected:
555     void updateGeometry() override;
556 };
557 
558 #endif    // DIMENSION_H
559