1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2013-2019 CERN
5 * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
8 * @author Maciej Suminski <maciej.suminski@cern.ch>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, you may find one here:
22 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
23 * or you may search the http://www.gnu.org website for the version 2 license,
24 * or you may write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
26 */
27
28 #include <board.h>
29 #include <board_design_settings.h>
30 #include <pcb_track.h>
31 #include <pcb_group.h>
32 #include <footprint.h>
33 #include <pad.h>
34 #include <pcb_shape.h>
35 #include <string_utils.h>
36 #include <zone.h>
37 #include <pcb_text.h>
38 #include <pcb_marker.h>
39 #include <pcb_dimension.h>
40 #include <pcb_target.h>
41 #include <advanced_config.h>
42 #include <core/arraydim.h>
43
44 #include <layer_ids.h>
45 #include <pcb_painter.h>
46 #include <pcb_display_options.h>
47 #include <project/net_settings.h>
48 #include <settings/color_settings.h>
49
50 #include <convert_basic_shapes_to_polygon.h>
51 #include <gal/graphics_abstraction_layer.h>
52 #include <geometry/geometry_utils.h>
53 #include <geometry/shape_line_chain.h>
54 #include <geometry/shape_rect.h>
55 #include <geometry/shape_segment.h>
56 #include <geometry/shape_simple.h>
57 #include <geometry/shape_circle.h>
58 #include <bezier_curves.h>
59
60 using namespace KIGFX;
61
PCB_RENDER_SETTINGS()62 PCB_RENDER_SETTINGS::PCB_RENDER_SETTINGS()
63 {
64 m_backgroundColor = COLOR4D( 0.0, 0.0, 0.0, 1.0 );
65 m_padNumbers = true;
66 m_netNamesOnPads = true;
67 m_netNamesOnTracks = true;
68 m_netNamesOnVias = true;
69 m_zoneOutlines = true;
70 m_zoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_FILLED;
71 m_clearanceDisplayFlags = CL_NONE;
72 m_sketchGraphics = false;
73 m_sketchText = false;
74 m_netColorMode = NET_COLOR_MODE::RATSNEST;
75 m_contrastModeDisplay = HIGH_CONTRAST_MODE::NORMAL;
76 m_ratsnestDisplayMode = RATSNEST_MODE::ALL;
77
78 m_trackOpacity = 1.0;
79 m_viaOpacity = 1.0;
80 m_padOpacity = 1.0;
81 m_zoneOpacity = 1.0;
82
83 // By default everything should be displayed as filled
84 for( unsigned int i = 0; i < arrayDim( m_sketchMode ); ++i )
85 m_sketchMode[i] = false;
86
87 update();
88 }
89
90
LoadColors(const COLOR_SETTINGS * aSettings)91 void PCB_RENDER_SETTINGS::LoadColors( const COLOR_SETTINGS* aSettings )
92 {
93 SetBackgroundColor( aSettings->GetColor( LAYER_PCB_BACKGROUND ) );
94
95 // Init board layers colors:
96 for( int i = 0; i < PCB_LAYER_ID_COUNT; i++ )
97 {
98 m_layerColors[i] = aSettings->GetColor( i );
99
100 // Guard: if the alpha channel is too small, the layer is not visible.
101 if( m_layerColors[i].a < 0.2 )
102 m_layerColors[i].a = 0.2;
103 }
104
105 // Init specific graphic layers colors:
106 for( int i = GAL_LAYER_ID_START; i < GAL_LAYER_ID_END; i++ )
107 m_layerColors[i] = aSettings->GetColor( i );
108
109 // Colors for layers that aren't theme-able
110 m_layerColors[LAYER_PAD_PLATEDHOLES] = aSettings->GetColor( LAYER_PCB_BACKGROUND );
111 m_layerColors[LAYER_VIA_NETNAMES] = COLOR4D( 0.2, 0.2, 0.2, 0.9 );
112 m_layerColors[LAYER_PAD_NETNAMES] = COLOR4D( 1.0, 1.0, 1.0, 0.9 );
113 m_layerColors[LAYER_PAD_FR] = aSettings->GetColor( F_Cu );
114 m_layerColors[LAYER_PAD_BK] = aSettings->GetColor( B_Cu );
115 m_layerColors[LAYER_PAD_FR_NETNAMES] = COLOR4D( 1.0, 1.0, 1.0, 0.9 );
116 m_layerColors[LAYER_PAD_BK_NETNAMES] = COLOR4D( 1.0, 1.0, 1.0, 0.9 );
117
118 // Netnames for copper layers
119 for( LSEQ cu = LSET::AllCuMask().CuStack(); cu; ++cu )
120 {
121 const COLOR4D lightLabel( 0.8, 0.8, 0.8, 0.7 );
122 const COLOR4D darkLabel = lightLabel.Inverted();
123 PCB_LAYER_ID layer = *cu;
124
125 if( m_layerColors[layer].GetBrightness() > 0.5 )
126 m_layerColors[GetNetnameLayer( layer )] = darkLabel;
127 else
128 m_layerColors[GetNetnameLayer( layer )] = lightLabel;
129 }
130
131 update();
132 }
133
134
LoadDisplayOptions(const PCB_DISPLAY_OPTIONS & aOptions,bool aShowPageLimits)135 void PCB_RENDER_SETTINGS::LoadDisplayOptions( const PCB_DISPLAY_OPTIONS& aOptions,
136 bool aShowPageLimits )
137 {
138 m_hiContrastEnabled = ( aOptions.m_ContrastModeDisplay !=
139 HIGH_CONTRAST_MODE::NORMAL );
140 m_padNumbers = aOptions.m_DisplayPadNum;
141 m_sketchGraphics = !aOptions.m_DisplayGraphicsFill;
142 m_sketchText = !aOptions.m_DisplayTextFill;
143 m_curvedRatsnestlines = aOptions.m_DisplayRatsnestLinesCurved;
144 m_globalRatsnestlines = aOptions.m_ShowGlobalRatsnest;
145
146 // Whether to draw tracks, vias & pads filled or as outlines
147 m_sketchMode[LAYER_PADS_TH] = !aOptions.m_DisplayPadFill;
148 m_sketchMode[LAYER_VIA_THROUGH] = !aOptions.m_DisplayViaFill;
149 m_sketchMode[LAYER_VIA_BBLIND] = !aOptions.m_DisplayViaFill;
150 m_sketchMode[LAYER_VIA_MICROVIA] = !aOptions.m_DisplayViaFill;
151 m_sketchMode[LAYER_TRACKS] = !aOptions.m_DisplayPcbTrackFill;
152
153 // Net names display settings
154 switch( aOptions.m_DisplayNetNamesMode )
155 {
156 case 0:
157 m_netNamesOnPads = false;
158 m_netNamesOnTracks = false;
159 m_netNamesOnVias = false;
160 break;
161
162 case 1:
163 m_netNamesOnPads = true;
164 m_netNamesOnTracks = false;
165 m_netNamesOnVias = true; // Follow pads or tracks? For now we chose pads....
166 break;
167
168 case 2:
169 m_netNamesOnPads = false;
170 m_netNamesOnTracks = true;
171 m_netNamesOnVias = false; // Follow pads or tracks? For now we chose pads....
172 break;
173
174 case 3:
175 m_netNamesOnPads = true;
176 m_netNamesOnTracks = true;
177 m_netNamesOnVias = true;
178 break;
179 }
180
181 // Zone display settings
182 m_zoneDisplayMode = aOptions.m_ZoneDisplayMode;
183
184 // Clearance settings
185 switch( aOptions.m_ShowTrackClearanceMode )
186 {
187 case PCB_DISPLAY_OPTIONS::DO_NOT_SHOW_CLEARANCE:
188 m_clearanceDisplayFlags = CL_NONE;
189 break;
190
191 case PCB_DISPLAY_OPTIONS::SHOW_TRACK_CLEARANCE_WHILE_ROUTING:
192 m_clearanceDisplayFlags = CL_NEW | CL_TRACKS;
193 break;
194
195 case PCB_DISPLAY_OPTIONS::SHOW_TRACK_CLEARANCE_WITH_VIA_WHILE_ROUTING:
196 m_clearanceDisplayFlags = CL_NEW | CL_TRACKS | CL_VIAS;
197 break;
198
199 case PCB_DISPLAY_OPTIONS::SHOW_WHILE_ROUTING_OR_DRAGGING:
200 m_clearanceDisplayFlags = CL_NEW | CL_EDITED | CL_TRACKS | CL_VIAS;
201 break;
202
203 case PCB_DISPLAY_OPTIONS::SHOW_TRACK_CLEARANCE_WITH_VIA_ALWAYS:
204 m_clearanceDisplayFlags = CL_NEW | CL_EDITED | CL_EXISTING | CL_TRACKS | CL_VIAS;
205 break;
206 }
207
208 if( aOptions.m_DisplayPadClearance )
209 m_clearanceDisplayFlags |= CL_PADS;
210
211 m_contrastModeDisplay = aOptions.m_ContrastModeDisplay;
212
213 m_netColorMode = aOptions.m_NetColorMode;
214
215 m_ratsnestDisplayMode = aOptions.m_RatsnestMode;
216
217 m_trackOpacity = aOptions.m_TrackOpacity;
218 m_viaOpacity = aOptions.m_ViaOpacity;
219 m_padOpacity = aOptions.m_PadOpacity;
220 m_zoneOpacity = aOptions.m_ZoneOpacity;
221
222 m_showPageLimits = aShowPageLimits;
223 }
224
225
GetColor(const VIEW_ITEM * aItem,int aLayer) const226 COLOR4D PCB_RENDER_SETTINGS::GetColor( const VIEW_ITEM* aItem, int aLayer ) const
227 {
228 const EDA_ITEM* item = dynamic_cast<const EDA_ITEM*>( aItem );
229 const BOARD_CONNECTED_ITEM* conItem = dynamic_cast<const BOARD_CONNECTED_ITEM*> ( aItem );
230 int netCode = -1;
231 int originalLayer = aLayer;
232
233 // Marker shadows
234 if( aLayer == LAYER_MARKER_SHADOWS )
235 return m_backgroundColor.WithAlpha( 0.6 );
236
237 if( IsHoleLayer( aLayer ) && m_isPrinting )
238 {
239 // Careful that we don't end up with the same colour for the annular ring and the hole
240 // when printing in B&W.
241 const PAD* pad = dynamic_cast<const PAD*>( item );
242 const PCB_VIA* via = dynamic_cast<const PCB_VIA*>( item );
243 int holeLayer = aLayer;
244 int annularRingLayer = UNDEFINED_LAYER;
245
246 if( pad && pad->GetAttribute() == PAD_ATTRIB::PTH )
247 annularRingLayer = LAYER_PADS_TH;
248 else if( via && via->GetViaType() == VIATYPE::MICROVIA )
249 annularRingLayer = LAYER_VIA_MICROVIA;
250 else if( via && via->GetViaType() == VIATYPE::BLIND_BURIED )
251 annularRingLayer = LAYER_VIA_BBLIND;
252 else if( via && via->GetViaType() == VIATYPE::THROUGH )
253 annularRingLayer = LAYER_VIA_THROUGH;
254
255 if( annularRingLayer != UNDEFINED_LAYER
256 && m_layerColors[ holeLayer ] == m_layerColors[ annularRingLayer ] )
257 {
258 aLayer = LAYER_PCB_BACKGROUND;
259 }
260 }
261
262 // Zones should pull from the copper layer
263 if( item && ( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T ) )
264 {
265 if( IsZoneLayer( aLayer ) )
266 aLayer = aLayer - LAYER_ZONE_START;
267 }
268
269 // Hole walls should pull from the copper layer
270 if( aLayer == LAYER_PAD_HOLEWALLS )
271 aLayer = LAYER_PADS_TH;
272 else if( aLayer == LAYER_VIA_HOLEWALLS )
273 aLayer = LAYER_VIA_THROUGH;
274
275 // Normal path: get the layer base color
276 COLOR4D color = m_layerColors[aLayer];
277
278 if( !item )
279 return m_layerColors[aLayer];
280
281 // Selection disambiguation
282 if( item->IsBrightened() )
283 return color.Brightened( m_selectFactor ).WithAlpha( 0.8 );
284
285 // Normal selection
286 if( item->IsSelected() )
287 color = m_layerColorsSel[aLayer];
288
289 // Try to obtain the netcode for the item
290 if( conItem )
291 netCode = conItem->GetNetCode();
292
293 bool highlighted = m_highlightEnabled && m_highlightNetcodes.count( netCode );
294 bool selected = item->IsSelected();
295
296 // Apply net color overrides
297 if( conItem && m_netColorMode == NET_COLOR_MODE::ALL && IsNetCopperLayer( aLayer ) )
298 {
299 COLOR4D netColor = COLOR4D::UNSPECIFIED;
300
301 auto ii = m_netColors.find( netCode );
302
303 if( ii != m_netColors.end() )
304 netColor = ii->second;
305
306 if( netColor == COLOR4D::UNSPECIFIED )
307 {
308 auto jj = m_netclassColors.find( conItem->GetNetClassName() );
309
310 if( jj != m_netclassColors.end() )
311 netColor = jj->second;
312 }
313
314 if( netColor == COLOR4D::UNSPECIFIED )
315 {
316 netColor = color;
317 }
318 else if( selected )
319 {
320 // Selection brightening overrides highlighting
321 netColor.Brighten( m_selectFactor );
322 }
323 else if( m_highlightEnabled )
324 {
325 // Highlight brightens objects on all layers and darkens everything else for contrast
326 if( highlighted )
327 netColor.Brighten( m_highlightFactor );
328 else
329 netColor.Darken( 1.0 - m_highlightFactor );
330 }
331
332 color = netColor;
333 }
334 else if( !selected && m_highlightEnabled )
335 {
336 // Single net highlight mode
337 color = m_highlightNetcodes.count( netCode ) ? m_layerColorsHi[aLayer]
338 : m_layerColorsDark[aLayer];
339 }
340
341 // Apply high-contrast dimming
342 if( m_hiContrastEnabled && m_highContrastLayers.size() && !highlighted && !selected )
343 {
344 PCB_LAYER_ID primary = GetPrimaryHighContrastLayer();
345 bool isActive = m_highContrastLayers.count( aLayer );
346
347 switch( originalLayer )
348 {
349 case LAYER_PADS_TH:
350 if( !static_cast<const PAD*>( item )->FlashLayer( primary ) )
351 isActive = false;
352
353 break;
354
355 case LAYER_VIA_BBLIND:
356 case LAYER_VIA_MICROVIA:
357 // Target graphic is active if the via crosses the primary layer
358 if( static_cast<const PCB_VIA*>( item )->GetLayerSet().test( primary ) == 0 )
359 isActive = false;
360
361 break;
362
363 case LAYER_VIA_THROUGH:
364 if( !static_cast<const PCB_VIA*>( item )->FlashLayer( primary ) )
365 isActive = false;
366
367 break;
368
369 case LAYER_PAD_PLATEDHOLES:
370 case LAYER_PAD_HOLEWALLS:
371 case LAYER_NON_PLATEDHOLES:
372 // Pad holes are active is any physical layer is active
373 if( LSET::PhysicalLayersMask().test( primary ) == 0 )
374 isActive = false;
375
376 break;
377
378 case LAYER_VIA_HOLES:
379 case LAYER_VIA_HOLEWALLS:
380 if( static_cast<const PCB_VIA*>( item )->GetViaType() == VIATYPE::BLIND_BURIED
381 || static_cast<const PCB_VIA*>( item )->GetViaType() == VIATYPE::MICROVIA )
382 {
383 // A blind or micro via's hole is active if it crosses the primary layer
384 if( static_cast<const PCB_VIA*>( item )->GetLayerSet().test( primary ) == 0 )
385 isActive = false;
386 }
387 else
388 {
389 // A through via's hole is active if any physical layer is active
390 if( LSET::PhysicalLayersMask().test( primary ) == 0 )
391 isActive = false;
392 }
393
394 break;
395
396 default:
397 break;
398 }
399
400 if( !isActive )
401 {
402 if( m_contrastModeDisplay == HIGH_CONTRAST_MODE::HIDDEN || IsNetnameLayer( aLayer ) )
403 color = COLOR4D::CLEAR;
404 else
405 color = color.Mix( m_layerColors[LAYER_PCB_BACKGROUND], m_hiContrastFactor );
406 }
407 }
408
409 // Apply per-type opacity overrides
410 if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T )
411 color.a *= m_trackOpacity;
412 else if( item->Type() == PCB_VIA_T )
413 color.a *= m_viaOpacity;
414 else if( item->Type() == PCB_PAD_T )
415 color.a *= m_padOpacity;
416 else if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
417 color.a *= m_zoneOpacity;
418
419 // No special modifiers enabled
420 return color;
421 }
422
423
PCB_PAINTER(GAL * aGal)424 PCB_PAINTER::PCB_PAINTER( GAL* aGal ) :
425 PAINTER( aGal ),
426 m_maxError( ARC_HIGH_DEF ),
427 m_holePlatingThickness( 0 )
428 {
429 }
430
431
getLineThickness(int aActualThickness) const432 int PCB_PAINTER::getLineThickness( int aActualThickness ) const
433 {
434 // if items have 0 thickness, draw them with the outline
435 // width, otherwise respect the set value (which, no matter
436 // how small will produce something)
437 if( aActualThickness == 0 )
438 return m_pcbSettings.m_outlineWidth;
439
440 return aActualThickness;
441 }
442
443
getDrillShape(const PAD * aPad) const444 int PCB_PAINTER::getDrillShape( const PAD* aPad ) const
445 {
446 return aPad->GetDrillShape();
447 }
448
449
getDrillSize(const PAD * aPad) const450 VECTOR2D PCB_PAINTER::getDrillSize( const PAD* aPad ) const
451 {
452 return VECTOR2D( aPad->GetDrillSize() );
453 }
454
455
getDrillSize(const PCB_VIA * aVia) const456 int PCB_PAINTER::getDrillSize( const PCB_VIA* aVia ) const
457 {
458 return aVia->GetDrillValue();
459 }
460
461
Draw(const VIEW_ITEM * aItem,int aLayer)462 bool PCB_PAINTER::Draw( const VIEW_ITEM* aItem, int aLayer )
463 {
464 const BOARD_ITEM* item = dynamic_cast<const BOARD_ITEM*>( aItem );
465
466 if( !item )
467 return false;
468
469 if( const BOARD* board = item->GetBoard() )
470 {
471 BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
472 m_maxError = bds.m_MaxError;
473 m_holePlatingThickness = bds.GetHolePlatingThickness();
474 }
475 else
476 {
477 m_maxError = ARC_HIGH_DEF;
478 m_holePlatingThickness = 0;
479 }
480
481 // the "cast" applied in here clarifies which overloaded draw() is called
482 switch( item->Type() )
483 {
484 case PCB_TRACE_T:
485 draw( static_cast<const PCB_TRACK*>( item ), aLayer );
486 break;
487
488 case PCB_ARC_T:
489 draw( static_cast<const PCB_ARC*>( item ), aLayer );
490 break;
491
492 case PCB_VIA_T:
493 draw( static_cast<const PCB_VIA*>( item ), aLayer );
494 break;
495
496 case PCB_PAD_T:
497 draw( static_cast<const PAD*>( item ), aLayer );
498 break;
499
500 case PCB_SHAPE_T:
501 case PCB_FP_SHAPE_T:
502 draw( static_cast<const PCB_SHAPE*>( item ), aLayer );
503 break;
504
505 case PCB_TEXT_T:
506 draw( static_cast<const PCB_TEXT*>( item ), aLayer );
507 break;
508
509 case PCB_FP_TEXT_T:
510 draw( static_cast<const FP_TEXT*>( item ), aLayer );
511 break;
512
513 case PCB_FOOTPRINT_T:
514 draw( static_cast<const FOOTPRINT*>( item ), aLayer );
515 break;
516
517 case PCB_GROUP_T:
518 draw( static_cast<const PCB_GROUP*>( item ), aLayer );
519 break;
520
521 case PCB_ZONE_T:
522 draw( static_cast<const ZONE*>( item ), aLayer );
523 break;
524
525 case PCB_FP_ZONE_T:
526 draw( static_cast<const ZONE*>( item ), aLayer );
527 break;
528
529 case PCB_DIM_ALIGNED_T:
530 case PCB_DIM_CENTER_T:
531 case PCB_DIM_ORTHOGONAL_T:
532 case PCB_DIM_LEADER_T:
533 draw( static_cast<const PCB_DIMENSION_BASE*>( item ), aLayer );
534 break;
535
536 case PCB_TARGET_T:
537 draw( static_cast<const PCB_TARGET*>( item ) );
538 break;
539
540 case PCB_MARKER_T:
541 draw( static_cast<const PCB_MARKER*>( item ), aLayer );
542 break;
543
544 default:
545 // Painter does not know how to draw the object
546 return false;
547 }
548
549 // Draw bounding boxes after drawing objects so they can be seen.
550 if( ADVANCED_CFG::GetCfg().m_DrawBoundingBoxes )
551 {
552 // Show bounding boxes of painted objects for debugging.
553 EDA_RECT box = item->GetBoundingBox();
554 m_gal->SetIsFill( false );
555 m_gal->SetIsStroke( true );
556
557 if( item->Type() == PCB_FOOTPRINT_T )
558 {
559 m_gal->SetStrokeColor( item->IsSelected() ? COLOR4D( 1.0, 0.2, 0.2, 1 ) :
560 COLOR4D( MAGENTA ) );
561 }
562 else
563 {
564 m_gal->SetStrokeColor( item->IsSelected() ? COLOR4D( 1.0, 0.2, 0.2, 1 ) :
565 COLOR4D( 0.4, 0.4, 0.4, 1 ) );
566 }
567
568 m_gal->SetLineWidth( 1 );
569 m_gal->DrawRectangle( box.GetOrigin(), box.GetEnd() );
570
571 if( item->Type() == PCB_FOOTPRINT_T )
572 {
573 m_gal->SetStrokeColor( item->IsSelected() ? COLOR4D( 1.0, 0.2, 0.2, 1 ) :
574 COLOR4D( CYAN ) );
575
576 const FOOTPRINT* fp = static_cast<const FOOTPRINT*>( item );
577
578 if( fp )
579 {
580 SHAPE_POLY_SET convex = fp->GetBoundingHull();
581
582 m_gal->DrawPolyline( convex.COutline( 0 ) );
583 }
584 }
585 }
586
587 return true;
588 }
589
590
draw(const PCB_TRACK * aTrack,int aLayer)591 void PCB_PAINTER::draw( const PCB_TRACK* aTrack, int aLayer )
592 {
593 VECTOR2D start( aTrack->GetStart() );
594 VECTOR2D end( aTrack->GetEnd() );
595 int width = aTrack->GetWidth();
596 COLOR4D color = m_pcbSettings.GetColor( aTrack, aLayer );
597
598 if( IsNetnameLayer( aLayer ) )
599 {
600 if( !m_pcbSettings.m_netNamesOnTracks )
601 return;
602
603 if( aTrack->GetNetCode() <= NETINFO_LIST::UNCONNECTED )
604 return;
605
606 VECTOR2D line = ( end - start );
607 double length = line.EuclideanNorm();
608
609 // Check if the track is long enough to have a netname displayed
610 if( length < 10 * width )
611 return;
612
613 const wxString& netName = UnescapeString( aTrack->GetShortNetname() );
614 double textSize = width;
615 double penWidth = width / 12.0;
616 VECTOR2D textPosition = start + line / 2.0; // center of the track
617 double textOrientation;
618
619 if( end.y == start.y ) // horizontal
620 {
621 textOrientation = 0;
622 textPosition.y += penWidth;
623 }
624 else if( end.x == start.x ) // vertical
625 {
626 textOrientation = M_PI / 2;
627 textPosition.x += penWidth;
628 }
629 else
630 {
631 textOrientation = -atan( line.y / line.x );
632 textPosition.x += penWidth / 1.4;
633 textPosition.y += penWidth / 1.4;
634 }
635
636
637 m_gal->SetIsStroke( true );
638 m_gal->SetIsFill( false );
639 m_gal->SetStrokeColor( color );
640 m_gal->SetLineWidth( penWidth );
641 m_gal->SetFontBold( false );
642 m_gal->SetFontItalic( false );
643 m_gal->SetFontUnderlined( false );
644 m_gal->SetTextMirrored( false );
645 m_gal->SetGlyphSize( VECTOR2D( textSize * 0.55, textSize * 0.55 ) );
646 m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER );
647 m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER );
648 m_gal->BitmapText( netName, textPosition, textOrientation );
649
650 return;
651 }
652 else if( IsCopperLayer( aLayer ) )
653 {
654 // Draw a regular track
655 bool outline_mode = m_pcbSettings.m_sketchMode[LAYER_TRACKS];
656 m_gal->SetStrokeColor( color );
657 m_gal->SetFillColor( color );
658 m_gal->SetIsStroke( outline_mode );
659 m_gal->SetIsFill( not outline_mode );
660 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
661
662 m_gal->DrawSegment( start, end, width );
663 }
664
665 // Clearance lines
666 constexpr int clearanceFlags = PCB_RENDER_SETTINGS::CL_EXISTING
667 | PCB_RENDER_SETTINGS::CL_TRACKS;
668
669 if( ( m_pcbSettings.m_clearanceDisplayFlags & clearanceFlags ) == clearanceFlags )
670 {
671 int clearance = aTrack->GetOwnClearance( m_pcbSettings.GetActiveLayer() );
672
673 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
674 m_gal->SetIsFill( false );
675 m_gal->SetIsStroke( true );
676 m_gal->SetStrokeColor( color );
677 m_gal->DrawSegment( start, end, width + clearance * 2 );
678 }
679 }
680
681
draw(const PCB_ARC * aArc,int aLayer)682 void PCB_PAINTER::draw( const PCB_ARC* aArc, int aLayer )
683 {
684 VECTOR2D center( aArc->GetCenter() );
685 int width = aArc->GetWidth();
686 COLOR4D color = m_pcbSettings.GetColor( aArc, aLayer );
687 double radius = aArc->GetRadius();
688 double start_angle = DECIDEG2RAD( aArc->GetArcAngleStart() );
689 double angle = DECIDEG2RAD( aArc->GetAngle() );
690
691 if( IsNetnameLayer( aLayer ) )
692 {
693 // Ummm, yeah. Anyone fancy implementing text on a path?
694 return;
695 }
696 else if( IsCopperLayer( aLayer ) )
697 {
698 // Draw a regular track
699 bool outline_mode = m_pcbSettings.m_sketchMode[LAYER_TRACKS];
700 m_gal->SetStrokeColor( color );
701 m_gal->SetFillColor( color );
702 m_gal->SetIsStroke( outline_mode );
703 m_gal->SetIsFill( not outline_mode );
704 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
705
706 m_gal->DrawArcSegment( center, radius, start_angle, start_angle + angle, width, m_maxError );
707 }
708
709 // Clearance lines
710 constexpr int clearanceFlags = PCB_RENDER_SETTINGS::CL_EXISTING
711 | PCB_RENDER_SETTINGS::CL_TRACKS;
712
713 if( ( m_pcbSettings.m_clearanceDisplayFlags & clearanceFlags ) == clearanceFlags )
714 {
715 int clearance = aArc->GetOwnClearance( m_pcbSettings.GetActiveLayer() );
716
717 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
718 m_gal->SetIsFill( false );
719 m_gal->SetIsStroke( true );
720 m_gal->SetStrokeColor( color );
721
722 m_gal->DrawArcSegment( center, radius, start_angle, start_angle + angle,
723 width + clearance * 2, m_maxError );
724 }
725
726 // Debug only: enable this code only to test the TransformArcToPolygon function
727 // and display the polygon outline created by it.
728 // arcs on F_Cu are approximated with ERROR_INSIDE, others with ERROR_OUTSIDE
729 #if 0
730 SHAPE_POLY_SET cornerBuffer;
731 ERROR_LOC errorloc = aLayer == F_Cu ? ERROR_LOC::ERROR_INSIDE : ERROR_LOC::ERROR_OUTSIDE;
732 TransformArcToPolygon( cornerBuffer, aArc->GetStart(), aArc->GetMid(), aArc->GetEnd(), width,
733 m_maxError, errorloc );
734 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
735 m_gal->SetIsFill( false );
736 m_gal->SetIsStroke( true );
737 m_gal->SetStrokeColor( COLOR4D( 0, 0, 1.0, 1.0 ) );
738 m_gal->DrawPolygon( cornerBuffer );
739 #endif
740
741 // Debug only: enable this code only to test the SHAPE_ARC::ConvertToPolyline function
742 // and display the polyline created by it.
743 #if 0
744 SHAPE_ARC arc( aArc->GetCenter(), aArc->GetStart(), aArc->GetAngle() / 10.0, aArc->GetWidth() );
745 SHAPE_LINE_CHAIN arcSpine = arc.ConvertToPolyline( m_maxError );
746 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
747 m_gal->SetIsFill( false );
748 m_gal->SetIsStroke( true );
749 m_gal->SetStrokeColor( COLOR4D( 0.3, 0.2, 0.5, 1.0 ) );
750
751 for( int idx = 1; idx < arcSpine.PointCount(); idx++ )
752 m_gal->DrawSegment( arcSpine.CPoint( idx-1 ), arcSpine.CPoint( idx ), aArc->GetWidth() );
753 #endif
754 }
755
756
draw(const PCB_VIA * aVia,int aLayer)757 void PCB_PAINTER::draw( const PCB_VIA* aVia, int aLayer )
758 {
759 COLOR4D color = m_pcbSettings.GetColor( aVia, aLayer );
760 VECTOR2D center( aVia->GetStart() );
761
762 if( color == COLOR4D::CLEAR )
763 return;
764
765 // Draw description layer
766 if( IsNetnameLayer( aLayer ) )
767 {
768 VECTOR2D position( center );
769
770 // Is anything that we can display enabled?
771 if( !m_pcbSettings.m_netNamesOnVias || aVia->GetNetname().empty() )
772 return;
773
774 double maxSize = PCB_RENDER_SETTINGS::MAX_FONT_SIZE;
775 double size = aVia->GetWidth();
776
777 // Font size limits
778 if( size > maxSize )
779 size = maxSize;
780
781 m_gal->Save();
782 m_gal->Translate( position );
783
784 // Default font settings
785 m_gal->ResetTextAttributes();
786 m_gal->SetStrokeColor( m_pcbSettings.GetColor( nullptr, aLayer ) );
787
788 // Set the text position to the pad shape position (the pad position is not the best place)
789 VECTOR2D textpos( 0.0, 0.0 );
790
791 wxString netname = UnescapeString( aVia->GetShortNetname() );
792
793 // approximate the size of net name text:
794 double tsize = 1.5 * size / std::max( PrintableCharCount( netname ), 1 );
795 tsize = std::min( tsize, size );
796
797 // Use a smaller text size to handle interline, pen size..
798 tsize *= 0.7;
799 VECTOR2D namesize( tsize, tsize );
800
801 m_gal->SetGlyphSize( namesize );
802 m_gal->SetLineWidth( namesize.x / 12.0 );
803 m_gal->BitmapText( netname, textpos, 0.0 );
804
805 m_gal->Restore();
806
807 return;
808 }
809 else if( aLayer == LAYER_VIA_HOLEWALLS )
810 {
811 m_gal->SetIsFill( false );
812 m_gal->SetIsStroke( true );
813 m_gal->SetStrokeColor( color );
814 m_gal->SetLineWidth( m_holePlatingThickness );
815
816 m_gal->DrawCircle( center, ( getDrillSize( aVia ) + m_holePlatingThickness ) / 2.0 );
817
818 return;
819 }
820
821 bool sketchMode = false;
822
823 switch( aVia->GetViaType() )
824 {
825 case VIATYPE::THROUGH: sketchMode = m_pcbSettings.m_sketchMode[LAYER_VIA_THROUGH]; break;
826 case VIATYPE::BLIND_BURIED: sketchMode = m_pcbSettings.m_sketchMode[LAYER_VIA_BBLIND]; break;
827 case VIATYPE::MICROVIA: sketchMode = m_pcbSettings.m_sketchMode[LAYER_VIA_MICROVIA]; break;
828 default: wxASSERT( false ); break;
829 }
830
831 if( sketchMode )
832 {
833 // Outline mode
834 m_gal->SetIsStroke( true );
835 m_gal->SetIsFill( false );
836 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
837 m_gal->SetStrokeColor( color );
838 }
839 else
840 {
841 // Filled mode
842 m_gal->SetIsFill( true );
843 m_gal->SetIsStroke( false );
844 m_gal->SetFillColor( color );
845 }
846
847 if( aLayer == LAYER_VIA_HOLES )
848 {
849 m_gal->DrawCircle( center, getDrillSize( aVia ) / 2.0 );
850 }
851 else if( aLayer == LAYER_VIA_THROUGH || m_pcbSettings.GetDrawIndividualViaLayers() )
852 {
853 m_gal->DrawCircle( center, aVia->GetWidth() / 2.0 );
854 }
855 else if( aLayer == LAYER_VIA_BBLIND || aLayer == LAYER_VIA_MICROVIA )
856 {
857 // Outer circles of blind/buried and micro-vias are drawn in a special way to indicate the
858 // top and bottom layers
859 PCB_LAYER_ID layerTop, layerBottom;
860 aVia->LayerPair( &layerTop, &layerBottom );
861
862 double radius = aVia->GetWidth() / 2.0;
863
864 if( !sketchMode )
865 m_gal->SetLineWidth( ( aVia->GetWidth() - aVia->GetDrillValue() ) / 2.0 );
866
867 m_gal->DrawArc( center, radius, M_PI * -0.375, M_PI * 0.375 );
868 m_gal->DrawArc( center, radius, M_PI * 0.625, M_PI * 1.375 );
869
870 if( sketchMode )
871 m_gal->SetStrokeColor( m_pcbSettings.GetColor( aVia, layerTop ) );
872 else
873 m_gal->SetFillColor( m_pcbSettings.GetColor( aVia, layerTop ) );
874
875 m_gal->DrawArc( center, radius, M_PI * 1.375, M_PI * 1.625 );
876
877 if( sketchMode )
878 m_gal->SetStrokeColor( m_pcbSettings.GetColor( aVia, layerBottom ) );
879 else
880 m_gal->SetFillColor( m_pcbSettings.GetColor( aVia, layerBottom ) );
881
882 m_gal->DrawArc( center, radius, M_PI * 0.375, M_PI * 0.625 );
883 }
884
885 // Clearance lines
886 constexpr int clearanceFlags = PCB_RENDER_SETTINGS::CL_EXISTING | PCB_RENDER_SETTINGS::CL_VIAS;
887
888 if( ( m_pcbSettings.m_clearanceDisplayFlags & clearanceFlags ) == clearanceFlags
889 && aLayer != LAYER_VIA_HOLES )
890 {
891 PCB_LAYER_ID activeLayer = m_pcbSettings.GetActiveLayer();
892 double radius;
893
894 if( aVia->FlashLayer( activeLayer ) )
895 radius = aVia->GetWidth() / 2.0;
896 else
897 radius = getDrillSize( aVia ) / 2.0 + m_holePlatingThickness;
898
899 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
900 m_gal->SetIsFill( false );
901 m_gal->SetIsStroke( true );
902 m_gal->SetStrokeColor( color );
903 m_gal->DrawCircle( center, radius + aVia->GetOwnClearance( activeLayer ) );
904 }
905 }
906
907
draw(const PAD * aPad,int aLayer)908 void PCB_PAINTER::draw( const PAD* aPad, int aLayer )
909 {
910 COLOR4D color = m_pcbSettings.GetColor( aPad, aLayer );
911
912 if( IsNetnameLayer( aLayer ) )
913 {
914 // Is anything that we can display enabled?
915 if( m_pcbSettings.m_netNamesOnPads || m_pcbSettings.m_padNumbers )
916 {
917 bool displayNetname = ( m_pcbSettings.m_netNamesOnPads && !aPad->GetNetname().empty() );
918 EDA_RECT padBBox = aPad->GetBoundingBox();
919 VECTOR2D position = padBBox.Centre();
920 VECTOR2D padsize = VECTOR2D( padBBox.GetSize() );
921
922 if( aPad->GetShape() != PAD_SHAPE::CUSTOM )
923 {
924 // Don't allow a 45º rotation to bloat a pad's bounding box unnecessarily
925 double limit = std::min( aPad->GetSize().x, aPad->GetSize().y ) * 1.1;
926
927 if( padsize.x > limit && padsize.y > limit )
928 {
929 padsize.x = limit;
930 padsize.y = limit;
931 }
932 }
933
934 double maxSize = PCB_RENDER_SETTINGS::MAX_FONT_SIZE;
935 double size = padsize.y;
936
937 m_gal->Save();
938 m_gal->Translate( position );
939
940 // Keep the size ratio for the font, but make it smaller
941 if( padsize.x < padsize.y )
942 {
943 m_gal->Rotate( DECIDEG2RAD( -900.0 ) );
944 size = padsize.x;
945 std::swap( padsize.x, padsize.y );
946 }
947
948 // Font size limits
949 if( size > maxSize )
950 size = maxSize;
951
952 // Default font settings
953 m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER );
954 m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER );
955 m_gal->SetFontBold( false );
956 m_gal->SetFontItalic( false );
957 m_gal->SetFontUnderlined( false );
958 m_gal->SetTextMirrored( false );
959 m_gal->SetStrokeColor( m_pcbSettings.GetColor( aPad, aLayer ) );
960 m_gal->SetIsStroke( true );
961 m_gal->SetIsFill( false );
962
963 // We have already translated the GAL to be centered at the center of the pad's
964 // bounding box
965 VECTOR2D textpos( 0.0, 0.0 );
966
967 // Divide the space, to display both pad numbers and netnames and set the Y text
968 // position to display 2 lines
969 if( displayNetname && m_pcbSettings.m_padNumbers )
970 {
971 size = size / 2.5;
972 textpos.y = size / 1.7;
973 }
974
975 if( displayNetname )
976 {
977 wxString netname = UnescapeString( aPad->GetShortNetname() );
978 wxString pinType = aPad->GetPinType();
979
980 // If the pad is actually not connected (unique pad in the net),
981 // shorten the displayed netname (actual name not useful)
982 // Can happen if the pad netname is edited inside the board editor, therefore
983 // having a netname not coming from schematic
984 if( netname.StartsWith( "unconnected-(" ) )
985 {
986 if( pinType == wxT( "no_connect" ) || pinType.EndsWith( wxT( "+no_connect" ) ) )
987 netname = "x";
988 else if( pinType == wxT( "free" ) )
989 netname = "*";
990 }
991
992 // approximate the size of net name text:
993 double tsize = 1.5 * padsize.x / std::max( PrintableCharCount( netname ), 1 );
994 tsize = std::min( tsize, size );
995
996 // Use a smaller text size to handle interline, pen size...
997 tsize *= 0.7;
998 VECTOR2D namesize( tsize, tsize );
999
1000 m_gal->SetGlyphSize( namesize );
1001 m_gal->SetLineWidth( namesize.x / 12.0 );
1002 m_gal->BitmapText( netname, textpos, 0.0 );
1003 }
1004
1005 if( m_pcbSettings.m_padNumbers )
1006 {
1007 const wxString& padNumber = aPad->GetNumber();
1008 textpos.y = -textpos.y;
1009
1010 // approximate the size of the pad number text:
1011 double tsize = 1.5 * padsize.x / std::max( PrintableCharCount( padNumber ), 1 );
1012 tsize = std::min( tsize, size );
1013
1014 // Use a smaller text size to handle interline, pen size...
1015 tsize *= 0.7;
1016 tsize = std::min( tsize, size );
1017 VECTOR2D numsize( tsize, tsize );
1018
1019 m_gal->SetGlyphSize( numsize );
1020 m_gal->SetLineWidth( numsize.x / 12.0 );
1021 m_gal->BitmapText( padNumber, textpos, 0.0 );
1022 }
1023
1024 m_gal->Restore();
1025 }
1026
1027 return;
1028 }
1029 else if( aLayer == LAYER_PAD_HOLEWALLS )
1030 {
1031 m_gal->SetIsFill( false );
1032 m_gal->SetIsStroke( true );
1033 m_gal->SetLineWidth( m_holePlatingThickness );
1034 m_gal->SetStrokeColor( color );
1035
1036 const SHAPE_SEGMENT* seg = aPad->GetEffectiveHoleShape();
1037 int holeSize = seg->GetWidth() + m_holePlatingThickness;
1038
1039 if( seg->GetSeg().A == seg->GetSeg().B ) // Circular hole
1040 m_gal->DrawCircle( seg->GetSeg().A, holeSize / 2 );
1041 else
1042 m_gal->DrawSegment( seg->GetSeg().A, seg->GetSeg().B, holeSize );
1043
1044 return;
1045 }
1046
1047 if( m_pcbSettings.m_sketchMode[LAYER_PADS_TH] )
1048 {
1049 // Outline mode
1050 m_gal->SetIsFill( false );
1051 m_gal->SetIsStroke( true );
1052 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
1053 m_gal->SetStrokeColor( color );
1054 }
1055 else
1056 {
1057 // Filled mode
1058 m_gal->SetIsFill( true );
1059 m_gal->SetIsStroke( false );
1060 m_gal->SetFillColor( color );
1061 }
1062
1063 if( aLayer == LAYER_PAD_PLATEDHOLES || aLayer == LAYER_NON_PLATEDHOLES )
1064 {
1065 const SHAPE_SEGMENT* seg = aPad->GetEffectiveHoleShape();
1066
1067 if( seg->GetSeg().A == seg->GetSeg().B ) // Circular hole
1068 m_gal->DrawCircle( seg->GetSeg().A, getDrillSize( aPad ).x / 2 );
1069 else
1070 m_gal->DrawSegment( seg->GetSeg().A, seg->GetSeg().B, seg->GetWidth() );
1071 }
1072 else
1073 {
1074 wxSize pad_size = aPad->GetSize();
1075 wxSize margin;
1076
1077 switch( aLayer )
1078 {
1079 case F_Mask:
1080 case B_Mask:
1081 margin.x = margin.y = aPad->GetSolderMaskMargin();
1082 break;
1083
1084 case F_Paste:
1085 case B_Paste:
1086 margin = aPad->GetSolderPasteMargin();
1087 break;
1088
1089 default:
1090 margin.x = margin.y = 0;
1091 break;
1092 }
1093
1094 std::unique_ptr<PAD> dummyPad;
1095 std::shared_ptr<SHAPE_COMPOUND> shapes;
1096
1097 // Drawing components of compound shapes in outline mode produces a mess.
1098 bool simpleShapes = !m_pcbSettings.m_sketchMode[LAYER_PADS_TH];
1099
1100 if( simpleShapes )
1101 {
1102 if( ( margin.x != margin.y && aPad->GetShape() != PAD_SHAPE::CUSTOM )
1103 || ( aPad->GetShape() == PAD_SHAPE::ROUNDRECT && ( margin.x < 0 || margin.y < 0 ) ) )
1104 {
1105 // Our algorithms below (polygon inflation in particular) can't handle differential
1106 // inflation along separate axes. So for those cases we build a dummy pad instead,
1107 // and inflate it.
1108
1109 // Margin is added to both sides. If the total margin is larger than the pad
1110 // then don't display this layer
1111 if( pad_size.x + 2 * margin.x <= 0 || pad_size.y + 2 * margin.y <= 0 )
1112 return;
1113
1114 dummyPad.reset( static_cast<PAD*>( aPad->Duplicate() ) );
1115 int initial_radius = dummyPad->GetRoundRectCornerRadius();
1116
1117 dummyPad->SetSize( pad_size + margin + margin );
1118
1119 if( dummyPad->GetShape() == PAD_SHAPE::ROUNDRECT )
1120 {
1121 // To keep the right margin around the corners, we need to modify the corner radius.
1122 // We must have only one radius correction, so use the smallest absolute margin.
1123 int radius_margin = std::max( margin.x, margin.y ); // radius_margin is < 0
1124 dummyPad->SetRoundRectCornerRadius( std::max( initial_radius + radius_margin, 0 ) );
1125 }
1126
1127 shapes = std::dynamic_pointer_cast<SHAPE_COMPOUND>( dummyPad->GetEffectiveShape() );
1128 margin.x = margin.y = 0;
1129 }
1130 else
1131 {
1132 shapes = std::dynamic_pointer_cast<SHAPE_COMPOUND>( aPad->GetEffectiveShape() );
1133 }
1134
1135 if( aPad->GetShape() == PAD_SHAPE::CUSTOM && ( margin.x || margin.y ) )
1136 {
1137 // We can't draw as shapes because we don't know which edges are internal and which
1138 // are external (so we don't know when to apply the margin and when not to).
1139 simpleShapes = false;
1140 }
1141
1142 for( const SHAPE* shape : shapes->Shapes() )
1143 {
1144 if( !simpleShapes )
1145 break;
1146
1147 switch( shape->Type() )
1148 {
1149 case SH_SEGMENT:
1150 case SH_CIRCLE:
1151 case SH_RECT:
1152 case SH_SIMPLE:
1153 // OK so far
1154 break;
1155
1156 default:
1157 // Not OK
1158 simpleShapes = false;
1159 break;
1160 }
1161 }
1162 }
1163
1164 if( simpleShapes )
1165 {
1166 for( const SHAPE* shape : shapes->Shapes() )
1167 {
1168 switch( shape->Type() )
1169 {
1170 case SH_SEGMENT:
1171 {
1172 const SHAPE_SEGMENT* seg = (const SHAPE_SEGMENT*) shape;
1173 int effectiveWidth = seg->GetWidth() + 2 * margin.x;
1174
1175 if( effectiveWidth > 0 )
1176 m_gal->DrawSegment( seg->GetSeg().A, seg->GetSeg().B, effectiveWidth );
1177
1178 break;
1179 }
1180
1181 case SH_CIRCLE:
1182 {
1183 const SHAPE_CIRCLE* circle = (const SHAPE_CIRCLE*) shape;
1184 int effectiveRadius = circle->GetRadius() + margin.x;
1185
1186 if( effectiveRadius > 0 )
1187 m_gal->DrawCircle( circle->GetCenter(), effectiveRadius );
1188
1189 break;
1190 }
1191
1192 case SH_RECT:
1193 {
1194 const SHAPE_RECT* r = (const SHAPE_RECT*) shape;
1195 VECTOR2I pos = r->GetPosition();
1196 VECTOR2I effectiveMargin = margin;
1197
1198 if( effectiveMargin.x < 0 )
1199 {
1200 // A negative margin just produces a smaller rect.
1201 VECTOR2I effectiveSize = r->GetSize() + effectiveMargin;
1202
1203 if( effectiveSize.x > 0 && effectiveSize.y > 0 )
1204 m_gal->DrawRectangle( pos - effectiveMargin, pos + effectiveSize );
1205 }
1206 else if( effectiveMargin.x > 0 )
1207 {
1208 // A positive margin produces a larger rect, but with rounded corners
1209 m_gal->DrawRectangle( r->GetPosition(), r->GetPosition() + r->GetSize() );
1210
1211 // Use segments to produce the margin with rounded corners
1212 m_gal->DrawSegment( pos,
1213 pos + VECTOR2I( r->GetWidth(), 0 ),
1214 effectiveMargin.x * 2 );
1215 m_gal->DrawSegment( pos + VECTOR2I( r->GetWidth(), 0 ),
1216 pos + r->GetSize(),
1217 effectiveMargin.x * 2 );
1218 m_gal->DrawSegment( pos + r->GetSize(),
1219 pos + VECTOR2I( 0, r->GetHeight() ),
1220 effectiveMargin.x * 2 );
1221 m_gal->DrawSegment( pos + VECTOR2I( 0, r->GetHeight() ),
1222 pos,
1223 effectiveMargin.x * 2 );
1224 }
1225 else
1226 {
1227 m_gal->DrawRectangle( r->GetPosition(), r->GetPosition() + r->GetSize() );
1228 }
1229
1230 break;
1231 }
1232
1233 case SH_SIMPLE:
1234 {
1235 const SHAPE_SIMPLE* poly = static_cast<const SHAPE_SIMPLE*>( shape );
1236
1237 if( margin.x < 0 ) // The poly shape must be deflated
1238 {
1239 int numSegs = GetArcToSegmentCount( -margin.x, m_maxError, 360.0 );
1240 SHAPE_POLY_SET outline;
1241 outline.NewOutline();
1242
1243 for( int ii = 0; ii < poly->PointCount(); ++ii )
1244 outline.Append( poly->CPoint( ii ) );
1245
1246 outline.Deflate( -margin.x, numSegs );
1247
1248 m_gal->DrawPolygon( outline );
1249 }
1250 else
1251 {
1252 m_gal->DrawPolygon( poly->Vertices() );
1253 }
1254
1255 // Now add on a rounded margin (using segments) if the margin > 0
1256 if( margin.x > 0 )
1257 {
1258 for( size_t ii = 0; ii < poly->GetSegmentCount(); ++ii )
1259 {
1260 SEG seg = poly->GetSegment( ii );
1261 m_gal->DrawSegment( seg.A, seg.B, margin.x * 2 );
1262 }
1263 }
1264
1265 break;
1266 }
1267
1268 default:
1269 // Better not get here; we already pre-flighted the shapes...
1270 break;
1271 }
1272 }
1273 }
1274 else
1275 {
1276 // This is expensive. Avoid if possible.
1277 SHAPE_POLY_SET polySet;
1278 aPad->TransformShapeWithClearanceToPolygon( polySet, ToLAYER_ID( aLayer ), margin.x,
1279 m_maxError, ERROR_INSIDE );
1280 m_gal->DrawPolygon( polySet );
1281 }
1282 }
1283
1284 constexpr int clearanceFlags = PCB_RENDER_SETTINGS::CL_PADS;
1285
1286 if( ( m_pcbSettings.m_clearanceDisplayFlags & clearanceFlags ) == clearanceFlags
1287 && ( aLayer == LAYER_PAD_FR || aLayer == LAYER_PAD_BK || aLayer == LAYER_PADS_TH ) )
1288 {
1289 /* Showing the clearance area is not obvious.
1290 * - A pad can be removed from some copper layers.
1291 * - For non copper layers, what is the clearance area?
1292 * So for copper layers, the clearance area is the shape if the pad is flashed on this
1293 * layer and the hole clearance area for other copper layers.
1294 * For other layers, use the pad shape, although one can use an other criteria,
1295 * depending on the non copper layer.
1296 */
1297 int activeLayer = m_pcbSettings.GetActiveLayer();
1298 bool flashActiveLayer = true;
1299
1300 if( IsCopperLayer( activeLayer ) )
1301 flashActiveLayer = aPad->FlashLayer( activeLayer );
1302
1303 if( flashActiveLayer || aPad->GetDrillSize().x )
1304 {
1305 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
1306 m_gal->SetIsStroke( true );
1307 m_gal->SetIsFill( false );
1308 m_gal->SetStrokeColor( color );
1309
1310 int clearance = aPad->GetOwnClearance( m_pcbSettings.GetActiveLayer() );
1311
1312 if( flashActiveLayer && clearance > 0 )
1313 {
1314 auto shape = std::dynamic_pointer_cast<SHAPE_COMPOUND>( aPad->GetEffectiveShape() );
1315
1316 if( shape && shape->Size() == 1 && shape->Shapes()[0]->Type() == SH_SEGMENT )
1317 {
1318 const SHAPE_SEGMENT* seg = (SHAPE_SEGMENT*) shape->Shapes()[0];
1319 m_gal->DrawSegment( seg->GetSeg().A, seg->GetSeg().B,
1320 seg->GetWidth() + 2 * clearance );
1321 }
1322 else if( shape && shape->Size() == 1 && shape->Shapes()[0]->Type() == SH_CIRCLE )
1323 {
1324 const SHAPE_CIRCLE* circle = (SHAPE_CIRCLE*) shape->Shapes()[0];
1325 m_gal->DrawCircle( circle->GetCenter(), circle->GetRadius() + clearance );
1326 }
1327 else
1328 {
1329 SHAPE_POLY_SET polySet;
1330
1331 // Use ERROR_INSIDE because it avoids Clipper and is therefore much faster.
1332 aPad->TransformShapeWithClearanceToPolygon( polySet, ToLAYER_ID( aLayer ),
1333 clearance, m_maxError, ERROR_INSIDE );
1334 m_gal->DrawPolygon( polySet );
1335 }
1336 }
1337 else if( aPad->GetEffectiveHoleShape() && clearance > 0 )
1338 {
1339 clearance += m_holePlatingThickness;
1340
1341 const SHAPE_SEGMENT* seg = aPad->GetEffectiveHoleShape();
1342 m_gal->DrawSegment( seg->GetSeg().A, seg->GetSeg().B,
1343 seg->GetWidth() + 2 * clearance );
1344 }
1345 }
1346 }
1347 }
1348
1349
draw(const PCB_SHAPE * aShape,int aLayer)1350 void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer )
1351 {
1352 const COLOR4D& color = m_pcbSettings.GetColor( aShape, aShape->GetLayer() );
1353 bool sketch = m_pcbSettings.m_sketchGraphics;
1354 int thickness = getLineThickness( aShape->GetWidth() );
1355
1356 if( sketch )
1357 {
1358 m_gal->SetIsFill( false );
1359 m_gal->SetIsStroke( true );
1360 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
1361 }
1362
1363 m_gal->SetFillColor( color );
1364 m_gal->SetStrokeColor( color );
1365
1366 switch( aShape->GetShape() )
1367 {
1368 case SHAPE_T::SEGMENT:
1369 if( sketch )
1370 {
1371 m_gal->DrawSegment( aShape->GetStart(), aShape->GetEnd(), thickness );
1372 }
1373 else
1374 {
1375 m_gal->SetIsFill( true );
1376 m_gal->SetIsStroke( false );
1377
1378 m_gal->DrawSegment( aShape->GetStart(), aShape->GetEnd(), thickness );
1379 }
1380
1381 break;
1382
1383 case SHAPE_T::RECT:
1384 {
1385 std::vector<wxPoint> pts = aShape->GetRectCorners();
1386
1387 if( sketch )
1388 {
1389 m_gal->DrawSegment( pts[0], pts[1], thickness );
1390 m_gal->DrawSegment( pts[1], pts[2], thickness );
1391 m_gal->DrawSegment( pts[2], pts[3], thickness );
1392 m_gal->DrawSegment( pts[3], pts[0], thickness );
1393 }
1394 else
1395 {
1396 m_gal->SetIsFill( true );
1397 m_gal->SetIsStroke( false );
1398
1399 if( thickness > 0 )
1400 {
1401 m_gal->DrawSegment( pts[0], pts[1], thickness );
1402 m_gal->DrawSegment( pts[1], pts[2], thickness );
1403 m_gal->DrawSegment( pts[2], pts[3], thickness );
1404 m_gal->DrawSegment( pts[3], pts[0], thickness );
1405 }
1406
1407 if( aShape->IsFilled() )
1408 {
1409 SHAPE_POLY_SET poly;
1410 poly.NewOutline();
1411
1412 for( const wxPoint& pt : pts )
1413 poly.Append( pt );
1414
1415 m_gal->DrawPolygon( poly );
1416 }
1417 }
1418
1419 break;
1420 }
1421
1422 case SHAPE_T::ARC:
1423 {
1424 double startAngle;
1425 double endAngle;
1426 aShape->CalcArcAngles( startAngle, endAngle );
1427
1428 if( sketch )
1429 {
1430 m_gal->DrawArcSegment( aShape->GetCenter(), aShape->GetRadius(),
1431 DEG2RAD( startAngle ), DEG2RAD( endAngle ), thickness,
1432 m_maxError );
1433 }
1434 else
1435 {
1436 m_gal->SetIsFill( true );
1437 m_gal->SetIsStroke( false );
1438
1439 m_gal->DrawArcSegment( aShape->GetCenter(), aShape->GetRadius(),
1440 DEG2RAD( startAngle ), DEG2RAD( endAngle ), thickness,
1441 m_maxError );
1442 }
1443 break;
1444 }
1445
1446 case SHAPE_T::CIRCLE:
1447 if( sketch )
1448 {
1449 m_gal->DrawCircle( aShape->GetStart(), aShape->GetRadius() - thickness / 2 );
1450 m_gal->DrawCircle( aShape->GetStart(), aShape->GetRadius() + thickness / 2 );
1451 }
1452 else
1453 {
1454 m_gal->SetIsFill( aShape->IsFilled() );
1455 m_gal->SetIsStroke( thickness > 0 );
1456 m_gal->SetLineWidth( thickness );
1457
1458 m_gal->DrawCircle( aShape->GetStart(), aShape->GetRadius() );
1459 }
1460 break;
1461
1462 case SHAPE_T::POLY:
1463 {
1464 SHAPE_POLY_SET& shape = const_cast<PCB_SHAPE*>( aShape )->GetPolyShape();
1465 const FOOTPRINT* parentFootprint = aShape->GetParentFootprint();
1466
1467 if( shape.OutlineCount() == 0 )
1468 break;
1469
1470 if( parentFootprint )
1471 {
1472 m_gal->Save();
1473 m_gal->Translate( parentFootprint->GetPosition() );
1474 m_gal->Rotate( -parentFootprint->GetOrientationRadians() );
1475 }
1476
1477 if( sketch )
1478 {
1479 for( int ii = 0; ii < shape.Outline( 0 ).SegmentCount(); ++ii )
1480 {
1481 SEG seg = shape.Outline( 0 ).Segment( ii );
1482 m_gal->DrawSegment( seg.A, seg.B, thickness );
1483 }
1484 }
1485 else
1486 {
1487 m_gal->SetIsFill( true );
1488 m_gal->SetIsStroke( false );
1489
1490 if( thickness > 0 )
1491 {
1492 for( int ii = 0; ii < shape.Outline( 0 ).SegmentCount(); ++ii )
1493 {
1494 SEG seg = shape.Outline( 0 ).Segment( ii );
1495 m_gal->DrawSegment( seg.A, seg.B, thickness );
1496 }
1497 }
1498
1499 if( aShape->IsFilled() )
1500 {
1501 // On Opengl, a not convex filled polygon is usually drawn by using triangles
1502 // as primitives. CacheTriangulation() can create basic triangle primitives to
1503 // draw the polygon solid shape on Opengl. GLU tessellation is much slower, so
1504 // currently we are using our tessellation.
1505 if( m_gal->IsOpenGlEngine() && !shape.IsTriangulationUpToDate() )
1506 shape.CacheTriangulation();
1507
1508 m_gal->DrawPolygon( shape );
1509 }
1510 }
1511
1512 if( parentFootprint )
1513 m_gal->Restore();
1514
1515 break;
1516 }
1517
1518 case SHAPE_T::BEZIER:
1519 if( sketch )
1520 {
1521 std::vector<VECTOR2D> output;
1522 std::vector<VECTOR2D> pointCtrl;
1523
1524 pointCtrl.push_back( aShape->GetStart() );
1525 pointCtrl.push_back( aShape->GetBezierC1() );
1526 pointCtrl.push_back( aShape->GetBezierC2() );
1527 pointCtrl.push_back( aShape->GetEnd() );
1528
1529 BEZIER_POLY converter( pointCtrl );
1530 converter.GetPoly( output, thickness );
1531
1532 for( unsigned ii = 0; ii + 1 < output.size(); ++ii )
1533 m_gal->DrawSegment( output[ii], output[ii+1], thickness );
1534 }
1535 else
1536 {
1537 m_gal->SetIsFill( aShape->IsFilled() );
1538 m_gal->SetIsStroke( thickness > 0 );
1539 m_gal->SetLineWidth( thickness );
1540
1541 // Use thickness as filter value to convert the curve to polyline when the curve
1542 // is not supported
1543 m_gal->DrawCurve( VECTOR2D( aShape->GetStart() ),
1544 VECTOR2D( aShape->GetBezierC1() ),
1545 VECTOR2D( aShape->GetBezierC2() ),
1546 VECTOR2D( aShape->GetEnd() ), thickness );
1547 }
1548
1549 break;
1550
1551 case SHAPE_T::LAST:
1552 break;
1553 }
1554 }
1555
1556
draw(const PCB_TEXT * aText,int aLayer)1557 void PCB_PAINTER::draw( const PCB_TEXT* aText, int aLayer )
1558 {
1559 wxString shownText( aText->GetShownText() );
1560
1561 if( shownText.Length() == 0 )
1562 return;
1563
1564 const COLOR4D& color = m_pcbSettings.GetColor( aText, aText->GetLayer() );
1565 VECTOR2D position( aText->GetTextPos().x, aText->GetTextPos().y );
1566
1567 if( m_pcbSettings.m_sketchText || m_pcbSettings.m_sketchMode[aLayer] )
1568 {
1569 // Outline mode
1570 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
1571 }
1572 else
1573 {
1574 // Filled mode
1575 m_gal->SetLineWidth( getLineThickness( aText->GetEffectiveTextPenWidth() ) );
1576 }
1577
1578 m_gal->SetStrokeColor( color );
1579 m_gal->SetIsFill( false );
1580 m_gal->SetIsStroke( true );
1581 m_gal->SetTextAttributes( aText );
1582 m_gal->StrokeText( shownText, position, aText->GetTextAngleRadians() );
1583 }
1584
1585
draw(const FP_TEXT * aText,int aLayer)1586 void PCB_PAINTER::draw( const FP_TEXT* aText, int aLayer )
1587 {
1588 wxString shownText( aText->GetShownText() );
1589
1590 if( shownText.Length() == 0 )
1591 return;
1592
1593 const COLOR4D& color = m_pcbSettings.GetColor( aText, aLayer );
1594 VECTOR2D position( aText->GetTextPos().x, aText->GetTextPos().y );
1595
1596 if( m_pcbSettings.m_sketchText )
1597 {
1598 // Outline mode
1599 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
1600 }
1601 else
1602 {
1603 // Filled mode
1604 m_gal->SetLineWidth( getLineThickness( aText->GetEffectiveTextPenWidth() ) );
1605 }
1606
1607 m_gal->SetStrokeColor( color );
1608 m_gal->SetIsFill( false );
1609 m_gal->SetIsStroke( true );
1610 m_gal->SetTextAttributes( aText );
1611 m_gal->StrokeText( shownText, position, aText->GetDrawRotationRadians() );
1612
1613 // Draw the umbilical line
1614 if( aText->IsSelected() )
1615 {
1616 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
1617 m_gal->SetStrokeColor( m_pcbSettings.GetColor( nullptr, LAYER_ANCHOR ) );
1618 m_gal->DrawLine( position, aText->GetParent()->GetPosition() );
1619 }
1620 }
1621
1622
draw(const FOOTPRINT * aFootprint,int aLayer)1623 void PCB_PAINTER::draw( const FOOTPRINT* aFootprint, int aLayer )
1624 {
1625 if( aLayer == LAYER_ANCHOR )
1626 {
1627 const COLOR4D color = m_pcbSettings.GetColor( aFootprint, aLayer );
1628
1629 // Keep the size and width constant, not related to the scale because the anchor
1630 // is just a marker on screen
1631 double anchorSize = 5.0 / m_gal->GetWorldScale(); // 5 pixels size
1632 double anchorThickness = 1.0 / m_gal->GetWorldScale(); // 1 pixels width
1633
1634 // Draw anchor
1635 m_gal->SetIsFill( false );
1636 m_gal->SetIsStroke( true );
1637 m_gal->SetStrokeColor( color );
1638 m_gal->SetLineWidth( anchorThickness );
1639
1640 VECTOR2D center = aFootprint->GetPosition();
1641 m_gal->DrawLine( center - VECTOR2D( anchorSize, 0 ), center + VECTOR2D( anchorSize, 0 ) );
1642 m_gal->DrawLine( center - VECTOR2D( 0, anchorSize ), center + VECTOR2D( 0, anchorSize ) );
1643 }
1644 }
1645
1646
draw(const PCB_GROUP * aGroup,int aLayer)1647 void PCB_PAINTER::draw( const PCB_GROUP* aGroup, int aLayer )
1648 {
1649 if( aLayer == LAYER_ANCHOR )
1650 {
1651 if( aGroup->IsSelected() && !( aGroup->GetParent() && aGroup->GetParent()->IsSelected() ) )
1652 {
1653 // Selected on our own; draw enclosing box
1654 }
1655 else if( aGroup->IsEntered() )
1656 {
1657 // Entered group; draw enclosing box
1658 }
1659 else
1660 {
1661 // Neither selected nor entered; draw nothing at the group level (ie: only draw
1662 // its members)
1663 return;
1664 }
1665
1666 const COLOR4D color = m_pcbSettings.GetColor( aGroup, LAYER_ANCHOR );
1667
1668 EDA_RECT bbox = aGroup->GetBoundingBox();
1669 m_gal->SetStrokeColor( color );
1670 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth * 2.0f );
1671 wxPoint topLeft = bbox.GetPosition();
1672 wxPoint width = wxPoint( bbox.GetWidth(), 0 );
1673 wxPoint height = wxPoint( 0, bbox.GetHeight() );
1674
1675 m_gal->DrawLine( topLeft, topLeft + width );
1676 m_gal->DrawLine( topLeft + width, topLeft + width + height );
1677 m_gal->DrawLine( topLeft + width + height, topLeft + height );
1678 m_gal->DrawLine( topLeft + height, topLeft );
1679
1680 wxString name = aGroup->GetName();
1681
1682 if( name.IsEmpty() )
1683 return;
1684
1685 int ptSize = 12;
1686 int scaledSize = abs( KiROUND( m_gal->GetScreenWorldMatrix().GetScale().x * ptSize ) );
1687 int unscaledSize = Mils2iu( ptSize );
1688
1689 // Scale by zoom a bit, but not too much
1690 int textSize = ( scaledSize + ( unscaledSize * 2 ) ) / 3;
1691 int penWidth = textSize / 10;
1692 wxPoint textOffset = wxPoint( width.x / 2, - KiROUND( textSize * 0.5 ) );
1693 wxPoint titleHeight = wxPoint( 0, KiROUND( textSize * 2.0 ) );
1694
1695 if( PrintableCharCount( name ) * textSize < bbox.GetWidth() )
1696 {
1697 m_gal->DrawLine( topLeft, topLeft - titleHeight );
1698 m_gal->DrawLine( topLeft - titleHeight, topLeft + width - titleHeight );
1699 m_gal->DrawLine( topLeft + width - titleHeight, topLeft + width );
1700
1701 m_gal->SetFontBold( false );
1702 m_gal->SetFontItalic( true );
1703 m_gal->SetFontUnderlined( false );
1704 m_gal->SetTextMirrored( m_gal->IsFlippedX() );
1705 m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER );
1706 m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_BOTTOM );
1707 m_gal->SetIsFill( false );
1708 m_gal->SetGlyphSize( VECTOR2D( textSize, textSize ) );
1709 m_gal->SetLineWidth( penWidth );
1710 m_gal->StrokeText( aGroup->GetName(), topLeft + textOffset, 0.0 );
1711 }
1712 }
1713 }
1714
1715
draw(const ZONE * aZone,int aLayer)1716 void PCB_PAINTER::draw( const ZONE* aZone, int aLayer )
1717 {
1718 /*
1719 * aLayer will be the virtual zone layer (LAYER_ZONE_START, ... in GAL_LAYER_ID)
1720 * This is used for draw ordering in the GAL.
1721 * The color for the zone comes from the associated copper layer ( aLayer - LAYER_ZONE_START )
1722 * and the visibility comes from the combination of that copper layer and LAYER_ZONES
1723 */
1724 wxASSERT( IsZoneLayer( aLayer ) );
1725 PCB_LAYER_ID layer = static_cast<PCB_LAYER_ID>( aLayer - LAYER_ZONE_START );
1726
1727 if( !aZone->IsOnLayer( layer ) )
1728 return;
1729
1730 COLOR4D color = m_pcbSettings.GetColor( aZone, layer );
1731 std::deque<VECTOR2D> corners;
1732 ZONE_DISPLAY_MODE displayMode = m_pcbSettings.m_zoneDisplayMode;
1733
1734 // Draw the outline
1735 const SHAPE_POLY_SET* outline = aZone->Outline();
1736
1737 if( m_pcbSettings.m_zoneOutlines && outline && outline->OutlineCount() > 0 )
1738 {
1739 m_gal->SetStrokeColor( color.a > 0.0 ? color.WithAlpha( 1.0 ) : color );
1740 m_gal->SetIsFill( false );
1741 m_gal->SetIsStroke( true );
1742 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
1743
1744 // Draw each contour (main contour and holes)
1745
1746 /* This line:
1747 * m_gal->DrawPolygon( *outline );
1748 * should be enough, but currently does not work to draw holes contours in a complex polygon
1749 * so each contour is draw as a simple polygon
1750 */
1751
1752 // Draw the main contour
1753 m_gal->DrawPolyline( outline->COutline( 0 ) );
1754
1755 // Draw holes
1756 int holes_count = outline->HoleCount( 0 );
1757
1758 for( int ii = 0; ii < holes_count; ++ii )
1759 m_gal->DrawPolyline( outline->CHole( 0, ii ) );
1760
1761 // Draw hatch lines
1762 for( const SEG& hatchLine : aZone->GetHatchLines() )
1763 m_gal->DrawLine( hatchLine.A, hatchLine.B );
1764 }
1765
1766 // Draw the filling
1767 if( displayMode == ZONE_DISPLAY_MODE::SHOW_FILLED
1768 || displayMode == ZONE_DISPLAY_MODE::SHOW_FRACTURE_BORDERS
1769 || displayMode == ZONE_DISPLAY_MODE::SHOW_TRIANGULATION )
1770 {
1771 const SHAPE_POLY_SET& polySet = aZone->GetFilledPolysList( layer );
1772
1773 if( polySet.OutlineCount() == 0 ) // Nothing to draw
1774 return;
1775
1776 // Set up drawing options
1777 int outline_thickness = 0;
1778
1779 if( aZone->GetFilledPolysUseThickness( layer ) )
1780 outline_thickness = aZone->GetMinThickness();
1781
1782 m_gal->SetStrokeColor( color );
1783 m_gal->SetFillColor( color );
1784 m_gal->SetLineWidth( outline_thickness );
1785
1786 if( displayMode == ZONE_DISPLAY_MODE::SHOW_FILLED )
1787 {
1788 m_gal->SetIsFill( true );
1789 m_gal->SetIsStroke( outline_thickness > 0 );
1790 }
1791 else
1792 {
1793 m_gal->SetIsFill( false );
1794 m_gal->SetIsStroke( true );
1795 }
1796
1797 m_gal->DrawPolygon( polySet, displayMode == ZONE_DISPLAY_MODE::SHOW_TRIANGULATION );
1798 }
1799 }
1800
1801
draw(const PCB_DIMENSION_BASE * aDimension,int aLayer)1802 void PCB_PAINTER::draw( const PCB_DIMENSION_BASE* aDimension, int aLayer )
1803 {
1804 const COLOR4D& strokeColor = m_pcbSettings.GetColor( aDimension, aLayer );
1805
1806 m_gal->SetStrokeColor( strokeColor );
1807 m_gal->SetIsFill( false );
1808 m_gal->SetIsStroke( true );
1809
1810 if( m_pcbSettings.m_sketchGraphics )
1811 {
1812 // Outline mode
1813 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
1814 }
1815 else
1816 {
1817 // Filled mode
1818 m_gal->SetLineWidth( getLineThickness( aDimension->GetLineThickness() ) );
1819 }
1820
1821 // Draw dimension shapes
1822 // TODO(JE) lift this out
1823 for( const std::shared_ptr<SHAPE>& shape : aDimension->GetShapes() )
1824 {
1825 switch( shape->Type() )
1826 {
1827 case SH_SEGMENT:
1828 {
1829 const SEG& seg = static_cast<const SHAPE_SEGMENT*>( shape.get() )->GetSeg();
1830 m_gal->DrawLine( seg.A, seg.B );
1831 break;
1832 }
1833
1834 case SH_CIRCLE:
1835 {
1836 int radius = static_cast<const SHAPE_CIRCLE*>( shape.get() )->GetRadius();
1837 m_gal->DrawCircle( shape->Centre(), radius );
1838 break;
1839 }
1840
1841 default:
1842 break;
1843 }
1844 }
1845
1846 // Draw text
1847 const PCB_TEXT& text = aDimension->Text();
1848 VECTOR2D position( text.GetTextPos().x, text.GetTextPos().y );
1849
1850 if( m_pcbSettings.m_sketchText )
1851 {
1852 // Outline mode
1853 m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
1854 }
1855 else
1856 {
1857 // Filled mode
1858 m_gal->SetLineWidth( getLineThickness( text.GetEffectiveTextPenWidth() ) );
1859 }
1860
1861 m_gal->SetTextAttributes( &text );
1862 m_gal->StrokeText( text.GetShownText(), position, text.GetTextAngleRadians() );
1863 }
1864
1865
draw(const PCB_TARGET * aTarget)1866 void PCB_PAINTER::draw( const PCB_TARGET* aTarget )
1867 {
1868 const COLOR4D& strokeColor = m_pcbSettings.GetColor( aTarget, aTarget->GetLayer() );
1869 VECTOR2D position( aTarget->GetPosition() );
1870 double size, radius;
1871
1872 m_gal->SetLineWidth( getLineThickness( aTarget->GetWidth() ) );
1873 m_gal->SetStrokeColor( strokeColor );
1874 m_gal->SetIsFill( false );
1875 m_gal->SetIsStroke( true );
1876
1877 m_gal->Save();
1878 m_gal->Translate( position );
1879
1880 if( aTarget->GetShape() )
1881 {
1882 // shape x
1883 m_gal->Rotate( M_PI / 4.0 );
1884 size = 2.0 * aTarget->GetSize() / 3.0;
1885 radius = aTarget->GetSize() / 2.0;
1886 }
1887 else
1888 {
1889 // shape +
1890 size = aTarget->GetSize() / 2.0;
1891 radius = aTarget->GetSize() / 3.0;
1892 }
1893
1894 m_gal->DrawLine( VECTOR2D( -size, 0.0 ), VECTOR2D( size, 0.0 ) );
1895 m_gal->DrawLine( VECTOR2D( 0.0, -size ), VECTOR2D( 0.0, size ) );
1896 m_gal->DrawCircle( VECTOR2D( 0.0, 0.0 ), radius );
1897
1898 m_gal->Restore();
1899 }
1900
1901
draw(const PCB_MARKER * aMarker,int aLayer)1902 void PCB_PAINTER::draw( const PCB_MARKER* aMarker, int aLayer )
1903 {
1904 bool isShadow = aLayer == LAYER_MARKER_SHADOWS;
1905
1906 // Don't paint shadows for invisible markers.
1907 // It would be nice to do this through layer dependencies but we can't do an "or" there today
1908 if( isShadow && aMarker->GetBoard()
1909 && !aMarker->GetBoard()->IsElementVisible( aMarker->GetColorLayer() ) )
1910 {
1911 return;
1912 }
1913
1914 SHAPE_LINE_CHAIN polygon;
1915 aMarker->ShapeToPolygon( polygon );
1916
1917 COLOR4D color = m_pcbSettings.GetColor( aMarker, isShadow ? LAYER_MARKER_SHADOWS
1918 : aMarker->GetColorLayer() );
1919
1920 m_gal->Save();
1921 m_gal->Translate( aMarker->GetPosition() );
1922
1923 if( isShadow )
1924 {
1925 m_gal->SetStrokeColor( color );
1926 m_gal->SetIsStroke( true );
1927 m_gal->SetLineWidth( aMarker->MarkerScale() );
1928 }
1929 else
1930 {
1931 m_gal->SetFillColor( color );
1932 m_gal->SetIsFill( true );
1933 }
1934
1935 m_gal->DrawPolygon( polygon );
1936 m_gal->Restore();
1937 }
1938
1939
1940 const double PCB_RENDER_SETTINGS::MAX_FONT_SIZE = Millimeter2iu( 10.0 );
1941