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-2021 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] Anzahl der Zeichen horizontal
59  * v [-127.9999 .. 127.9999] Anzahl der Zeichen vertikal
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 
203 #include <plotters/plotter_hpgl.h>
204 
205 
207 static double dpoint_dist( const DPOINT& a, const DPOINT& 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
214 static 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
219 static const double PLUsPERDECIMIL = 0.1016;
220 
221 
223  : arcTargetChordLength( 0 ),
224  arcMinChordDegrees( 5.0 ),
225  dashType( PLOT_DASH_TYPE::SOLID ),
226  useUserCoords( false ),
227  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 
236 void HPGL_PLOTTER::SetViewport( const wxPoint& 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 
252 void HPGL_PLOTTER::SetTargetChordLength( double chord_len )
253 {
254  arcTargetChordLength = userToDeviceSize( chord_len );
255 }
256 
257 
259 {
260  wxASSERT( m_outputFile );
261  fprintf( m_outputFile, "IN;VS%d;PU;PA;SP%d;\n", penSpeed, penNumber );
262 
263  // Set HPGL Pen Thickness (in mm) (useful in polygon fill command)
264  double penThicknessMM = userToDeviceSize( 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();
278  sortItems( m_items );
279 
280  if( m_items.size() > 0 )
281  {
282  if( useUserCoords )
283  {
284  if( fitUserCoords )
285  {
286  BOX2D bbox = m_items.front().bbox;
287  for( HPGL_ITEM const& item : m_items )
288  {
289  bbox.Merge( item.bbox );
290  }
291 
292  fprintf( m_outputFile, "SC%.0f,%.0f,%.0f,%.0f;\n", bbox.GetX(),
293  bbox.GetX() + bbox.GetWidth(), bbox.GetY(),
294  bbox.GetY() + bbox.GetHeight() );
295  }
296  else
297  {
298  DPOINT pagesize_dev( m_paperSize * m_iuPerDeviceUnit );
299  fprintf( m_outputFile, "SC%.0f,%.0f,%.0f,%.0f;\n", 0., pagesize_dev.x, 0.,
300  pagesize_dev.y );
301  }
302  }
303 
304  DPOINT loc = m_items.begin()->loc_start;
305  bool pen_up = true;
306  PLOT_DASH_TYPE current_dash = PLOT_DASH_TYPE::SOLID;
307  int current_pen = penNumber;
308 
309  for( HPGL_ITEM const& item : m_items )
310  {
311  if( item.loc_start != loc || pen_up )
312  {
313  if( !pen_up )
314  {
315  fputs( "PU;", m_outputFile );
316  pen_up = true;
317  }
318 
319  fprintf( m_outputFile, "PA %.0f,%.0f;", item.loc_start.x, item.loc_start.y );
320  }
321 
322  if( item.dashType != current_dash )
323  {
324  current_dash = item.dashType;
325  fputs( lineTypeCommand( item.dashType ), m_outputFile );
326  }
327 
328  if( item.pen != current_pen )
329  {
330  if( !pen_up )
331  {
332  fputs( "PU;", m_outputFile );
333  pen_up = true;
334  }
335 
336  fprintf( m_outputFile, "SP%d;", item.pen );
337  current_pen = item.pen;
338  }
339 
340  if( pen_up && !item.lift_before )
341  {
342  fputs( "PD;", m_outputFile );
343  pen_up = false;
344  }
345  else if( !pen_up && item.lift_before )
346  {
347  fputs( "PU;", m_outputFile );
348  pen_up = true;
349  }
350 
351  fputs( static_cast<const char*>( item.content.utf8_str() ), m_outputFile );
352 
353  if( !item.pen_returns )
354  {
355  // Assume commands drop the pen
356  pen_up = false;
357  }
358 
359  if( item.lift_after )
360  {
361  fputs( "PU;", m_outputFile );
362  pen_up = true;
363  }
364  else
365  {
366  loc = item.loc_end;
367  }
368 
369  fputs( "\n", m_outputFile );
370  }
371  }
372 
373  fputs( "PU;PA;SP0;\n", m_outputFile );
374  fclose( m_outputFile );
375  m_outputFile = nullptr;
376  return true;
377 }
378 
379 
380 void HPGL_PLOTTER::SetPenDiameter( double diameter )
381 {
382  penDiameter = diameter;
383 }
384 
385 
386 void HPGL_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width )
387 {
388  wxASSERT( m_outputFile );
389 
390  DPOINT p1dev = userToDeviceCoordinates( p1 );
391  DPOINT p2dev = userToDeviceCoordinates( p2 );
392 
393  MoveTo( p1 );
394 
395  if( fill == FILL_T::FILLED_SHAPE )
396  {
397  startOrAppendItem( p1dev, wxString::Format( "RA %.0f,%.0f;", p2dev.x, p2dev.y ) );
398  }
399 
400  startOrAppendItem( p1dev, wxString::Format( "EA %.0f,%.0f;", p2dev.x, p2dev.y ) );
401 
403  m_current_item->bbox.Merge( p2dev );
404  PenFinish();
405 }
406 
407 
408 void HPGL_PLOTTER::Circle( const wxPoint& centre, int diameter, FILL_T fill, int width )
409 {
410  wxASSERT( m_outputFile );
411  double radius = userToDeviceSize( diameter / 2 );
412  DPOINT center_dev = userToDeviceCoordinates( centre );
413  SetCurrentLineWidth( width );
414 
415  double const circumf = 2.0 * M_PI * radius;
416  double const target_chord_length = arcTargetChordLength;
417  double chord_degrees = 360.0 * target_chord_length / circumf;
418 
419  if( chord_degrees < arcMinChordDegrees )
420  {
421  chord_degrees = arcMinChordDegrees;
422  }
423  else if( chord_degrees > 45 )
424  {
425  chord_degrees = 45;
426  }
427 
428  if( fill == FILL_T::FILLED_SHAPE )
429  {
430  // Draw the filled area
431  MoveTo( centre );
432  startOrAppendItem( center_dev, wxString::Format( "PM 0;CI %g,%g;%s", radius, chord_degrees,
434  m_current_item->lift_before = true;
435  m_current_item->pen_returns = true;
436  m_current_item->bbox.Merge( BOX2D( center_dev - radius,
437  VECTOR2D( 2 * radius, 2 * radius ) ) );
438  PenFinish();
439  }
440 
441  if( radius > 0 )
442  {
443  MoveTo( centre );
444  startOrAppendItem( center_dev, wxString::Format( "CI %g,%g;", radius, chord_degrees ) );
445  m_current_item->lift_before = true;
446  m_current_item->pen_returns = true;
447  m_current_item->bbox.Merge( BOX2D( center_dev - radius,
448  VECTOR2D( 2 * radius, 2 * radius ) ) );
449  PenFinish();
450  }
451 }
452 
453 
454 void HPGL_PLOTTER::PlotPoly( const std::vector<wxPoint>& aCornerList, FILL_T aFill, int aWidth,
455  void* aData )
456 {
457  if( aCornerList.size() <= 1 )
458  return;
459 
460  // Width less than zero is occasionally used to create background-only
461  // polygons. Don't set that as the plotter line width, that'll cause
462  // trouble. Also, later, skip plotting the outline if this is the case.
463  if( aWidth > 0 )
464  {
465  SetCurrentLineWidth( aWidth );
466  }
467 
468  MoveTo( aCornerList[0] );
469  startItem( userToDeviceCoordinates( aCornerList[0] ) );
470 
471  if( aFill == FILL_T::FILLED_SHAPE )
472  {
473  // Draw the filled area
475 
476  m_current_item->content << wxString( "PM 0;\n" ); // Start polygon
477 
478  for( unsigned ii = 1; ii < aCornerList.size(); ++ii )
479  LineTo( aCornerList[ii] );
480 
481  int ii = aCornerList.size() - 1;
482 
483  if( aCornerList[ii] != aCornerList[0] )
484  LineTo( aCornerList[0] );
485 
486  m_current_item->content << hpgl_end_polygon_cmd; // Close, fill polygon and draw outlines
487  m_current_item->pen_returns = true;
488  }
489  else if( aWidth > 0 )
490  {
491  // Plot only the polygon outline.
492  for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
493  LineTo( aCornerList[ii] );
494 
495  // Always close polygon if filled.
496  if( aFill != FILL_T::NO_FILL )
497  {
498  int ii = aCornerList.size() - 1;
499 
500  if( aCornerList[ii] != aCornerList[0] )
501  LineTo( aCornerList[0] );
502  }
503  }
504 
505  PenFinish();
506 }
507 
508 
509 void HPGL_PLOTTER::PenTo( const wxPoint& pos, char plume )
510 {
511  wxASSERT( m_outputFile );
512 
513  if( plume == 'Z' )
514  {
515  m_penState = 'Z';
516  flushItem();
517  return;
518  }
519 
520  DPOINT pos_dev = userToDeviceCoordinates( pos );
522 
523  if( plume == 'U' )
524  {
525  m_penState = 'U';
526  flushItem();
527  }
528  else if( plume == 'D' )
529  {
530  m_penState = 'D';
531  startOrAppendItem( lastpos_dev, wxString::Format( "PA %.0f,%.0f;", pos_dev.x, pos_dev.y ) );
532  m_current_item->loc_end = pos_dev;
533  m_current_item->bbox.Merge( pos_dev );
534  }
535 
536  m_penLastpos = pos;
537 }
538 
539 
541 {
542  dashType = dashed;
543  flushItem();
544 }
545 
546 
547 void HPGL_PLOTTER::ThickSegment( const wxPoint& start, const wxPoint& end,
548  int width, OUTLINE_MODE tracemode, void* aData )
549 {
550  wxASSERT( m_outputFile );
551 
552  // Suppress overlap if pen is too big
553  if( penDiameter >= width )
554  {
555  MoveTo( start );
556  FinishTo( end );
557  }
558  else
559  {
560  segmentAsOval( start, end, width, tracemode );
561  }
562 }
563 
564 
565 void HPGL_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius,
566  FILL_T fill, int width )
567 {
568  wxASSERT( m_outputFile );
569  double angle;
570 
571  if( radius <= 0 )
572  return;
573 
574  double const radius_dev = userToDeviceSize( radius );
575  double const circumf_dev = 2.0 * M_PI * radius_dev;
576  double const target_chord_length = arcTargetChordLength;
577  double chord_degrees = 360.0 * target_chord_length / circumf_dev;
578 
579  if( chord_degrees < arcMinChordDegrees )
580  {
581  chord_degrees = arcMinChordDegrees;
582  }
583  else if( chord_degrees > 45 )
584  {
585  chord_degrees = 45;
586  }
587 
588  DPOINT centre_dev = userToDeviceCoordinates( centre );
589 
590  if( m_plotMirror )
591  angle = StAngle - EndAngle;
592  else
593  angle = EndAngle - StAngle;
594 
596  angle /= 10;
597 
598  // Calculate arc start point:
599  wxPoint cmap;
600  cmap.x = centre.x + KiROUND( cosdecideg( radius, StAngle ) );
601  cmap.y = centre.y - KiROUND( sindecideg( radius, StAngle ) );
602  DPOINT cmap_dev = userToDeviceCoordinates( cmap );
603 
604  startOrAppendItem( cmap_dev, wxString::Format( "AA %.0f,%.0f,%.0f,%g", centre_dev.x,
605  centre_dev.y, angle, chord_degrees ) );
606 
607  // TODO We could compute the final position and full bounding box instead...
608  m_current_item->bbox.Merge( BOX2D( centre_dev - radius_dev,
609  VECTOR2D( radius_dev * 2, radius_dev * 2 ) ) );
610  m_current_item->lift_after = true;
611  flushItem();
612 }
613 
614 
615 void HPGL_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, double orient,
616  OUTLINE_MODE trace_mode, void* aData )
617 {
618  wxASSERT( m_outputFile );
619  int deltaxy, cx, cy;
620  wxSize size( aSize );
621 
622  // The pad will be drawn as an oblong shape with size.y > size.x (Oval vertical orientation 0).
623  if( size.x > size.y )
624  {
625  std::swap( size.x, size.y );
626  orient = AddAngles( orient, 900 );
627  }
628 
629  deltaxy = size.y - size.x; // distance between centers of the oval
630 
631  if( trace_mode == FILLED )
632  {
633  FlashPadRect( pos, wxSize( size.x, deltaxy + KiROUND( penDiameter ) ),
634  orient, trace_mode, aData );
635  cx = 0; cy = deltaxy / 2;
636  RotatePoint( &cx, &cy, orient );
637  FlashPadCircle( wxPoint( cx + pos.x, cy + pos.y ), size.x, trace_mode, aData );
638  cx = 0; cy = -deltaxy / 2;
639  RotatePoint( &cx, &cy, orient );
640  FlashPadCircle( wxPoint( cx + pos.x, cy + pos.y ), size.x, trace_mode, aData );
641  }
642  else // Plot in outline mode.
643  {
644  sketchOval( pos, size, orient, KiROUND( penDiameter ) );
645  }
646 }
647 
648 
649 void HPGL_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre,
650  OUTLINE_MODE trace_mode, void* aData )
651 {
652  wxASSERT( m_outputFile );
653  DPOINT pos_dev = userToDeviceCoordinates( pos );
654 
655  int radius = diametre / 2;
656 
657  if( trace_mode == FILLED )
658  {
659  // if filled mode, the pen diameter is removed from diameter
660  // to keep the pad size
661  radius -= KiROUND( penDiameter ) / 2;
662  }
663 
664  if( radius < 0 )
665  radius = 0;
666 
667  double rsize = userToDeviceSize( radius );
668 
669  if( trace_mode == FILLED ) // Plot in filled mode.
670  {
671  // A filled polygon uses always the current point to start the polygon.
672  // Gives a correct current starting point for the circle
673  MoveTo( wxPoint( pos.x+radius, pos.y ) );
674 
675  // Plot filled area and its outline
676  startOrAppendItem( userToDeviceCoordinates( wxPoint( pos.x + radius, pos.y ) ),
677  wxString::Format( "PM 0; PA %.0f,%.0f;CI %.0f;%s",
678  pos_dev.x, pos_dev.y, rsize, hpgl_end_polygon_cmd ) );
679  m_current_item->lift_before = true;
680  m_current_item->pen_returns = true;
681  }
682  else
683  {
684  // Draw outline only:
685  startOrAppendItem( pos_dev, wxString::Format( "CI %.0f;", rsize ) );
686  m_current_item->lift_before = true;
687  m_current_item->pen_returns = true;
688  }
689 
690  PenFinish();
691 }
692 
693 
694 void HPGL_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& padsize,
695  double orient, OUTLINE_MODE trace_mode, void* aData )
696 {
697  // Build rect polygon:
698  std::vector<wxPoint> corners;
699 
700  int dx = padsize.x / 2;
701  int dy = padsize.y / 2;
702 
703  if( trace_mode == FILLED )
704  {
705  // in filled mode, the pen diameter is removed from size
706  // to compensate the extra size due to this pen size
707  dx -= KiROUND( penDiameter ) / 2;
708  dx = std::max( dx, 0);
709  dy -= KiROUND( penDiameter ) / 2;
710  dy = std::max( dy, 0);
711  }
712 
713 
714  corners.emplace_back( - dx, - dy );
715  corners.emplace_back( - dx, + dy );
716  corners.emplace_back( + dx, + dy );
717  corners.emplace_back( + dx, - dy );
718 
719  // Close polygon
720  corners.emplace_back( - dx, - dy );
721 
722  for( unsigned ii = 0; ii < corners.size(); ii++ )
723  {
724  RotatePoint( &corners[ii], orient );
725  corners[ii] += pos;
726  }
727 
728  PlotPoly( corners, trace_mode == FILLED ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL );
729 }
730 
731 
732 void HPGL_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aSize,
733  int aCornerRadius, double aOrient,
734  OUTLINE_MODE aTraceMode, void* aData )
735 {
736  SHAPE_POLY_SET outline;
737 
738  wxSize size = aSize;
739 
740  if( aTraceMode == FILLED )
741  {
742  // In filled mode, the pen diameter is removed from size to keep the pad size.
743  size.x -= KiROUND( penDiameter ) / 2;
744  size.x = std::max( size.x, 0);
745  size.y -= KiROUND( penDiameter ) / 2;
746  size.y = std::max( size.y, 0);
747 
748  // keep aCornerRadius to a value < min size x,y < 2:
749  aCornerRadius = std::min( aCornerRadius, std::min( size.x, size.y ) /2 );
750  }
751 
752  TransformRoundChamferedRectToPolygon( outline, aPadPos, size, aOrient, aCornerRadius,
753  0.0, 0, 0, GetPlotterArcHighDef(), ERROR_INSIDE );
754 
755  // TransformRoundRectToPolygon creates only one convex polygon
756  std::vector<wxPoint> cornerList;
757  SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
758  cornerList.reserve( poly.PointCount() );
759 
760  for( int ii = 0; ii < poly.PointCount(); ++ii )
761  cornerList.emplace_back( poly.CPoint( ii ).x, poly.CPoint( ii ).y );
762 
763  if( cornerList.back() != cornerList.front() )
764  cornerList.push_back( cornerList.front() );
765 
766  PlotPoly( cornerList, aTraceMode == FILLED ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL );
767 }
768 
769 
770 void HPGL_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize, double aOrient,
771  SHAPE_POLY_SET* aPolygons, OUTLINE_MODE aTraceMode, void* aData )
772 {
773  std::vector< wxPoint > cornerList;
774 
775  for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt )
776  {
777  SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt );
778 
779  cornerList.clear();
780  cornerList.reserve( poly.PointCount() );
781 
782  for( int ii = 0; ii < poly.PointCount(); ++ii )
783  cornerList.emplace_back( poly.CPoint( ii ).x, poly.CPoint( ii ).y );
784 
785  if( cornerList.back() != cornerList.front() )
786  cornerList.push_back( cornerList.front() );
787 
788  PlotPoly( cornerList, aTraceMode == FILLED ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL );
789  }
790 }
791 
792 
793 void HPGL_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint* aCorners,
794  double aPadOrient, OUTLINE_MODE aTraceMode, void* aData )
795 {
796  std::vector< wxPoint > cornerList;
797  cornerList.reserve( 5 );
798 
799  for( int ii = 0; ii < 4; ii++ )
800  {
801  wxPoint coord( aCorners[ii] );
802  RotatePoint( &coord, aPadOrient );
803  coord += aPadPos;
804  cornerList.push_back( coord );
805  }
806 
807  // Close polygon
808  cornerList.push_back( cornerList.front() );
809 
810  PlotPoly( cornerList, aTraceMode == FILLED ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL );
811 }
812 
813 
814 void HPGL_PLOTTER::FlashRegularPolygon( const wxPoint& aShapePos, int aRadius, int aCornerCount,
815  double aOrient, OUTLINE_MODE aTraceMode, void* aData )
816 {
817  // Do nothing
818  wxASSERT( 0 );
819 }
820 
821 
822 bool HPGL_PLOTTER::startItem( const DPOINT& location )
823 {
824  return startOrAppendItem( location, wxEmptyString );
825 }
826 
827 
829 {
830  m_current_item = nullptr;
831 }
832 
833 
834 bool HPGL_PLOTTER::startOrAppendItem( const DPOINT& location, wxString const& content )
835 {
836  if( m_current_item == nullptr )
837  {
838  HPGL_ITEM item;
839  item.loc_start = location;
840  item.loc_end = location;
841  item.bbox = BOX2D( location );
842  item.pen = penNumber;
843  item.dashType = dashType;
844  item.content = content;
845  m_items.push_back( item );
846  m_current_item = &m_items.back();
847  return true;
848  }
849  else
850  {
851  m_current_item->content << content;
852  return false;
853  }
854 }
855 
856 
857 void HPGL_PLOTTER::sortItems( std::list<HPGL_ITEM>& items )
858 {
859  if( items.size() < 2 )
860  {
861  return;
862  }
863 
864  std::list<HPGL_ITEM> target;
865 
866  // Plot items are sorted to improve print time on mechanical plotters. This
867  // means
868  // 1) Avoid excess pen-switching - once a pen is selected, keep printing
869  // with it until no more items using that pen remain.
870  // 2) Within the items for one pen, avoid bouncing back and forth around
871  // the page; items should be sequenced with nearby items.
872  //
873  // This is essentially a variant of the Traveling Salesman Problem where
874  // the cities are themselves edges that must be traversed. This is of course
875  // a famously NP-Hard problem and this particular variant has a monstrous
876  // number of "cities". For now, we're using a naive nearest-neighbor search,
877  // which is less than optimal but (usually!) better than nothing, very
878  // simple to implement, and fast enough.
879  //
880  // Items are moved one at a time from `items` into `target`, searching
881  // each time for the first one matching the above criteria. Then, all of
882  // `target` is moved back into `items`.
883 
884  // Get the first one started
885  HPGL_ITEM last_item = items.front();
886  items.pop_front();
887  target.emplace_back( last_item );
888 
889  while( !items.empty() )
890  {
891  auto best_it = items.begin();
892  double best_dist = dpoint_dist( last_item.loc_end, best_it->loc_start );
893 
894  for( auto search_it = best_it; search_it != items.end(); search_it++ )
895  {
896  // Immediately forget an item as "best" if another one is a better pen match
897  if( best_it->pen != last_item.pen && search_it->pen == last_item.pen )
898  {
899  best_it = search_it;
900  continue;
901  }
902 
903  double const dist = dpoint_dist( last_item.loc_end, search_it->loc_start );
904 
905  if( dist < best_dist )
906  {
907  best_it = search_it;
908  best_dist = dist;
909  continue;
910  }
911  }
912 
913  target.emplace_back( *best_it );
914  last_item = *best_it;
915  items.erase( best_it );
916  }
917 
918  items.splice( items.begin(), target );
919 }
920 
921 
923 {
924  switch( linetype )
925  {
927  return "LT -2 4 1;";
928  break;
929  case PLOT_DASH_TYPE::DOT:
930  return "LT -1 2 1;";
931  break;
933  return "LT -4 6 1;";
934  break;
935  default:
936  return "LT;";
937  break;
938  }
939 }
940 
941 
942 static double dpoint_dist( const DPOINT& a, const DPOINT& b )
943 {
944  DPOINT diff = a - b;
945  return sqrt( diff.x * diff.x + diff.y * diff.y );
946 }
void segmentAsOval(const wxPoint &start, const wxPoint &end, int width, OUTLINE_MODE tracemode)
Convert a thick segment and plot it as an oval.
Definition: plotter.cpp:460
void FinishTo(const wxPoint &pos)
Definition: plotter.h:273
virtual bool EndPlot() override
HPGL end of plot: sort and emit graphics, pen return and release.
OUTLINE_MODE
Definition: outline_mode.h:24
virtual void FlashPadCircle(const wxPoint &aPadPos, int aDiameter, OUTLINE_MODE aTraceMode, void *aData) override
bool pen_returns
Whether the pen returns to its original state after the command.
Definition: plotter_hpgl.h:203
void PenFinish()
Definition: plotter.h:279
int OutlineCount() const
Return the number of vertices in a given outline/hole.
PLOT_DASH_TYPE dashType
Line style for this command.
Definition: plotter_hpgl.h:209
void flushItem()
Flush the current HPGL_ITEM and clear out the current item pointer.
BOX2D bbox
Bounding box of this item.
Definition: plotter_hpgl.h:191
coord_type GetX() const
Definition: box2.h:173
BOX2< VECTOR2D > BOX2D
Definition: box2.h:507
FILE * m_outputFile
Output file.
Definition: plotter.h:590
static void sortItems(std::list< HPGL_ITEM > &items)
Sort a list of HPGL items to improve plotting speed on mechanical plotters.
double m_iuPerDeviceUnit
Definition: plotter.h:581
virtual void SetPenDiameter(double diameter)
wxString content
Text of the command.
Definition: plotter_hpgl.h:212
bool useUserCoords
Definition: plotter_hpgl.h:171
std::list< HPGL_ITEM > m_items
Definition: plotter_hpgl.h:223
virtual void FlashPadTrapez(const wxPoint &aPadPos, const wxPoint *aCorners, double aPadOrient, OUTLINE_MODE aTraceMode, void *aData) override
Flash a trapezoidal pad.
void NORMALIZE_ANGLE_180(T &Angle)
Definition: trigo.h:398
void RotatePoint(int *pX, int *pY, double angle)
Definition: trigo.cpp:229
double m_IUsPerDecimil
Definition: plotter.h:579
int PointCount() const
Return the number of points (vertices) in this line chain.
virtual void PlotPoly(const std::vector< wxPoint > &aCornerList, FILL_T aFill, int aWidth=USE_DEFAULT_LINE_WIDTH, void *aData=nullptr) override
Draw a polygon ( filled or not ).
virtual bool StartPlot() override
At the start of the HPGL plot pen speed and number are requested.
static double dpoint_dist(const DPOINT &a, const DPOINT &b)
Compute the distance between two DPOINT points.
virtual void FlashPadOval(const wxPoint &aPadPos, const wxSize &aSize, double aPadOrient, OUTLINE_MODE aTraceMode, void *aData) override
virtual void FlashPadRoundRect(const wxPoint &aPadPos, const wxSize &aSize, int aCornerRadius, double aOrient, OUTLINE_MODE aTraceMode, void *aData) override
FILL_T
Definition: eda_shape.h:53
DPOINT loc_end
Location the pen will be at when it finishes.
Definition: plotter_hpgl.h:188
virtual void SetViewport(const wxPoint &aOffset, double aIusPerDecimil, double aScale, bool aMirror) override
Set the plot offset and scaling for the current plot.
void TransformRoundChamferedRectToPolygon(SHAPE_POLY_SET &aCornerBuffer, const wxPoint &aPosition, const wxSize &aSize, double 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.
virtual void Rect(const wxPoint &p1, const wxPoint &p2, FILL_T fill, int width=USE_DEFAULT_LINE_WIDTH) override
double m_plotScale
Plot scale - chosen by the user (even implicitly with 'fit in a4')
Definition: plotter.h:573
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
void LineTo(const wxPoint &pos)
Definition: plotter.h:268
static const int USE_DEFAULT_LINE_WIDTH
Definition: plotter.h:126
virtual void FlashRegularPolygon(const wxPoint &aShapePos, int aDiameter, int aCornerCount, double aOrient, OUTLINE_MODE aTraceMode, void *aData) override
Flash a regular polygon.
bool m_plotMirror
Definition: plotter.h:584
VECTOR2< double > VECTOR2D
Definition: vector2d.h:622
bool startOrAppendItem(const DPOINT &location, const wxString &content)
Start a new HPGL_ITEM with the given string if necessary, or append the string to the current item.
T AddAngles(T a1, T2 a2)
Add two angles (keeping the result normalized). T2 is here.
Definition: trigo.h:341
Represent a set of closed polygons.
SHAPE_LINE_CHAIN & Outline(int aIndex)
bool lift_after
Whether the pen must be lifted after the command.
Definition: plotter_hpgl.h:199
coord_type GetWidth() const
Definition: box2.h:180
virtual void SetPenSpeed(int speed)
Definition: plotter_hpgl.h:85
Plotting engine (HPGL)
virtual void ThickSegment(const wxPoint &start, const wxPoint &end, int width, OUTLINE_MODE tracemode, void *aData) override
static const char hpgl_end_polygon_cmd[]
wxPoint m_penLastpos
Definition: plotter.h:597
const wxSize & GetSizeMils() const
Definition: page_info.h:135
virtual void PenTo(const wxPoint &pos, char plume) override
Moveto/lineto primitive, moves the 'pen' to the specified direction.
BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition: box2.h:363
virtual DPOINT userToDeviceSize(const wxSize &size)
Modify size according to the plotter scale factors (wxSize version, returns a DPOINT).
Definition: plotter.cpp:123
int pen
Pen number for this command.
Definition: plotter_hpgl.h:206
PLOT_DASH_TYPE
Dashed line types.
Definition: plotter.h:104
void MoveTo(const wxPoint &pos)
Definition: plotter.h:263
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
virtual DPOINT userToDeviceCoordinates(const wxPoint &aCoordinate)
Modify coordinates according to the orientation, scale factor, and offsets trace.
Definition: plotter.cpp:92
HPGL_ITEM * m_current_item
Definition: plotter_hpgl.h:224
coord_type GetY() const
Definition: box2.h:174
void SetTargetChordLength(double chord_len)
Set the target length of chords used to draw approximated circles and arcs.
double cosdecideg(double r, double a)
Circle generation utility: computes r * cos(a) Where a is in decidegrees, not in radians.
Definition: trigo.h:452
PLOT_DASH_TYPE dashType
Definition: plotter_hpgl.h:170
double sindecideg(double r, double a)
Circle generation utility: computes r * sin(a) Where a is in decidegrees, not in radians.
Definition: trigo.h:443
wxPoint m_plotOffset
Definition: plotter.h:583
bool startItem(const DPOINT &location)
Start a new HPGL_ITEM if necessary, keeping the current one if it exists.
PAGE_INFO m_pageInfo
Definition: plotter.h:602
Represent a polyline (an zero-thickness chain of connected line segments).
static const char * lineTypeCommand(PLOT_DASH_TYPE linetype)
Return the plot command corresponding to a line type.
static DIRECTION_45::AngleType angle(const VECTOR2I &a, const VECTOR2I &b)
double penDiameter
Definition: plotter_hpgl.h:167
virtual void SetPenNumber(int number)
Definition: plotter_hpgl.h:90
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:73
coord_type GetHeight() const
Definition: box2.h:181
int GetPlotterArcHighDef() const
Definition: plotter.h:228
char m_penState
Definition: plotter.h:596
virtual void FlashPadRect(const wxPoint &aPadPos, const wxSize &aSize, double aOrient, OUTLINE_MODE aTraceMode, void *aData) override
static const double PLUsPERDECIMIL
double arcMinChordDegrees
Definition: plotter_hpgl.h:169
bool lift_before
Whether the command should be executed with the pen lifted.
Definition: plotter_hpgl.h:194
double arcTargetChordLength
Definition: plotter_hpgl.h:168
DPOINT loc_start
Location the pen should start at.
Definition: plotter_hpgl.h:184
wxSize m_paperSize
Definition: plotter.h:603
virtual void FlashPadCustom(const wxPoint &aPadPos, const wxSize &aSize, double aOrient, SHAPE_POLY_SET *aPolygons, OUTLINE_MODE aTraceMode, void *aData) override
virtual void SetDash(PLOT_DASH_TYPE dashed) override
HPGL supports dashed lines.
virtual void Circle(const wxPoint &pos, int diametre, FILL_T fill, int width=USE_DEFAULT_LINE_WIDTH) override
void sketchOval(const wxPoint &pos, const wxSize &size, double orient, int width)
Definition: plotter.cpp:481
bool fitUserCoords
Definition: plotter_hpgl.h:172
virtual void SetCurrentLineWidth(int width, void *aData=nullptr) override
HPGL doesn't handle line thickness or color.
Definition: plotter_hpgl.h:72
virtual void Arc(const wxPoint &centre, double StAngle, double EndAngle, int rayon, FILL_T fill, int width=USE_DEFAULT_LINE_WIDTH) override
Plot an arc.