You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1313 lines
39 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2012 Jean-Pierre Charras, jean-pierre.charras@ujf-grenoble.fr
  5. * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
  6. * Copyright (C) 2012 Wayne Stambaugh <stambaughw@gmail.com>
  7. * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
  8. *
  9. * This program is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU General Public License
  11. * as published by the Free Software Foundation; either version 2
  12. * of the License, or (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program; if not, you may find one here:
  21. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  22. * or you may search the http://www.gnu.org website for the version 2 license,
  23. * or you may write to the Free Software Foundation, Inc.,
  24. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  25. */
  26. #include <bitmaps.h>
  27. #include <pcb_edit_frame.h>
  28. #include <base_units.h>
  29. #include <convert_basic_shapes_to_polygon.h>
  30. #include <pcb_dimension.h>
  31. #include <pcb_text.h>
  32. #include <geometry/shape_compound.h>
  33. #include <geometry/shape_circle.h>
  34. #include <geometry/shape_segment.h>
  35. #include <settings/color_settings.h>
  36. #include <settings/settings_manager.h>
  37. #include <trigo.h>
  38. static const EDA_ANGLE s_arrowAngle( 27.5, DEGREES_T );
  39. PCB_DIMENSION_BASE::PCB_DIMENSION_BASE( BOARD_ITEM* aParent, KICAD_T aType ) :
  40. BOARD_ITEM( aParent, aType ),
  41. m_overrideTextEnabled( false ),
  42. m_units( EDA_UNITS::INCHES ),
  43. m_autoUnits( false ),
  44. m_unitsFormat( DIM_UNITS_FORMAT::BARE_SUFFIX ),
  45. m_precision( 4 ),
  46. m_suppressZeroes( false ),
  47. m_lineThickness( pcbIUScale.mmToIU( 0.2 ) ),
  48. m_arrowLength( pcbIUScale.MilsToIU( 50 ) ),
  49. m_extensionOffset( 0 ),
  50. m_textPosition( DIM_TEXT_POSITION::OUTSIDE ),
  51. m_keepTextAligned( true ),
  52. m_text( aParent ),
  53. m_measuredValue( 0 )
  54. {
  55. m_layer = Dwgs_User;
  56. }
  57. void PCB_DIMENSION_BASE::SetParent( EDA_ITEM* aParent )
  58. {
  59. BOARD_ITEM::SetParent( aParent );
  60. m_text.SetParent( aParent );
  61. }
  62. void PCB_DIMENSION_BASE::updateText()
  63. {
  64. wxString text = m_overrideTextEnabled ? m_valueString : GetValueText();
  65. switch( m_unitsFormat )
  66. {
  67. case DIM_UNITS_FORMAT::NO_SUFFIX: // no units
  68. break;
  69. case DIM_UNITS_FORMAT::BARE_SUFFIX: // normal
  70. text += EDA_UNIT_UTILS::GetAbbreviatedUnitsLabel( m_units );
  71. break;
  72. case DIM_UNITS_FORMAT::PAREN_SUFFIX: // parenthetical
  73. text += wxT( " (" );
  74. text += EDA_UNIT_UTILS::GetAbbreviatedUnitsLabel( m_units ).Trim( false );
  75. text += wxT( ")" );
  76. break;
  77. }
  78. text.Prepend( m_prefix );
  79. text.Append( m_suffix );
  80. m_text.SetText( text );
  81. }
  82. template<typename ShapeType>
  83. void PCB_DIMENSION_BASE::addShape( const ShapeType& aShape )
  84. {
  85. m_shapes.push_back( std::make_shared<ShapeType>( aShape ) );
  86. }
  87. wxString PCB_DIMENSION_BASE::GetValueText() const
  88. {
  89. struct lconv* lc = localeconv();
  90. wxChar sep = lc->decimal_point[0];
  91. int val = GetMeasuredValue();
  92. int precision = m_precision;
  93. wxString text;
  94. if( precision >= 6 )
  95. {
  96. switch( m_units )
  97. {
  98. case EDA_UNITS::INCHES: precision = precision - 4; break;
  99. case EDA_UNITS::MILS: precision = std::max( 0, precision - 7 ); break;
  100. case EDA_UNITS::MILLIMETRES: precision = precision - 5; break;
  101. default: precision = precision - 4; break;
  102. }
  103. }
  104. wxString format = wxT( "%." ) + wxString::Format( wxT( "%i" ), precision ) + wxT( "f" );
  105. text.Printf( format, EDA_UNIT_UTILS::UI::ToUserUnit( pcbIUScale, m_units, val ) );
  106. if( m_suppressZeroes )
  107. {
  108. while( text.Last() == '0' )
  109. {
  110. text.RemoveLast();
  111. if( text.Last() == '.' || text.Last() == sep )
  112. {
  113. text.RemoveLast();
  114. break;
  115. }
  116. }
  117. }
  118. return text;
  119. }
  120. void PCB_DIMENSION_BASE::SetPrefix( const wxString& aPrefix )
  121. {
  122. m_prefix = aPrefix;
  123. }
  124. void PCB_DIMENSION_BASE::SetSuffix( const wxString& aSuffix )
  125. {
  126. m_suffix = aSuffix;
  127. }
  128. void PCB_DIMENSION_BASE::SetUnits( EDA_UNITS aUnits )
  129. {
  130. m_units = aUnits;
  131. }
  132. DIM_UNITS_MODE PCB_DIMENSION_BASE::GetUnitsMode() const
  133. {
  134. if( m_autoUnits )
  135. {
  136. return DIM_UNITS_MODE::AUTOMATIC;
  137. }
  138. else
  139. {
  140. switch( m_units )
  141. {
  142. default:
  143. case EDA_UNITS::INCHES: return DIM_UNITS_MODE::INCHES;
  144. case EDA_UNITS::MILLIMETRES: return DIM_UNITS_MODE::MILLIMETRES;
  145. case EDA_UNITS::MILS: return DIM_UNITS_MODE::MILS;
  146. }
  147. }
  148. }
  149. void PCB_DIMENSION_BASE::SetUnitsMode( DIM_UNITS_MODE aMode )
  150. {
  151. m_autoUnits = false;
  152. switch( aMode )
  153. {
  154. case DIM_UNITS_MODE::INCHES: m_units = EDA_UNITS::INCHES; break;
  155. case DIM_UNITS_MODE::MILS: m_units = EDA_UNITS::MILS; break;
  156. case DIM_UNITS_MODE::MILLIMETRES: m_units = EDA_UNITS::MILLIMETRES; break;
  157. case DIM_UNITS_MODE::AUTOMATIC: m_autoUnits = true; break;
  158. }
  159. }
  160. void PCB_DIMENSION_BASE::SetText( const wxString& aNewText )
  161. {
  162. m_valueString = aNewText;
  163. updateText();
  164. }
  165. const wxString PCB_DIMENSION_BASE::GetText() const
  166. {
  167. return m_text.GetText();
  168. }
  169. void PCB_DIMENSION_BASE::SetLayer( PCB_LAYER_ID aLayer )
  170. {
  171. m_layer = aLayer;
  172. m_text.SetLayer( aLayer );
  173. }
  174. void PCB_DIMENSION_BASE::Move( const VECTOR2I& offset )
  175. {
  176. m_text.Offset( offset );
  177. m_start += offset;
  178. m_end += offset;
  179. Update();
  180. }
  181. void PCB_DIMENSION_BASE::Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
  182. {
  183. EDA_ANGLE newAngle = m_text.GetTextAngle() + aAngle;
  184. newAngle.Normalize();
  185. m_text.SetTextAngle( newAngle );
  186. VECTOR2I pt = m_text.GetTextPos();
  187. RotatePoint( pt, aRotCentre, aAngle );
  188. m_text.SetTextPos( pt );
  189. RotatePoint( m_start, aRotCentre, aAngle );
  190. RotatePoint( m_end, aRotCentre, aAngle );
  191. Update();
  192. }
  193. void PCB_DIMENSION_BASE::Flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
  194. {
  195. Mirror( aCentre );
  196. SetLayer( FlipLayer( GetLayer(), GetBoard()->GetCopperLayerCount() ) );
  197. }
  198. void PCB_DIMENSION_BASE::Mirror( const VECTOR2I& axis_pos, bool aMirrorLeftRight )
  199. {
  200. int axis = aMirrorLeftRight ? axis_pos.x : axis_pos.y;
  201. VECTOR2I newPos = m_text.GetTextPos();
  202. #define INVERT( pos ) ( ( pos ) = axis - ( ( pos ) - axis ) )
  203. if( aMirrorLeftRight )
  204. INVERT( newPos.x );
  205. else
  206. INVERT( newPos.y );
  207. m_text.SetTextPos( newPos );
  208. // invert angle
  209. m_text.SetTextAngle( -m_text.GetTextAngle() );
  210. if( aMirrorLeftRight )
  211. {
  212. INVERT( m_start.x );
  213. INVERT( m_end.x );
  214. }
  215. else
  216. {
  217. INVERT( m_start.y );
  218. INVERT( m_end.y );
  219. }
  220. m_text.SetMirrored( !m_text.IsMirrored() );
  221. Update();
  222. }
  223. void PCB_DIMENSION_BASE::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame,
  224. std::vector<MSG_PANEL_ITEM>& aList )
  225. {
  226. // for now, display only the text within the DIMENSION using class PCB_TEXT.
  227. wxString msg;
  228. wxCHECK_RET( m_parent != nullptr, wxT( "PCB_TEXT::GetMsgPanelInfo() m_Parent is NULL." ) );
  229. aList.emplace_back( _( "Dimension" ), m_text.GetShownText() );
  230. aList.emplace_back( _( "Prefix" ), GetPrefix() );
  231. if( GetOverrideTextEnabled() )
  232. {
  233. aList.emplace_back( _( "Override Text" ), GetOverrideText() );
  234. }
  235. else
  236. {
  237. aList.emplace_back( _( "Value" ), GetValueText() );
  238. switch( GetPrecision() )
  239. {
  240. case 6:
  241. msg = wxT( "0.00 in / 0 mils / 0.0 mm" );
  242. break;
  243. case 7:
  244. msg = wxT( "0.000 in / 0 mils / 0.00 mm" );
  245. break;
  246. case 8:
  247. msg = wxT( "0.0000 in / 0.0 mils / 0.000 mm" );
  248. break;
  249. case 9:
  250. msg = wxT( "0.00000 in / 0.00 mils / 0.0000 mm" );
  251. break;
  252. default:
  253. msg = wxT( "%" ) + wxString::Format( wxT( "1.%df" ), GetPrecision() );
  254. }
  255. aList.emplace_back( _( "Precision" ), wxString::Format( msg, 0.0 ) );
  256. }
  257. aList.emplace_back( _( "Suffix" ), GetSuffix() );
  258. EDA_UNITS units;
  259. GetUnits( units );
  260. aList.emplace_back( _( "Units" ), EDA_UNIT_UTILS::GetAbbreviatedUnitsLabel( units ).Trim( false ) );
  261. aList.emplace_back( _( "Font" ), m_text.GetDrawFont()->GetName() );
  262. aList.emplace_back( _( "Text Thickness" ),
  263. EDA_UNIT_UTILS::UI::MessageTextFromValue( pcbIUScale, units, m_text.GetTextThickness() ) );
  264. aList.emplace_back( _( "Text Width" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( pcbIUScale, units, m_text.GetTextWidth() ) );
  265. aList.emplace_back( _( "Text Height" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( pcbIUScale, units, m_text.GetTextHeight() ) );
  266. ORIGIN_TRANSFORMS originTransforms = aFrame->GetOriginTransforms();
  267. units = aFrame->GetUserUnits();
  268. if( Type() == PCB_DIM_CENTER_T || Type() == PCB_FP_DIM_CENTER_T )
  269. {
  270. VECTOR2I startCoord = originTransforms.ToDisplayAbs( GetStart() );
  271. wxString start = wxString::Format( wxT( "@(%s, %s)" ),
  272. EDA_UNIT_UTILS::UI::MessageTextFromValue( pcbIUScale, units, startCoord.x ),
  273. EDA_UNIT_UTILS::UI::MessageTextFromValue( pcbIUScale, units, startCoord.y ) );
  274. aList.emplace_back( start, wxEmptyString );
  275. }
  276. else
  277. {
  278. VECTOR2I startCoord = originTransforms.ToDisplayAbs( GetStart() );
  279. wxString start = wxString::Format( wxT( "@(%s, %s)" ),
  280. EDA_UNIT_UTILS::UI::MessageTextFromValue( pcbIUScale, units, startCoord.x ),
  281. EDA_UNIT_UTILS::UI::MessageTextFromValue( pcbIUScale, units, startCoord.y ) );
  282. VECTOR2I endCoord = originTransforms.ToDisplayAbs( GetEnd() );
  283. wxString end = wxString::Format( wxT( "@(%s, %s)" ),
  284. EDA_UNIT_UTILS::UI::MessageTextFromValue( pcbIUScale, units, endCoord.x ),
  285. EDA_UNIT_UTILS::UI::MessageTextFromValue( pcbIUScale, units, endCoord.y ) );
  286. aList.emplace_back( start, end );
  287. }
  288. if( aFrame->GetName() == PCB_EDIT_FRAME_NAME && IsLocked() )
  289. aList.emplace_back( _( "Status" ), _( "Locked" ) );
  290. aList.emplace_back( _( "Layer" ), GetLayerName() );
  291. }
  292. std::shared_ptr<SHAPE> PCB_DIMENSION_BASE::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash ) const
  293. {
  294. std::shared_ptr<SHAPE_COMPOUND> effectiveShape = std::make_shared<SHAPE_COMPOUND>();
  295. effectiveShape->AddShape( Text().GetEffectiveTextShape()->Clone() );
  296. for( const std::shared_ptr<SHAPE>& shape : GetShapes() )
  297. effectiveShape->AddShape( shape->Clone() );
  298. return effectiveShape;
  299. }
  300. bool PCB_DIMENSION_BASE::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const
  301. {
  302. if( m_text.TextHitTest( aPosition ) )
  303. return true;
  304. int dist_max = aAccuracy + ( m_lineThickness / 2 );
  305. // Locate SEGMENTS
  306. for( const std::shared_ptr<SHAPE>& shape : GetShapes() )
  307. {
  308. if( shape->Collide( aPosition, dist_max ) )
  309. return true;
  310. }
  311. return false;
  312. }
  313. bool PCB_DIMENSION_BASE::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
  314. {
  315. BOX2I arect = aRect;
  316. arect.Inflate( aAccuracy );
  317. BOX2I rect = GetBoundingBox();
  318. if( aAccuracy )
  319. rect.Inflate( aAccuracy );
  320. if( aContained )
  321. return arect.Contains( rect );
  322. return arect.Intersects( rect );
  323. }
  324. const BOX2I PCB_DIMENSION_BASE::GetBoundingBox() const
  325. {
  326. BOX2I bBox;
  327. int xmin, xmax, ymin, ymax;
  328. bBox = m_text.GetTextBox();
  329. xmin = bBox.GetX();
  330. xmax = bBox.GetRight();
  331. ymin = bBox.GetY();
  332. ymax = bBox.GetBottom();
  333. for( const std::shared_ptr<SHAPE>& shape : GetShapes() )
  334. {
  335. BOX2I shapeBox = shape->BBox();
  336. shapeBox.Inflate( m_lineThickness / 2 );
  337. xmin = std::min( xmin, shapeBox.GetOrigin().x );
  338. xmax = std::max( xmax, shapeBox.GetEnd().x );
  339. ymin = std::min( ymin, shapeBox.GetOrigin().y );
  340. ymax = std::max( ymax, shapeBox.GetEnd().y );
  341. }
  342. bBox.SetX( xmin );
  343. bBox.SetY( ymin );
  344. bBox.SetWidth( xmax - xmin + 1 );
  345. bBox.SetHeight( ymax - ymin + 1 );
  346. bBox.Normalize();
  347. return bBox;
  348. }
  349. wxString PCB_DIMENSION_BASE::GetSelectMenuText( EDA_UNITS aUnits ) const
  350. {
  351. return wxString::Format( _( "Dimension '%s' on %s" ), GetText(), GetLayerName() );
  352. }
  353. const BOX2I PCB_DIMENSION_BASE::ViewBBox() const
  354. {
  355. BOX2I dimBBox = BOX2I( VECTOR2I( GetBoundingBox().GetPosition() ),
  356. VECTOR2I( GetBoundingBox().GetSize() ) );
  357. dimBBox.Merge( m_text.ViewBBox() );
  358. return dimBBox;
  359. }
  360. OPT_VECTOR2I PCB_DIMENSION_BASE::segPolyIntersection( const SHAPE_POLY_SET& aPoly, const SEG& aSeg,
  361. bool aStart )
  362. {
  363. VECTOR2I start( aStart ? aSeg.A : aSeg.B );
  364. VECTOR2I endpoint( aStart ? aSeg.B : aSeg.A );
  365. if( aPoly.Contains( start ) )
  366. return std::nullopt;
  367. for( SHAPE_POLY_SET::CONST_SEGMENT_ITERATOR seg = aPoly.CIterateSegments(); seg; ++seg )
  368. {
  369. if( OPT_VECTOR2I intersection = ( *seg ).Intersect( aSeg ) )
  370. {
  371. if( ( *intersection - start ).SquaredEuclideanNorm() <
  372. ( endpoint - start ).SquaredEuclideanNorm() )
  373. endpoint = *intersection;
  374. }
  375. }
  376. if( start == endpoint )
  377. return std::nullopt;
  378. return OPT_VECTOR2I( endpoint );
  379. }
  380. OPT_VECTOR2I PCB_DIMENSION_BASE::segCircleIntersection( CIRCLE& aCircle, SEG& aSeg, bool aStart )
  381. {
  382. VECTOR2I start( aStart ? aSeg.A : aSeg.B );
  383. VECTOR2I endpoint( aStart ? aSeg.B : aSeg.A );
  384. if( aCircle.Contains( start ) )
  385. return std::nullopt;
  386. std::vector<VECTOR2I> intersections = aCircle.Intersect( aSeg );
  387. for( VECTOR2I& intersection : aCircle.Intersect( aSeg ) )
  388. {
  389. if( ( intersection - start ).SquaredEuclideanNorm() <
  390. ( endpoint - start ).SquaredEuclideanNorm() )
  391. endpoint = intersection;
  392. }
  393. if( start == endpoint )
  394. return std::nullopt;
  395. return OPT_VECTOR2I( endpoint );
  396. }
  397. void PCB_DIMENSION_BASE::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
  398. PCB_LAYER_ID aLayer, int aClearance,
  399. int aError, ERROR_LOC aErrorLoc,
  400. bool aIgnoreLineWidth ) const
  401. {
  402. wxASSERT_MSG( !aIgnoreLineWidth, wxT( "IgnoreLineWidth has no meaning for dimensions." ) );
  403. for( const std::shared_ptr<SHAPE>& shape : m_shapes )
  404. {
  405. const SHAPE_CIRCLE* circle = dynamic_cast<const SHAPE_CIRCLE*>( shape.get() );
  406. const SHAPE_SEGMENT* seg = dynamic_cast<const SHAPE_SEGMENT*>( shape.get() );
  407. if( circle )
  408. {
  409. TransformCircleToPolygon( aCornerBuffer, circle->GetCenter(),
  410. circle->GetRadius() + m_lineThickness / 2 + aClearance,
  411. aError, aErrorLoc );
  412. }
  413. else if( seg )
  414. {
  415. TransformOvalToPolygon( aCornerBuffer, seg->GetSeg().A,
  416. seg->GetSeg().B, m_lineThickness + 2 * aClearance,
  417. aError, aErrorLoc );
  418. }
  419. else
  420. {
  421. wxFAIL_MSG( wxT( "PCB_DIMENSION_BASE::TransformShapeWithClearanceToPolygon unexpected "
  422. "shape type." ) );
  423. }
  424. }
  425. }
  426. PCB_DIM_ALIGNED::PCB_DIM_ALIGNED( BOARD_ITEM* aParent, KICAD_T aType ) :
  427. PCB_DIMENSION_BASE( aParent, aType ),
  428. m_height( 0 )
  429. {
  430. // To preserve look of old dimensions, initialize extension height based on default arrow length
  431. m_extensionHeight = static_cast<int>( m_arrowLength * s_arrowAngle.Sin() );
  432. }
  433. EDA_ITEM* PCB_DIM_ALIGNED::Clone() const
  434. {
  435. return new PCB_DIM_ALIGNED( *this );
  436. }
  437. void PCB_DIM_ALIGNED::SwapData( BOARD_ITEM* aImage )
  438. {
  439. wxASSERT( aImage->Type() == Type() );
  440. m_shapes.clear();
  441. static_cast<PCB_DIM_ALIGNED*>( aImage )->m_shapes.clear();
  442. std::swap( *static_cast<PCB_DIM_ALIGNED*>( this ), *static_cast<PCB_DIM_ALIGNED*>( aImage ) );
  443. Update();
  444. }
  445. void PCB_DIM_ALIGNED::Mirror( const VECTOR2I& axis_pos, bool aMirrorLeftRight )
  446. {
  447. PCB_DIMENSION_BASE::Mirror( axis_pos, aMirrorLeftRight );
  448. m_height = -m_height;
  449. }
  450. BITMAPS PCB_DIM_ALIGNED::GetMenuImage() const
  451. {
  452. return BITMAPS::add_aligned_dimension;
  453. }
  454. void PCB_DIM_ALIGNED::UpdateHeight( const VECTOR2I& aCrossbarStart, const VECTOR2I& aCrossbarEnd )
  455. {
  456. VECTOR2D height( aCrossbarStart - GetStart() );
  457. VECTOR2D crossBar( aCrossbarEnd - aCrossbarStart );
  458. if( height.Cross( crossBar ) > 0 )
  459. m_height = -height.EuclideanNorm();
  460. else
  461. m_height = height.EuclideanNorm();
  462. Update();
  463. }
  464. void PCB_DIM_ALIGNED::updateGeometry()
  465. {
  466. m_shapes.clear();
  467. VECTOR2I dimension( m_end - m_start );
  468. m_measuredValue = KiROUND( dimension.EuclideanNorm() );
  469. VECTOR2I extension;
  470. if( m_height > 0 )
  471. extension = VECTOR2I( -dimension.y, dimension.x );
  472. else
  473. extension = VECTOR2I( dimension.y, -dimension.x );
  474. // Add extension lines
  475. int extensionHeight = std::abs( m_height ) - m_extensionOffset + m_extensionHeight;
  476. VECTOR2I extStart( m_start );
  477. extStart += extension.Resize( m_extensionOffset );
  478. addShape( SHAPE_SEGMENT( extStart, extStart + extension.Resize( extensionHeight ) ) );
  479. extStart = VECTOR2I( m_end );
  480. extStart += extension.Resize( m_extensionOffset );
  481. addShape( SHAPE_SEGMENT( extStart, extStart + extension.Resize( extensionHeight ) ) );
  482. // Add crossbar
  483. VECTOR2I crossBarDistance = sign( m_height ) * extension.Resize( m_height );
  484. m_crossBarStart = m_start + crossBarDistance;
  485. m_crossBarEnd = m_end + crossBarDistance;
  486. // Update text after calculating crossbar position but before adding crossbar lines
  487. updateText();
  488. // Now that we have the text updated, we can determine how to draw the crossbar.
  489. // First we need to create an appropriate bounding polygon to collide with
  490. BOX2I textBox = m_text.GetTextBox().Inflate( m_text.GetTextWidth() / 2,
  491. - m_text.GetEffectiveTextPenWidth() );
  492. SHAPE_POLY_SET polyBox;
  493. polyBox.NewOutline();
  494. polyBox.Append( textBox.GetOrigin() );
  495. polyBox.Append( textBox.GetOrigin().x, textBox.GetEnd().y );
  496. polyBox.Append( textBox.GetEnd() );
  497. polyBox.Append( textBox.GetEnd().x, textBox.GetOrigin().y );
  498. polyBox.Rotate( m_text.GetTextAngle(), textBox.GetCenter() );
  499. // The ideal crossbar, if the text doesn't collide
  500. SEG crossbar( m_crossBarStart, m_crossBarEnd );
  501. // Now we can draw 0, 1, or 2 crossbar lines depending on how the polygon collides
  502. bool containsA = polyBox.Contains( crossbar.A );
  503. bool containsB = polyBox.Contains( crossbar.B );
  504. OPT_VECTOR2I endpointA = segPolyIntersection( polyBox, crossbar );
  505. OPT_VECTOR2I endpointB = segPolyIntersection( polyBox, crossbar, false );
  506. if( endpointA )
  507. m_shapes.emplace_back( new SHAPE_SEGMENT( crossbar.A, *endpointA ) );
  508. if( endpointB )
  509. m_shapes.emplace_back( new SHAPE_SEGMENT( *endpointB, crossbar.B ) );
  510. if( !containsA && !containsB && !endpointA && !endpointB )
  511. m_shapes.emplace_back( new SHAPE_SEGMENT( crossbar ) );
  512. // Add arrows
  513. VECTOR2I arrowEndPos( m_arrowLength, 0 );
  514. VECTOR2I arrowEndNeg( m_arrowLength, 0 );
  515. RotatePoint( arrowEndPos, -EDA_ANGLE( dimension ) + s_arrowAngle );
  516. RotatePoint( arrowEndNeg, -EDA_ANGLE( dimension ) - s_arrowAngle );
  517. m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarStart, m_crossBarStart + arrowEndPos ) );
  518. m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarStart, m_crossBarStart + arrowEndNeg ) );
  519. m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarEnd, m_crossBarEnd - arrowEndPos ) );
  520. m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarEnd, m_crossBarEnd - arrowEndNeg ) );
  521. }
  522. void PCB_DIM_ALIGNED::updateText()
  523. {
  524. VECTOR2I crossbarCenter( ( m_crossBarEnd - m_crossBarStart ) / 2 );
  525. if( m_textPosition == DIM_TEXT_POSITION::OUTSIDE )
  526. {
  527. int textOffsetDistance = m_text.GetEffectiveTextPenWidth() + m_text.GetTextHeight();
  528. EDA_ANGLE rotation;
  529. if( crossbarCenter.x == 0 )
  530. rotation = ANGLE_90 * sign( -crossbarCenter.y );
  531. else if( crossbarCenter.x < 0 )
  532. rotation = -ANGLE_90;
  533. else
  534. rotation = ANGLE_90;
  535. VECTOR2I textOffset = crossbarCenter;
  536. RotatePoint( textOffset, rotation );
  537. textOffset = crossbarCenter + textOffset.Resize( textOffsetDistance );
  538. m_text.SetTextPos( m_crossBarStart + textOffset );
  539. }
  540. else if( m_textPosition == DIM_TEXT_POSITION::INLINE )
  541. {
  542. m_text.SetTextPos( m_crossBarStart + crossbarCenter );
  543. }
  544. if( m_keepTextAligned )
  545. {
  546. EDA_ANGLE textAngle = FULL_CIRCLE - EDA_ANGLE( crossbarCenter );
  547. textAngle.Normalize();
  548. if( textAngle > ANGLE_90 && textAngle <= ANGLE_270 )
  549. textAngle -= ANGLE_180;
  550. m_text.SetTextAngle( textAngle );
  551. }
  552. PCB_DIMENSION_BASE::updateText();
  553. }
  554. void PCB_DIM_ALIGNED::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
  555. {
  556. PCB_DIMENSION_BASE::GetMsgPanelInfo( aFrame, aList );
  557. aList.emplace_back( _( "Height" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( pcbIUScale, aFrame->GetUserUnits(), m_height ) );
  558. }
  559. PCB_DIM_ORTHOGONAL::PCB_DIM_ORTHOGONAL( BOARD_ITEM* aParent, bool aInFP ) :
  560. PCB_DIM_ALIGNED( aParent, aInFP ? PCB_FP_DIM_ORTHOGONAL_T : PCB_DIM_ORTHOGONAL_T )
  561. {
  562. // To preserve look of old dimensions, initialize extension height based on default arrow length
  563. m_extensionHeight = static_cast<int>( m_arrowLength * s_arrowAngle.Sin() );
  564. m_orientation = DIR::HORIZONTAL;
  565. }
  566. EDA_ITEM* PCB_DIM_ORTHOGONAL::Clone() const
  567. {
  568. return new PCB_DIM_ORTHOGONAL( *this );
  569. }
  570. void PCB_DIM_ORTHOGONAL::SwapData( BOARD_ITEM* aImage )
  571. {
  572. wxASSERT( aImage->Type() == Type() );
  573. m_shapes.clear();
  574. static_cast<PCB_DIM_ORTHOGONAL*>( aImage )->m_shapes.clear();
  575. std::swap( *static_cast<PCB_DIM_ORTHOGONAL*>( this ),
  576. *static_cast<PCB_DIM_ORTHOGONAL*>( aImage ) );
  577. Update();
  578. }
  579. BITMAPS PCB_DIM_ORTHOGONAL::GetMenuImage() const
  580. {
  581. return BITMAPS::add_orthogonal_dimension;
  582. }
  583. void PCB_DIM_ORTHOGONAL::updateGeometry()
  584. {
  585. m_shapes.clear();
  586. int measurement = ( m_orientation == DIR::HORIZONTAL ? m_end.x - m_start.x :
  587. m_end.y - m_start.y );
  588. m_measuredValue = KiROUND( std::abs( measurement ) );
  589. VECTOR2I extension;
  590. if( m_orientation == DIR::HORIZONTAL )
  591. extension = VECTOR2I( 0, m_height );
  592. else
  593. extension = VECTOR2I( m_height, 0 );
  594. // Add first extension line
  595. int extensionHeight = std::abs( m_height ) - m_extensionOffset + m_extensionHeight;
  596. VECTOR2I extStart( m_start );
  597. extStart += extension.Resize( m_extensionOffset );
  598. addShape( SHAPE_SEGMENT( extStart, extStart + extension.Resize( extensionHeight ) ) );
  599. // Add crossbar
  600. VECTOR2I crossBarDistance = sign( m_height ) * extension.Resize( m_height );
  601. m_crossBarStart = m_start + crossBarDistance;
  602. if( m_orientation == DIR::HORIZONTAL )
  603. m_crossBarEnd = VECTOR2I( m_end.x, m_crossBarStart.y );
  604. else
  605. m_crossBarEnd = VECTOR2I( m_crossBarStart.x, m_end.y );
  606. // Add second extension line (m_end to crossbar end)
  607. if( m_orientation == DIR::HORIZONTAL )
  608. extension = VECTOR2I( 0, m_end.y - m_crossBarEnd.y );
  609. else
  610. extension = VECTOR2I( m_end.x - m_crossBarEnd.x, 0 );
  611. extensionHeight = extension.EuclideanNorm() - m_extensionOffset + m_extensionHeight;
  612. extStart = VECTOR2I( m_crossBarEnd );
  613. extStart -= extension.Resize( m_extensionHeight );
  614. addShape( SHAPE_SEGMENT( extStart, extStart + extension.Resize( extensionHeight ) ) );
  615. // Update text after calculating crossbar position but before adding crossbar lines
  616. updateText();
  617. // Now that we have the text updated, we can determine how to draw the crossbar.
  618. // First we need to create an appropriate bounding polygon to collide with
  619. BOX2I textBox = m_text.GetTextBox().Inflate( m_text.GetTextWidth() / 2,
  620. m_text.GetEffectiveTextPenWidth() );
  621. SHAPE_POLY_SET polyBox;
  622. polyBox.NewOutline();
  623. polyBox.Append( textBox.GetOrigin() );
  624. polyBox.Append( textBox.GetOrigin().x, textBox.GetEnd().y );
  625. polyBox.Append( textBox.GetEnd() );
  626. polyBox.Append( textBox.GetEnd().x, textBox.GetOrigin().y );
  627. polyBox.Rotate( m_text.GetTextAngle(), textBox.GetCenter() );
  628. // The ideal crossbar, if the text doesn't collide
  629. SEG crossbar( m_crossBarStart, m_crossBarEnd );
  630. // Now we can draw 0, 1, or 2 crossbar lines depending on how the polygon collides
  631. bool containsA = polyBox.Contains( crossbar.A );
  632. bool containsB = polyBox.Contains( crossbar.B );
  633. OPT_VECTOR2I endpointA = segPolyIntersection( polyBox, crossbar );
  634. OPT_VECTOR2I endpointB = segPolyIntersection( polyBox, crossbar, false );
  635. if( endpointA )
  636. m_shapes.emplace_back( new SHAPE_SEGMENT( crossbar.A, *endpointA ) );
  637. if( endpointB )
  638. m_shapes.emplace_back( new SHAPE_SEGMENT( *endpointB, crossbar.B ) );
  639. if( !containsA && !containsB && !endpointA && !endpointB )
  640. m_shapes.emplace_back( new SHAPE_SEGMENT( crossbar ) );
  641. // Add arrows
  642. EDA_ANGLE crossBarAngle( m_crossBarEnd - m_crossBarStart );
  643. VECTOR2I arrowEndPos( m_arrowLength, 0 );
  644. VECTOR2I arrowEndNeg( m_arrowLength, 0 );
  645. RotatePoint( arrowEndPos, -crossBarAngle + s_arrowAngle );
  646. RotatePoint( arrowEndNeg, -crossBarAngle - s_arrowAngle );
  647. m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarStart, m_crossBarStart + arrowEndPos ) );
  648. m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarStart, m_crossBarStart + arrowEndNeg ) );
  649. m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarEnd, m_crossBarEnd - arrowEndPos ) );
  650. m_shapes.emplace_back( new SHAPE_SEGMENT( m_crossBarEnd, m_crossBarEnd - arrowEndNeg ) );
  651. }
  652. void PCB_DIM_ORTHOGONAL::updateText()
  653. {
  654. VECTOR2I crossbarCenter( ( m_crossBarEnd - m_crossBarStart ) / 2 );
  655. if( m_textPosition == DIM_TEXT_POSITION::OUTSIDE )
  656. {
  657. int textOffsetDistance = m_text.GetEffectiveTextPenWidth() + m_text.GetTextHeight();
  658. VECTOR2I textOffset;
  659. if( m_orientation == DIR::HORIZONTAL )
  660. textOffset.y = -textOffsetDistance;
  661. else
  662. textOffset.x = -textOffsetDistance;
  663. textOffset += crossbarCenter;
  664. m_text.SetTextPos( m_crossBarStart + textOffset );
  665. }
  666. else if( m_textPosition == DIM_TEXT_POSITION::INLINE )
  667. {
  668. m_text.SetTextPos( m_crossBarStart + crossbarCenter );
  669. }
  670. if( m_keepTextAligned )
  671. {
  672. if( abs( crossbarCenter.x ) > abs( crossbarCenter.y ) )
  673. m_text.SetTextAngle( ANGLE_HORIZONTAL );
  674. else
  675. m_text.SetTextAngle( ANGLE_VERTICAL );
  676. }
  677. PCB_DIMENSION_BASE::updateText();
  678. }
  679. void PCB_DIM_ORTHOGONAL::Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
  680. {
  681. EDA_ANGLE angle( aAngle );
  682. // restrict angle to -179.9 to 180.0 degrees
  683. angle.Normalize180();
  684. // adjust orientation and height to new angle
  685. // we can only handle the cases of -90, 0, 90, 180 degrees exactly;
  686. // in the other cases we will use the nearest 90 degree angle to
  687. // choose at least an approximate axis for the target orientation
  688. // In case of exactly 45 or 135 degrees, we will round towards zero for consistency
  689. if( angle > ANGLE_45 && angle <= ANGLE_135 )
  690. {
  691. // about 90 degree
  692. if( m_orientation == DIR::HORIZONTAL )
  693. {
  694. m_orientation = DIR::VERTICAL;
  695. }
  696. else
  697. {
  698. m_orientation = DIR::HORIZONTAL;
  699. m_height = -m_height;
  700. }
  701. }
  702. else if( angle < -ANGLE_45 && angle >= -ANGLE_135 )
  703. {
  704. // about -90 degree
  705. if( m_orientation == DIR::HORIZONTAL )
  706. {
  707. m_orientation = DIR::VERTICAL;
  708. m_height = -m_height;
  709. }
  710. else
  711. {
  712. m_orientation = DIR::HORIZONTAL;
  713. }
  714. }
  715. else if( angle > ANGLE_135 || angle < -ANGLE_135 )
  716. {
  717. // about 180 degree
  718. m_height = -m_height;
  719. }
  720. // this will update m_crossBarStart and m_crossbarEnd
  721. PCB_DIMENSION_BASE::Rotate( aRotCentre, angle );
  722. }
  723. PCB_DIM_LEADER::PCB_DIM_LEADER( BOARD_ITEM* aParent, bool aInFP ) :
  724. PCB_DIMENSION_BASE( aParent, aInFP ? PCB_FP_DIM_LEADER_T : PCB_DIM_LEADER_T ),
  725. m_textBorder( DIM_TEXT_BORDER::NONE )
  726. {
  727. m_unitsFormat = DIM_UNITS_FORMAT::NO_SUFFIX;
  728. m_overrideTextEnabled = true;
  729. m_keepTextAligned = false;
  730. SetText( _( "Leader" ) );
  731. }
  732. EDA_ITEM* PCB_DIM_LEADER::Clone() const
  733. {
  734. return new PCB_DIM_LEADER( *this );
  735. }
  736. void PCB_DIM_LEADER::SwapData( BOARD_ITEM* aImage )
  737. {
  738. wxASSERT( aImage->Type() == Type() );
  739. m_shapes.clear();
  740. static_cast<PCB_DIM_LEADER*>( aImage )->m_shapes.clear();
  741. std::swap( *static_cast<PCB_DIM_LEADER*>( this ), *static_cast<PCB_DIM_LEADER*>( aImage ) );
  742. Update();
  743. }
  744. BITMAPS PCB_DIM_LEADER::GetMenuImage() const
  745. {
  746. return BITMAPS::add_leader;
  747. }
  748. void PCB_DIM_LEADER::updateGeometry()
  749. {
  750. m_shapes.clear();
  751. updateText();
  752. // Now that we have the text updated, we can determine how to draw the second line
  753. // First we need to create an appropriate bounding polygon to collide with
  754. BOX2I textBox = m_text.GetTextBox().Inflate( m_text.GetTextWidth() / 2,
  755. m_text.GetEffectiveTextPenWidth() );
  756. SHAPE_POLY_SET polyBox;
  757. polyBox.NewOutline();
  758. polyBox.Append( textBox.GetOrigin() );
  759. polyBox.Append( textBox.GetOrigin().x, textBox.GetEnd().y );
  760. polyBox.Append( textBox.GetEnd() );
  761. polyBox.Append( textBox.GetEnd().x, textBox.GetOrigin().y );
  762. polyBox.Rotate( m_text.GetTextAngle(), textBox.GetCenter() );
  763. VECTOR2I firstLine( m_end - m_start );
  764. VECTOR2I start( m_start );
  765. start += firstLine.Resize( m_extensionOffset );
  766. SEG arrowSeg( m_start, m_end );
  767. SEG textSeg( m_end, m_text.GetPosition() );
  768. OPT_VECTOR2I arrowSegEnd;
  769. OPT_VECTOR2I textSegEnd;
  770. if( m_textBorder == DIM_TEXT_BORDER::CIRCLE )
  771. {
  772. double penWidth = m_text.GetEffectiveTextPenWidth() / 2.0;
  773. double radius = ( textBox.GetWidth() / 2.0 ) - penWidth;
  774. CIRCLE circle( textBox.GetCenter(), radius );
  775. arrowSegEnd = segCircleIntersection( circle, arrowSeg );
  776. textSegEnd = segCircleIntersection( circle, textSeg );
  777. }
  778. else
  779. {
  780. arrowSegEnd = segPolyIntersection( polyBox, arrowSeg );
  781. textSegEnd = segPolyIntersection( polyBox, textSeg );
  782. }
  783. if( !arrowSegEnd )
  784. arrowSegEnd = m_end;
  785. m_shapes.emplace_back( new SHAPE_SEGMENT( start, *arrowSegEnd ) );
  786. // Add arrows
  787. VECTOR2I arrowEndPos( m_arrowLength, 0 );
  788. VECTOR2I arrowEndNeg( m_arrowLength, 0 );
  789. RotatePoint( arrowEndPos, -EDA_ANGLE( firstLine ) + s_arrowAngle );
  790. RotatePoint( arrowEndNeg, -EDA_ANGLE( firstLine ) - s_arrowAngle );
  791. m_shapes.emplace_back( new SHAPE_SEGMENT( start, start + arrowEndPos ) );
  792. m_shapes.emplace_back( new SHAPE_SEGMENT( start, start + arrowEndNeg ) );
  793. if( !GetText().IsEmpty() )
  794. {
  795. switch( m_textBorder )
  796. {
  797. case DIM_TEXT_BORDER::RECTANGLE:
  798. {
  799. for( SHAPE_POLY_SET::SEGMENT_ITERATOR seg = polyBox.IterateSegments(); seg; seg++ )
  800. m_shapes.emplace_back( new SHAPE_SEGMENT( *seg ) );
  801. break;
  802. }
  803. case DIM_TEXT_BORDER::CIRCLE:
  804. {
  805. double penWidth = m_text.GetEffectiveTextPenWidth() / 2.0;
  806. double radius = ( textBox.GetWidth() / 2.0 ) - penWidth;
  807. m_shapes.emplace_back( new SHAPE_CIRCLE( textBox.GetCenter(), radius ) );
  808. break;
  809. }
  810. default:
  811. break;
  812. }
  813. }
  814. if( textSegEnd && *arrowSegEnd == m_end )
  815. m_shapes.emplace_back( new SHAPE_SEGMENT( m_end, *textSegEnd ) );
  816. }
  817. void PCB_DIM_LEADER::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
  818. {
  819. aList.emplace_back( _( "Leader" ), m_text.GetShownText() );
  820. ORIGIN_TRANSFORMS originTransforms = aFrame->GetOriginTransforms();
  821. EDA_UNITS units = aFrame->GetUserUnits();
  822. VECTOR2I startCoord = originTransforms.ToDisplayAbs( GetStart() );
  823. wxString start = wxString::Format( wxT( "@(%s, %s)" ),
  824. EDA_UNIT_UTILS::UI::MessageTextFromValue( pcbIUScale, units, startCoord.x ),
  825. EDA_UNIT_UTILS::UI::MessageTextFromValue( pcbIUScale, units, startCoord.y ) );
  826. aList.emplace_back( start, wxEmptyString );
  827. aList.emplace_back( _( "Layer" ), GetLayerName() );
  828. }
  829. PCB_DIM_RADIAL::PCB_DIM_RADIAL( BOARD_ITEM* aParent, bool aInFP ) :
  830. PCB_DIMENSION_BASE( aParent, aInFP ? PCB_FP_DIM_RADIAL_T : PCB_DIM_RADIAL_T )
  831. {
  832. m_unitsFormat = DIM_UNITS_FORMAT::NO_SUFFIX;
  833. m_overrideTextEnabled = false;
  834. m_keepTextAligned = true;
  835. m_isDiameter = false;
  836. m_prefix = "R ";
  837. m_leaderLength = m_arrowLength * 3;
  838. }
  839. EDA_ITEM* PCB_DIM_RADIAL::Clone() const
  840. {
  841. return new PCB_DIM_RADIAL( *this );
  842. }
  843. void PCB_DIM_RADIAL::SwapData( BOARD_ITEM* aImage )
  844. {
  845. wxASSERT( aImage->Type() == Type() );
  846. m_shapes.clear();
  847. static_cast<PCB_DIM_RADIAL*>( aImage )->m_shapes.clear();
  848. std::swap( *static_cast<PCB_DIM_RADIAL*>( this ), *static_cast<PCB_DIM_RADIAL*>( aImage ) );
  849. Update();
  850. }
  851. BITMAPS PCB_DIM_RADIAL::GetMenuImage() const
  852. {
  853. return BITMAPS::add_radial_dimension;
  854. }
  855. VECTOR2I PCB_DIM_RADIAL::GetKnee() const
  856. {
  857. VECTOR2I radial( m_end - m_start );
  858. return m_end + radial.Resize( m_leaderLength );
  859. }
  860. void PCB_DIM_RADIAL::updateText()
  861. {
  862. if( m_keepTextAligned )
  863. {
  864. VECTOR2I textLine( Text().GetPosition() - GetKnee() );
  865. EDA_ANGLE textAngle = FULL_CIRCLE - EDA_ANGLE( textLine );
  866. textAngle.Normalize();
  867. if( textAngle > ANGLE_90 && textAngle <= ANGLE_270 )
  868. textAngle -= ANGLE_180;
  869. // Round to nearest degree
  870. textAngle = EDA_ANGLE( KiROUND( textAngle.AsDegrees() ), DEGREES_T );
  871. m_text.SetTextAngle( textAngle );
  872. }
  873. PCB_DIMENSION_BASE::updateText();
  874. }
  875. void PCB_DIM_RADIAL::updateGeometry()
  876. {
  877. m_shapes.clear();
  878. VECTOR2I center( m_start );
  879. VECTOR2I centerArm( 0, m_arrowLength );
  880. m_shapes.emplace_back( new SHAPE_SEGMENT( center - centerArm, center + centerArm ) );
  881. RotatePoint( centerArm, -ANGLE_90 );
  882. m_shapes.emplace_back( new SHAPE_SEGMENT( center - centerArm, center + centerArm ) );
  883. VECTOR2I radius( m_end - m_start );
  884. if( m_isDiameter )
  885. m_measuredValue = KiROUND( radius.EuclideanNorm() * 2 );
  886. else
  887. m_measuredValue = KiROUND( radius.EuclideanNorm() );
  888. updateText();
  889. // Now that we have the text updated, we can determine how to draw the second line
  890. // First we need to create an appropriate bounding polygon to collide with
  891. BOX2I textBox = m_text.GetTextBox().Inflate( m_text.GetTextWidth() / 2,
  892. m_text.GetEffectiveTextPenWidth() );
  893. SHAPE_POLY_SET polyBox;
  894. polyBox.NewOutline();
  895. polyBox.Append( textBox.GetOrigin() );
  896. polyBox.Append( textBox.GetOrigin().x, textBox.GetEnd().y );
  897. polyBox.Append( textBox.GetEnd() );
  898. polyBox.Append( textBox.GetEnd().x, textBox.GetOrigin().y );
  899. polyBox.Rotate( m_text.GetTextAngle(), textBox.GetCenter() );
  900. VECTOR2I radial( m_end - m_start );
  901. radial = radial.Resize( m_leaderLength );
  902. SEG arrowSeg( m_end, m_end + radial );
  903. SEG textSeg( arrowSeg.B, m_text.GetPosition() );
  904. OPT_VECTOR2I arrowSegEnd = segPolyIntersection( polyBox, arrowSeg );
  905. OPT_VECTOR2I textSegEnd = segPolyIntersection( polyBox, textSeg );
  906. if( arrowSegEnd )
  907. arrowSeg.B = *arrowSegEnd;
  908. if( textSegEnd )
  909. textSeg.B = *textSegEnd;
  910. m_shapes.emplace_back( new SHAPE_SEGMENT( arrowSeg ) );
  911. // Add arrows
  912. VECTOR2I arrowEndPos( m_arrowLength, 0 );
  913. VECTOR2I arrowEndNeg( m_arrowLength, 0 );
  914. RotatePoint( arrowEndPos, -EDA_ANGLE( radial ) + s_arrowAngle );
  915. RotatePoint( arrowEndNeg, -EDA_ANGLE( radial ) - s_arrowAngle );
  916. m_shapes.emplace_back( new SHAPE_SEGMENT( m_end, m_end + arrowEndPos ) );
  917. m_shapes.emplace_back( new SHAPE_SEGMENT( m_end, m_end + arrowEndNeg ) );
  918. m_shapes.emplace_back( new SHAPE_SEGMENT( textSeg ) );
  919. }
  920. PCB_DIM_CENTER::PCB_DIM_CENTER( BOARD_ITEM* aParent, bool aInFP ) :
  921. PCB_DIMENSION_BASE( aParent, aInFP ? PCB_FP_DIM_CENTER_T : PCB_DIM_CENTER_T )
  922. {
  923. m_unitsFormat = DIM_UNITS_FORMAT::NO_SUFFIX;
  924. m_overrideTextEnabled = true;
  925. }
  926. EDA_ITEM* PCB_DIM_CENTER::Clone() const
  927. {
  928. return new PCB_DIM_CENTER( *this );
  929. }
  930. void PCB_DIM_CENTER::SwapData( BOARD_ITEM* aImage )
  931. {
  932. wxASSERT( aImage->Type() == Type() );
  933. std::swap( *static_cast<PCB_DIM_CENTER*>( this ), *static_cast<PCB_DIM_CENTER*>( aImage ) );
  934. }
  935. BITMAPS PCB_DIM_CENTER::GetMenuImage() const
  936. {
  937. return BITMAPS::add_center_dimension;
  938. }
  939. const BOX2I PCB_DIM_CENTER::GetBoundingBox() const
  940. {
  941. int halfWidth = VECTOR2I( m_end - m_start ).x + ( m_lineThickness / 2.0 );
  942. BOX2I bBox;
  943. bBox.SetX( m_start.x - halfWidth );
  944. bBox.SetY( m_start.y - halfWidth );
  945. bBox.SetWidth( halfWidth * 2 );
  946. bBox.SetHeight( halfWidth * 2 );
  947. bBox.Normalize();
  948. return bBox;
  949. }
  950. const BOX2I PCB_DIM_CENTER::ViewBBox() const
  951. {
  952. return BOX2I( VECTOR2I( GetBoundingBox().GetPosition() ),
  953. VECTOR2I( GetBoundingBox().GetSize() ) );
  954. }
  955. void PCB_DIM_CENTER::updateGeometry()
  956. {
  957. m_shapes.clear();
  958. VECTOR2I center( m_start );
  959. VECTOR2I arm( m_end - m_start );
  960. m_shapes.emplace_back( new SHAPE_SEGMENT( center - arm, center + arm ) );
  961. RotatePoint( arm, -ANGLE_90 );
  962. m_shapes.emplace_back( new SHAPE_SEGMENT( center - arm, center + arm ) );
  963. }
  964. static struct DIMENSION_DESC
  965. {
  966. DIMENSION_DESC()
  967. {
  968. PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
  969. REGISTER_TYPE( PCB_DIMENSION_BASE );
  970. propMgr.InheritsAfter( TYPE_HASH( PCB_DIMENSION_BASE ), TYPE_HASH( BOARD_ITEM ) );
  971. // TODO: add dimension properties:
  972. //propMgr.AddProperty( new PROPERTY<DIMENSION, int>( _HKI( "Height" ),
  973. //&DIMENSION::SetHeight, &DIMENSION::GetHeight, PROPERTY_DISPLAY::SIZE ) );
  974. }
  975. } _DIMENSION_DESC;