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.

679 lines
22 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2015 Chris Pavlina <pavlina.chris@gmail.com>
  5. * Copyright (C) 2015, 2019 KiCad Developers, see change_log.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. * Field autoplacer: Tries to find an optimal place for component fields, and
  26. * places them there. There are two modes: "auto"-autoplace, and "manual" autoplace.
  27. * Auto mode is for when the process is run automatically, like when rotating parts,
  28. * and it avoids doing things that would be helpful for the final positioning but
  29. * annoying if they happened without permission.
  30. * Short description of the process:
  31. *
  32. * 1. Compute the dimensions of the fields' bounding box ::ComputeFBoxSize
  33. * 2. Determine which side the fields will go on. ::choose_side_for_fields
  34. * 1. Sort the four sides in preference order,
  35. * depending on the component's shape and
  36. * orientation ::get_preferred_sides
  37. * 2. If in manual mode, sift out the sides that would
  38. * cause fields to overlap other items ::get_colliding_sides
  39. * 3. If any remaining sides have zero pins there,
  40. * choose the highest zero-pin side according to
  41. * preference order.
  42. * 4. If all sides have pins, choose the side with the
  43. * fewest pins.
  44. * 3. Compute the position of the fields' bounding box ::field_box_placement
  45. * 4. In manual mode, shift the box vertically if possible
  46. * to fit fields between adjacent wires ::fit_fields_between_wires
  47. * 5. Move all fields to their final positions
  48. * 1. Re-justify fields if options allow that ::justify_field
  49. * 2. Round to a 50-mil grid coordinate if desired
  50. */
  51. #include <boost/range/adaptor/reversed.hpp>
  52. #include <sch_edit_frame.h>
  53. #include <hotkeys_basic.h>
  54. #include <sch_component.h>
  55. #include <sch_line.h>
  56. #include <lib_pin.h>
  57. #include <sch_draw_panel.h>
  58. #include <class_libentry.h>
  59. #include <eeschema_config.h>
  60. #include <kiface_i.h>
  61. #include <vector>
  62. #include <algorithm>
  63. #include <tool/tool_manager.h>
  64. #include <tools/ee_selection_tool.h>
  65. #define FIELD_PADDING Mils2iu( 10 ) // arbitrarily chosen for aesthetics
  66. #define FIELD_PADDING_ALIGNED Mils2iu( 18 ) // aligns 50 mil text to a 100 mil grid
  67. #define WIRE_V_SPACING Mils2iu( 100 )
  68. #define HPADDING Mils2iu( 25 )
  69. #define VPADDING Mils2iu( 25 )
  70. /**
  71. * Round up/down to the nearest multiple of n
  72. */
  73. template<typename T> T round_n( const T& value, const T& n, bool aRoundUp )
  74. {
  75. if( value % n )
  76. return n * (value / n + (aRoundUp ? 1 : 0));
  77. else
  78. return value;
  79. }
  80. /**
  81. * Convert an integer to a horizontal justification; neg=L zero=C pos=R
  82. */
  83. EDA_TEXT_HJUSTIFY_T TO_HJUSTIFY( int x )
  84. {
  85. return static_cast<EDA_TEXT_HJUSTIFY_T>( x );
  86. }
  87. class AUTOPLACER
  88. {
  89. SCH_SCREEN* m_screen;
  90. SCH_COMPONENT* m_component;
  91. std::vector<SCH_FIELD*> m_fields;
  92. std::vector<SCH_ITEM*> m_colliders;
  93. EDA_RECT m_comp_bbox;
  94. wxSize m_fbox_size;
  95. bool m_allow_rejustify, m_align_to_grid;
  96. bool m_power_symbol;
  97. public:
  98. typedef wxPoint SIDE;
  99. static const SIDE SIDE_TOP, SIDE_BOTTOM, SIDE_LEFT, SIDE_RIGHT;
  100. enum COLLISION { COLLIDE_NONE, COLLIDE_OBJECTS, COLLIDE_H_WIRES };
  101. struct SIDE_AND_NPINS
  102. {
  103. SIDE side;
  104. unsigned pins;
  105. };
  106. struct SIDE_AND_COLL
  107. {
  108. SIDE side;
  109. COLLISION collision;
  110. };
  111. AUTOPLACER( SCH_COMPONENT* aComponent, SCH_SCREEN* aScreen )
  112. :m_screen( aScreen ), m_component( aComponent )
  113. {
  114. m_component->GetFields( m_fields, /* aVisibleOnly */ true );
  115. Kiface().KifaceSettings()->Read( AutoplaceJustifyEntry, &m_allow_rejustify, true );
  116. Kiface().KifaceSettings()->Read( AutoplaceAlignEntry, &m_align_to_grid, false );
  117. m_comp_bbox = m_component->GetBodyBoundingBox();
  118. m_fbox_size = ComputeFBoxSize( /* aDynamic */ true );
  119. m_power_symbol = ! m_component->IsInNetlist();
  120. if( aScreen )
  121. get_possible_colliders( m_colliders );
  122. }
  123. /**
  124. * Do the actual autoplacement.
  125. * @param aManual - if true, use extra heuristics for smarter placement when manually
  126. * called up.
  127. */
  128. void DoAutoplace( bool aManual )
  129. {
  130. bool force_wire_spacing = false;
  131. SIDE field_side = choose_side_for_fields( aManual );
  132. wxPoint fbox_pos = field_box_placement( field_side );
  133. EDA_RECT field_box( fbox_pos, m_fbox_size );
  134. if( aManual )
  135. force_wire_spacing = fit_fields_between_wires( &field_box, field_side );
  136. // Move the fields
  137. int last_y_coord = field_box.GetTop();
  138. for( unsigned field_idx = 0; field_idx < m_fields.size(); ++field_idx )
  139. {
  140. SCH_FIELD* field = m_fields[field_idx];
  141. if( m_allow_rejustify )
  142. justify_field( field, field_side );
  143. wxPoint pos(
  144. field_horiz_placement( field, field_box ),
  145. field_vert_placement( field, field_box, &last_y_coord, !force_wire_spacing ) );
  146. if( m_align_to_grid )
  147. {
  148. pos.x = round_n( pos.x, Mils2iu( 50 ), field_side.x >= 0 );
  149. pos.y = round_n( pos.y, Mils2iu( 50 ), field_side.y == 1 );
  150. }
  151. field->SetPosition( pos );
  152. }
  153. }
  154. protected:
  155. /**
  156. * Compute and return the size of the fields' bounding box.
  157. * @param aDynamic - if true, use dynamic spacing
  158. */
  159. wxSize ComputeFBoxSize( bool aDynamic )
  160. {
  161. int max_field_width = 0;
  162. int total_height = 0;
  163. for( SCH_FIELD* field : m_fields )
  164. {
  165. int field_width;
  166. int field_height;
  167. if( m_component->GetTransform().y1 )
  168. {
  169. field->SetTextAngle( TEXT_ANGLE_VERT );
  170. }
  171. else
  172. {
  173. field->SetTextAngle( TEXT_ANGLE_HORIZ );
  174. }
  175. field_width = field->GetBoundingBox().GetWidth();
  176. field_height = field->GetBoundingBox().GetHeight();
  177. max_field_width = std::max( max_field_width, field_width );
  178. if( aDynamic )
  179. total_height += field_height + get_field_padding();
  180. else
  181. total_height += WIRE_V_SPACING;
  182. }
  183. return wxSize( max_field_width, total_height );
  184. }
  185. /**
  186. * Return the side that a pin is on.
  187. */
  188. SIDE get_pin_side( LIB_PIN* aPin )
  189. {
  190. int pin_orient = aPin->PinDrawOrient( m_component->GetTransform() );
  191. switch( pin_orient )
  192. {
  193. case PIN_RIGHT: return SIDE_LEFT;
  194. case PIN_LEFT: return SIDE_RIGHT;
  195. case PIN_UP: return SIDE_BOTTOM;
  196. case PIN_DOWN: return SIDE_TOP;
  197. default:
  198. wxFAIL_MSG( "Invalid pin orientation" );
  199. return SIDE_LEFT;
  200. }
  201. }
  202. /**
  203. * Count the number of pins on a side of the component.
  204. */
  205. unsigned pins_on_side( SIDE aSide )
  206. {
  207. unsigned pin_count = 0;
  208. std::vector<LIB_PIN*> pins;
  209. m_component->GetPins( pins );
  210. for( LIB_PIN* each_pin : pins )
  211. {
  212. if( !each_pin->IsVisible() && !m_power_symbol )
  213. continue;
  214. if( get_pin_side( each_pin ) == aSide )
  215. ++pin_count;
  216. }
  217. return pin_count;
  218. }
  219. /**
  220. * Populate a list of all drawing items that *may* collide with the fields. That is,
  221. * all drawing items, including other fields, that are not the current component or
  222. * its own fields.
  223. */
  224. void get_possible_colliders( std::vector<SCH_ITEM*>& aItems )
  225. {
  226. wxCHECK_RET( m_screen, "get_possible_colliders() with null m_screen" );
  227. for( auto item : m_screen->Items().Overlapping( m_component->GetBoundingBox() ) )
  228. {
  229. if( SCH_COMPONENT* comp = dynamic_cast<SCH_COMPONENT*>( item ) )
  230. {
  231. if( comp == m_component )
  232. continue;
  233. std::vector<SCH_FIELD*> fields;
  234. comp->GetFields( fields, /* aVisibleOnly */ true );
  235. for( SCH_FIELD* field : fields )
  236. aItems.push_back( field );
  237. }
  238. aItems.push_back( item );
  239. }
  240. }
  241. /**
  242. * Filter a list of possible colliders to include only those that actually collide
  243. * with a given rectangle. Returns the new vector.
  244. */
  245. std::vector<SCH_ITEM*> filtered_colliders( const EDA_RECT& aRect )
  246. {
  247. std::vector<SCH_ITEM*> filtered;
  248. for( SCH_ITEM* item : m_colliders )
  249. {
  250. EDA_RECT item_box;
  251. if( SCH_COMPONENT* item_comp = dynamic_cast<SCH_COMPONENT*>( item ) )
  252. item_box = item_comp->GetBodyBoundingBox();
  253. else
  254. item_box = item->GetBoundingBox();
  255. if( item_box.Intersects( aRect ) )
  256. filtered.push_back( item );
  257. }
  258. return filtered;
  259. }
  260. /**
  261. * Return a list with the preferred field sides for the component, in
  262. * decreasing order of preference.
  263. */
  264. std::vector<SIDE_AND_NPINS> get_preferred_sides()
  265. {
  266. SIDE_AND_NPINS sides_init[] = {
  267. { SIDE_RIGHT, pins_on_side( SIDE_RIGHT ) },
  268. { SIDE_TOP, pins_on_side( SIDE_TOP ) },
  269. { SIDE_LEFT, pins_on_side( SIDE_LEFT ) },
  270. { SIDE_BOTTOM, pins_on_side( SIDE_BOTTOM ) },
  271. };
  272. std::vector<SIDE_AND_NPINS> sides( sides_init, sides_init + arrayDim( sides_init ) );
  273. int orient = m_component->GetOrientation();
  274. int orient_angle = orient & 0xff; // enum is a bitmask
  275. bool h_mirrored = ( ( orient & CMP_MIRROR_X )
  276. && ( orient_angle == CMP_ORIENT_0 || orient_angle == CMP_ORIENT_180 ) );
  277. double w = double( m_comp_bbox.GetWidth() );
  278. double h = double( m_comp_bbox.GetHeight() );
  279. // The preferred-sides heuristics are a bit magical. These were determined mostly
  280. // by trial and error.
  281. if( m_power_symbol )
  282. {
  283. // For power symbols, we generally want the label at the top first.
  284. switch( orient_angle )
  285. {
  286. case CMP_ORIENT_0:
  287. std::swap( sides[0], sides[1] );
  288. std::swap( sides[1], sides[3] );
  289. // TOP, BOTTOM, RIGHT, LEFT
  290. break;
  291. case CMP_ORIENT_90:
  292. std::swap( sides[0], sides[2] );
  293. std::swap( sides[1], sides[2] );
  294. // LEFT, RIGHT, TOP, BOTTOM
  295. break;
  296. case CMP_ORIENT_180:
  297. std::swap( sides[0], sides[3] );
  298. // BOTTOM, TOP, LEFT, RIGHT
  299. break;
  300. case CMP_ORIENT_270:
  301. std::swap( sides[1], sides[2] );
  302. // RIGHT, LEFT, TOP, BOTTOM
  303. break;
  304. }
  305. }
  306. else
  307. {
  308. // If the component is horizontally mirrored, swap left and right
  309. if( h_mirrored )
  310. {
  311. std::swap( sides[0], sides[2] );
  312. }
  313. // If the component is very long or is a power symbol, swap H and V
  314. if( w/h > 3.0 )
  315. {
  316. std::swap( sides[0], sides[1] );
  317. std::swap( sides[1], sides[3] );
  318. }
  319. }
  320. return sides;
  321. }
  322. /**
  323. * Return a list of the sides where a field set would collide with another item.
  324. */
  325. std::vector<SIDE_AND_COLL> get_colliding_sides()
  326. {
  327. SIDE sides_init[] = { SIDE_RIGHT, SIDE_TOP, SIDE_LEFT, SIDE_BOTTOM };
  328. std::vector<SIDE> sides( sides_init, sides_init + arrayDim( sides_init ) );
  329. std::vector<SIDE_AND_COLL> colliding;
  330. // Iterate over all sides and find the ones that collide
  331. for( SIDE side : sides )
  332. {
  333. EDA_RECT box( field_box_placement( side ), m_fbox_size );
  334. COLLISION collision = COLLIDE_NONE;
  335. for( SCH_ITEM* collider : filtered_colliders( box ) )
  336. {
  337. SCH_LINE* line = dynamic_cast<SCH_LINE*>( collider );
  338. if( line && !side.x )
  339. {
  340. wxPoint start = line->GetStartPoint(), end = line->GetEndPoint();
  341. if( start.y == end.y && collision != COLLIDE_OBJECTS )
  342. collision = COLLIDE_H_WIRES;
  343. else
  344. collision = COLLIDE_OBJECTS;
  345. }
  346. else
  347. collision = COLLIDE_OBJECTS;
  348. }
  349. if( collision != COLLIDE_NONE )
  350. colliding.push_back( { side, collision } );
  351. }
  352. return colliding;
  353. }
  354. /**
  355. * Choose a side for the fields, filtered on only one side collision type.
  356. * Removes the sides matching the filter from the list.
  357. */
  358. SIDE_AND_NPINS choose_side_filtered( std::vector<SIDE_AND_NPINS>& aSides,
  359. const std::vector<SIDE_AND_COLL>& aCollidingSides, COLLISION aCollision,
  360. SIDE_AND_NPINS aLastSelection)
  361. {
  362. SIDE_AND_NPINS sel = aLastSelection;
  363. std::vector<SIDE_AND_NPINS>::iterator it = aSides.begin();
  364. while( it != aSides.end() )
  365. {
  366. bool collide = false;
  367. for( SIDE_AND_COLL collision : aCollidingSides )
  368. {
  369. if( collision.side == it->side && collision.collision == aCollision )
  370. collide = true;
  371. }
  372. if( !collide )
  373. ++it;
  374. else
  375. {
  376. if( it->pins <= sel.pins )
  377. {
  378. sel.pins = it->pins;
  379. sel.side = it->side;
  380. }
  381. it = aSides.erase( it );
  382. }
  383. }
  384. return sel;
  385. }
  386. /**
  387. * Look where a component's pins are to pick a side to put the fields on
  388. * @param aAvoidCollisions - if true, pick last the sides where the label will collide
  389. * with other items.
  390. */
  391. SIDE choose_side_for_fields( bool aAvoidCollisions )
  392. {
  393. std::vector<SIDE_AND_NPINS> sides = get_preferred_sides();
  394. std::reverse( sides.begin(), sides.end() );
  395. SIDE_AND_NPINS side = { wxPoint( 1, 0 ), UINT_MAX };
  396. if( aAvoidCollisions )
  397. {
  398. std::vector<SIDE_AND_COLL> colliding_sides = get_colliding_sides();
  399. side = choose_side_filtered( sides, colliding_sides, COLLIDE_OBJECTS, side );
  400. side = choose_side_filtered( sides, colliding_sides, COLLIDE_H_WIRES, side );
  401. }
  402. for( SIDE_AND_NPINS& each_side : sides | boost::adaptors::reversed )
  403. {
  404. if( !each_side.pins ) return each_side.side;
  405. }
  406. for( SIDE_AND_NPINS& each_side : sides )
  407. {
  408. if( each_side.pins <= side.pins )
  409. {
  410. side.pins = each_side.pins;
  411. side.side = each_side.side;
  412. }
  413. }
  414. return side.side;
  415. }
  416. /**
  417. * Set the justification of a field based on the side it's supposed to be on, taking
  418. * into account whether the field will be displayed with flipped justification due to
  419. * mirroring.
  420. */
  421. void justify_field( SCH_FIELD* aField, SIDE aFieldSide )
  422. {
  423. // Justification is set twice to allow IsHorizJustifyFlipped() to work correctly.
  424. aField->SetHorizJustify( TO_HJUSTIFY( -aFieldSide.x ) );
  425. aField->SetHorizJustify( TO_HJUSTIFY( -aFieldSide.x *
  426. ( aField->IsHorizJustifyFlipped() ? -1 : 1 ) ) );
  427. aField->SetVertJustify( GR_TEXT_VJUSTIFY_CENTER );
  428. }
  429. /**
  430. * Return the position of the field bounding box.
  431. */
  432. wxPoint field_box_placement( SIDE aFieldSide )
  433. {
  434. wxPoint fbox_center = m_comp_bbox.Centre();
  435. int offs_x = ( m_comp_bbox.GetWidth() + m_fbox_size.GetWidth() ) / 2 + HPADDING;
  436. int offs_y = ( m_comp_bbox.GetHeight() + m_fbox_size.GetHeight() ) / 2 + VPADDING;
  437. fbox_center.x += aFieldSide.x * offs_x;
  438. fbox_center.y += aFieldSide.y * offs_y;
  439. wxPoint fbox_pos(
  440. fbox_center.x - m_fbox_size.GetWidth() / 2,
  441. fbox_center.y - m_fbox_size.GetHeight() / 2 );
  442. return fbox_pos;
  443. }
  444. /**
  445. * Shift a field box up or down a bit to make the fields fit between some wires.
  446. * Returns true if a shift was made.
  447. */
  448. bool fit_fields_between_wires( EDA_RECT* aBox, SIDE aSide )
  449. {
  450. if( aSide != SIDE_TOP && aSide != SIDE_BOTTOM )
  451. return false;
  452. std::vector<SCH_ITEM*> colliders = filtered_colliders( *aBox );
  453. if( colliders.empty() )
  454. return false;
  455. // Find the offset of the wires for proper positioning
  456. int offset = 0;
  457. for( SCH_ITEM* item : colliders )
  458. {
  459. SCH_LINE* line = dynamic_cast<SCH_LINE*>( item );
  460. if( !line )
  461. return false;
  462. wxPoint start = line->GetStartPoint(), end = line->GetEndPoint();
  463. if( start.y != end.y )
  464. return false;
  465. int this_offset = (3 * WIRE_V_SPACING / 2) - ( start.y % WIRE_V_SPACING );
  466. if( offset == 0 )
  467. offset = this_offset;
  468. else if( offset != this_offset )
  469. return false;
  470. }
  471. // At this point we are recomputing the field box size. Do not
  472. // return false after this point.
  473. m_fbox_size = ComputeFBoxSize( /* aDynamic */ false );
  474. wxPoint pos = aBox->GetPosition();
  475. // Remove the existing padding to get a bit more space to work with
  476. if( aSide == SIDE_BOTTOM )
  477. {
  478. pos.y = m_comp_bbox.GetBottom() - get_field_padding();
  479. }
  480. else
  481. {
  482. pos.y = m_comp_bbox.GetTop() - m_fbox_size.y + get_field_padding();
  483. }
  484. pos.y = round_n( pos.y, WIRE_V_SPACING, aSide == SIDE_BOTTOM );
  485. aBox->SetOrigin( pos );
  486. return true;
  487. }
  488. /**
  489. * Place a field horizontally, taking into account the field width and justification.
  490. *
  491. * @param aField - the field to place.
  492. * @param aFieldBox - box in which fields will be placed
  493. *
  494. * @return Correct field horizontal position
  495. */
  496. int field_horiz_placement( SCH_FIELD *aField, const EDA_RECT &aFieldBox )
  497. {
  498. int field_hjust;
  499. int field_xcoord;
  500. if( aField->IsHorizJustifyFlipped() )
  501. field_hjust = -aField->GetHorizJustify();
  502. else
  503. field_hjust = aField->GetHorizJustify();
  504. switch( field_hjust )
  505. {
  506. case GR_TEXT_HJUSTIFY_LEFT:
  507. field_xcoord = aFieldBox.GetLeft();
  508. break;
  509. case GR_TEXT_HJUSTIFY_CENTER:
  510. field_xcoord = aFieldBox.Centre().x;
  511. break;
  512. case GR_TEXT_HJUSTIFY_RIGHT:
  513. field_xcoord = aFieldBox.GetRight();
  514. break;
  515. default:
  516. wxFAIL_MSG( "Unexpected value for SCH_FIELD::GetHorizJustify()" );
  517. field_xcoord = aFieldBox.Centre().x; // Most are centered
  518. }
  519. return field_xcoord;
  520. }
  521. /**
  522. * Place a field vertically. Because field vertical placements accumulate,
  523. * this takes a pointer to a vertical position accumulator.
  524. *
  525. * @param aField - the field to place.
  526. * @param aFieldBox - box in which fields will be placed.
  527. * @param aPosAccum - pointer to a position accumulator
  528. * @param aDynamic - use dynamic spacing
  529. *
  530. * @return Correct field vertical position
  531. */
  532. int field_vert_placement( SCH_FIELD *aField, const EDA_RECT &aFieldBox, int *aPosAccum,
  533. bool aDynamic )
  534. {
  535. int field_height;
  536. int padding;
  537. if( aDynamic )
  538. {
  539. field_height = aField->GetBoundingBox().GetHeight();
  540. padding = get_field_padding();
  541. }
  542. else
  543. {
  544. field_height = WIRE_V_SPACING / 2;
  545. padding = WIRE_V_SPACING / 2;
  546. }
  547. int placement = *aPosAccum + padding / 2 + field_height / 2;
  548. *aPosAccum += padding + field_height;
  549. return placement;
  550. }
  551. /**
  552. * Return the desired padding between fields.
  553. */
  554. int get_field_padding()
  555. {
  556. if( m_align_to_grid )
  557. return FIELD_PADDING_ALIGNED;
  558. else
  559. return FIELD_PADDING;
  560. }
  561. };
  562. const AUTOPLACER::SIDE AUTOPLACER::SIDE_TOP( 0, -1 );
  563. const AUTOPLACER::SIDE AUTOPLACER::SIDE_BOTTOM( 0, 1 );
  564. const AUTOPLACER::SIDE AUTOPLACER::SIDE_LEFT( -1, 0 );
  565. const AUTOPLACER::SIDE AUTOPLACER::SIDE_RIGHT( 1, 0 );
  566. void SCH_COMPONENT::AutoplaceFields( SCH_SCREEN* aScreen, bool aManual )
  567. {
  568. if( aManual )
  569. wxASSERT_MSG( aScreen, "A SCH_SCREEN pointer must be given for manual autoplacement" );
  570. AUTOPLACER autoplacer( this, aScreen );
  571. autoplacer.DoAutoplace( aManual );
  572. m_fieldsAutoplaced = ( aManual? AUTOPLACED_MANUAL : AUTOPLACED_AUTO );
  573. }