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