KiCad PCB EDA Suite
Loading...
Searching...
No Matches
graphics_importer_pl_editor.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <algorithm>
23#include <cmath>
24
25#include <bezier_curves.h>
26
28static constexpr double FLATTEN_ERROR_MM = 0.01;
29
30
32 m_polygons( nullptr )
33{
34 // A drawing sheet item holds millimetres, so there is no internal unit to convert to.
35 m_millimeterToIu = 1.0;
36}
37
38
40{
41 const VECTOR2D scale = GetScale();
42 const VECTOR2D offset = GetImportOffsetMM();
43
44 return VECTOR2D( aPoint.x * scale.x + offset.x, aPoint.y * scale.y + offset.y );
45}
46
47
49{
50 const VECTOR2D scale = GetScale();
51 const double factor = ( std::abs( scale.x ) + std::abs( scale.y ) ) * 0.5;
52
53 // A width the file did not give falls back to the one the dialog offers, and a zero
54 // width leaves the drawing sheet to use its own default.
55 if( aStroke.GetWidth() <= 0.0 )
56 return GetLineWidthMM() * factor;
57
58 return aStroke.GetWidth() * factor;
59}
60
61
62void GRAPHICS_IMPORTER_PL_EDITOR::addSegment( const VECTOR2D& aStart, const VECTOR2D& aEnd, double aWidth )
63{
64 auto item = std::make_unique<DS_DATA_ITEM>( DS_DATA_ITEM::DS_SEGMENT );
65
66 // Imported geometry runs right and down, which is what the left top corner anchor means.
67 item->SetStart( aStart.x, aStart.y, LT_CORNER );
68 item->SetEnd( aEnd.x, aEnd.y, LT_CORNER );
69 item->m_LineWidth = aWidth;
70
71 m_dataItems.push_back( std::move( item ) );
72}
73
74
75void GRAPHICS_IMPORTER_PL_EDITOR::addPolyline( const std::vector<VECTOR2D>& aPoints, double aWidth, bool aClose )
76{
77 for( size_t ii = 1; ii < aPoints.size(); ii++ )
78 addSegment( aPoints[ii - 1], aPoints[ii], aWidth );
79
80 if( aClose && aPoints.size() > 2 )
81 addSegment( aPoints.back(), aPoints.front(), aWidth );
82}
83
84
85void GRAPHICS_IMPORTER_PL_EDITOR::addFilledPolygon( const std::vector<VECTOR2D>& aPoints )
86{
87 if( aPoints.size() < 3 )
88 return;
89
90 // A traced logo is hundreds of contours but one thing to the user, so they all go into
91 // a single item, which is also what the standalone converter writes.
92 if( !m_polygons )
93 {
94 auto item = std::make_unique<DS_DATA_ITEM_POLYGONS>();
95
96 item->SetStart( 0.0, 0.0, LT_CORNER );
97
98 // Drawing sheet polygons are filled; this is what their outline is drawn with.
99 item->m_LineWidth = 0.01;
100
101 m_polygons = item.get();
102 m_dataItems.push_back( std::move( item ) );
103 }
104
105 // A polygon's corners are kept relative to its position, which sits at the origin.
106 for( const VECTOR2D& point : aPoints )
107 m_polygons->AppendCorner( point );
108
109 // The contour has to come back to where it started.
110 m_polygons->AppendCorner( aPoints.front() );
111 m_polygons->CloseContour();
112}
113
114
115std::vector<VECTOR2D> GRAPHICS_IMPORTER_PL_EDITOR::flattenEllipseArc( const VECTOR2D& aCenter, double aMajorRadius,
116 double aMinorRadius, const EDA_ANGLE& aRotation,
117 double aStartRadians, double aSweepRadians ) const
118{
119 std::vector<VECTOR2D> points;
120
121 const double major = std::abs( aMajorRadius );
122 const double minor = std::abs( aMinorRadius );
123 const double largest = std::max( major, minor );
124
125 if( largest <= 0.0 || aSweepRadians == 0.0 )
126 return points;
127
128 // Short enough that the chord never sits further from the curve than the error allows.
129 const double ratio = std::clamp( 1.0 - FLATTEN_ERROR_MM / largest, -1.0, 1.0 );
130 const double step = std::max( 2.0 * std::acos( ratio ), 1e-3 );
131 const int count = std::max( 2, (int) std::ceil( std::abs( aSweepRadians ) / step ) );
132
133 const double rotCos = std::cos( aRotation.AsRadians() );
134 const double rotSin = std::sin( aRotation.AsRadians() );
135
136 for( int ii = 0; ii <= count; ii++ )
137 {
138 const double angle = aStartRadians + aSweepRadians * ii / count;
139 const double x = major * std::cos( angle );
140 const double y = minor * std::sin( angle );
141
142 points.emplace_back( aCenter.x + x * rotCos - y * rotSin, aCenter.y + x * rotSin + y * rotCos );
143 }
144
145 return points;
146}
147
148
150 const IMPORTED_STROKE& aStroke )
151{
152 addSegment( mapPoint( aOrigin ), mapPoint( aEnd ), mapLineWidth( aStroke ) );
153}
154
155
156void GRAPHICS_IMPORTER_PL_EDITOR::AddCircle( const VECTOR2D& aCenter, double aRadius, const IMPORTED_STROKE& aStroke,
157 bool aFilled, const COLOR4D& aFillColor )
158{
159 AddEllipse( aCenter, aRadius, aRadius, ANGLE_0, aStroke, aFilled, aFillColor );
160}
161
162
163void GRAPHICS_IMPORTER_PL_EDITOR::AddArc( const VECTOR2D& aCenter, const VECTOR2D& aStart, const EDA_ANGLE& aAngle,
164 const IMPORTED_STROKE& aStroke )
165{
166 const VECTOR2D startVec = aStart - aCenter;
167 const double radius = startVec.EuclideanNorm();
168 const double startAngle = std::atan2( startVec.y, startVec.x );
169
170 // The arc runs from its start by the angle given, the way the other importers turn it.
171 std::vector<VECTOR2D> points =
172 flattenEllipseArc( aCenter, radius, radius, ANGLE_0, startAngle, -aAngle.AsRadians() );
173
174 for( VECTOR2D& point : points )
175 point = mapPoint( point );
176
177 addPolyline( points, mapLineWidth( aStroke ), false );
178}
179
180
181void GRAPHICS_IMPORTER_PL_EDITOR::AddPolygon( const std::vector<VECTOR2D>& aVertices, const IMPORTED_STROKE& aStroke,
182 bool aFilled, const COLOR4D& aFillColor )
183{
184 std::vector<VECTOR2D> points;
185
186 points.reserve( aVertices.size() );
187
188 for( const VECTOR2D& vertex : aVertices )
189 points.push_back( mapPoint( vertex ) );
190
191 if( aFilled )
192 addFilledPolygon( points );
193 else
194 addPolyline( points, mapLineWidth( aStroke ), true );
195}
196
197
198void GRAPHICS_IMPORTER_PL_EDITOR::AddText( const VECTOR2D& aOrigin, const wxString& aText, double aHeight,
199 double aWidth, double aThickness, double aOrientation,
200 GR_TEXT_H_ALIGN_T aHJustify, GR_TEXT_V_ALIGN_T aVJustify,
201 const COLOR4D& aColor )
202{
203 auto item = std::make_unique<DS_DATA_ITEM_TEXT>( aText );
204
205 const VECTOR2D position = mapPoint( aOrigin );
206 const VECTOR2D scale = GetScale();
207
208 item->SetStart( position.x, position.y, LT_CORNER );
209
210 // A size of zero is what the drawing sheet spells "use the default", which beats the
211 // invisible text a file that gives no size would otherwise produce.
212 if( aWidth > 0.0 && aHeight > 0.0 )
213 item->m_TextSize = VECTOR2D( aWidth * std::abs( scale.x ), aHeight * std::abs( scale.y ) );
214
215 item->m_LineWidth = aThickness * std::abs( scale.x );
216 item->m_Orient = aOrientation;
217 item->m_Hjustify = aHJustify;
218 item->m_Vjustify = aVJustify;
219
220 m_dataItems.push_back( std::move( item ) );
221}
222
223
224void GRAPHICS_IMPORTER_PL_EDITOR::AddSpline( const VECTOR2D& aStart, const VECTOR2D& aBezierControl1,
225 const VECTOR2D& aBezierControl2, const VECTOR2D& aEnd,
226 const IMPORTED_STROKE& aStroke )
227{
228 std::vector<VECTOR2D> controls = { mapPoint( aStart ), mapPoint( aBezierControl1 ), mapPoint( aBezierControl2 ),
229 mapPoint( aEnd ) };
230 std::vector<VECTOR2D> points;
231
232 BEZIER_POLY( controls ).GetPoly( points, FLATTEN_ERROR_MM );
233
234 addPolyline( points, mapLineWidth( aStroke ), false );
235}
236
237
238void GRAPHICS_IMPORTER_PL_EDITOR::AddEllipse( const VECTOR2D& aCenter, double aMajorRadius, double aMinorRadius,
239 const EDA_ANGLE& aRotation, const IMPORTED_STROKE& aStroke, bool aFilled,
240 const COLOR4D& aFillColor )
241{
242 std::vector<VECTOR2D> points = flattenEllipseArc( aCenter, aMajorRadius, aMinorRadius, aRotation, 0.0, 2.0 * M_PI );
243
244 if( points.empty() )
245 return;
246
247 // The walk closes on its own start, which a closed shape does not want repeated.
248 points.pop_back();
249
250 for( VECTOR2D& point : points )
251 point = mapPoint( point );
252
253 if( aFilled )
254 addFilledPolygon( points );
255 else
256 addPolyline( points, mapLineWidth( aStroke ), true );
257}
258
259
260void GRAPHICS_IMPORTER_PL_EDITOR::AddEllipseArc( const VECTOR2D& aCenter, double aMajorRadius, double aMinorRadius,
261 const EDA_ANGLE& aRotation, const EDA_ANGLE& aStartAngle,
262 const EDA_ANGLE& aEndAngle, const IMPORTED_STROKE& aStroke )
263{
264 double sweep = ( aEndAngle - aStartAngle ).AsRadians();
265
266 // An ellipse arc is given by its two ends, and runs between them the positive way.
267 while( sweep <= 0.0 )
268 sweep += 2.0 * M_PI;
269
270 std::vector<VECTOR2D> points =
271 flattenEllipseArc( aCenter, aMajorRadius, aMinorRadius, aRotation, aStartAngle.AsRadians(), sweep );
272
273 for( VECTOR2D& point : points )
274 point = mapPoint( point );
275
276 addPolyline( points, mapLineWidth( aStroke ), false );
277}
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
double AsRadians() const
Definition eda_angle.h:119
void addPolyline(const std::vector< VECTOR2D > &aPoints, double aWidth, bool aClose)
Add a run of points as segments, closing back to the first point when asked.
std::vector< VECTOR2D > flattenEllipseArc(const VECTOR2D &aCenter, double aMajorRadius, double aMinorRadius, const EDA_ANGLE &aRotation, double aStartRadians, double aSweepRadians) const
Walk an elliptical arc, which covers circles and arcs as well.
double mapLineWidth(const IMPORTED_STROKE &aStroke) const
The line width to draw a stroke with, in millimetres.
void AddEllipseArc(const VECTOR2D &aCenter, double aMajorRadius, double aMinorRadius, const EDA_ANGLE &aRotation, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aEndAngle, const IMPORTED_STROKE &aStroke) override
Create an object representing an elliptical arc.
void AddText(const VECTOR2D &aOrigin, const wxString &aText, double aHeight, double aWidth, double aThickness, double aOrientation, GR_TEXT_H_ALIGN_T aHJustify, GR_TEXT_V_ALIGN_T aVJustify, const COLOR4D &aColor) override
Create an object representing a text.
void AddArc(const VECTOR2D &aCenter, const VECTOR2D &aStart, const EDA_ANGLE &aAngle, const IMPORTED_STROKE &aStroke) override
Create an object representing an arc.
void addSegment(const VECTOR2D &aStart, const VECTOR2D &aEnd, double aWidth)
DS_DATA_ITEM_POLYGONS * m_polygons
All filled areas go into one item, the way a traced logo is one object.
VECTOR2D mapPoint(const VECTOR2D &aPoint) const
Scale and offset an imported point into the drawing sheet's millimetres.
std::vector< std::unique_ptr< DS_DATA_ITEM > > m_dataItems
void AddSpline(const VECTOR2D &aStart, const VECTOR2D &aBezierControl1, const VECTOR2D &aBezierControl2, const VECTOR2D &aEnd, const IMPORTED_STROKE &aStroke) override
Create an object representing an arc.
void AddLine(const VECTOR2D &aOrigin, const VECTOR2D &aEnd, const IMPORTED_STROKE &aStroke) override
Create an object representing a line segment.
void AddPolygon(const std::vector< VECTOR2D > &aVertices, const IMPORTED_STROKE &aStroke, bool aFilled, const COLOR4D &aFillColor) override
Create an object representing a polygon.
void addFilledPolygon(const std::vector< VECTOR2D > &aPoints)
Add a run of points as one contour of the filled polygon item.
void AddCircle(const VECTOR2D &aCenter, double aRadius, const IMPORTED_STROKE &aStroke, bool aFilled, const COLOR4D &aFillColor) override
Create an object representing a circle.
void AddEllipse(const VECTOR2D &aCenter, double aMajorRadius, double aMinorRadius, const EDA_ANGLE &aRotation, const IMPORTED_STROKE &aStroke, bool aFilled, const COLOR4D &aFillColor=COLOR4D::UNSPECIFIED) override
Create an object representing a closed ellipse.
VECTOR2D GetScale() const
double GetLineWidthMM() const
Return the line width used for importing the outlines (in mm).
double m_millimeterToIu
Factor to convert millimeters to Internal Units.
const VECTOR2D & GetImportOffsetMM() const
A clone of IMPORTED_STROKE, but with floating-point width.
double GetWidth() const
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
@ LT_CORNER
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
static constexpr double FLATTEN_ERROR_MM
How far a flattened curve may sit from the real one, in mm.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
const int scale
int radius
GR_TEXT_H_ALIGN_T
This is API surface mapped to common.types.HorizontalAlignment.
GR_TEXT_V_ALIGN_T
This is API surface mapped to common.types.VertialAlignment.
#define M_PI
VECTOR2< double > VECTOR2D
Definition vector2d.h:707