KiCad PCB EDA Suite
Loading...
Searching...
No Matches
graphics_importer_buffer.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 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Janito Vaqueiro Ferreira Filho <[email protected]>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26#include <eda_item.h>
27
30
32
33using namespace std;
34
35template <typename T, typename... Args>
36static std::unique_ptr<T> make_shape( const Args&... aArguments )
37{
38 return std::make_unique<T>( aArguments... );
39}
40
41void GRAPHICS_IMPORTER_BUFFER::AddLine( const VECTOR2D& aStart, const VECTOR2D& aEnd,
42 const IMPORTED_STROKE& aStroke )
43{
44 m_shapes.push_back( make_shape<IMPORTED_LINE>( aStart, aEnd, aStroke ) );
45}
46
47
48void GRAPHICS_IMPORTER_BUFFER::AddCircle( const VECTOR2D& aCenter, double aRadius,
49 const IMPORTED_STROKE& aStroke, bool aFilled,
50 const COLOR4D& aFillColor )
51{
52 m_shapes.push_back(
53 make_shape<IMPORTED_CIRCLE>( aCenter, aRadius, aStroke, aFilled, aFillColor ) );
54}
55
56
57void GRAPHICS_IMPORTER_BUFFER::AddArc( const VECTOR2D& aCenter, const VECTOR2D& aStart,
58 const EDA_ANGLE& aAngle, const IMPORTED_STROKE& aStroke )
59{
60 m_shapes.push_back( make_shape<IMPORTED_ARC>( aCenter, aStart, aAngle, aStroke ) );
61}
62
63
64void GRAPHICS_IMPORTER_BUFFER::AddPolygon( const std::vector<VECTOR2D>& aVertices,
65 const IMPORTED_STROKE& aStroke, bool aFilled,
66 const COLOR4D& aFillColor )
67{
68 m_shapes.push_back( make_shape<IMPORTED_POLYGON>( aVertices, aStroke, aFilled, aFillColor ) );
69
70 m_shapes.back()->SetParentShapeIndex( m_shapeFillRules.size() - 1 );
71}
72
73
74void GRAPHICS_IMPORTER_BUFFER::AddText( const VECTOR2D& aOrigin, const wxString& aText,
75 double aHeight, double aWidth, double aThickness,
76 double aOrientation, GR_TEXT_H_ALIGN_T aHJustify,
77 GR_TEXT_V_ALIGN_T aVJustify, const COLOR4D& aColor )
78{
79 m_shapes.push_back( make_shape<IMPORTED_TEXT>( aOrigin, aText, aHeight, aWidth, aThickness,
80 aOrientation, aHJustify, aVJustify, aColor ) );
81}
82
83
84void GRAPHICS_IMPORTER_BUFFER::AddSpline( const VECTOR2D& aStart, const VECTOR2D& aBezierControl1,
85 const VECTOR2D& aBezierControl2, const VECTOR2D& aEnd,
86 const IMPORTED_STROKE& aStroke )
87{
88 m_shapes.push_back( make_shape<IMPORTED_SPLINE>( aStart, aBezierControl1, aBezierControl2, aEnd,
89 aStroke ) );
90}
91
92
93void GRAPHICS_IMPORTER_BUFFER::AddEllipse( const VECTOR2D& aCenter, double aMajorRadius, double aMinorRadius,
94 const EDA_ANGLE& aRotation, const IMPORTED_STROKE& aStroke, bool aFilled,
95 const COLOR4D& aFillColor )
96{
97 m_shapes.push_back( make_shape<IMPORTED_ELLIPSE>( aCenter, aMajorRadius, aMinorRadius, aRotation, aStroke, aFilled,
98 aFillColor ) );
99}
100
101
102void GRAPHICS_IMPORTER_BUFFER::AddEllipseArc( const VECTOR2D& aCenter, double aMajorRadius, double aMinorRadius,
103 const EDA_ANGLE& aRotation, const EDA_ANGLE& aStartAngle,
104 const EDA_ANGLE& aEndAngle, const IMPORTED_STROKE& aStroke )
105{
106 m_shapes.push_back( make_shape<IMPORTED_ELLIPSE_ARC>( aCenter, aMajorRadius, aMinorRadius, aRotation, aStartAngle,
107 aEndAngle, aStroke ) );
108}
109
110
111void GRAPHICS_IMPORTER_BUFFER::AddShape( std::unique_ptr<IMPORTED_SHAPE>& aShape )
112{
113 m_shapes.push_back( std::move( aShape ) );
114}
115
116
118{
120
121 for( std::unique_ptr<IMPORTED_SHAPE>& shape : m_shapes )
122 {
123 BOX2D box = shape->GetBoundingBox();
124
125 if( box.IsValid() )
126 boundingBox.Merge( box );
127 }
128
129 boundingBox.SetOrigin( boundingBox.GetPosition().x * aImporter.GetScale().x,
130 boundingBox.GetPosition().y * aImporter.GetScale().y );
131 boundingBox.SetSize( boundingBox.GetSize().x * aImporter.GetScale().x,
132 boundingBox.GetSize().y * aImporter.GetScale().y );
133
134 // Check that the scaled graphics fit in the KiCad numeric limits
135 if( boundingBox.GetSize().x * aImporter.GetMillimeterToIuFactor()
136 > std::numeric_limits<int>::max()
137 || boundingBox.GetSize().y * aImporter.GetMillimeterToIuFactor()
138 > std::numeric_limits<int>::max() )
139 {
140 double scale_factor = std::numeric_limits<int>::max() /
141 ( aImporter.GetMillimeterToIuFactor() + 100 );
142 double max_scale = std::max( scale_factor / boundingBox.GetSize().x,
143 scale_factor / boundingBox.GetSize().y );
144 aImporter.ReportMsg( wxString::Format( _( "Imported graphic is too large. Maximum scale is %f" ),
145 max_scale ) );
146 return;
147 }
148 // They haven't set the import offset, so we set it to the bounding box origin to keep
149 // the graphics in the KiCad drawing area.
150 else if( aImporter.GetImportOffsetMM() == VECTOR2D( 0, 0 ) )
151 {
152 double iuFactor = aImporter.GetMillimeterToIuFactor();
153
154 if( boundingBox.GetRight() * iuFactor > std::numeric_limits<int>::max()
155 || boundingBox.GetBottom() * iuFactor > std::numeric_limits<int>::max()
156 || boundingBox.GetLeft() * iuFactor < std::numeric_limits<int>::min()
157 || boundingBox.GetTop() * iuFactor < std::numeric_limits<int>::min() )
158 {
159 VECTOR2D offset = boundingBox.GetOrigin();
160 aImporter.SetImportOffsetMM( -offset );
161 }
162 }
163 else
164 {
165 double total_scale_x = aImporter.GetScale().x * aImporter.GetMillimeterToIuFactor();
166 double total_scale_y = aImporter.GetScale().y * aImporter.GetMillimeterToIuFactor();
167
168 double max_offset_x = ( aImporter.GetImportOffsetMM().x + boundingBox.GetRight() ) * total_scale_x;
169 double max_offset_y = ( aImporter.GetImportOffsetMM().y + boundingBox.GetBottom() ) * total_scale_y;
170 double min_offset_x = ( aImporter.GetImportOffsetMM().x + boundingBox.GetLeft() ) * total_scale_x;
171 double min_offset_y = ( aImporter.GetImportOffsetMM().y + boundingBox.GetTop() ) * total_scale_y;
172
173 VECTOR2D newOffset = aImporter.GetImportOffsetMM();
174 bool needsAdjustment = false;
175
176 if( max_offset_x >= std::numeric_limits<int>::max() )
177 {
178 newOffset.x -= ( max_offset_x - std::numeric_limits<int>::max() + 100.0 ) / total_scale_x;
179 needsAdjustment = true;
180 }
181 else if( min_offset_x <= std::numeric_limits<int>::min() )
182 {
183 newOffset.x -= ( min_offset_x - std::numeric_limits<int>::min() - 100 ) / total_scale_x;
184 needsAdjustment = true;
185 }
186
187 if( max_offset_y >= std::numeric_limits<int>::max() )
188 {
189 newOffset.y -= ( max_offset_y - std::numeric_limits<int>::max() + 100 ) / total_scale_y;
190 needsAdjustment = true;
191 }
192 else if( min_offset_y <= std::numeric_limits<int>::min() )
193 {
194 newOffset.y -= ( min_offset_y - std::numeric_limits<int>::min() - 100 ) / total_scale_y;
195 needsAdjustment = true;
196 }
197
198 if( needsAdjustment )
199 {
200 aImporter.ReportMsg( wxString::Format( _( "Import offset adjusted to (%f, %f) to fit "
201 "within numeric limits" ),
202 newOffset.x, newOffset.y ) );
203 aImporter.SetImportOffsetMM( newOffset );
204 }
205 }
206
207 for( std::unique_ptr<IMPORTED_SHAPE>& shape : m_shapes )
208 shape->ImportTo( aImporter );
209}
210
211// converts a single SVG-style polygon (multiple outlines, hole detection based on orientation,
212// custom fill rule) to a format that can be digested by KiCad (single outline, fractured).
213static void convertPolygon( std::list<std::unique_ptr<IMPORTED_SHAPE>>& aShapes,
214 std::vector<IMPORTED_POLYGON*>& aPaths,
216 const IMPORTED_STROKE& aStroke, bool aFilled,
217 const COLOR4D& aFillColor )
218{
219 double minX = std::numeric_limits<double>::max();
220 double minY = minX;
221 double maxX = std::numeric_limits<double>::min();
222 double maxY = maxX;
223
224 // as Clipper/SHAPE_POLY_SET uses ints we first need to upscale to a reasonably large size
225 // (in integer coordinates) to avoid losing accuracy.
226 const double convert_scale = 1000000000.0;
227
228 for( IMPORTED_POLYGON* path : aPaths )
229 {
230 for( VECTOR2D& v : path->Vertices() )
231 {
232 minX = std::min( minX, v.x );
233 minY = std::min( minY, v.y );
234 maxX = std::max( maxX, v.x );
235 maxY = std::max( maxY, v.y );
236 }
237 }
238
239 double origW = ( maxX - minX );
240 double origH = ( maxY - minY );
241 double upscaledW, upscaledH;
242
243 wxCHECK( origH && origW, /* void */ );
244
245 if( origW > origH )
246 {
247 upscaledW = convert_scale;
248 upscaledH = ( origH == 0.0f ? 0.0 : origH * convert_scale / origW );
249 }
250 else
251 {
252 upscaledH = convert_scale;
253 upscaledW = ( origW == 0.0f ? 0.0 : origW * convert_scale / origH );
254 }
255
256 std::vector<IMPORTED_POLYGON*> openPaths;
257 std::vector<SHAPE_LINE_CHAIN> upscaledPaths;
258
259 for( IMPORTED_POLYGON* path : aPaths )
260 {
261 if( path->Vertices().size() < 3 )
262 {
263 openPaths.push_back( path );
264 continue;
265 }
266
268
269 for( VECTOR2D& v : path->Vertices() )
270 {
271 int xp = KiROUND( ( v.x - minX ) * ( upscaledW / origW ) );
272 int yp = KiROUND( ( v.y - minY ) * ( upscaledH / origH ) );
273 lc.Append( xp, yp );
274 }
275
276 lc.SetClosed( true );
277 upscaledPaths.push_back( lc );
278 }
279
281 result.BuildPolysetFromOrientedPaths( upscaledPaths,
282 aFillRule == GRAPHICS_IMPORTER::PF_EVEN_ODD );
283 result.Fracture();
284
285 for( int outl = 0; outl < result.OutlineCount(); outl++ )
286 {
287 const SHAPE_LINE_CHAIN& ro = result.COutline( outl );
288 std::vector<VECTOR2D> pts;
289
290 for( int i = 0; i < ro.PointCount(); i++ )
291 {
292 double xp = (double) ro.CPoint( i ).x * ( origW / upscaledW ) + minX;
293 double yp = (double) ro.CPoint( i ).y * ( origH / upscaledH ) + minY;
294 pts.emplace_back( VECTOR2D( xp, yp ) );
295 }
296
297 aShapes.push_back( std::make_unique<IMPORTED_POLYGON>( pts, aStroke, aFilled, aFillColor ) );
298 }
299
300 for( IMPORTED_POLYGON* openPath : openPaths )
301 aShapes.push_back( std::make_unique<IMPORTED_POLYGON>( *openPath ) );
302}
303
304
306{
307 int curShapeIdx = -1;
308 IMPORTED_STROKE lastStroke;
309 bool lastFilled = false;
310 COLOR4D lastFillColor = COLOR4D::UNSPECIFIED;
311
312 std::list<std::unique_ptr<IMPORTED_SHAPE>> newShapes;
313 std::vector<IMPORTED_POLYGON*> polypaths;
314
315 for( std::unique_ptr<IMPORTED_SHAPE>& shape : m_shapes )
316 {
317 IMPORTED_POLYGON* poly = dynamic_cast<IMPORTED_POLYGON*>( shape.get() );
318
319 if( !poly || poly->GetParentShapeIndex() < 0 )
320 {
321 newShapes.push_back( shape->clone() );
322 continue;
323 }
324
325 int index = poly->GetParentShapeIndex();
326
327 if( index != curShapeIdx && curShapeIdx >= 0 )
328 {
329 convertPolygon( newShapes, polypaths, m_shapeFillRules[curShapeIdx], lastStroke,
330 lastFilled, lastFillColor );
331
332 polypaths.clear();
333 }
334
335 curShapeIdx = index;
336 lastStroke = poly->GetStroke();
337 lastFilled = poly->IsFilled();
338 lastFillColor = poly->GetFillColor();
339 polypaths.push_back( poly );
340 }
341
342 if( curShapeIdx >= 0 )
343 {
344 convertPolygon( newShapes, polypaths, m_shapeFillRules[curShapeIdx], lastStroke, lastFilled,
345 lastFillColor );
346 }
347
348 m_shapes.swap( newShapes );
349}
int index
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
BOX2< VECTOR2D > BOX2D
Definition box2.h:923
constexpr bool IsValid() const
Definition box2.h:909
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:402
void AddCircle(const VECTOR2D &aCenter, double aRadius, const IMPORTED_STROKE &aStroke, bool aFilled, const COLOR4D &aFillColor=COLOR4D::UNSPECIFIED) override
Create an object representing a circle.
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 &aStart, const VECTOR2D &aEnd, const IMPORTED_STROKE &aStroke) override
Create an object representing a line segment.
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.
void ImportTo(GRAPHICS_IMPORTER &aImporter)
void AddArc(const VECTOR2D &aCenter, const VECTOR2D &aStart, const EDA_ANGLE &aAngle, const IMPORTED_STROKE &aStroke) override
Create an object representing an 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=COLOR4D::UNSPECIFIED) override
Create an object representing a text.
std::list< std::unique_ptr< IMPORTED_SHAPE > > m_shapes
List of imported shapes.
void AddShape(std::unique_ptr< IMPORTED_SHAPE > &aShape)
void AddPolygon(const std::vector< VECTOR2D > &aVertices, const IMPORTED_STROKE &aStroke, bool aFilled, const COLOR4D &aFillColor=COLOR4D::UNSPECIFIED) override
Create an object representing a polygon.
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.
std::vector< POLY_FILL_RULE > m_shapeFillRules
void ReportMsg(const wxString &aMessage)
VECTOR2D GetScale() const
void SetImportOffsetMM(const VECTOR2D &aOffset)
Set the offset in millimeters to add to coordinates when importing graphic items.
const VECTOR2D & GetImportOffsetMM() const
const IMPORTED_STROKE & GetStroke() const
const COLOR4D & GetFillColor() const
A clone of IMPORTED_STROKE, but with floating-point width.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:105
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
Represent a set of closed polygons.
#define _(s)
static std::unique_ptr< T > make_shape(const Args &... aArguments)
static void convertPolygon(std::list< std::unique_ptr< IMPORTED_SHAPE > > &aShapes, std::vector< IMPORTED_POLYGON * > &aPaths, GRAPHICS_IMPORTER::POLY_FILL_RULE aFillRule, const IMPORTED_STROKE &aStroke, bool aFilled, const COLOR4D &aFillColor)
STL namespace.
BOX2I boundingBox(T aObject, int aLayer)
Used by SHAPE_INDEX to get the bounding box of a generic T object.
Definition shape_index.h:62
std::string path
wxString result
Test unit parsing edge cases and error handling.
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.
VECTOR2< double > VECTOR2D
Definition vector2d.h:686