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.

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