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