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 <eda_base_frame.h>
199 #include <fill_type.h>
200 #include <kicad_string.h>
202 #include <math/util.h> // for KiROUND
203 #include <trigo.h>
204 
205 #include "plotter_hpgl.h"
206 
207 
209 static double dpoint_dist( DPOINT a, DPOINT b );
210 
211 
212 // The hpgl command to close a polygon def, fill it and plot outline:
213 // PM 2; ends the polygon definition and closes it if not closed
214 // FP; fills the polygon
215 // EP; draws the polygon outline. It usually gives a better look to the filled polygon
216 static const char hpgl_end_polygon_cmd[] = "PM 2; FP; EP;\n";
217 
218 // HPGL scale factor (1 Plotter Logical Unit = 1/40mm = 25 micrometers)
219 // PLUsPERDECIMIL = (25.4 / 10000) / 0.025
220 static const double PLUsPERDECIMIL = 0.1016;
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 void HPGL_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil,
236  double aScale, bool aMirror )
237 {
238  m_plotOffset = aOffset;
239  m_plotScale = aScale;
240  m_IUsPerDecimil = aIusPerDecimil;
241  m_iuPerDeviceUnit = PLUsPERDECIMIL / aIusPerDecimil;
242 
243  /* Compute the paper size in IUs */
245  m_paperSize.x *= 10.0 * aIusPerDecimil;
246  m_paperSize.y *= 10.0 * aIusPerDecimil;
247  m_plotMirror = aMirror;
248 }
249 
250 
251 void HPGL_PLOTTER::SetTargetChordLength( double chord_len )
252 {
253  arcTargetChordLength = userToDeviceSize( chord_len );
254 }
255 
256 
258 {
259  wxASSERT( m_outputFile );
260  fprintf( m_outputFile, "IN;VS%d;PU;PA;SP%d;\n", penSpeed, penNumber );
261 
262  // Set HPGL Pen Thickness (in mm) (useful in polygon fill command)
263  double penThicknessMM = userToDeviceSize( penDiameter )/40;
264  fprintf( m_outputFile, "PT %.1f;\n", penThicknessMM );
265 
266  return true;
267 }
268 
269 
271 {
272  wxASSERT( m_outputFile );
273 
274  fputs( "PU;\n", m_outputFile );
275 
276  flushItem();
277  sortItems( m_items );
278 
279  if( m_items.size() > 0 )
280  {
281  if( useUserCoords )
282  {
283  if( fitUserCoords )
284  {
285  BOX2D bbox = m_items.front().bbox;
286  for( HPGL_ITEM const& item : m_items )
287  {
288  bbox.Merge( item.bbox );
289  }
290 
291  fprintf( m_outputFile, "SC%.0f,%.0f,%.0f,%.0f;\n", bbox.GetX(),
292  bbox.GetX() + bbox.GetWidth(), bbox.GetY(),
293  bbox.GetY() + bbox.GetHeight() );
294  }
295  else
296  {
297  DPOINT pagesize_dev( m_paperSize * m_iuPerDeviceUnit );
298  fprintf( m_outputFile, "SC%.0f,%.0f,%.0f,%.0f;\n", 0., pagesize_dev.x, 0.,
299  pagesize_dev.y );
300  }
301  }
302 
303  DPOINT loc = m_items.begin()->loc_start;
304  bool pen_up = true;
305  PLOT_DASH_TYPE current_dash = PLOT_DASH_TYPE::SOLID;
306  int current_pen = penNumber;
307 
308  for( HPGL_ITEM const& item : m_items )
309  {
310  if( item.loc_start != loc || pen_up )
311  {
312  if( !pen_up )
313  {
314  fputs( "PU;", m_outputFile );
315  pen_up = true;
316  }
317 
318  fprintf( m_outputFile, "PA %.0f,%.0f;", item.loc_start.x, item.loc_start.y );
319  }
320 
321  if( item.dashType != current_dash )
322  {
323  current_dash = item.dashType;
324  fputs( lineTypeCommand( item.dashType ), m_outputFile );
325  }
326 
327  if( item.pen != current_pen )
328  {
329  if( !pen_up )
330  {
331  fputs( "PU;", m_outputFile );
332  pen_up = true;
333  }
334  fprintf( m_outputFile, "SP%d;", item.pen );
335  current_pen = item.pen;
336  }
337 
338  if( pen_up && !item.lift_before )
339  {
340  fputs( "PD;", m_outputFile );
341  pen_up = false;
342  }
343  else if( !pen_up && item.lift_before )
344  {
345  fputs( "PU;", m_outputFile );
346  pen_up = true;
347  }
348 
349  fputs( static_cast<const char*>( item.content.utf8_str() ), m_outputFile );
350 
351  if( !item.pen_returns )
352  {
353  // Assume commands drop the pen
354  pen_up = false;
355  }
356 
357  if( item.lift_after )
358  {
359  fputs( "PU;", m_outputFile );
360  pen_up = true;
361  }
362  else
363  {
364  loc = item.loc_end;
365  }
366 
367  fputs( "\n", m_outputFile );
368  }
369  }
370 
371  fputs( "PU;PA;SP0;\n", m_outputFile );
372  fclose( m_outputFile );
373  m_outputFile = nullptr;
374  return true;
375 }
376 
377 
378 void HPGL_PLOTTER::SetPenDiameter( double diameter )
379 {
380  penDiameter = diameter;
381 }
382 
383 
384 void HPGL_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_TYPE fill, int width )
385 {
386  wxASSERT( m_outputFile );
387 
388  DPOINT p1dev = userToDeviceCoordinates( p1 );
389  DPOINT p2dev = userToDeviceCoordinates( p2 );
390 
391  MoveTo( p1 );
392 
393  if( fill == FILL_TYPE::FILLED_SHAPE )
394  {
395  startOrAppendItem( p1dev, wxString::Format( "RA %.0f,%.0f;", p2dev.x, p2dev.y ) );
396  }
397 
398  startOrAppendItem( p1dev, wxString::Format( "EA %.0f,%.0f;", p2dev.x, p2dev.y ) );
399 
401  m_current_item->bbox.Merge( p2dev );
402  PenFinish();
403 }
404 
405 
406 void HPGL_PLOTTER::Circle( const wxPoint& centre, int diameter, FILL_TYPE fill, int width )
407 {
408  wxASSERT( m_outputFile );
409  double radius = userToDeviceSize( diameter / 2 );
410  DPOINT center_dev = userToDeviceCoordinates( centre );
411  SetCurrentLineWidth( width );
412 
413  double const circumf = 2.0 * M_PI * radius;
414  double const target_chord_length = arcTargetChordLength;
415  double chord_degrees = 360.0 * target_chord_length / circumf;
416 
417  if( chord_degrees < arcMinChordDegrees )
418  {
419  chord_degrees = arcMinChordDegrees;
420  }
421  else if( chord_degrees > 45 )
422  {
423  chord_degrees = 45;
424  }
425 
426  if( fill == FILL_TYPE::FILLED_SHAPE )
427  {
428  // Draw the filled area
429  MoveTo( centre );
430  startOrAppendItem( center_dev, wxString::Format( "PM 0;CI %g,%g;%s", radius, chord_degrees,
432  m_current_item->lift_before = true;
433  m_current_item->pen_returns = true;
435  BOX2D( center_dev - radius, VECTOR2D( 2 * radius, 2 * radius ) ) );
436  PenFinish();
437  }
438 
439  if( radius > 0 )
440  {
441  MoveTo( centre );
442  startOrAppendItem( center_dev, wxString::Format( "CI %g,%g;", radius, chord_degrees ) );
443  m_current_item->lift_before = true;
444  m_current_item->pen_returns = true;
446  BOX2D( center_dev - radius, VECTOR2D( 2 * radius, 2 * radius ) ) );
447  PenFinish();
448  }
449 }
450 
451 
452 void HPGL_PLOTTER::PlotPoly( const std::vector<wxPoint>& aCornerList, FILL_TYPE aFill,
453  int aWidth, void* aData )
454 {
455  if( aCornerList.size() <= 1 )
456  return;
457 
458  // Width less than zero is occasionally used to create background-only
459  // polygons. Don't set that as the plotter line width, that'll cause
460  // trouble. Also, later, skip plotting the outline if this is the case.
461  if( aWidth > 0 )
462  {
463  SetCurrentLineWidth( aWidth );
464  }
465 
466  MoveTo( aCornerList[0] );
467  startItem( userToDeviceCoordinates( aCornerList[0] ) );
468 
469  if( aFill == FILL_TYPE::FILLED_SHAPE )
470  {
471  // Draw the filled area
473 
474  m_current_item->content << wxString( "PM 0;\n" ); // Start polygon
475 
476  for( unsigned ii = 1; ii < aCornerList.size(); ++ii )
477  LineTo( aCornerList[ii] );
478 
479  int ii = aCornerList.size() - 1;
480 
481  if( aCornerList[ii] != aCornerList[0] )
482  LineTo( aCornerList[0] );
483 
484  m_current_item->content << hpgl_end_polygon_cmd; // Close, fill polygon and draw outlines
485  m_current_item->pen_returns = true;
486  }
487  else if( aWidth > 0 )
488  {
489  // Plot only the polygon outline.
490  for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
491  LineTo( aCornerList[ii] );
492 
493  // Always close polygon if filled.
494  if( aFill != FILL_TYPE::NO_FILL )
495  {
496  int ii = aCornerList.size() - 1;
497 
498  if( aCornerList[ii] != aCornerList[0] )
499  LineTo( aCornerList[0] );
500  }
501  }
502 
503  PenFinish();
504 }
505 
506 
507 void HPGL_PLOTTER::PenTo( const wxPoint& pos, char plume )
508 {
509  wxASSERT( m_outputFile );
510 
511  if( plume == 'Z' )
512  {
513  m_penState = 'Z';
514  flushItem();
515  return;
516  }
517 
518  DPOINT pos_dev = userToDeviceCoordinates( pos );
520 
521  if( plume == 'U' )
522  {
523  m_penState = 'U';
524  flushItem();
525  }
526  else if( plume == 'D' )
527  {
528  m_penState = 'D';
530  lastpos_dev,
532  "PA %.0f,%.0f;",
533  pos_dev.x,
534  pos_dev.y
535  )
536  );
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 
546 {
547  dashType = dashed;
548  flushItem();
549 }
550 
551 
552 void HPGL_PLOTTER::ThickSegment( const wxPoint& start, const wxPoint& 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( penDiameter >= width )
559  {
560  MoveTo( start );
561  FinishTo( end );
562  }
563  else
564  {
565  segmentAsOval( start, end, width, tracemode );
566  }
567 }
568 
569 
570 void HPGL_PLOTTER::Arc( const wxPoint& centre, double StAngle, double EndAngle, int radius,
571  FILL_TYPE fill, int width )
572 {
573  wxASSERT( m_outputFile );
574  double angle;
575 
576  if( radius <= 0 )
577  return;
578 
579  double const radius_dev = userToDeviceSize( radius );
580  double const circumf_dev = 2.0 * M_PI * radius_dev;
581  double const target_chord_length = arcTargetChordLength;
582  double chord_degrees = 360.0 * target_chord_length / circumf_dev;
583 
584  if( chord_degrees < arcMinChordDegrees )
585  {
586  chord_degrees = arcMinChordDegrees;
587  }
588  else if( chord_degrees > 45 )
589  {
590  chord_degrees = 45;
591  }
592 
593  DPOINT centre_dev = userToDeviceCoordinates( centre );
594 
595  if( m_plotMirror )
596  angle = StAngle - EndAngle;
597  else
598  angle = EndAngle - StAngle;
599 
601  angle /= 10;
602 
603  // Calculate arc start point:
604  wxPoint cmap;
605  cmap.x = centre.x + KiROUND( cosdecideg( radius, StAngle ) );
606  cmap.y = centre.y - KiROUND( sindecideg( radius, StAngle ) );
607  DPOINT cmap_dev = userToDeviceCoordinates( cmap );
608 
609  startOrAppendItem( cmap_dev, wxString::Format( "AA %.0f,%.0f,%.0f,%g", centre_dev.x,
610  centre_dev.y, angle, chord_degrees ) );
611 
612  // TODO We could compute the final position and full bounding box instead...
614  BOX2D( centre_dev - radius_dev, VECTOR2D( radius_dev * 2, radius_dev * 2 ) ) );
615  m_current_item->lift_after = true;
616  flushItem();
617 }
618 
619 
620 void HPGL_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, double orient,
621  OUTLINE_MODE trace_mode, void* aData )
622 {
623  wxASSERT( m_outputFile );
624  int deltaxy, cx, cy;
625  wxSize size( aSize );
626 
627  // The pad will be drawn as an oblong shape with size.y > size.x (Oval vertical orientation 0).
628  if( size.x > size.y )
629  {
630  std::swap( size.x, size.y );
631  orient = AddAngles( orient, 900 );
632  }
633 
634  deltaxy = size.y - size.x; // distance between centers of the oval
635 
636  if( trace_mode == FILLED )
637  {
638  FlashPadRect( pos, wxSize( size.x, deltaxy + KiROUND( penDiameter ) ),
639  orient, trace_mode, aData );
640  cx = 0; cy = deltaxy / 2;
641  RotatePoint( &cx, &cy, orient );
642  FlashPadCircle( wxPoint( cx + pos.x, cy + pos.y ), size.x, trace_mode, aData );
643  cx = 0; cy = -deltaxy / 2;
644  RotatePoint( &cx, &cy, orient );
645  FlashPadCircle( wxPoint( cx + pos.x, cy + pos.y ), size.x, trace_mode, aData );
646  }
647  else // Plot in outline mode.
648  {
649  sketchOval( pos, size, orient, KiROUND( penDiameter ) );
650  }
651 }
652 
653 
654 void HPGL_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre,
655  OUTLINE_MODE trace_mode, void* aData )
656 {
657  wxASSERT( m_outputFile );
658  DPOINT pos_dev = userToDeviceCoordinates( pos );
659 
660  int radius = diametre / 2;
661 
662  if( trace_mode == FILLED )
663  {
664  // if filled mode, the pen diameter is removed from diameter
665  // to keep the pad size
666  radius -= KiROUND( penDiameter ) / 2;
667  }
668 
669  if( radius < 0 )
670  radius = 0;
671 
672  double rsize = userToDeviceSize( radius );
673 
674  if( trace_mode == FILLED ) // Plot in filled mode.
675  {
676  // A filled polygon uses always the current point to start the polygon.
677  // Gives a correct current starting point for the circle
678  MoveTo( wxPoint( pos.x+radius, pos.y ) );
679  // Plot filled area and its outline
680  startOrAppendItem( userToDeviceCoordinates( wxPoint( pos.x + radius, pos.y ) ),
681  wxString::Format( "PM 0; PA %.0f,%.0f;CI %.0f;%s", pos_dev.x, pos_dev.y, rsize,
683  m_current_item->lift_before = true;
684  m_current_item->pen_returns = true;
685  }
686  else
687  {
688  // Draw outline only:
689  startOrAppendItem( pos_dev, wxString::Format( "CI %.0f;", rsize ) );
690  m_current_item->lift_before = true;
691  m_current_item->pen_returns = true;
692  }
693 
694  PenFinish();
695 }
696 
697 
698 void HPGL_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& padsize,
699  double orient, OUTLINE_MODE trace_mode, void* aData )
700 {
701  // Build rect polygon:
702  std::vector<wxPoint> corners;
703 
704  int dx = padsize.x / 2;
705  int dy = padsize.y / 2;
706 
707  if( trace_mode == FILLED )
708  {
709  // in filled mode, the pen diameter is removed from size
710  // to compensate the extra size due to this pen size
711  dx -= KiROUND( penDiameter ) / 2;
712  dx = std::max( dx, 0);
713  dy -= KiROUND( penDiameter ) / 2;
714  dy = std::max( dy, 0);
715  }
716 
717 
718  corners.emplace_back( - dx, - dy );
719  corners.emplace_back( - dx, + dy );
720  corners.emplace_back( + dx, + dy );
721  corners.emplace_back( + dx, - dy );
722  // Close polygon
723  corners.emplace_back( - dx, - dy );
724 
725  for( unsigned ii = 0; ii < corners.size(); ii++ )
726  {
727  RotatePoint( &corners[ii], orient );
728  corners[ii] += pos;
729  }
730 
731  PlotPoly( corners, trace_mode == FILLED ? FILL_TYPE::FILLED_SHAPE : FILL_TYPE::NO_FILL );
732 }
733 
734 
735 void HPGL_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aSize,
736  int aCornerRadius, double aOrient,
737  OUTLINE_MODE aTraceMode, void* aData )
738 {
739  SHAPE_POLY_SET outline;
740 
741  wxSize size = aSize;
742 
743  if( aTraceMode == FILLED )
744  {
745  // in filled mode, the pen diameter is removed from size
746  // to keep the pad size
747  size.x -= KiROUND( penDiameter ) / 2;
748  size.x = std::max( size.x, 0);
749  size.y -= KiROUND( penDiameter ) / 2;
750  size.y = std::max( size.y, 0);
751 
752  // keep aCornerRadius to a value < min size x,y < 2:
753  aCornerRadius = std::min( aCornerRadius, std::min( size.x, size.y ) /2 );
754  }
755 
756  TransformRoundChamferedRectToPolygon( outline, aPadPos, size, aOrient, aCornerRadius,
757  0.0, 0, 0, GetPlotterArcHighDef(), ERROR_INSIDE );
758 
759  // TransformRoundRectToPolygon creates only one convex polygon
760  std::vector<wxPoint> cornerList;
761  SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
762  cornerList.reserve( poly.PointCount() );
763 
764  for( int ii = 0; ii < poly.PointCount(); ++ii )
765  cornerList.emplace_back( poly.CPoint( ii ).x, poly.CPoint( ii ).y );
766 
767  if( cornerList.back() != cornerList.front() )
768  cornerList.push_back( cornerList.front() );
769 
770  PlotPoly( cornerList, aTraceMode == FILLED ? FILL_TYPE::FILLED_SHAPE : FILL_TYPE::NO_FILL );
771 }
772 
773 void HPGL_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize,
774  double aOrient, SHAPE_POLY_SET* aPolygons,
775  OUTLINE_MODE aTraceMode, void* aData )
776 {
777  std::vector< wxPoint > cornerList;
778 
779  for( int cnt = 0; cnt < aPolygons->OutlineCount(); ++cnt )
780  {
781  SHAPE_LINE_CHAIN& poly = aPolygons->Outline( cnt );
782 
783  cornerList.clear();
784  cornerList.reserve( poly.PointCount() );
785 
786  for( int ii = 0; ii < poly.PointCount(); ++ii )
787  cornerList.emplace_back( poly.CPoint( ii ).x, poly.CPoint( ii ).y );
788 
789  if( cornerList.back() != cornerList.front() )
790  cornerList.push_back( cornerList.front() );
791 
792  PlotPoly( cornerList, aTraceMode == FILLED ? FILL_TYPE::FILLED_SHAPE : FILL_TYPE::NO_FILL );
793  }
794 }
795 
796 
797 void HPGL_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos, const wxPoint* aCorners,
798  double aPadOrient, OUTLINE_MODE aTraceMode, void* aData )
799 {
800  std::vector< wxPoint > cornerList;
801  cornerList.reserve( 5 );
802 
803  for( int ii = 0; ii < 4; ii++ )
804  {
805  wxPoint coord( aCorners[ii] );
806  RotatePoint( &coord, aPadOrient );
807  coord += aPadPos;
808  cornerList.push_back( coord );
809  }
810 
811  // Close polygon
812  cornerList.push_back( cornerList.front() );
813 
814  PlotPoly( cornerList, aTraceMode == FILLED ? FILL_TYPE::FILLED_SHAPE : FILL_TYPE::NO_FILL );
815 }
816 
817 
818 void HPGL_PLOTTER::FlashRegularPolygon( const wxPoint& aShapePos, int aRadius, int aCornerCount,
819  double aOrient, OUTLINE_MODE aTraceMode, void* aData )
820 {
821  // Do nothing
822  wxASSERT( 0 );
823 }
824 
825 
827 {
828  return startOrAppendItem( location, wxEmptyString );
829 }
830 
831 
833 {
834  m_current_item = nullptr;
835 }
836 
837 
838 bool HPGL_PLOTTER::startOrAppendItem( DPOINT location, wxString const& content )
839 {
840  if( m_current_item == nullptr )
841  {
842  HPGL_ITEM item;
843  item.loc_start = location;
844  item.loc_end = location;
845  item.bbox = BOX2D( location );
846  item.pen = penNumber;
847  item.dashType = dashType;
848  item.content = content;
849  m_items.push_back( item );
850  m_current_item = &m_items.back();
851  return true;
852  }
853  else
854  {
855  m_current_item->content << content;
856  return false;
857  }
858 }
859 
860 
861 void HPGL_PLOTTER::sortItems( std::list<HPGL_ITEM>& items )
862 {
863  if( items.size() < 2 )
864  {
865  return;
866  }
867 
868  std::list<HPGL_ITEM> target;
869 
870  // Plot items are sorted to improve print time on mechanical plotters. This
871  // means
872  // 1) Avoid excess pen-switching - once a pen is selected, keep printing
873  // with it until no more items using that pen remain.
874  // 2) Within the items for one pen, avoid bouncing back and forth around
875  // the page; items should be sequenced with nearby items.
876  //
877  // This is essentially a variant of the Travelling Salesman Problem where
878  // the cities are themselves edges that must be traversed. This is of course
879  // a famously NP-Hard problem and this particular variant has a monstrous
880  // number of "cities". For now, we're using a naive nearest-neighbor search,
881  // which is less than optimal but (usually!) better than nothing, very
882  // simple to implement, and fast enough.
883  //
884  // Items are moved one at a time from `items` into `target`, searching
885  // each time for the first one matching the above criteria. Then, all of
886  // `target` is moved back into `items`.
887 
888  // Get the first one started
889  HPGL_ITEM last_item = items.front();
890  items.pop_front();
891  target.emplace_back( last_item );
892 
893  while( !items.empty() )
894  {
895  auto best_it = items.begin();
896  double best_dist = dpoint_dist( last_item.loc_end, best_it->loc_start );
897 
898  for( auto search_it = best_it; search_it != items.end(); search_it++ )
899  {
900  // Immediately forget an item as "best" if another one is a better
901  // pen match
902  if( best_it->pen != last_item.pen && search_it->pen == last_item.pen )
903  {
904  best_it = search_it;
905  continue;
906  }
907 
908  double const dist = dpoint_dist( last_item.loc_end, search_it->loc_start );
909 
910  if( dist < best_dist )
911  {
912  best_it = search_it;
913  best_dist = dist;
914  continue;
915  }
916  }
917 
918  target.emplace_back( *best_it );
919  last_item = *best_it;
920  items.erase( best_it );
921  }
922 
923  items.splice( items.begin(), target );
924 }
925 
926 
928 {
929  switch( linetype )
930  {
932  return "LT -2 4 1;";
933  break;
934  case PLOT_DASH_TYPE::DOT:
935  return "LT -1 2 1;";
936  break;
938  return "LT -4 6 1;";
939  break;
940  default:
941  return "LT;";
942  break;
943  }
944 }
945 
946 
947 static double dpoint_dist( DPOINT a, DPOINT b )
948 {
949  DPOINT diff = a - b;
950  return sqrt( diff.x * diff.x + diff.y * diff.y );
951 }
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:453
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
FILL_TYPE
The set of fill types used in plotting or drawing enclosed areas.
Definition: fill_type.h:28
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
bool startItem(DPOINT location)
Start a new HPGL_ITEM if necessary, keeping the current one if it exists.
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 bool StartPlot() override
At the start of the HPGL plot pen speed and number are requested.
virtual void FlashPadOval(const wxPoint &aPadPos, const wxSize &aSize, double aPadOrient, OUTLINE_MODE aTraceMode, void *aData) override
bool startOrAppendItem(DPOINT 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 FlashPadRoundRect(const wxPoint &aPadPos, const wxSize &aSize, int aCornerRadius, double aOrient, OUTLINE_MODE aTraceMode, void *aData) override
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.
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
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:86
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
Base window classes and related definitions.
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.
static double dpoint_dist(DPOINT a, DPOINT b)
Compute the distance between two DPOINT points.
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:124
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 void Circle(const wxPoint &pos, int diametre, FILL_TYPE fill, int width=USE_DEFAULT_LINE_WIDTH) override
virtual DPOINT userToDeviceCoordinates(const wxPoint &aCoordinate)
Modify coordinates according to the orientation, scale factor, and offsets trace.
Definition: plotter.cpp:93
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
virtual void Arc(const wxPoint &centre, double StAngle, double EndAngle, int rayon, FILL_TYPE fill, int width=USE_DEFAULT_LINE_WIDTH) override
Plot an arc.
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
virtual void Rect(const wxPoint &p1, const wxPoint &p2, FILL_TYPE fill, int width=USE_DEFAULT_LINE_WIDTH) override
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:91
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
virtual void PlotPoly(const std::vector< wxPoint > &aCornerList, FILL_TYPE aFill, int aWidth=USE_DEFAULT_LINE_WIDTH, void *aData=nullptr) override
Draw a polygon ( filled or not ).
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.
void sketchOval(const wxPoint &pos, const wxSize &size, double orient, int width)
Definition: plotter.cpp:474
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:73