KiCad PCB EDA Suite
HPGL_plotter.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2020-2022 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
33/* Some HPGL commands:
34 * Note: the HPGL unit is 25 micrometers
35 * All commands MUST be terminated by a semi-colon or a linefeed.
36 * Spaces can NOT be substituted for required commas in the syntax of a command.
37 *
38 *
39 * AA (Arc Absolute): Angle is a floating point # (requires non integer value)
40 * Draws an arc with the center at (X,Y).
41 * A positive angle creates a counter-clockwise arc.
42 * If the chord angle is specified,
43 * this will be the number of degrees used for stepping around the arc.
44 * If no value is given then a default value of five degrees is used.
45 * AA x, y, a {,b};
46 *
47 * AR (Arc Relative):
48 * AR Dx, Dy, a {, b};
49 *
50 * CA (Alternate Character Set):
51 * CA {n};
52 *
53 * CI (Circle):
54 * CI r {,b};
55 *
56 * CP (Character Plot):
57 * CP {h, v};
58 * h [-127.9999 .. 127.9999] Number of characters horizontally
59 * v [-127.9999 .. 127.9999] Number of characters vertically
60 *
61 * CS (Standard Character Set):
62 * CS {n};
63 *
64 * DR (Relative Direction for Label Text):
65 * DR s, a;
66 *
67 * DI (Absolute Direction for Label Text):
68 * DI {s, a};
69 *
70 * DT (Define Terminator - this character becomes unavailable except to terminate a label string.
71 * Default is ^C control-C):
72 * DT t;
73 *
74 * EA (rEctangle Absolute - Unfilled, from current position to diagonal x,y):
75 * EA x, y;
76 *
77 * ER (rEctangle Relative - Unfilled, from current position to diagonal x,y):
78 * ER x,y;
79 *
80 * FT (Fill Type):
81 * FT {s {,l {a}}};
82 *
83 * IM (Input Mask):
84 * IM {f};
85 *
86 * IN (Initialize): This command instructs the controller to begin processing the HPGL plot file.
87 * Without this, the commands in the file are received but never executed.
88 * If multiple IN s are found during execution of the file,
89 * the controller performs a Pause/Cancel operation.
90 * All motion from the previous job, yet to be executed, is lost,
91 * and the new information is executed.
92 * IN;
93 *
94 * IP Input P1 and P2:
95 * IP {P1x, P1y {, P2x, P2y}};
96 *
97 * IW (Input Window):
98 * IW {XUL, YUL, XOR, YOR};
99 *
100 * LB (Label):
101 * LB c1 .. cn t;
102 *
103 * PA (Plot Absolute): Moves to an absolute HPGL position and sets absolute mode for
104 * future PU and PD commands. If no arguments follow the command,
105 * only absolute mode is set.
106 * PA {x1, y1 {{PU|PD|,} ..., ..., xn, yn}};
107 * P1x, P1y, P2x, P2y [Integer in ASCII]
108 *
109 * PD (Pen Down): Executes <current pen> pen then moves to the requested position
110 * if one is specified. This position is dependent on whether absolute
111 * or relative mode is set. This command performs no motion in 3-D mode,
112 * but the outputs and feedrates are affected.
113 * PD {x, y};
114 *
115 * PM Polygon mode
116 * associated commands:
117 * PM2 End polygon mode
118 * FP Fill polygon
119 * EP Draw polygon outline
120 *
121 * PR (Plot Relative): Moves to the relative position specified and sets relative mode
122 * for future PU and PD commands.
123 * If no arguments follow the command, only relative mode is set.
124 * PR {Dx1, Dy1 {{PU|PD|,} ..., ..., Dxn, Dyn}};
125 *
126 * PS (Paper Size):
127 * PS {n};
128 *
129 * PT (Pen Thickness): in mm
130 * PT {l};
131 *
132 * PU (Pen Up): Executes <current pen> pen then moves to the requested position
133 * if one is specified. This position is dependent on whether absolute
134 * or relative mode is set.
135 * This command performs no motion in 3-D mode, but the outputs
136 * and feedrates are affected.
137 * PU {x, y};
138 *
139 * RA (Rectangle Absolute - Filled, from current position to diagonal x,y):
140 * RA x, y;
141 *
142 * RO (Rotate Coordinate System):
143 * RO;
144 *
145 * RR (Rectangle Relative - Filled, from current position to diagonal x,y):
146 * RR x, y;
147 *
148 * SA (Select Alternate Set):
149 * SA;
150 *
151 * SC (Scale):
152 * SC {Xmin, Xmax, Ymin, Ymax};
153 *
154 * SI (Absolute Character Size):
155 * SI b, h;
156 * b [-127.9999 .. 127.9999, keine 0]
157 * h [-127.9999 .. 127.9999, keine 0]
158 *
159 * SL (Character Slant):
160 * SL {a};
161 * a [-3.5 .. -0.5, 0.5 .. 3.5]
162*
163 * SP (Select Pen): Selects a new pen or tool for use.
164 * If no pen number or a value of zero is given,
165 * the controller performs an EOF (end of file command).
166 * Once an EOF is performed, no motion is executed,
167 * until a new IN command is received.
168 * SP n;
169 *
170 * SR (Relative Character Size):
171 * SR {b, h};
172 * b [-127.9999 .. 127.9999, keine 0]
173 * h [-127.9999 .. 127.9999, keine 0]
174 *
175 * SS (Select Standard Set):
176 * SS;
177 *
178 * TL (Tick Length):
179 * TL {tp {, tm}};
180 *
181 * UC (User Defined Character):
182 * UC {i,} x1, y1, {i,} x2, y2, ... {i,} xn, yn;
183 *
184 * VS (Velocity Select):
185 * VS {v {, n}};
186 * v [1 .. 40] in cm/s
187 * n [1 .. 8]
188 *
189 * XT (X Tick):
190 * XT;
191 *
192 * YT (Y Tick):
193 * YT;
194 */
195
196#include <cstdio>
197
198#include <string_utils.h>
200#include <math/util.h> // for KiROUND
201#include <trigo.h>
202
204
205
207static double dpoint_dist( const VECTOR2D& a, const VECTOR2D& b );
208
209
210// The hpgl command to close a polygon def, fill it and plot outline:
211// PM 2; ends the polygon definition and closes it if not closed
212// FP; fills the polygon
213// EP; draws the polygon outline. It usually gives a better look to the filled polygon
214static const char hpgl_end_polygon_cmd[] = "PM 2; FP; EP;\n";
215
216
217// HPGL scale factor (1 Plotter Logical Unit = 1/40mm = 25 micrometers)
218// PLUsPERDECIMIL = (25.4 / 10000) / 0.025
219static const double PLUsPERDECIMIL = 0.1016;
220
221
223 m_arcTargetChordLength( 0 ),
224 m_arcMinChordDegrees( 5.0, DEGREES_T ),
225 m_lineStyle( PLOT_DASH_TYPE::SOLID ),
226 m_useUserCoords( false ),
227 m_fitUserCoords( false ),
228 m_current_item( nullptr )
229{
230 SetPenSpeed( 40 ); // Default pen speed = 40 cm/s; Pen speed is *always* in cm
231 SetPenNumber( 1 ); // Default pen num = 1
232 SetPenDiameter( 0.0 );
233}
234
235
236void HPGL_PLOTTER::SetViewport( const VECTOR2I& aOffset, double aIusPerDecimil,
237 double aScale, bool aMirror )
238{
239 m_plotOffset = aOffset;
240 m_plotScale = aScale;
241 m_IUsPerDecimil = aIusPerDecimil;
242 m_iuPerDeviceUnit = PLUsPERDECIMIL / aIusPerDecimil;
243
244 // Compute the paper size in IUs.
246 m_paperSize.x *= 10.0 * aIusPerDecimil;
247 m_paperSize.y *= 10.0 * aIusPerDecimil;
248 m_plotMirror = aMirror;
249}
250
251
253{
255}
256
257
258bool HPGL_PLOTTER::StartPlot( const wxString& aPageNumber )
259{
260 wxASSERT( m_outputFile );
261 fprintf( m_outputFile, "IN;VS%d;PU;PA;SP%d;\n", m_penSpeed, m_penNumber );
262
263 // Set HPGL Pen Thickness (in mm) (useful in polygon fill command)
264 double penThicknessMM = userToDeviceSize( m_penDiameter ) / 40;
265 fprintf( m_outputFile, "PT %.1f;\n", penThicknessMM );
266
267 return true;
268}
269
270
272{
273 wxASSERT( m_outputFile );
274
275 fputs( "PU;\n", m_outputFile );
276
277 flushItem();
279
280 if( m_items.size() > 0 )
281 {
282 if( m_useUserCoords )
283 {
284 if( m_fitUserCoords )
285 {
286 BOX2D bbox = m_items.front().bbox;
287
288 for( HPGL_ITEM const& item : m_items )
289 bbox.Merge( item.bbox );
290
291 fprintf( m_outputFile, "SC%.0f,%.0f,%.0f,%.0f;\n",
292 bbox.GetX(),
293 bbox.GetX() + bbox.GetWidth(),
294 bbox.GetY(),
295 bbox.GetY() + bbox.GetHeight() );
296 }
297 else
298 {
299 VECTOR2D pagesize_device( m_paperSize * m_iuPerDeviceUnit );
300 fprintf( m_outputFile, "SC%.0f,%.0f,%.0f,%.0f;\n",
301 0.0,
302 pagesize_device.x,
303 0.0,
304 pagesize_device.y );
305 }
306 }
307
308 VECTOR2I loc = m_items.begin()->loc_start;
309 bool pen_up = true;
311 int current_pen = m_penNumber;
312
313 for( HPGL_ITEM const& item : m_items )
314 {
315 if( item.loc_start != loc || pen_up )
316 {
317 if( !pen_up )
318 {
319 fputs( "PU;", m_outputFile );
320 pen_up = true;
321 }
322
323 fprintf( m_outputFile, "PA %.0f,%.0f;", item.loc_start.x, item.loc_start.y );
324 }
325
326 if( item.dashType != current_dash )
327 {
328 current_dash = item.dashType;
329 fputs( lineStyleCommand( item.dashType ), m_outputFile );
330 }
331
332 if( item.pen != current_pen )
333 {
334 if( !pen_up )
335 {
336 fputs( "PU;", m_outputFile );
337 pen_up = true;
338 }
339
340 fprintf( m_outputFile, "SP%d;", item.pen );
341 current_pen = item.pen;
342 }
343
344 if( pen_up && !item.lift_before )
345 {
346 fputs( "PD;", m_outputFile );
347 pen_up = false;
348 }
349 else if( !pen_up && item.lift_before )
350 {
351 fputs( "PU;", m_outputFile );
352 pen_up = true;
353 }
354
355 fputs( static_cast<const char*>( item.content.utf8_str() ), m_outputFile );
356
357 if( !item.pen_returns )
358 {
359 // Assume commands drop the pen
360 pen_up = false;
361 }
362
363 if( item.lift_after )
364 {
365 fputs( "PU;", m_outputFile );
366 pen_up = true;
367 }
368 else
369 {
370 loc = item.loc_end;
371 }
372
373 fputs( "\n", m_outputFile );
374 }
375 }
376
377 fputs( "PU;PA;SP0;\n", m_outputFile );
378 fclose( m_outputFile );
379 m_outputFile = nullptr;
380 return true;
381}
382
383
384void HPGL_PLOTTER::SetPenDiameter( double diameter )
385{
386 m_penDiameter = diameter;
387}
388
389
390void HPGL_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T aFill, int aWidth )
391{
392 wxASSERT( m_outputFile );
393
394 VECTOR2D p1_device = userToDeviceCoordinates( p1 );
395 VECTOR2D p2_device = userToDeviceCoordinates( p2 );
396
397 MoveTo( p1 );
398
399 if( aFill == FILL_T::FILLED_SHAPE )
400 {
401 startOrAppendItem( p1_device, wxString::Format( "RA %.0f,%.0f;",
402 p2_device.x,
403 p2_device.y ) );
404 }
405
406 startOrAppendItem( p1_device, wxString::Format( "EA %.0f,%.0f;",
407 p2_device.x,
408 p2_device.y ) );
409
411 m_current_item->bbox.Merge( p2_device );
412 PenFinish();
413}
414
415
416void HPGL_PLOTTER::Circle( const VECTOR2I& aCenter, int aDiameter, FILL_T aFill, int aWidth )
417{
418 wxASSERT( m_outputFile );
419 double radius = userToDeviceSize( aDiameter / 2 );
420 VECTOR2D center_dev = userToDeviceCoordinates( aCenter );
421 SetCurrentLineWidth( aWidth );
422
423 double const circumf = 2.0 * M_PI * radius;
424 double const target_chord_length = m_arcTargetChordLength;
425 EDA_ANGLE chord_angle = ANGLE_360 * target_chord_length / circumf;
426
427 chord_angle = std::max( m_arcMinChordDegrees, std::min( chord_angle, ANGLE_45 ) );
428
429 if( aFill == FILL_T::FILLED_SHAPE )
430 {
431 // Draw the filled area
432 MoveTo( aCenter );
433 startOrAppendItem( center_dev, wxString::Format( "PM 0;CI %g,%g;%s",
434 radius,
435 chord_angle.AsDegrees(),
439 m_current_item->bbox.Merge( BOX2D( center_dev - radius,
440 VECTOR2D( 2 * radius, 2 * radius ) ) );
441 PenFinish();
442 }
443
444 if( radius > 0 )
445 {
446 MoveTo( aCenter );
447 startOrAppendItem( center_dev, wxString::Format( "CI %g,%g;",
448 radius,
449 chord_angle.AsDegrees() ) );
452 m_current_item->bbox.Merge( BOX2D( center_dev - radius,
453 VECTOR2D( 2 * radius, 2 * radius ) ) );
454 PenFinish();
455 }
456}
457
458
459void HPGL_PLOTTER::PlotPoly( const std::vector<VECTOR2I>& aCornerList, FILL_T aFill, int aWidth,
460 void* aData )
461{
462 if( aCornerList.size() <= 1 )
463 return;
464
465 // Width less than zero is occasionally used to create background-only
466 // polygons. Don't set that as the plotter line width, that'll cause
467 // trouble. Also, later, skip plotting the outline if this is the case.
468 if( aWidth > 0 )
469 {
470 SetCurrentLineWidth( aWidth );
471 }
472
473 MoveTo( aCornerList[0] );
474 startItem( userToDeviceCoordinates( aCornerList[0] ) );
475
476 if( aFill == FILL_T::FILLED_SHAPE )
477 {
478 // Draw the filled area
480
481 m_current_item->content << wxString( "PM 0;\n" ); // Start polygon
482
483 for( unsigned ii = 1; ii < aCornerList.size(); ++ii )
484 LineTo( aCornerList[ii] );
485
486 int ii = aCornerList.size() - 1;
487
488 if( aCornerList[ii] != aCornerList[0] )
489 LineTo( aCornerList[0] );
490
491 m_current_item->content << hpgl_end_polygon_cmd; // Close, fill polygon and draw outlines
493 }
494 else if( aWidth != 0 )
495 {
496 // Plot only the polygon outline.
497 for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
498 LineTo( aCornerList[ii] );
499
500 // Always close polygon if filled.
501 if( aFill != FILL_T::NO_FILL )
502 {
503 int ii = aCornerList.size() - 1;
504
505 if( aCornerList[ii] != aCornerList[0] )
506 LineTo( aCornerList[0] );
507 }
508 }
509
510 PenFinish();
511}
512
513
514void HPGL_PLOTTER::PenTo( const VECTOR2I& pos, char plume )
515{
516 wxASSERT( m_outputFile );
517
518 if( plume == 'Z' )
519 {
520 m_penState = 'Z';
521 flushItem();
522 return;
523 }
524
525 VECTOR2D pos_dev = userToDeviceCoordinates( pos );
527
528 if( plume == 'U' )
529 {
530 m_penState = 'U';
531 flushItem();
532 }
533 else if( plume == 'D' )
534 {
535 m_penState = 'D';
536 startOrAppendItem( lastpos_dev, wxString::Format( "PA %.0f,%.0f;", pos_dev.x, pos_dev.y ) );
537 m_current_item->loc_end = pos_dev;
538 m_current_item->bbox.Merge( pos_dev );
539 }
540
541 m_penLastpos = pos;
542}
543
544
545void HPGL_PLOTTER::SetDash( int aLineWidth, PLOT_DASH_TYPE aLineStyle )
546{
547 m_lineStyle = aLineStyle;
548 flushItem();
549}
550
551
552void HPGL_PLOTTER::ThickSegment( const VECTOR2I& start, const VECTOR2I& end,
553 int width, OUTLINE_MODE tracemode, void* aData )
554{
555 wxASSERT( m_outputFile );
556
557 // Suppress overlap if pen is too big
558 if( m_penDiameter >= width )
559 {
560 MoveTo( start );
561 FinishTo( end );
562 }
563 else
564 {
565 segmentAsOval( start, end, width, tracemode );
566 }
567}
568
569
570void HPGL_PLOTTER::Arc( const VECTOR2I& aCenter, const EDA_ANGLE& aStartAngle,
571 const EDA_ANGLE& aEndAngle, int aRadius, FILL_T aFill, int aWidth )
572{
573 if( aRadius <= 0 )
574 return;
575
576 double const radius_device = userToDeviceSize( aRadius );
577 double const circumf_device = 2.0 * M_PI * radius_device;
578 double const target_chord_length = m_arcTargetChordLength;
579 EDA_ANGLE chord_angle = ANGLE_360 * target_chord_length / circumf_device;
580
581 chord_angle = std::max( m_arcMinChordDegrees, std::min( chord_angle, ANGLE_45 ) );
582
583 VECTOR2D centre_device = userToDeviceCoordinates( aCenter );
585
586 if( m_plotMirror )
587 angle = aStartAngle - aEndAngle;
588 else
589 angle = aEndAngle - aStartAngle;
590
591 angle.Normalize180();
592
593 // Calculate arc start point:
594 VECTOR2I cmap( aCenter.x + KiROUND( aRadius * aStartAngle.Cos() ),
595 aCenter.y - KiROUND( aRadius * aStartAngle.Sin() ) );
596 VECTOR2D cmap_dev = userToDeviceCoordinates( cmap );
597
598 startOrAppendItem( cmap_dev, wxString::Format( "AA %.0f,%.0f,%.0f,%g",
599 centre_device.x,
600 centre_device.y,
601 angle.AsDegrees(),
602 chord_angle.AsDegrees() ) );
603
604 // TODO We could compute the final position and full bounding box instead...
605 m_current_item->bbox.Merge( BOX2D( centre_device - radius_device,
606 VECTOR2D( radius_device * 2, radius_device * 2 ) ) );
608 flushItem();
609}
610
611
612void HPGL_PLOTTER::Arc( const VECTOR2I& aCenter, const VECTOR2I& aStart,
613 const VECTOR2I& aEnd,
614 FILL_T aFill, int aWidth, int aMaxError )
615{
616 EDA_ANGLE startAngle( aStart - aCenter );
617 EDA_ANGLE endAngle( aEnd - aCenter );
618 int radius = ( aStart - aCenter ).EuclideanNorm();
619
620 Arc( aCenter, -endAngle, -startAngle, radius, aFill, aWidth );
621}
622
623
624void HPGL_PLOTTER::FlashPadOval( const VECTOR2I& aPos, const VECTOR2I& aSize,
625 const EDA_ANGLE& aOrient, OUTLINE_MODE aTraceMode, void* aData )
626{
627 wxASSERT( m_outputFile );
628
629 VECTOR2I size( aSize );
630 EDA_ANGLE orient( aOrient );
631
632 // The pad will be drawn as an oblong shape with size.y > size.x (Oval vertical orientation 0).
633 if( size.x > size.y )
634 {
635 std::swap( size.x, size.y );
636 orient += ANGLE_90;
637 }
638
639 if( aTraceMode == FILLED )
640 {
641 int deltaxy = size.y - size.x; // distance between centers of the oval
642
643 FlashPadRect( aPos, VECTOR2I( size.x, deltaxy + KiROUND( m_penDiameter ) ), orient,
644 aTraceMode, aData );
645
646 VECTOR2I pt( 0, deltaxy / 2 );
647 RotatePoint( pt, orient );
648 FlashPadCircle( pt + aPos, size.x, aTraceMode, aData );
649
650 pt = VECTOR2I( 0, -deltaxy / 2 );
651 RotatePoint( pt, orient );
652 FlashPadCircle( pt + aPos, size.x, aTraceMode, aData );
653 }
654 else // Plot in outline mode.
655 {
656 sketchOval( aPos, size, orient, KiROUND( m_penDiameter ) );
657 }
658}
659
660
661void HPGL_PLOTTER::FlashPadCircle( const VECTOR2I& pos, int diametre,
662 OUTLINE_MODE trace_mode, void* aData )
663{
664 wxASSERT( m_outputFile );
665 VECTOR2D pos_dev = userToDeviceCoordinates( pos );
666 int radius = diametre / 2;
667
668 if( trace_mode == FILLED )
669 {
670 // if filled mode, the pen diameter is removed from diameter
671 // to keep the pad size
672 radius -= KiROUND( m_penDiameter ) / 2;
673
674 if( radius < 0 )
675 radius = 0;
676 }
677
678 double rsize = userToDeviceSize( radius );
679
680 if( trace_mode == FILLED ) // Plot in filled mode.
681 {
682 // A filled polygon uses always the current point to start the polygon.
683 // Gives a correct current starting point for the circle
684 MoveTo( VECTOR2I( pos.x + radius, pos.y ) );
685
686 // Plot filled area and its outline
687 startOrAppendItem( userToDeviceCoordinates( VECTOR2I( pos.x + radius, pos.y ) ),
688 wxString::Format( "PM 0; PA %.0f,%.0f;CI %.0f;%s",
689 pos_dev.x, pos_dev.y, rsize, hpgl_end_polygon_cmd ) );
692 }
693 else
694 {
695 // Draw outline only:
696 startOrAppendItem( pos_dev, wxString::Format( "CI %.0f;", rsize ) );
699 }
700
701 PenFinish();
702}
703
704
705void HPGL_PLOTTER::FlashPadRect( const VECTOR2I& aPos, const VECTOR2I& aPadSize,
706 const EDA_ANGLE& aOrient, OUTLINE_MODE aTraceMode, void* aData )
707{
708 // Build rect polygon:
709 std::vector<VECTOR2I> corners;
710
711 int dx = aPadSize.x / 2;
712 int dy = aPadSize.y / 2;
713
714 if( aTraceMode == FILLED )
715 {
716 // in filled mode, the pen diameter is removed from size
717 // to compensate the extra size due to this pen size
718 dx -= KiROUND( m_penDiameter ) / 2;
719 dx = std::max( dx, 0);
720 dy -= KiROUND( m_penDiameter ) / 2;
721 dy = std::max( dy, 0);
722 }
723
724
725 corners.emplace_back( - dx, - dy );
726 corners.emplace_back( - dx, + dy );
727 corners.emplace_back( + dx, + dy );
728 corners.emplace_back( + dx, - dy );
729
730 // Close polygon
731 corners.emplace_back( - dx, - dy );
732
733 for( unsigned ii = 0; ii < corners.size(); ii++ )
734 {
735 RotatePoint( corners[ii], aOrient );
736 corners[ii] += aPos;
737 }
738
739 PlotPoly( corners, aTraceMode == FILLED ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL );
740}
741
742
743void HPGL_PLOTTER::FlashPadRoundRect( const VECTOR2I& aPadPos, const VECTOR2I& aSize,
744 int aCornerRadius, const EDA_ANGLE& aOrient,
745 OUTLINE_MODE aTraceMode, void* aData )
746{
747 SHAPE_POLY_SET outline;
748
749 VECTOR2I size = aSize;
750
751 if( aTraceMode == FILLED )
752 {
753 // In filled mode, the pen diameter is removed from size to keep the pad size.
754 size.x -= KiROUND( m_penDiameter ) / 2;
755 size.x = std::max( size.x, 0);
756 size.y -= KiROUND( m_penDiameter ) / 2;
757 size.y = std::max( size.y, 0);
758
759 // keep aCornerRadius to a value < min size x,y < 2:
760 aCornerRadius = std::min( aCornerRadius, std::min( size.x, size.y ) /2 );
761 }
762
763 TransformRoundChamferedRectToPolygon( outline, aPadPos, size, aOrient, aCornerRadius, 0.0, 0,
765
766 // TransformRoundRectToPolygon creates only one convex polygon
767 std::vector<VECTOR2I> cornerList;
768 SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
769 cornerList.reserve( poly.PointCount() );
770
771 for( int ii = 0; ii < poly.PointCount(); ++ii )
772 cornerList.emplace_back( poly.CPoint( ii ).x, poly.CPoint( ii ).y );
773
774 if( cornerList.back() != cornerList.front() )
775 cornerList.push_back( cornerList.front() );
776
777 PlotPoly( cornerList, aTraceMode == FILLED ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL );
778}
779
780
781void HPGL_PLOTTER::FlashPadCustom( const VECTOR2I& aPadPos, const VECTOR2I& aSize,
782 const EDA_ANGLE& aOrient, SHAPE_POLY_SET* aPolygons,
783 OUTLINE_MODE aTraceMode, void* aData )
784{
785 std::vector<VECTOR2I> cornerList;
786
787 for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt )
788 {
789 SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt );
790
791 cornerList.clear();
792 cornerList.reserve( poly.PointCount() );
793
794 for( int ii = 0; ii < poly.PointCount(); ++ii )
795 cornerList.emplace_back( poly.CPoint( ii ).x, poly.CPoint( ii ).y );
796
797 if( cornerList.back() != cornerList.front() )
798 cornerList.push_back( cornerList.front() );
799
800 PlotPoly( cornerList, aTraceMode == FILLED ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL );
801 }
802}
803
804
805void HPGL_PLOTTER::FlashPadTrapez( const VECTOR2I& aPadPos, const VECTOR2I* aCorners,
806 const EDA_ANGLE& aPadOrient, OUTLINE_MODE aTraceMode,
807 void* aData )
808{
809 std::vector<VECTOR2I> cornerList;
810 cornerList.reserve( 5 );
811
812 for( int ii = 0; ii < 4; ii++ )
813 {
814 VECTOR2I coord( aCorners[ii] );
815 RotatePoint( coord, aPadOrient );
816 coord += aPadPos;
817 cornerList.push_back( coord );
818 }
819
820 // Close polygon
821 cornerList.push_back( cornerList.front() );
822
823 PlotPoly( cornerList, aTraceMode == FILLED ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL );
824}
825
826
827void HPGL_PLOTTER::FlashRegularPolygon( const VECTOR2I& aShapePos, int aRadius, int aCornerCount,
828 const EDA_ANGLE& aOrient, OUTLINE_MODE aTraceMode,
829 void* aData )
830{
831 // Do nothing
832 wxASSERT( 0 );
833}
834
835
836bool HPGL_PLOTTER::startItem( const VECTOR2D& location )
837{
838 return startOrAppendItem( location, wxEmptyString );
839}
840
841
843{
844 m_current_item = nullptr;
845}
846
847
848bool HPGL_PLOTTER::startOrAppendItem( const VECTOR2D& location, wxString const& content )
849{
850 if( m_current_item == nullptr )
851 {
852 HPGL_ITEM item;
853 item.loc_start = location;
854 item.loc_end = location;
855 item.bbox = BOX2D( location );
856 item.pen = m_penNumber;
857 item.dashType = m_lineStyle;
858 item.content = content;
859 m_items.push_back( item );
860 m_current_item = &m_items.back();
861 return true;
862 }
863 else
864 {
865 m_current_item->content << content;
866 return false;
867 }
868}
869
870
871void HPGL_PLOTTER::sortItems( std::list<HPGL_ITEM>& items )
872{
873 if( items.size() < 2 )
874 return;
875
876 std::list<HPGL_ITEM> target;
877
878 // Plot items are sorted to improve print time on mechanical plotters. This
879 // means
880 // 1) Avoid excess pen-switching - once a pen is selected, keep printing
881 // with it until no more items using that pen remain.
882 // 2) Within the items for one pen, avoid bouncing back and forth around
883 // the page; items should be sequenced with nearby items.
884 //
885 // This is essentially a variant of the Traveling Salesman Problem where
886 // the cities are themselves edges that must be traversed. This is of course
887 // a famously NP-Hard problem and this particular variant has a monstrous
888 // number of "cities". For now, we're using a naive nearest-neighbor search,
889 // which is less than optimal but (usually!) better than nothing, very
890 // simple to implement, and fast enough.
891 //
892 // Items are moved one at a time from `items` into `target`, searching
893 // each time for the first one matching the above criteria. Then, all of
894 // `target` is moved back into `items`.
895
896 // Get the first one started
897 HPGL_ITEM last_item = items.front();
898 items.pop_front();
899 target.emplace_back( last_item );
900
901 while( !items.empty() )
902 {
903 auto best_it = items.begin();
904 double best_dist = dpoint_dist( last_item.loc_end, best_it->loc_start );
905
906 for( auto search_it = best_it; search_it != items.end(); search_it++ )
907 {
908 // Immediately forget an item as "best" if another one is a better pen match
909 if( best_it->pen != last_item.pen && search_it->pen == last_item.pen )
910 {
911 best_it = search_it;
912 continue;
913 }
914
915 double const dist = dpoint_dist( last_item.loc_end, search_it->loc_start );
916
917 if( dist < best_dist )
918 {
919 best_it = search_it;
920 best_dist = dist;
921 continue;
922 }
923 }
924
925 target.emplace_back( *best_it );
926 last_item = *best_it;
927 items.erase( best_it );
928 }
929
930 items.splice( items.begin(), target );
931}
932
933
935{
936 switch( aLineStyle )
937 {
938 case PLOT_DASH_TYPE::DASH: return "LT 2 4 1;";
939 case PLOT_DASH_TYPE::DOT: return "LT 1 1 1;";
940 case PLOT_DASH_TYPE::DASHDOT: return "LT 4 6 1;";
941 case PLOT_DASH_TYPE::DASHDOTDOT: return "LT 7 8 1;";
942 default: return "LT;";
943 }
944}
945
946
947static double dpoint_dist( const VECTOR2D& a, const VECTOR2D& b )
948{
949 VECTOR2D diff = a - b;
950 return sqrt( diff.x * diff.x + diff.y * diff.y );
951}
static const double PLUsPERDECIMIL
static double dpoint_dist(const VECTOR2D &a, const VECTOR2D &b)
Compute the distance between two VECTOR2D points.
static const char hpgl_end_polygon_cmd[]
BOX2< VECTOR2D > BOX2D
Definition: box2.h:848
coord_type GetHeight() const
Definition: box2.h:188
coord_type GetY() const
Definition: box2.h:181
coord_type GetWidth() const
Definition: box2.h:187
coord_type GetX() const
Definition: box2.h:180
BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition: box2.h:588
double Sin() const
Definition: eda_angle.h:206
double AsDegrees() const
Definition: eda_angle.h:149
double Cos() const
Definition: eda_angle.h:221
virtual void FlashPadRoundRect(const VECTOR2I &aPadPos, const VECTOR2I &aSize, int aCornerRadius, const EDA_ANGLE &aOrient, OUTLINE_MODE aTraceMode, void *aData) override
static const char * lineStyleCommand(PLOT_DASH_TYPE aLineStyle)
Return the plot command corresponding to a line type.
HPGL_ITEM * m_current_item
Definition: plotter_hpgl.h:228
virtual void SetPenSpeed(int speed)
Definition: plotter_hpgl.h:85
bool startItem(const VECTOR2D &location)
Start a new HPGL_ITEM if necessary, keeping the current one if it exists.
double m_arcTargetChordLength
Definition: plotter_hpgl.h:221
void flushItem()
Flush the current HPGL_ITEM and clear out the current item pointer.
virtual void FlashPadTrapez(const VECTOR2I &aPadPos, const VECTOR2I *aCorners, const EDA_ANGLE &aPadOrient, OUTLINE_MODE aTraceMode, void *aData) override
Flash a trapezoidal pad.
virtual void Arc(const VECTOR2I &aCenter, const VECTOR2I &aStart, const VECTOR2I &aEnd, FILL_T aFill, int aWidth, int aMaxError) override
Generic fallback: arc rendered as a polyline.
void SetTargetChordLength(double chord_len)
Set the target length of chords used to draw approximated circles and arcs.
virtual void SetPenNumber(int number)
Definition: plotter_hpgl.h:90
static void sortItems(std::list< HPGL_ITEM > &items)
Sort a list of HPGL items to improve plotting speed on mechanical plotters.
virtual void FlashPadCircle(const VECTOR2I &aPadPos, int aDiameter, OUTLINE_MODE aTraceMode, void *aData) override
virtual bool StartPlot(const wxString &aPageNumber) override
At the start of the HPGL plot pen speed and number are requested.
EDA_ANGLE m_arcMinChordDegrees
Definition: plotter_hpgl.h:222
virtual void SetCurrentLineWidth(int width, void *aData=nullptr) override
HPGL doesn't handle line thickness or color.
Definition: plotter_hpgl.h:72
virtual void FlashRegularPolygon(const VECTOR2I &aShapePos, int aDiameter, int aCornerCount, const EDA_ANGLE &aOrient, OUTLINE_MODE aTraceMode, void *aData) override
Flash a regular polygon.
virtual void PlotPoly(const std::vector< VECTOR2I > &aCornerList, FILL_T aFill, int aWidth=USE_DEFAULT_LINE_WIDTH, void *aData=nullptr) override
Draw a polygon ( filled or not ).
virtual void Rect(const VECTOR2I &p1, const VECTOR2I &p2, FILL_T aFill, int aWidth=USE_DEFAULT_LINE_WIDTH) override
virtual void SetViewport(const VECTOR2I &aOffset, double aIusPerDecimil, double aScale, bool aMirror) override
Set the plot offset and scaling for the current plot.
bool m_useUserCoords
Definition: plotter_hpgl.h:224
std::list< HPGL_ITEM > m_items
Definition: plotter_hpgl.h:227
virtual void SetDash(int aLineWidth, PLOT_DASH_TYPE aLineStyle) override
HPGL supports dashed lines.
virtual void FlashPadCustom(const VECTOR2I &aPadPos, const VECTOR2I &aSize, const EDA_ANGLE &aOrient, SHAPE_POLY_SET *aPolygons, OUTLINE_MODE aTraceMode, void *aData) override
virtual void SetPenDiameter(double diameter)
virtual void FlashPadOval(const VECTOR2I &aPadPos, const VECTOR2I &aSize, const EDA_ANGLE &aPadOrient, OUTLINE_MODE aTraceMode, void *aData) override
double m_penDiameter
Definition: plotter_hpgl.h:220
virtual void PenTo(const VECTOR2I &pos, char plume) override
Moveto/lineto primitive, moves the 'pen' to the specified direction.
bool m_fitUserCoords
Definition: plotter_hpgl.h:225
PLOT_DASH_TYPE m_lineStyle
Definition: plotter_hpgl.h:223
virtual void ThickSegment(const VECTOR2I &start, const VECTOR2I &end, int width, OUTLINE_MODE tracemode, void *aData) override
bool startOrAppendItem(const VECTOR2D &location, const wxString &content)
Start a new HPGL_ITEM with the given string if necessary, or append the string to the current item.
virtual void Circle(const VECTOR2I &aCenter, int aDiameter, FILL_T aFill, int aWidth=USE_DEFAULT_LINE_WIDTH) override
virtual bool EndPlot() override
HPGL end of plot: sort and emit graphics, pen return and release.
virtual void FlashPadRect(const VECTOR2I &aPadPos, const VECTOR2I &aSize, const EDA_ANGLE &aOrient, OUTLINE_MODE aTraceMode, void *aData) override
const VECTOR2I & GetSizeMils() const
Definition: page_info.h:135
PAGE_INFO m_pageInfo
Definition: plotter.h:645
bool m_plotMirror
Definition: plotter.h:627
static const int USE_DEFAULT_LINE_WIDTH
Definition: plotter.h:114
void MoveTo(const VECTOR2I &pos)
Definition: plotter.h:247
void FinishTo(const VECTOR2I &pos)
Definition: plotter.h:257
double m_iuPerDeviceUnit
Definition: plotter.h:624
VECTOR2I m_plotOffset
Definition: plotter.h:626
VECTOR2I m_penLastpos
Definition: plotter.h:640
virtual VECTOR2D userToDeviceCoordinates(const VECTOR2I &aCoordinate)
Modify coordinates according to the orientation, scale factor, and offsets trace.
Definition: plotter.cpp:90
VECTOR2I m_paperSize
Definition: plotter.h:646
virtual VECTOR2D userToDeviceSize(const VECTOR2I &size)
Modify size according to the plotter scale factors (VECTOR2I version, returns a VECTOR2D).
Definition: plotter.cpp:115
void sketchOval(const VECTOR2I &aPos, const VECTOR2I &aSize, const EDA_ANGLE &aOrient, int aWidth)
Definition: plotter.cpp:496
int GetPlotterArcHighDef() const
Definition: plotter.h:213
char m_penState
Definition: plotter.h:639
double m_plotScale
Plot scale - chosen by the user (even implicitly with 'fit in a4')
Definition: plotter.h:616
FILE * m_outputFile
Output file.
Definition: plotter.h:633
void LineTo(const VECTOR2I &pos)
Definition: plotter.h:252
void PenFinish()
Definition: plotter.h:263
void segmentAsOval(const VECTOR2I &start, const VECTOR2I &end, int width, OUTLINE_MODE tracemode)
Convert a thick segment and plot it as an oval.
Definition: plotter.cpp:481
double m_IUsPerDecimil
Definition: plotter.h:622
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
Represent a set of closed polygons.
SHAPE_LINE_CHAIN & Outline(int aIndex)
int OutlineCount() const
Return the number of vertices in a given outline/hole.
void TransformRoundChamferedRectToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aPosition, const VECTOR2I &aSize, const EDA_ANGLE &aRotation, int aCornerRadius, double aChamferRatio, int aChamferCorners, int aInflate, int aError, ERROR_LOC aErrorLoc)
Convert a rectangle with rounded corners and/or chamfered corners to a polygon.
@ DEGREES_T
Definition: eda_angle.h:31
static constexpr EDA_ANGLE & ANGLE_360
Definition: eda_angle.h:418
static constexpr EDA_ANGLE & ANGLE_45
Definition: eda_angle.h:413
static constexpr EDA_ANGLE & ANGLE_90
Definition: eda_angle.h:414
FILL_T
Definition: eda_shape.h:54
@ FILLED_SHAPE
@ ERROR_INSIDE
static DIRECTION_45::AngleType angle(const VECTOR2I &a, const VECTOR2I &b)
OUTLINE_MODE
Definition: outline_mode.h:25
@ FILLED
Definition: outline_mode.h:27
Plotting engine (HPGL)
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
PLOT_DASH_TYPE
Dashed line types.
Definition: stroke_params.h:48
wxString content
Line style for this command.
Definition: plotter_hpgl.h:206
bool pen_returns
Whether the pen returns to its original state after the command.
Definition: plotter_hpgl.h:202
BOX2D bbox
Bounding box of this item.
Definition: plotter_hpgl.h:190
bool lift_after
Whether the pen must be lifted after the command.
Definition: plotter_hpgl.h:198
PLOT_DASH_TYPE dashType
Pen number for this command.
Definition: plotter_hpgl.h:205
bool lift_before
Whether the command should be executed with the pen lifted.
Definition: plotter_hpgl.h:193
VECTOR2D loc_start
Location the pen should start at.
Definition: plotter_hpgl.h:183
VECTOR2D loc_end
Location the pen will be at when it finishes.
Definition: plotter_hpgl.h:187
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Definition: trigo.cpp:183
double EuclideanNorm(const VECTOR2I &vector)
Definition: trigo.h:129
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:85
VECTOR2< double > VECTOR2D
Definition: vector2d.h:617
VECTOR2< int > VECTOR2I
Definition: vector2d.h:618