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
33#include <algorithm>
34
35using namespace std;
36
37template <typename T, typename... Args>
38static std::unique_ptr<T> make_shape( const Args&... aArguments )
39{
40 return std::make_unique<T>( aArguments... );
41}
42
43void GRAPHICS_IMPORTER_BUFFER::AddLine( const VECTOR2D& aStart, const VECTOR2D& aEnd,
44 const IMPORTED_STROKE& aStroke )
45{
46 m_shapes.push_back( make_shape<IMPORTED_LINE>( aStart, aEnd, aStroke ) );
47 m_shapes.back()->SetSourceLayer( m_currentSourceLayer );
48}
49
50
51void GRAPHICS_IMPORTER_BUFFER::AddCircle( const VECTOR2D& aCenter, double aRadius,
52 const IMPORTED_STROKE& aStroke, bool aFilled,
53 const COLOR4D& aFillColor )
54{
55 m_shapes.push_back(
56 make_shape<IMPORTED_CIRCLE>( aCenter, aRadius, aStroke, aFilled, aFillColor ) );
57 m_shapes.back()->SetSourceLayer( m_currentSourceLayer );
58}
59
60
61void GRAPHICS_IMPORTER_BUFFER::AddArc( const VECTOR2D& aCenter, const VECTOR2D& aStart,
62 const EDA_ANGLE& aAngle, const IMPORTED_STROKE& aStroke )
63{
64 m_shapes.push_back( make_shape<IMPORTED_ARC>( aCenter, aStart, aAngle, aStroke ) );
65 m_shapes.back()->SetSourceLayer( m_currentSourceLayer );
66}
67
68
69void GRAPHICS_IMPORTER_BUFFER::AddPolygon( const std::vector<VECTOR2D>& aVertices,
70 const IMPORTED_STROKE& aStroke, bool aFilled,
71 const COLOR4D& aFillColor )
72{
73 m_shapes.push_back( make_shape<IMPORTED_POLYGON>( aVertices, aStroke, aFilled, aFillColor ) );
74 m_shapes.back()->SetSourceLayer( m_currentSourceLayer );
75
76 m_shapes.back()->SetParentShapeIndex( m_shapeFillRules.size() - 1 );
77}
78
79
80void GRAPHICS_IMPORTER_BUFFER::AddText( const VECTOR2D& aOrigin, const wxString& aText,
81 double aHeight, double aWidth, double aThickness,
82 double aOrientation, GR_TEXT_H_ALIGN_T aHJustify,
83 GR_TEXT_V_ALIGN_T aVJustify, const COLOR4D& aColor )
84{
85 m_shapes.push_back( make_shape<IMPORTED_TEXT>( aOrigin, aText, aHeight, aWidth, aThickness,
86 aOrientation, aHJustify, aVJustify, aColor ) );
87 m_shapes.back()->SetSourceLayer( m_currentSourceLayer );
88}
89
90
91void GRAPHICS_IMPORTER_BUFFER::AddSpline( const VECTOR2D& aStart, const VECTOR2D& aBezierControl1,
92 const VECTOR2D& aBezierControl2, const VECTOR2D& aEnd,
93 const IMPORTED_STROKE& aStroke )
94{
95 m_shapes.push_back( make_shape<IMPORTED_SPLINE>( aStart, aBezierControl1, aBezierControl2, aEnd,
96 aStroke ) );
97 m_shapes.back()->SetSourceLayer( m_currentSourceLayer );
98}
99
100
101void GRAPHICS_IMPORTER_BUFFER::AddEllipse( const VECTOR2D& aCenter, double aMajorRadius, double aMinorRadius,
102 const EDA_ANGLE& aRotation, const IMPORTED_STROKE& aStroke, bool aFilled,
103 const COLOR4D& aFillColor )
104{
105 m_shapes.push_back( make_shape<IMPORTED_ELLIPSE>( aCenter, aMajorRadius, aMinorRadius, aRotation, aStroke, aFilled,
106 aFillColor ) );
107 m_shapes.back()->SetSourceLayer( m_currentSourceLayer );
108}
109
110
111void GRAPHICS_IMPORTER_BUFFER::AddEllipseArc( const VECTOR2D& aCenter, double aMajorRadius, double aMinorRadius,
112 const EDA_ANGLE& aRotation, const EDA_ANGLE& aStartAngle,
113 const EDA_ANGLE& aEndAngle, const IMPORTED_STROKE& aStroke )
114{
115 m_shapes.push_back( make_shape<IMPORTED_ELLIPSE_ARC>( aCenter, aMajorRadius, aMinorRadius, aRotation, aStartAngle,
116 aEndAngle, aStroke ) );
117 m_shapes.back()->SetSourceLayer( m_currentSourceLayer );
118}
119
120
121void GRAPHICS_IMPORTER_BUFFER::AddShape( std::unique_ptr<IMPORTED_SHAPE>& aShape )
122{
123 if( aShape && aShape->GetSourceLayer().IsEmpty() )
124 aShape->SetSourceLayer( m_currentSourceLayer );
125
126 m_shapes.push_back( std::move( aShape ) );
127}
128
129
130std::vector<wxString> GRAPHICS_IMPORTER_BUFFER::GetSourceLayers() const
131{
132 std::vector<wxString> sourceLayers;
133
134 for( const std::unique_ptr<IMPORTED_SHAPE>& shape : m_shapes )
135 {
136 const wxString& sourceLayer = shape->GetSourceLayer();
137
138 if( sourceLayer.IsEmpty() )
139 continue;
140
141 if( std::find( sourceLayers.begin(), sourceLayers.end(), sourceLayer ) == sourceLayers.end() )
142 sourceLayers.push_back( sourceLayer );
143 }
144
145 return sourceLayers;
146}
147
148
150{
152
153 for( std::unique_ptr<IMPORTED_SHAPE>& shape : m_shapes )
154 {
155 if( !aImporter.CanImportSourceLayer( shape->GetSourceLayer() ) )
156 continue;
157
158 BOX2D box = shape->GetBoundingBox();
159
160 if( box.IsValid() )
161 boundingBox.Merge( box );
162 }
163
164 if( !boundingBox.IsValid() )
165 return;
166
167 boundingBox.SetOrigin( boundingBox.GetPosition().x * aImporter.GetScale().x,
168 boundingBox.GetPosition().y * aImporter.GetScale().y );
169 boundingBox.SetSize( boundingBox.GetSize().x * aImporter.GetScale().x,
170 boundingBox.GetSize().y * aImporter.GetScale().y );
171
172 // Check that the scaled graphics fit in the KiCad numeric limits
173 if( boundingBox.GetSize().x * aImporter.GetMillimeterToIuFactor()
174 > std::numeric_limits<int>::max()
175 || boundingBox.GetSize().y * aImporter.GetMillimeterToIuFactor()
176 > std::numeric_limits<int>::max() )
177 {
178 double scale_factor = std::numeric_limits<int>::max() /
179 ( aImporter.GetMillimeterToIuFactor() + 100 );
180 double max_scale = std::max( scale_factor / boundingBox.GetSize().x,
181 scale_factor / boundingBox.GetSize().y );
182 aImporter.ReportMsg( wxString::Format( _( "Imported graphic is too large. Maximum scale is %f" ),
183 max_scale ) );
184 return;
185 }
186 // They haven't set the import offset, so we set it to the bounding box origin to keep
187 // the graphics in the KiCad drawing area.
188 else if( aImporter.GetImportOffsetMM() == VECTOR2D( 0, 0 ) )
189 {
190 double iuFactor = aImporter.GetMillimeterToIuFactor();
191
192 if( boundingBox.GetRight() * iuFactor > std::numeric_limits<int>::max()
193 || boundingBox.GetBottom() * iuFactor > std::numeric_limits<int>::max()
194 || boundingBox.GetLeft() * iuFactor < std::numeric_limits<int>::min()
195 || boundingBox.GetTop() * iuFactor < std::numeric_limits<int>::min() )
196 {
197 VECTOR2D offset = boundingBox.GetOrigin();
198 aImporter.SetImportOffsetMM( -offset );
199 }
200 }
201 else
202 {
203 double total_scale_x = aImporter.GetScale().x * aImporter.GetMillimeterToIuFactor();
204 double total_scale_y = aImporter.GetScale().y * aImporter.GetMillimeterToIuFactor();
205
206 double max_offset_x = ( aImporter.GetImportOffsetMM().x + boundingBox.GetRight() ) * total_scale_x;
207 double max_offset_y = ( aImporter.GetImportOffsetMM().y + boundingBox.GetBottom() ) * total_scale_y;
208 double min_offset_x = ( aImporter.GetImportOffsetMM().x + boundingBox.GetLeft() ) * total_scale_x;
209 double min_offset_y = ( aImporter.GetImportOffsetMM().y + boundingBox.GetTop() ) * total_scale_y;
210
211 VECTOR2D newOffset = aImporter.GetImportOffsetMM();
212 bool needsAdjustment = false;
213
214 if( max_offset_x >= std::numeric_limits<int>::max() )
215 {
216 newOffset.x -= ( max_offset_x - std::numeric_limits<int>::max() + 100.0 ) / total_scale_x;
217 needsAdjustment = true;
218 }
219 else if( min_offset_x <= std::numeric_limits<int>::min() )
220 {
221 newOffset.x -= ( min_offset_x - std::numeric_limits<int>::min() - 100 ) / total_scale_x;
222 needsAdjustment = true;
223 }
224
225 if( max_offset_y >= std::numeric_limits<int>::max() )
226 {
227 newOffset.y -= ( max_offset_y - std::numeric_limits<int>::max() + 100 ) / total_scale_y;
228 needsAdjustment = true;
229 }
230 else if( min_offset_y <= std::numeric_limits<int>::min() )
231 {
232 newOffset.y -= ( min_offset_y - std::numeric_limits<int>::min() - 100 ) / total_scale_y;
233 needsAdjustment = true;
234 }
235
236 if( needsAdjustment )
237 {
238 aImporter.ReportMsg( wxString::Format( _( "Import offset adjusted to (%f, %f) to fit "
239 "within numeric limits" ),
240 newOffset.x, newOffset.y ) );
241 aImporter.SetImportOffsetMM( newOffset );
242 }
243 }
244
245 for( std::unique_ptr<IMPORTED_SHAPE>& shape : m_shapes )
246 {
247 if( !aImporter.CanImportSourceLayer( shape->GetSourceLayer() ) )
248 continue;
249
250 aImporter.SetCurrentSourceLayer( shape->GetSourceLayer() );
251 shape->ImportTo( aImporter );
252 }
253
254 aImporter.SetCurrentSourceLayer( wxEmptyString );
255}
256
257// converts a single SVG-style polygon (multiple outlines, hole detection based on orientation,
258// custom fill rule) to a format that can be digested by KiCad (single outline, fractured).
259static void convertPolygon( std::list<std::unique_ptr<IMPORTED_SHAPE>>& aShapes,
260 std::vector<IMPORTED_POLYGON*>& aPaths,
262 const IMPORTED_STROKE& aStroke, bool aFilled,
263 const COLOR4D& aFillColor )
264{
265 double minX = std::numeric_limits<double>::max();
266 double minY = minX;
267 double maxX = std::numeric_limits<double>::min();
268 double maxY = maxX;
269
270 // as Clipper/SHAPE_POLY_SET uses ints we first need to upscale to a reasonably large size
271 // (in integer coordinates) to avoid losing accuracy.
272 const double convert_scale = 1000000000.0;
273
274 for( IMPORTED_POLYGON* path : aPaths )
275 {
276 for( VECTOR2D& v : path->Vertices() )
277 {
278 minX = std::min( minX, v.x );
279 minY = std::min( minY, v.y );
280 maxX = std::max( maxX, v.x );
281 maxY = std::max( maxY, v.y );
282 }
283 }
284
285 double origW = ( maxX - minX );
286 double origH = ( maxY - minY );
287 double upscaledW, upscaledH;
288
289 wxCHECK( origH && origW, /* void */ );
290
291 if( origW > origH )
292 {
293 upscaledW = convert_scale;
294 upscaledH = ( origH == 0.0f ? 0.0 : origH * convert_scale / origW );
295 }
296 else
297 {
298 upscaledH = convert_scale;
299 upscaledW = ( origW == 0.0f ? 0.0 : origW * convert_scale / origH );
300 }
301
302 std::vector<IMPORTED_POLYGON*> openPaths;
303 std::vector<SHAPE_LINE_CHAIN> upscaledPaths;
304
305 for( IMPORTED_POLYGON* path : aPaths )
306 {
307 if( path->Vertices().size() < 3 )
308 {
309 openPaths.push_back( path );
310 continue;
311 }
312
314
315 for( VECTOR2D& v : path->Vertices() )
316 {
317 int xp = KiROUND( ( v.x - minX ) * ( upscaledW / origW ) );
318 int yp = KiROUND( ( v.y - minY ) * ( upscaledH / origH ) );
319 lc.Append( xp, yp );
320 }
321
322 lc.SetClosed( true );
323 upscaledPaths.push_back( lc );
324 }
325
327 result.BuildPolysetFromOrientedPaths( upscaledPaths,
328 aFillRule == GRAPHICS_IMPORTER::PF_EVEN_ODD );
329 result.Fracture();
330
331 for( int outl = 0; outl < result.OutlineCount(); outl++ )
332 {
333 const SHAPE_LINE_CHAIN& ro = result.COutline( outl );
334 std::vector<VECTOR2D> pts;
335
336 for( int i = 0; i < ro.PointCount(); i++ )
337 {
338 double xp = (double) ro.CPoint( i ).x * ( origW / upscaledW ) + minX;
339 double yp = (double) ro.CPoint( i ).y * ( origH / upscaledH ) + minY;
340 pts.emplace_back( VECTOR2D( xp, yp ) );
341 }
342
343 aShapes.push_back( std::make_unique<IMPORTED_POLYGON>( pts, aStroke, aFilled, aFillColor ) );
344 }
345
346 for( IMPORTED_POLYGON* openPath : openPaths )
347 aShapes.push_back( std::make_unique<IMPORTED_POLYGON>( *openPath ) );
348}
349
350
352{
353 int curShapeIdx = -1;
354 IMPORTED_STROKE lastStroke;
355 bool lastFilled = false;
356 COLOR4D lastFillColor = COLOR4D::UNSPECIFIED;
357
358 std::list<std::unique_ptr<IMPORTED_SHAPE>> newShapes;
359 std::vector<IMPORTED_POLYGON*> polypaths;
360
361 for( std::unique_ptr<IMPORTED_SHAPE>& shape : m_shapes )
362 {
363 IMPORTED_POLYGON* poly = dynamic_cast<IMPORTED_POLYGON*>( shape.get() );
364
365 if( !poly || poly->GetParentShapeIndex() < 0 )
366 {
367 newShapes.push_back( shape->clone() );
368 continue;
369 }
370
371 int index = poly->GetParentShapeIndex();
372
373 if( index != curShapeIdx && curShapeIdx >= 0 )
374 {
375 convertPolygon( newShapes, polypaths, m_shapeFillRules[curShapeIdx], lastStroke,
376 lastFilled, lastFillColor );
377
378 polypaths.clear();
379 }
380
381 curShapeIdx = index;
382 lastStroke = poly->GetStroke();
383 lastFilled = poly->IsFilled();
384 lastFillColor = poly->GetFillColor();
385 polypaths.push_back( poly );
386 }
387
388 if( curShapeIdx >= 0 )
389 {
390 convertPolygon( newShapes, polypaths, m_shapeFillRules[curShapeIdx], lastStroke, lastFilled,
391 lastFillColor );
392 }
393
394 m_shapes.swap( newShapes );
395}
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)
std::vector< wxString > GetSourceLayers() const
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
virtual void SetCurrentSourceLayer(const wxString &)
Set the source layer for the next buffered shape to be imported.
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.
virtual bool CanImportSourceLayer(const wxString &) const
Return true if shapes from a given source layer should be imported.
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