/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2004 Jean-Pierre Charras, jaen-pierre.charras@gipsa-lab.inpg.com * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * 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, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef PCB_DIMENSION_H #define PCB_DIMENSION_H #include #include #include #include class LINE_READER; class MSG_PANEL_ITEM; /// How to display the units in a dimension's text enum class DIM_UNITS_FORMAT { NO_SUFFIX, // 1234.0 BARE_SUFFIX, // 1234.0 mm PAREN_SUFFIX // 1234.0 (mm) }; enum class DIM_PRECISION { X = 0, // 0 X_X, // 0.0 X_XX, // 0.00 X_XXX, // 0.000 X_XXXX, // 0.0000 X_XXXXX, // 0.00000 V_VV, // 0.00 / 0 / 0.0 V_VVV, // 0.000 / 0 / 0.00 V_VVVV, // 0.0000 / 0.0 / 0.000 V_VVVVV // 0.00000 / 0.00 / 0.0000 }; /// Where to place the text on a dimension enum class DIM_TEXT_POSITION { OUTSIDE, ///< Text appears outside the dimension line (default) INLINE, ///< Text appears in line with the dimension line MANUAL ///< Text placement is manually set by the user }; /** * Used for storing the units selection in the file because EDA_UNITS alone doesn't cut it */ enum class DIM_UNITS_MODE { INCHES, MILS, MILLIMETRES, AUTOMATIC }; /** * Frame to show around dimension text */ enum class DIM_TEXT_BORDER { NONE, RECTANGLE, CIRCLE, ROUNDRECT }; /** * Abstract dimension API * * Some notes about dimension nomenclature: * * - "feature points" are the points being measured by the dimension. For an example, the start * and end points of a line to be measured. These are the first points picked when drawing a * new dimension. Dimensions can have one or more feature points: linear dimensions (the only * type supported in KiCad 5 and earlier) have two feature points; leader dimensions have one; * and ordinate dimensions can have in theory an unlimited number of feature points. * * - "feature lines" are lines that coincide with feature points. Not all dimension types have * feature lines. The actual start and end of feature lines is calculated from dimension style * properties (offset from feature point to start of feature line, height of crossbar, and height * of feature line past crossbar, for example in linear dimensions) * * - "crossbar" refers to the perpendicular line (usually with arrows at each end) between feature * lines on linear dimensions */ class PCB_DIMENSION_BASE : public PCB_TEXT { public: PCB_DIMENSION_BASE( BOARD_ITEM* aParent, KICAD_T aType = PCB_DIMENSION_T ); /** * The dimension's origin is the first feature point for the dimension. Every dimension has * one or more feature points, so every dimension has at least an origin. * @return the origin point of this dimension */ virtual const VECTOR2I& GetStart() const { return m_start; } virtual void SetStart( const VECTOR2I& aPoint ) { m_start = aPoint; } virtual const VECTOR2I& GetEnd() const { return m_end; } virtual void SetEnd( const VECTOR2I& aPoint ) { m_end = aPoint; } VECTOR2I GetPosition() const override { return m_start; } void SetPosition( const VECTOR2I& aPos ) override { m_start = aPos; } bool GetOverrideTextEnabled() const { return m_overrideTextEnabled; } void SetOverrideTextEnabled( bool aOverride ) { m_overrideTextEnabled = aOverride; } wxString GetOverrideText() const { return m_valueString; } void SetOverrideText( const wxString& aValue ) { m_valueString = aValue; } void ChangeOverrideText( const wxString& aValue ) { SetOverrideTextEnabled( true ); SetOverrideText( aValue ); Update(); } int GetMeasuredValue() const { return m_measuredValue; } // KiCad normally calculates the measured value but some importers need to set it. void SetMeasuredValue( int aValue ) { m_measuredValue = aValue; } /** * @return the dimension value, rendered with precision / zero suppression but no units, etc */ wxString GetValueText() const; /** * Update the dimension's cached text and geometry. * * Call this whenever you change something in the geometry * definition, or the text (which can affect geometry, e.g. by * a knockout of a crossbar line or similar) */ void Update() { // Calls updateText internally updateGeometry(); } void UpdateUnits() { SetUnitsMode( GetUnitsMode() ); Update(); } wxString GetPrefix() const { return m_prefix; } void SetPrefix( const wxString& aPrefix ); void ChangePrefix( const wxString& aPrefix ) { SetPrefix( aPrefix ); Update(); } wxString GetSuffix() const { return m_suffix; } void SetSuffix( const wxString& aSuffix ); void ChangeSuffix( const wxString& aSuffix ) { SetSuffix( aSuffix ); Update(); } EDA_UNITS GetUnits() const { return m_units; } void SetUnits( EDA_UNITS aUnits ); DIM_UNITS_MODE GetUnitsMode() const; void SetUnitsMode( DIM_UNITS_MODE aMode ); void ChangeUnitsMode( DIM_UNITS_MODE aMode ) { SetUnitsMode( aMode ); Update(); } void SetAutoUnits( bool aAuto = true ) { m_autoUnits = aAuto; } DIM_UNITS_FORMAT GetUnitsFormat() const { return m_unitsFormat; } void SetUnitsFormat( const DIM_UNITS_FORMAT aFormat ) { m_unitsFormat = aFormat; } void ChangeUnitsFormat( const DIM_UNITS_FORMAT aFormat ) { SetUnitsFormat( aFormat ); Update(); } DIM_PRECISION GetPrecision() const { return m_precision; } void SetPrecision( DIM_PRECISION aPrecision ) { m_precision = aPrecision; } void ChangePrecision( DIM_PRECISION aPrecision ) { SetPrecision( aPrecision ); Update(); } bool GetSuppressZeroes() const { return m_suppressZeroes; } void SetSuppressZeroes( bool aSuppress ) { m_suppressZeroes = aSuppress; } void ChangeSuppressZeroes( bool aSuppress ) { SetSuppressZeroes( aSuppress ); Update(); } bool GetKeepTextAligned() const { return m_keepTextAligned; } void SetKeepTextAligned( bool aKeepAligned ) { m_keepTextAligned = aKeepAligned; } double GetTextAngleDegreesProp() const { return GetTextAngleDegrees(); } void ChangeTextAngleDegrees( double aDegrees ); void ChangeKeepTextAligned( bool aKeepAligned ); void SetTextPositionMode( DIM_TEXT_POSITION aMode ) { m_textPosition = aMode; } DIM_TEXT_POSITION GetTextPositionMode() const { return m_textPosition; } int GetArrowLength() const { return m_arrowLength; } void SetArrowLength( int aLength ) { m_arrowLength = aLength; } void SetExtensionOffset( int aOffset ) { m_extensionOffset = aOffset; } int GetExtensionOffset() const { return m_extensionOffset; } int GetLineThickness() const { return m_lineThickness; } void SetLineThickness( int aWidth ) { m_lineThickness = aWidth; } /** * @return a list of line segments that make up this dimension (for drawing, plotting, etc). */ const std::vector>& GetShapes() const { return m_shapes; } // BOARD_ITEM overrides void Move( const VECTOR2I& offset ) override; void Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle ) override; void Flip( const VECTOR2I& aCentre, bool aFlipLeftRight ) override; /** * Mirror the dimension relative to a given horizontal axis. * * The text is not mirrored. Only its position (and angle) is mirrored. The layer is not * changed. * * @param axis_pos is the vertical axis position to mirror around. */ virtual void Mirror( const VECTOR2I& axis_pos, bool aMirrorLeftRight = false ); void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& aList ) override; bool HitTest( const VECTOR2I& aPosition, int aAccuracy ) const override; bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override; const BOX2I GetBoundingBox() const override; std::shared_ptr GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash = FLASHING::DEFAULT ) const override; wxString GetItemDescription( UNITS_PROVIDER* aUnitsProvider ) const override; const BOX2I ViewBBox() const override; void ClearRenderCache() override; void TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool aIgnoreLineWidth = false ) const override; double Similarity( const BOARD_ITEM& aOther ) const override; bool operator==( const BOARD_ITEM& aOther ) const override; #if defined(DEBUG) virtual void Show( int nestLevel, std::ostream& os ) const override { ShowDummy( os ); } #endif protected: /** * Update the cached geometry of the dimension after changing any of its properties. */ virtual void updateGeometry() = 0; /** * Update the text field value from the current geometry (called by updateGeometry normally). * * If you change the text, you should call updateGeometry which will call this, * and also handle any text-dependent geoemtry handling (like a knockout) */ virtual void updateText(); template void addShape( const ShapeType& aShape ); /** * Find the intersection between a given segment and polygon outline. * * @param aPoly is the polygon to collide. * @param aSeg is the segment to collide. * @param aStart if true will start from aSeg.A, otherwise aSeg.B. * @return a point on aSeg that collides with aPoly closest to the start, if one exists. */ static OPT_VECTOR2I segPolyIntersection( const SHAPE_POLY_SET& aPoly, const SEG& aSeg, bool aStart = true ); static OPT_VECTOR2I segCircleIntersection( CIRCLE& aCircle, SEG& aSeg, bool aStart = true ); // Value format bool m_overrideTextEnabled; ///< Manually specify the displayed measurement value wxString m_valueString; ///< Displayed value when m_overrideValue = true wxString m_prefix; ///< String prepended to the value wxString m_suffix; ///< String appended to the value EDA_UNITS m_units; ///< 0 = inches, 1 = mm bool m_autoUnits; ///< If true, follow the currently selected UI units DIM_UNITS_FORMAT m_unitsFormat; ///< How to render the units suffix DIM_PRECISION m_precision; ///< Number of digits to display after decimal bool m_suppressZeroes; ///< Suppress trailing zeroes // Geometry int m_lineThickness; ///< Thickness used for all graphics in the dimension int m_arrowLength; ///< Length of arrow shapes int m_extensionOffset; ///< Distance from feature points to extension line start DIM_TEXT_POSITION m_textPosition; ///< How to position the text bool m_keepTextAligned; ///< Calculate text orientation to match dimension // Internal int m_measuredValue; ///< value of PCB dimensions VECTOR2I m_start; VECTOR2I m_end; ///< Internal cache of drawn shapes std::vector> m_shapes; bool m_inClearRenderCache; ///< re-entrancy guard }; /** * For better understanding of the points that make a dimension: * * Note: historically KiCad called extension lines "feature lines", and also note that what we * call the "crossbar line" here is more commonly called the "dimension line" * * Start (feature point 1) End (feature point 2) * | | * | <-- extension lines --> | * | | * | m_arrowG2F m_arrowD2F | * | / \ | * Crossbar start |/_______crossbar line________\| Crossbar end * |\ m_text /| * | \ / | * | m_arrowG1F m_arrowD1F | * | | * m_featureLineGF m_featureLineDF */ /** * An aligned dimension measures the distance between two feature points. It has a crossbar * (dimension line) that stays parallel with the vector between the feature points. * * The height (distance from features to crossbar) can be set directly, or set by manipulating the * crossbar start or end point (with the point editor). */ class PCB_DIM_ALIGNED : public PCB_DIMENSION_BASE { public: PCB_DIM_ALIGNED( BOARD_ITEM* aParent, KICAD_T aType = PCB_DIM_ALIGNED_T ); // Do not create a copy constructor & operator=. // The ones generated by the compiler are adequate. ~PCB_DIM_ALIGNED() = default; static inline bool ClassOf( const EDA_ITEM* aItem ) { return aItem && aItem->Type() == PCB_DIM_ALIGNED_T; } EDA_ITEM* Clone() const override; void Mirror( const VECTOR2I& axis_pos, bool aMirrorLeftRight = false ) override; BITMAPS GetMenuImage() const override; const VECTOR2I& GetCrossbarStart() const { return m_crossBarStart; } const VECTOR2I& GetCrossbarEnd() const { return m_crossBarEnd; } /** * Set the distance from the feature points to the crossbar line. * * @param aHeight is the new height. */ void SetHeight( int aHeight ) { m_height = aHeight; } int GetHeight() const { return m_height; } void ChangeHeight( int aHeight ) { SetHeight( aHeight ); Update(); } /** * Update the stored height basing on points coordinates. * * @param aCrossbarStart is the start point of the crossbar. */ void UpdateHeight( const VECTOR2I& aCrossbarStart, const VECTOR2I& aCrossbarEnd ); void SetExtensionHeight( int aHeight ) { m_extensionHeight = aHeight; } int GetExtensionHeight() const { return m_extensionHeight; } void ChangeExtensionHeight( int aHeight ) { SetExtensionHeight( aHeight ); Update(); } /** * Return the angle of the crossbar. * * @return Angle of the crossbar line expressed in radians. */ double GetAngle() const { VECTOR2I delta( m_end - m_start ); return atan2( (double)delta.y, (double)delta.x ); } wxString GetClass() const override { return wxT( "PCB_DIM_ALIGNED" ); } void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& aList ) override; protected: virtual void swapData( BOARD_ITEM* aImage ) override; void updateGeometry() override; void updateText() override; // Geometry int m_height; ///< Perpendicular distance from features to crossbar int m_extensionHeight; ///< Length of extension lines past the crossbar VECTOR2I m_crossBarStart; ///< Crossbar start control point VECTOR2I m_crossBarEnd; ///< Crossbar end control point }; /** * An orthogonal dimension is like an aligned dimension, but the extension lines are locked to the * X or Y axes, and the measurement is only taken in the X or Y direction. */ class PCB_DIM_ORTHOGONAL : public PCB_DIM_ALIGNED { public: enum class DIR { HORIZONTAL, // Aligned with x-axis VERTICAL // Aligned with y-axis }; PCB_DIM_ORTHOGONAL( BOARD_ITEM* aParent ); ~PCB_DIM_ORTHOGONAL() = default; static inline bool ClassOf( const EDA_ITEM* aItem ) { return aItem && aItem->Type() == PCB_DIM_ORTHOGONAL_T; } EDA_ITEM* Clone() const override; BITMAPS GetMenuImage() const override; /** * Set the orientation of the dimension line (so, perpendicular to the feature lines). * * @param aOrientation is the orientation the dimension should take. */ void SetOrientation( DIR aOrientation ) { m_orientation = aOrientation; } DIR GetOrientation() const { return m_orientation; } wxString GetClass() const override { return wxT( "PCB_DIM_ORTHOGONAL" ); } void Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle ) override; protected: void swapData( BOARD_ITEM* aImage ) override; void updateGeometry() override; void updateText() override; private: // Geometry DIR m_orientation; ///< What axis to lock the dimension line to. }; /** * A radial dimension indicates either the radius or diameter of an arc or circle. * * A guide to the geometry of a circle dimension: * * | * --a-- * | * * * b_ * |\ * \ * c---d TEXT * * Point a (the center of the arc or circle) is m_start, point b (a point on the arc or circle) * is m_end, point c is m_leaderLength away from b on the a-b vector, and point d is the end of * the "text line". The c-d line is drawn from c to the text center, and clipped on the text * bounding box. */ class PCB_DIM_RADIAL : public PCB_DIMENSION_BASE { public: PCB_DIM_RADIAL( BOARD_ITEM* aParent ); static inline bool ClassOf( const EDA_ITEM* aItem ) { return aItem && aItem->Type() == PCB_DIM_RADIAL_T; } EDA_ITEM* Clone() const override; void SetLeaderLength( int aLength ) { m_leaderLength = aLength; } int GetLeaderLength() const { return m_leaderLength; } void ChangeLeaderLength( int aLength ) { SetLeaderLength( aLength ); Update(); } // Returns the point (c). VECTOR2I GetKnee() const; BITMAPS GetMenuImage() const override; wxString GetClass() const override { return wxT( "PCB_DIM_RADIAL" ); } protected: virtual void swapData( BOARD_ITEM* aImage ) override; void updateText() override; void updateGeometry() override; private: bool m_isDiameter; int m_leaderLength; }; /** * A leader is a dimension-like object pointing to a specific point. * * A guide to the geometry of a leader: * * a_ * |\ * \ * b---c TEXT * * Point (a) is m_start, point (b) is m_end, point (c) is the end of the "text line" * The b-c line is drawn from b to the text center, and clipped on the text bounding box. */ class PCB_DIM_LEADER : public PCB_DIMENSION_BASE { public: PCB_DIM_LEADER( BOARD_ITEM* aParent ); static inline bool ClassOf( const EDA_ITEM* aItem ) { return aItem && aItem->Type() == PCB_DIM_LEADER_T; } EDA_ITEM* Clone() const override; BITMAPS GetMenuImage() const override; wxString GetClass() const override { return wxT( "PCB_DIM_LEADER" ); } void SetTextBorder( DIM_TEXT_BORDER aBorder ) { m_textBorder = aBorder; } DIM_TEXT_BORDER GetTextBorder() const { return m_textBorder; } void ChangeTextBorder( DIM_TEXT_BORDER aBorder ) { SetTextBorder( aBorder ); Update(); } void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& aList ) override; protected: virtual void swapData( BOARD_ITEM* aImage ) override; void updateText() override; void updateGeometry() override; private: DIM_TEXT_BORDER m_textBorder; }; /** * Mark the center of a circle or arc with a cross shape. * * The size and orientation of the cross is adjustable. * m_start always marks the center being measured; m_end marks the end of one leg of the cross. */ class PCB_DIM_CENTER : public PCB_DIMENSION_BASE { public: PCB_DIM_CENTER( BOARD_ITEM* aParent ); static inline bool ClassOf( const EDA_ITEM* aItem ) { return aItem && aItem->Type() == PCB_DIM_CENTER_T; } EDA_ITEM* Clone() const override; BITMAPS GetMenuImage() const override; wxString GetClass() const override { return wxT( "PCB_DIM_CENTER" ); } const BOX2I GetBoundingBox() const override; const BOX2I ViewBBox() const override; protected: virtual void swapData( BOARD_ITEM* aImage ) override; void updateGeometry() override; }; #endif // DIMENSION_H