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.

682 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. #include <eeschema_settings.h>
  66. #define FIELD_PADDING Mils2iu( 10 ) // arbitrarily chosen for aesthetics
  67. #define FIELD_PADDING_ALIGNED Mils2iu( 18 ) // aligns 50 mil text to a 100 mil grid
  68. #define WIRE_V_SPACING Mils2iu( 100 )
  69. #define HPADDING Mils2iu( 25 )
  70. #define VPADDING Mils2iu( 25 )
  71. /**
  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. * Convert an integer to a horizontal justification; neg=L zero=C pos=R
  83. */
  84. EDA_TEXT_HJUSTIFY_T TO_HJUSTIFY( int x )
  85. {
  86. return static_cast<EDA_TEXT_HJUSTIFY_T>( x );
  87. }
  88. class AUTOPLACER
  89. {
  90. SCH_SCREEN* m_screen;
  91. SCH_COMPONENT* m_component;
  92. std::vector<SCH_FIELD*> m_fields;
  93. std::vector<SCH_ITEM*> m_colliders;
  94. EDA_RECT m_comp_bbox;
  95. wxSize m_fbox_size;
  96. bool m_allow_rejustify, m_align_to_grid;
  97. bool m_power_symbol;
  98. public:
  99. typedef wxPoint SIDE;
  100. static const SIDE SIDE_TOP, SIDE_BOTTOM, SIDE_LEFT, SIDE_RIGHT;
  101. enum COLLISION { COLLIDE_NONE, COLLIDE_OBJECTS, COLLIDE_H_WIRES };
  102. struct SIDE_AND_NPINS
  103. {
  104. SIDE side;
  105. unsigned pins;
  106. };
  107. struct SIDE_AND_COLL
  108. {
  109. SIDE side;
  110. COLLISION collision;
  111. };
  112. AUTOPLACER( SCH_COMPONENT* aComponent, SCH_SCREEN* aScreen )
  113. :m_screen( aScreen ), m_component( aComponent )
  114. {
  115. m_component->GetFields( m_fields, /* aVisibleOnly */ true );
  116. auto cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() );
  117. m_allow_rejustify = cfg->m_AutoplaceFields.allow_rejustify;
  118. m_align_to_grid = cfg->m_AutoplaceFields.align_to_grid;
  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, Mils2iu( 50 ), field_side.x >= 0 );
  151. pos.y = round_n( pos.y, Mils2iu( 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. * Return the side that a pin is on.
  189. */
  190. SIDE get_pin_side( LIB_PIN* aPin )
  191. {
  192. int pin_orient = aPin->PinDrawOrient( m_component->GetTransform() );
  193. switch( pin_orient )
  194. {
  195. case PIN_RIGHT: return SIDE_LEFT;
  196. case PIN_LEFT: return SIDE_RIGHT;
  197. case PIN_UP: return SIDE_BOTTOM;
  198. case PIN_DOWN: return SIDE_TOP;
  199. default:
  200. wxFAIL_MSG( "Invalid pin orientation" );
  201. return SIDE_LEFT;
  202. }
  203. }
  204. /**
  205. * Count the number of pins on a side of the component.
  206. */
  207. unsigned pins_on_side( SIDE aSide )
  208. {
  209. unsigned pin_count = 0;
  210. std::vector<LIB_PIN*> pins;
  211. m_component->GetPins( pins );
  212. for( LIB_PIN* each_pin : pins )
  213. {
  214. if( !each_pin->IsVisible() && !m_power_symbol )
  215. continue;
  216. if( get_pin_side( each_pin ) == aSide )
  217. ++pin_count;
  218. }
  219. return pin_count;
  220. }
  221. /**
  222. * Populate a list of all drawing items that *may* collide with the fields. That is,
  223. * all drawing items, including other fields, that are not the current component or
  224. * its own fields.
  225. */
  226. void get_possible_colliders( std::vector<SCH_ITEM*>& aItems )
  227. {
  228. wxCHECK_RET( m_screen, "get_possible_colliders() with null m_screen" );
  229. for( auto item : m_screen->Items().Overlapping( m_component->GetBoundingBox() ) )
  230. {
  231. if( SCH_COMPONENT* comp = dynamic_cast<SCH_COMPONENT*>( item ) )
  232. {
  233. if( comp == m_component )
  234. continue;
  235. std::vector<SCH_FIELD*> fields;
  236. comp->GetFields( fields, /* aVisibleOnly */ true );
  237. for( SCH_FIELD* field : fields )
  238. aItems.push_back( field );
  239. }
  240. aItems.push_back( item );
  241. }
  242. }
  243. /**
  244. * Filter a list of possible colliders to include only those that actually collide
  245. * with a given rectangle. Returns the new vector.
  246. */
  247. std::vector<SCH_ITEM*> filtered_colliders( const EDA_RECT& aRect )
  248. {
  249. std::vector<SCH_ITEM*> filtered;
  250. for( SCH_ITEM* item : m_colliders )
  251. {
  252. EDA_RECT item_box;
  253. if( SCH_COMPONENT* item_comp = dynamic_cast<SCH_COMPONENT*>( item ) )
  254. item_box = item_comp->GetBodyBoundingBox();
  255. else
  256. item_box = item->GetBoundingBox();
  257. if( item_box.Intersects( aRect ) )
  258. filtered.push_back( item );
  259. }
  260. return filtered;
  261. }
  262. /**
  263. * Return a list with the preferred field sides for the component, in
  264. * decreasing order of preference.
  265. */
  266. std::vector<SIDE_AND_NPINS> get_preferred_sides()
  267. {
  268. SIDE_AND_NPINS sides_init[] = {
  269. { SIDE_RIGHT, pins_on_side( SIDE_RIGHT ) },
  270. { SIDE_TOP, pins_on_side( SIDE_TOP ) },
  271. { SIDE_LEFT, pins_on_side( SIDE_LEFT ) },
  272. { SIDE_BOTTOM, pins_on_side( SIDE_BOTTOM ) },
  273. };
  274. std::vector<SIDE_AND_NPINS> sides( sides_init, sides_init + arrayDim( sides_init ) );
  275. int orient = m_component->GetOrientation();
  276. int orient_angle = orient & 0xff; // enum is a bitmask
  277. bool h_mirrored = ( ( orient & CMP_MIRROR_X )
  278. && ( orient_angle == CMP_ORIENT_0 || orient_angle == CMP_ORIENT_180 ) );
  279. double w = double( m_comp_bbox.GetWidth() );
  280. double h = double( m_comp_bbox.GetHeight() );
  281. // The preferred-sides heuristics are a bit magical. These were determined mostly
  282. // by trial and error.
  283. if( m_power_symbol )
  284. {
  285. // For power symbols, we generally want the label at the top first.
  286. switch( orient_angle )
  287. {
  288. case CMP_ORIENT_0:
  289. std::swap( sides[0], sides[1] );
  290. std::swap( sides[1], sides[3] );
  291. // TOP, BOTTOM, RIGHT, LEFT
  292. break;
  293. case CMP_ORIENT_90:
  294. std::swap( sides[0], sides[2] );
  295. std::swap( sides[1], sides[2] );
  296. // LEFT, RIGHT, TOP, BOTTOM
  297. break;
  298. case CMP_ORIENT_180:
  299. std::swap( sides[0], sides[3] );
  300. // BOTTOM, TOP, LEFT, RIGHT
  301. break;
  302. case CMP_ORIENT_270:
  303. std::swap( sides[1], sides[2] );
  304. // RIGHT, LEFT, TOP, BOTTOM
  305. break;
  306. }
  307. }
  308. else
  309. {
  310. // If the component is horizontally mirrored, swap left and right
  311. if( h_mirrored )
  312. {
  313. std::swap( sides[0], sides[2] );
  314. }
  315. // If the component is very long or is a power symbol, swap H and V
  316. if( w/h > 3.0 )
  317. {
  318. std::swap( sides[0], sides[1] );
  319. std::swap( sides[1], sides[3] );
  320. }
  321. }
  322. return sides;
  323. }
  324. /**
  325. * Return a list of the sides where a field set would collide with another item.
  326. */
  327. std::vector<SIDE_AND_COLL> get_colliding_sides()
  328. {
  329. SIDE sides_init[] = { SIDE_RIGHT, SIDE_TOP, SIDE_LEFT, SIDE_BOTTOM };
  330. std::vector<SIDE> sides( sides_init, sides_init + arrayDim( sides_init ) );
  331. std::vector<SIDE_AND_COLL> colliding;
  332. // Iterate over all sides and find the ones that collide
  333. for( SIDE side : sides )
  334. {
  335. EDA_RECT box( field_box_placement( side ), m_fbox_size );
  336. COLLISION collision = COLLIDE_NONE;
  337. for( SCH_ITEM* collider : filtered_colliders( box ) )
  338. {
  339. SCH_LINE* line = dynamic_cast<SCH_LINE*>( collider );
  340. if( line && !side.x )
  341. {
  342. wxPoint start = line->GetStartPoint(), end = line->GetEndPoint();
  343. if( start.y == end.y && collision != COLLIDE_OBJECTS )
  344. collision = COLLIDE_H_WIRES;
  345. else
  346. collision = COLLIDE_OBJECTS;
  347. }
  348. else
  349. collision = COLLIDE_OBJECTS;
  350. }
  351. if( collision != COLLIDE_NONE )
  352. colliding.push_back( { side, collision } );
  353. }
  354. return colliding;
  355. }
  356. /**
  357. * Choose a side for the fields, filtered on only one side collision type.
  358. * Removes the sides matching the filter from the list.
  359. */
  360. SIDE_AND_NPINS choose_side_filtered( std::vector<SIDE_AND_NPINS>& aSides,
  361. const std::vector<SIDE_AND_COLL>& aCollidingSides, COLLISION aCollision,
  362. SIDE_AND_NPINS aLastSelection)
  363. {
  364. SIDE_AND_NPINS sel = aLastSelection;
  365. std::vector<SIDE_AND_NPINS>::iterator it = aSides.begin();
  366. while( it != aSides.end() )
  367. {
  368. bool collide = false;
  369. for( SIDE_AND_COLL collision : aCollidingSides )
  370. {
  371. if( collision.side == it->side && collision.collision == aCollision )
  372. collide = true;
  373. }
  374. if( !collide )
  375. ++it;
  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 component'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 choose_side_for_fields( bool aAvoidCollisions )
  394. {
  395. std::vector<SIDE_AND_NPINS> sides = get_preferred_sides();
  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 = get_colliding_sides();
  401. side = choose_side_filtered( sides, colliding_sides, COLLIDE_OBJECTS, side );
  402. side = choose_side_filtered( 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 justify_field( 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 field_box_placement( SIDE aFieldSide )
  435. {
  436. wxPoint fbox_center = m_comp_bbox.Centre();
  437. int offs_x = ( m_comp_bbox.GetWidth() + m_fbox_size.GetWidth() ) / 2 + HPADDING;
  438. int offs_y = ( m_comp_bbox.GetHeight() + m_fbox_size.GetHeight() ) / 2 + VPADDING;
  439. fbox_center.x += aFieldSide.x * offs_x;
  440. fbox_center.y += aFieldSide.y * offs_y;
  441. wxPoint fbox_pos(
  442. fbox_center.x - m_fbox_size.GetWidth() / 2,
  443. fbox_center.y - m_fbox_size.GetHeight() / 2 );
  444. return fbox_pos;
  445. }
  446. /**
  447. * Shift a field box up or down a bit to make the fields fit between some wires.
  448. * Returns true if a shift was made.
  449. */
  450. bool fit_fields_between_wires( EDA_RECT* aBox, SIDE aSide )
  451. {
  452. if( aSide != SIDE_TOP && aSide != SIDE_BOTTOM )
  453. return false;
  454. std::vector<SCH_ITEM*> colliders = filtered_colliders( *aBox );
  455. if( colliders.empty() )
  456. return false;
  457. // Find the offset of the wires for proper positioning
  458. int offset = 0;
  459. for( SCH_ITEM* item : colliders )
  460. {
  461. SCH_LINE* line = dynamic_cast<SCH_LINE*>( item );
  462. if( !line )
  463. return false;
  464. wxPoint start = line->GetStartPoint(), end = line->GetEndPoint();
  465. if( start.y != end.y )
  466. return false;
  467. int this_offset = (3 * WIRE_V_SPACING / 2) - ( start.y % WIRE_V_SPACING );
  468. if( offset == 0 )
  469. offset = this_offset;
  470. else if( offset != this_offset )
  471. return false;
  472. }
  473. // At this point we are recomputing the field box size. Do not
  474. // return false after this point.
  475. m_fbox_size = ComputeFBoxSize( /* aDynamic */ false );
  476. wxPoint pos = aBox->GetPosition();
  477. // Remove the existing padding to get a bit more space to work with
  478. if( aSide == SIDE_BOTTOM )
  479. {
  480. pos.y = m_comp_bbox.GetBottom() - get_field_padding();
  481. }
  482. else
  483. {
  484. pos.y = m_comp_bbox.GetTop() - m_fbox_size.y + get_field_padding();
  485. }
  486. pos.y = round_n( pos.y, WIRE_V_SPACING, aSide == SIDE_BOTTOM );
  487. aBox->SetOrigin( pos );
  488. return true;
  489. }
  490. /**
  491. * Place a field horizontally, taking into account the field width and justification.
  492. *
  493. * @param aField - the field to place.
  494. * @param aFieldBox - box in which fields will be placed
  495. *
  496. * @return Correct field horizontal position
  497. */
  498. int field_horiz_placement( SCH_FIELD *aField, const EDA_RECT &aFieldBox )
  499. {
  500. int field_hjust;
  501. int field_xcoord;
  502. if( aField->IsHorizJustifyFlipped() )
  503. field_hjust = -aField->GetHorizJustify();
  504. else
  505. field_hjust = aField->GetHorizJustify();
  506. switch( field_hjust )
  507. {
  508. case GR_TEXT_HJUSTIFY_LEFT:
  509. field_xcoord = aFieldBox.GetLeft();
  510. break;
  511. case GR_TEXT_HJUSTIFY_CENTER:
  512. field_xcoord = aFieldBox.Centre().x;
  513. break;
  514. case GR_TEXT_HJUSTIFY_RIGHT:
  515. field_xcoord = aFieldBox.GetRight();
  516. break;
  517. default:
  518. wxFAIL_MSG( "Unexpected value for SCH_FIELD::GetHorizJustify()" );
  519. field_xcoord = aFieldBox.Centre().x; // Most are centered
  520. }
  521. return field_xcoord;
  522. }
  523. /**
  524. * Place a field vertically. Because field vertical placements accumulate,
  525. * this takes a pointer to a vertical position accumulator.
  526. *
  527. * @param aField - the field to place.
  528. * @param aFieldBox - box in which fields will be placed.
  529. * @param aPosAccum - pointer to a position accumulator
  530. * @param aDynamic - use dynamic spacing
  531. *
  532. * @return Correct field vertical position
  533. */
  534. int field_vert_placement( SCH_FIELD *aField, const EDA_RECT &aFieldBox, int *aPosAccum,
  535. bool aDynamic )
  536. {
  537. int field_height;
  538. int padding;
  539. if( aDynamic )
  540. {
  541. field_height = aField->GetBoundingBox().GetHeight();
  542. padding = get_field_padding();
  543. }
  544. else
  545. {
  546. field_height = WIRE_V_SPACING / 2;
  547. padding = WIRE_V_SPACING / 2;
  548. }
  549. int placement = *aPosAccum + padding / 2 + field_height / 2;
  550. *aPosAccum += padding + field_height;
  551. return placement;
  552. }
  553. /**
  554. * Return the desired padding between fields.
  555. */
  556. int get_field_padding()
  557. {
  558. if( m_align_to_grid )
  559. return FIELD_PADDING_ALIGNED;
  560. else
  561. return FIELD_PADDING;
  562. }
  563. };
  564. const AUTOPLACER::SIDE AUTOPLACER::SIDE_TOP( 0, -1 );
  565. const AUTOPLACER::SIDE AUTOPLACER::SIDE_BOTTOM( 0, 1 );
  566. const AUTOPLACER::SIDE AUTOPLACER::SIDE_LEFT( -1, 0 );
  567. const AUTOPLACER::SIDE AUTOPLACER::SIDE_RIGHT( 1, 0 );
  568. void SCH_COMPONENT::AutoplaceFields( SCH_SCREEN* aScreen, bool aManual )
  569. {
  570. if( aManual )
  571. wxASSERT_MSG( aScreen, "A SCH_SCREEN pointer must be given for manual autoplacement" );
  572. AUTOPLACER autoplacer( this, aScreen );
  573. autoplacer.DoAutoplace( aManual );
  574. m_fieldsAutoplaced = ( aManual? AUTOPLACED_MANUAL : AUTOPLACED_AUTO );
  575. }