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.

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