1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2017 Jon Evans <jon@craftyjon.com>
5 * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <gerbview_painter.h>
22 #include <gal/graphics_abstraction_layer.h>
23 #include <settings/color_settings.h>
24 #include <convert_basic_shapes_to_polygon.h>
25 #include <convert_to_biu.h>
26 #include <gerbview.h>
27 #include <trigo.h>
28
29 #include <dcode.h>
30 #include <gerber_draw_item.h>
31 #include <gerber_file_image.h>
32
33 using namespace KIGFX;
34
GERBVIEW_RENDER_SETTINGS()35 GERBVIEW_RENDER_SETTINGS::GERBVIEW_RENDER_SETTINGS()
36 {
37 m_backgroundColor = COLOR4D::BLACK;
38
39 m_spotFill = true;
40 m_lineFill = true;
41 m_polygonFill = true;
42 m_showNegativeItems = false;
43 m_showCodes = false;
44 m_diffMode = true;
45
46 m_componentHighlightString = "";
47 m_netHighlightString = "";
48 m_attributeHighlightString = "";
49 m_dcodeHighlightValue = -1;
50
51 update();
52 }
53
54
LoadColors(const COLOR_SETTINGS * aSettings)55 void GERBVIEW_RENDER_SETTINGS::LoadColors( const COLOR_SETTINGS* aSettings )
56 {
57 size_t palette_size = aSettings->m_Palette.size();
58 size_t palette_idx = 0;
59
60 // Layers to draw gerber data read from gerber files:
61 for( int i = GERBVIEW_LAYER_ID_START;
62 i < GERBVIEW_LAYER_ID_START + GERBER_DRAWLAYERS_COUNT; i++ )
63 {
64 COLOR4D baseColor = aSettings->GetColor( i );
65
66 if( baseColor == COLOR4D::UNSPECIFIED )
67 baseColor = aSettings->m_Palette[ ( palette_idx++ ) % palette_size ];
68
69 m_layerColors[i] = baseColor;
70 m_layerColorsHi[i] = baseColor.Brightened( 0.5 );
71 m_layerColorsSel[i] = baseColor.Brightened( 0.8 );
72 m_layerColorsDark[i] = baseColor.Darkened( 0.25 );
73 }
74
75 // Draw layers specific to Gerbview:
76 // LAYER_DCODES, LAYER_NEGATIVE_OBJECTS, LAYER_GERBVIEW_GRID,
77 // LAYER_GERBVIEW_AXES, LAYER_GERBVIEW_BACKGROUND, LAYER_GERBVIEW_DRAWINGSHEET,
78 for( int i = LAYER_DCODES; i < GERBVIEW_LAYER_ID_END; i++ )
79 m_layerColors[i] = aSettings->GetColor( i );
80
81 for( int i = GAL_LAYER_ID_START; i < GAL_LAYER_ID_END; i++ )
82 m_layerColors[i] = aSettings->GetColor( i );
83
84 update();
85 }
86
87
LoadDisplayOptions(const GBR_DISPLAY_OPTIONS & aOptions)88 void GERBVIEW_RENDER_SETTINGS::LoadDisplayOptions( const GBR_DISPLAY_OPTIONS& aOptions )
89 {
90 m_spotFill = aOptions.m_DisplayFlashedItemsFill;
91 m_lineFill = aOptions.m_DisplayLinesFill;
92 m_polygonFill = aOptions.m_DisplayPolygonsFill;
93 m_showNegativeItems = aOptions.m_DisplayNegativeObjects;
94 m_showCodes = aOptions.m_DisplayDCodes;
95 m_diffMode = aOptions.m_DiffMode;
96 m_hiContrastEnabled = aOptions.m_HighContrastMode;
97 m_showPageLimits = aOptions.m_DisplayPageLimits;
98 m_backgroundColor = aOptions.m_BgDrawColor;
99
100 update();
101 }
102
103
ClearHighlightSelections()104 void GERBVIEW_RENDER_SETTINGS::ClearHighlightSelections()
105 {
106 // Clear all highlight selections (dcode, net, component, attribute selection)
107 m_componentHighlightString.Empty();
108 m_netHighlightString.Empty();
109 m_attributeHighlightString.Empty();
110 m_dcodeHighlightValue = -1;
111 }
112
GetColor(const VIEW_ITEM * aItem,int aLayer) const113 COLOR4D GERBVIEW_RENDER_SETTINGS::GetColor( const VIEW_ITEM* aItem, int aLayer ) const
114 {
115 const EDA_ITEM* item = dynamic_cast<const EDA_ITEM*>( aItem );
116 static const COLOR4D transparent = COLOR4D( 0, 0, 0, 0 );
117 const GERBER_DRAW_ITEM* gbrItem = nullptr;
118
119 if( item && item->Type() == GERBER_DRAW_ITEM_T )
120 gbrItem = static_cast<const GERBER_DRAW_ITEM*>( item );
121
122 // All DCODE layers stored under a single color setting
123 if( IsDCodeLayer( aLayer ) )
124 return m_layerColors[ LAYER_DCODES ];
125
126 if( item && item->IsSelected() )
127 return m_layerColorsSel[aLayer];
128
129 if( gbrItem && gbrItem->GetLayerPolarity() )
130 {
131 if( m_showNegativeItems )
132 return m_layerColors[LAYER_NEGATIVE_OBJECTS];
133 else
134 return transparent;
135 }
136
137 if( !m_netHighlightString.IsEmpty() && gbrItem &&
138 m_netHighlightString == gbrItem->GetNetAttributes().m_Netname )
139 return m_layerColorsHi[aLayer];
140
141 if( !m_componentHighlightString.IsEmpty() && gbrItem &&
142 m_componentHighlightString == gbrItem->GetNetAttributes().m_Cmpref )
143 return m_layerColorsHi[aLayer];
144
145 if( !m_attributeHighlightString.IsEmpty() && gbrItem && gbrItem->GetDcodeDescr() &&
146 m_attributeHighlightString == gbrItem->GetDcodeDescr()->m_AperFunction )
147 return m_layerColorsHi[aLayer];
148
149 if( m_dcodeHighlightValue> 0 && gbrItem && gbrItem->GetDcodeDescr() &&
150 m_dcodeHighlightValue == gbrItem->GetDcodeDescr()->m_Num_Dcode )
151 return m_layerColorsHi[aLayer];
152
153 // Return grayish color for non-highlighted layers in the high contrast mode
154 if( m_hiContrastEnabled && m_highContrastLayers.count( aLayer ) == 0)
155 return m_hiContrastColor[aLayer];
156
157 // Catch the case when highlight and high-contraste modes are enabled
158 // and we are drawing a not highlighted track
159 if( m_highlightEnabled )
160 return m_layerColorsDark[aLayer];
161
162 // No special modificators enabled
163 return m_layerColors[aLayer];
164 }
165
166
GERBVIEW_PAINTER(GAL * aGal)167 GERBVIEW_PAINTER::GERBVIEW_PAINTER( GAL* aGal ) :
168 PAINTER( aGal )
169 {
170 }
171
172
173 // TODO(JE): Pull up to PAINTER?
getLineThickness(int aActualThickness) const174 int GERBVIEW_PAINTER::getLineThickness( int aActualThickness ) const
175 {
176 // if items have 0 thickness, draw them with the outline
177 // width, otherwise respect the set value (which, no matter
178 // how small will produce something)
179 if( aActualThickness == 0 )
180 return m_gerbviewSettings.m_outlineWidth;
181
182 return aActualThickness;
183 }
184
185
Draw(const VIEW_ITEM * aItem,int aLayer)186 bool GERBVIEW_PAINTER::Draw( const VIEW_ITEM* aItem, int aLayer )
187 {
188 const EDA_ITEM* item = dynamic_cast<const EDA_ITEM*>( aItem );
189
190 if( !item )
191 return false;
192
193 // the "cast" applied in here clarifies which overloaded draw() is called
194 switch( item->Type() )
195 {
196 case GERBER_DRAW_ITEM_T:
197 draw( static_cast<GERBER_DRAW_ITEM*>( const_cast<EDA_ITEM*>( item ) ), aLayer );
198 break;
199
200 default:
201 // Painter does not know how to draw the object
202 return false;
203 }
204
205 return true;
206 }
207
208
209 // TODO(JE) aItem can't be const because of GetDcodeDescr()
210 // Probably that can be refactored in GERBER_DRAW_ITEM to allow const here.
draw(GERBER_DRAW_ITEM * aItem,int aLayer)211 void GERBVIEW_PAINTER::draw( /*const*/ GERBER_DRAW_ITEM* aItem, int aLayer )
212 {
213 VECTOR2D start( aItem->GetABPosition( aItem->m_Start ) ); // TODO(JE) Getter
214 VECTOR2D end( aItem->GetABPosition( aItem->m_End ) ); // TODO(JE) Getter
215 int width = aItem->m_Size.x; // TODO(JE) Getter
216 bool isFilled = true;
217 COLOR4D color;
218 // TODO(JE) This doesn't actually work properly for ImageNegative
219 bool isNegative = ( aItem->GetLayerPolarity() ^ aItem->m_GerberImageFile->m_ImageNegative );
220
221 // Draw DCODE overlay text
222 if( IsDCodeLayer( aLayer ) )
223 {
224 wxString codeText;
225 VECTOR2D textPosition;
226 double textSize;
227 double orient;
228
229 if( !aItem->GetTextD_CodePrms( textSize, textPosition, orient ) )
230 return;
231
232 color = m_gerbviewSettings.GetColor( aItem, aLayer );
233 codeText.Printf( "D%d", aItem->m_DCode );
234
235 m_gal->SetIsStroke( true );
236 m_gal->SetIsFill( false );
237 m_gal->SetStrokeColor( color );
238 m_gal->SetFillColor( COLOR4D( 0, 0, 0, 0 ) );
239 m_gal->SetLineWidth( textSize/10 );
240 m_gal->SetFontBold( false );
241 m_gal->SetFontItalic( false );
242 m_gal->SetFontUnderlined( false );
243 m_gal->SetTextMirrored( false );
244 m_gal->SetGlyphSize( VECTOR2D( textSize, textSize) );
245 m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER );
246 m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER );
247 m_gal->BitmapText( codeText, textPosition, orient );
248
249 return;
250 }
251
252 color = m_gerbviewSettings.GetColor( aItem, aLayer );
253
254 // TODO: Should brightened color be a preference?
255 if( aItem->IsBrightened() )
256 color = COLOR4D( 0.0, 1.0, 0.0, 0.75 );
257
258 m_gal->SetNegativeDrawMode( isNegative && ! m_gerbviewSettings.IsShowNegativeItems() );
259 m_gal->SetStrokeColor( color );
260 m_gal->SetFillColor( color );
261 m_gal->SetIsFill( isFilled );
262 m_gal->SetIsStroke( !isFilled );
263
264 switch( aItem->m_Shape )
265 {
266 case GBR_POLYGON:
267 {
268 isFilled = m_gerbviewSettings.m_polygonFill;
269 m_gal->SetIsFill( isFilled );
270 m_gal->SetIsStroke( !isFilled );
271
272 if( isNegative && !isFilled )
273 {
274 m_gal->SetNegativeDrawMode( false );
275 m_gal->SetStrokeColor( GetSettings()->GetColor( aItem, aLayer ) );
276 }
277
278 if( !isFilled )
279 m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
280
281 if( aItem->m_AbsolutePolygon.OutlineCount() == 0 )
282 {
283 std::vector<VECTOR2I> pts = aItem->m_Polygon.COutline( 0 ).CPoints();
284
285 for( auto& pt : pts )
286 pt = aItem->GetABPosition( pt );
287
288 SHAPE_LINE_CHAIN chain( pts );
289 chain.SetClosed( true );
290 aItem->m_AbsolutePolygon.AddOutline( chain );
291 }
292
293 // Degenerated polygons (having < 3 points) are drawn as lines
294 // to avoid issues in draw polygon functions
295 if( !isFilled || aItem->m_AbsolutePolygon.COutline( 0 ).PointCount() < 3 )
296 m_gal->DrawPolyline( aItem->m_AbsolutePolygon.COutline( 0 ) );
297 else
298 {
299 // On Opengl, a not convex filled polygon is usually drawn by using triangles as primitives.
300 // CacheTriangulation() can create basic triangle primitives to draw the polygon solid shape
301 // on Opengl
302 if( m_gal->IsOpenGlEngine() && !aItem->m_AbsolutePolygon.IsTriangulationUpToDate() )
303 aItem->m_AbsolutePolygon.CacheTriangulation();
304
305 m_gal->DrawPolygon( aItem->m_AbsolutePolygon );
306 }
307
308 break;
309 }
310
311 case GBR_CIRCLE:
312 {
313 isFilled = m_gerbviewSettings.m_lineFill;
314 double radius = GetLineLength( aItem->m_Start, aItem->m_End );
315 m_gal->DrawCircle( start, radius );
316 break;
317 }
318
319 case GBR_ARC:
320 {
321 isFilled = m_gerbviewSettings.m_lineFill;
322
323 // These are swapped because wxDC fills arcs counterclockwise and GAL
324 // fills them clockwise.
325 wxPoint arcStart = aItem->m_End;
326 wxPoint arcEnd = aItem->m_Start;
327
328 // Gerber arcs are 3-point (start, center, end)
329 // GAL needs center, radius, start angle, end angle
330 double radius = GetLineLength( arcStart, aItem->m_ArcCentre );
331 VECTOR2D center = aItem->GetABPosition( aItem->m_ArcCentre );
332 VECTOR2D startVec = VECTOR2D( aItem->GetABPosition( arcStart ) ) - center;
333 VECTOR2D endVec = VECTOR2D( aItem->GetABPosition( arcEnd ) ) - center;
334
335 m_gal->SetIsFill( isFilled );
336 m_gal->SetIsStroke( !isFilled );
337 m_gal->SetLineWidth( isFilled ? width : m_gerbviewSettings.m_outlineWidth );
338
339 double startAngle = startVec.Angle();
340 double endAngle = endVec.Angle();
341
342 // GAL fills in direction of increasing angle, so we have to convert
343 // the angle from the -PI to PI domain of atan2() to ensure that
344 // the arc goes in the right direction
345 if( startAngle > endAngle )
346 endAngle += (2 * M_PI);
347
348 // In Gerber, 360-degree arcs are stored in the file with start equal to end
349 if( arcStart == arcEnd )
350 {
351 endAngle = startAngle + 2*M_PI;
352 }
353
354 m_gal->DrawArcSegment( center, radius, startAngle, endAngle, width, ARC_HIGH_DEF );
355
356 #if 0 // Arc Debugging only
357 m_gal->SetIsFill( false );
358 m_gal->SetIsStroke( true );
359 m_gal->SetLineWidth( 5 );
360 m_gal->SetStrokeColor( COLOR4D( 0.1, 0.5, 0.0, 0.5 ) );
361 m_gal->DrawLine( center, aItem->GetABPosition( arcStart ) );
362 m_gal->SetStrokeColor( COLOR4D( 0.6, 0.1, 0.0, 0.5 ) );
363 m_gal->DrawLine( center, aItem->GetABPosition( arcEnd ) );
364 #endif
365
366 #if 0 // Bbox arc Debugging only
367 m_gal->SetIsFill( false );
368 m_gal->SetIsStroke( true );
369 EDA_RECT box = aItem->GetBoundingBox();
370 m_gal->SetLineWidth( 5 );
371 m_gal->SetStrokeColor( COLOR4D(0.9, 0.9, 0, 0.4) );
372 // box coordinates are already in AB position.
373 m_gal->DrawRectangle( box.GetOrigin(), box.GetEnd() );
374 #endif
375 break;
376 }
377
378 case GBR_SPOT_CIRCLE:
379 case GBR_SPOT_RECT:
380 case GBR_SPOT_OVAL:
381 case GBR_SPOT_POLY:
382 case GBR_SPOT_MACRO:
383 {
384 isFilled = m_gerbviewSettings.m_spotFill;
385 drawFlashedShape( aItem, isFilled );
386 break;
387 }
388
389 case GBR_SEGMENT:
390 {
391 /* Plot a line from m_Start to m_End.
392 * Usually, a round pen is used, but some gerber files use a rectangular pen
393 * In fact, any aperture can be used to plot a line.
394 * currently: only a square pen is handled (I believe using a polygon gives a strange plot).
395 */
396 isFilled = m_gerbviewSettings.m_lineFill;
397 m_gal->SetIsFill( isFilled );
398 m_gal->SetIsStroke( !isFilled );
399
400 if( isNegative && !isFilled )
401 m_gal->SetStrokeColor( GetSettings()->GetColor( aItem, aLayer ) );
402
403 // TODO(JE) Refactor this to allow const aItem
404 D_CODE* code = aItem->GetDcodeDescr();
405 if( code && code->m_Shape == APT_RECT )
406 {
407 if( aItem->m_Polygon.OutlineCount() == 0 )
408 aItem->ConvertSegmentToPolygon();
409
410 drawPolygon( aItem, aItem->m_Polygon, isFilled );
411 }
412 else
413 {
414 if( !isFilled )
415 m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
416
417 m_gal->DrawSegment( start, end, width );
418 }
419 break;
420 }
421
422 default:
423 wxASSERT_MSG( false, "GERBER_DRAW_ITEM shape is unknown!" );
424 break;
425 }
426 m_gal->SetNegativeDrawMode( false );
427
428 // Enable for bounding box debugging
429 #if 0
430 const BOX2I& bb = aItem->ViewBBox();
431 m_gal->SetIsStroke( true );
432 m_gal->SetIsFill( true );
433 m_gal->SetLineWidth( 3 );
434 m_gal->SetStrokeColor( COLOR4D(0.9, 0.9, 0, 0.4) );
435 m_gal->SetFillColor( COLOR4D(0.9, 0.9, 0, 0.1) );
436 m_gal->DrawRectangle( bb.GetOrigin(), bb.GetEnd() );
437 #endif
438 }
439
440
drawPolygon(GERBER_DRAW_ITEM * aParent,const SHAPE_POLY_SET & aPolygon,bool aFilled,bool aShift)441 void GERBVIEW_PAINTER::drawPolygon(
442 GERBER_DRAW_ITEM* aParent, const SHAPE_POLY_SET& aPolygon, bool aFilled, bool aShift )
443 {
444 wxASSERT( aPolygon.OutlineCount() == 1 );
445
446 if( aPolygon.OutlineCount() == 0 )
447 return;
448
449 SHAPE_POLY_SET poly;
450 poly.NewOutline();
451 const std::vector<VECTOR2I> pts = aPolygon.COutline( 0 ).CPoints();
452 VECTOR2I offset = aShift ? VECTOR2I( aParent->m_Start ) : VECTOR2I( 0, 0 );
453
454 for( auto& pt : pts )
455 poly.Append( aParent->GetABPosition( pt + offset ) );
456
457 if( !m_gerbviewSettings.m_polygonFill )
458 m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
459
460 if( !aFilled )
461 {
462 m_gal->DrawPolyline( poly.COutline( 0 ) );
463 }
464 else
465 m_gal->DrawPolygon( poly );
466 }
467
468
drawFlashedShape(GERBER_DRAW_ITEM * aItem,bool aFilled)469 void GERBVIEW_PAINTER::drawFlashedShape( GERBER_DRAW_ITEM* aItem, bool aFilled )
470 {
471 D_CODE* code = aItem->GetDcodeDescr();
472
473 wxASSERT_MSG( code, "drawFlashedShape: Item has no D_CODE!" );
474
475 if( !code )
476 return;
477
478 m_gal->SetIsFill( aFilled );
479 m_gal->SetIsStroke( !aFilled );
480 m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
481
482 switch( aItem->m_Shape )
483 {
484 case GBR_SPOT_CIRCLE:
485 {
486 int radius = code->m_Size.x >> 1;
487 VECTOR2D start( aItem->GetABPosition( aItem->m_Start ) );
488
489 if( !aFilled || code->m_DrillShape == APT_DEF_NO_HOLE )
490 {
491 m_gal->DrawCircle( start, radius );
492 }
493 else // rectangular hole
494 {
495 if( code->m_Polygon.OutlineCount() == 0 )
496 code->ConvertShapeToPolygon();
497
498 drawPolygon( aItem, code->m_Polygon, aFilled, true );
499 }
500
501 break;
502 }
503
504 case GBR_SPOT_RECT:
505 {
506 wxPoint codeStart;
507 wxPoint aShapePos = aItem->m_Start;
508 codeStart.x = aShapePos.x - code->m_Size.x / 2;
509 codeStart.y = aShapePos.y - code->m_Size.y / 2;
510 wxPoint codeEnd = codeStart + code->m_Size;
511 codeStart = aItem->GetABPosition( codeStart );
512 codeEnd = aItem->GetABPosition( codeEnd );
513
514 if( !aFilled || code->m_DrillShape == APT_DEF_NO_HOLE )
515 {
516 m_gal->DrawRectangle( VECTOR2D( codeStart ), VECTOR2D( codeEnd ) );
517 }
518 else
519 {
520 if( code->m_Polygon.OutlineCount() == 0 )
521 code->ConvertShapeToPolygon();
522
523 drawPolygon( aItem, code->m_Polygon, aFilled, true );
524 }
525 break;
526 }
527
528 case GBR_SPOT_OVAL:
529 {
530 int radius = 0;
531
532 wxPoint codeStart = aItem->m_Start;
533 wxPoint codeEnd = aItem->m_Start;
534
535 if( code->m_Size.x > code->m_Size.y ) // horizontal oval
536 {
537 int delta = (code->m_Size.x - code->m_Size.y) / 2;
538 codeStart.x -= delta;
539 codeEnd.x += delta;
540 radius = code->m_Size.y;
541 }
542 else // horizontal oval
543 {
544 int delta = (code->m_Size.y - code->m_Size.x) / 2;
545 codeStart.y -= delta;
546 codeEnd.y += delta;
547 radius = code->m_Size.x;
548 }
549
550 codeStart = aItem->GetABPosition( codeStart );
551 codeEnd = aItem->GetABPosition( codeEnd );
552
553 if( !aFilled || code->m_DrillShape == APT_DEF_NO_HOLE )
554 {
555 m_gal->DrawSegment( codeStart, codeEnd, radius );
556 }
557 else
558 {
559 if( code->m_Polygon.OutlineCount() == 0 )
560 code->ConvertShapeToPolygon();
561
562 drawPolygon( aItem, code->m_Polygon, aFilled, true );
563 }
564 break;
565 }
566
567 case GBR_SPOT_POLY:
568 {
569 if( code->m_Polygon.OutlineCount() == 0 )
570 code->ConvertShapeToPolygon();
571
572 drawPolygon( aItem, code->m_Polygon, aFilled, true );
573 break;
574 }
575
576 case GBR_SPOT_MACRO:
577 drawApertureMacro( aItem, aFilled );
578 break;
579
580 default:
581 wxASSERT_MSG( false, wxT( "Unknown Gerber flashed shape!" ) );
582 break;
583 }
584 }
585
586
drawApertureMacro(GERBER_DRAW_ITEM * aParent,bool aFilled)587 void GERBVIEW_PAINTER::drawApertureMacro( GERBER_DRAW_ITEM* aParent, bool aFilled )
588 {
589 D_CODE* code = aParent->GetDcodeDescr();
590 APERTURE_MACRO* macro = code->GetMacro();
591
592 SHAPE_POLY_SET* macroShape = macro->GetApertureMacroShape( aParent, aParent->m_Start );
593
594 if( !m_gerbviewSettings.m_polygonFill )
595 m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
596
597 if( !aFilled )
598 {
599 for( int i = 0; i < macroShape->OutlineCount(); i++ )
600 m_gal->DrawPolyline( macroShape->COutline( i ) );
601 }
602 else
603 m_gal->DrawPolygon( *macroShape );
604 }
605
606
607 const double GERBVIEW_RENDER_SETTINGS::MAX_FONT_SIZE = Millimeter2iu( 10.0 );
608