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::AddShape( std::unique_ptr<IMPORTED_SHAPE>& aShape )
94{
95 m_shapes.push_back( std::move( aShape ) );
96}
97
98
100{
102
103 for( std::unique_ptr<IMPORTED_SHAPE>& shape : m_shapes )
104 {
105 BOX2D box = shape->GetBoundingBox();
106
107 if( box.IsValid() )
108 boundingBox.Merge( box );
109 }
110
112 boundingBox.GetPosition().y * aImporter.GetScale().y );
114 boundingBox.GetSize().y * aImporter.GetScale().y );
115
116 // Check that the scaled graphics fit in the KiCad numeric limits
117 if( boundingBox.GetSize().x * aImporter.GetMillimeterToIuFactor()
118 > std::numeric_limits<int>::max()
120 > std::numeric_limits<int>::max() )
121 {
122 double scale_factor = std::numeric_limits<int>::max() /
123 ( aImporter.GetMillimeterToIuFactor() + 100 );
124 double max_scale = std::max( scale_factor / boundingBox.GetSize().x,
125 scale_factor / boundingBox.GetSize().y );
126 aImporter.ReportMsg( wxString::Format( _( "Imported graphic is too large. Maximum scale is %f" ),
127 max_scale ) );
128 return;
129 }
130 // They haven't set the import offset, so we set it to the bounding box origin to keep
131 // the graphics in the KiCad drawing area.
132 else if( aImporter.GetImportOffsetMM() == VECTOR2D( 0, 0 ) )
133 {
134 if( boundingBox.GetRight() > std::numeric_limits<int>::max()
135 || boundingBox.GetBottom() > std::numeric_limits<int>::max()
136 || boundingBox.GetLeft() < std::numeric_limits<int>::min()
137 || boundingBox.GetTop() < std::numeric_limits<int>::min() )
138 {
139 VECTOR2D offset = boundingBox.GetOrigin();
140 aImporter.SetImportOffsetMM( -offset );
141 }
142 }
143 else
144 {
145 double total_scale_x = aImporter.GetScale().x * aImporter.GetMillimeterToIuFactor();
146 double total_scale_y = aImporter.GetScale().y * aImporter.GetMillimeterToIuFactor();
147
148 double max_offset_x = ( aImporter.GetImportOffsetMM().x + boundingBox.GetRight() ) * total_scale_x;
149 double max_offset_y = ( aImporter.GetImportOffsetMM().y + boundingBox.GetBottom() ) * total_scale_y;
150 double min_offset_x = ( aImporter.GetImportOffsetMM().x + boundingBox.GetLeft() ) * total_scale_x;
151 double min_offset_y = ( aImporter.GetImportOffsetMM().y + boundingBox.GetTop() ) * total_scale_y;
152
153 VECTOR2D newOffset = aImporter.GetImportOffsetMM();
154 bool needsAdjustment = false;
155
156 if( max_offset_x >= std::numeric_limits<int>::max() )
157 {
158 newOffset.x -= ( max_offset_x - std::numeric_limits<int>::max() + 100.0 ) / total_scale_x;
159 needsAdjustment = true;
160 }
161 else if( min_offset_x <= std::numeric_limits<int>::min() )
162 {
163 newOffset.x -= ( min_offset_x - std::numeric_limits<int>::min() - 100 ) / total_scale_x;
164 needsAdjustment = true;
165 }
166
167 if( max_offset_y >= std::numeric_limits<int>::max() )
168 {
169 newOffset.y -= ( max_offset_y - std::numeric_limits<int>::max() + 100 ) / total_scale_y;
170 needsAdjustment = true;
171 }
172 else if( min_offset_y <= std::numeric_limits<int>::min() )
173 {
174 newOffset.y -= ( min_offset_y - std::numeric_limits<int>::min() - 100 ) / total_scale_y;
175 needsAdjustment = true;
176 }
177
178 if( needsAdjustment )
179 {
180 aImporter.ReportMsg( wxString::Format( _( "Import offset adjusted to (%f, %f) to fit "
181 "within numeric limits" ),
182 newOffset.x, newOffset.y ) );
183 aImporter.SetImportOffsetMM( newOffset );
184 }
185 }
186
187 for( std::unique_ptr<IMPORTED_SHAPE>& shape : m_shapes )
188 shape->ImportTo( aImporter );
189}
190
191// converts a single SVG-style polygon (multiple outlines, hole detection based on orientation,
192// custom fill rule) to a format that can be digested by KiCad (single outline, fractured).
193static void convertPolygon( std::list<std::unique_ptr<IMPORTED_SHAPE>>& aShapes,
194 std::vector<IMPORTED_POLYGON*>& aPaths,
196 const IMPORTED_STROKE& aStroke, bool aFilled,
197 const COLOR4D& aFillColor )
198{
199 double minX = std::numeric_limits<double>::max();
200 double minY = minX;
201 double maxX = std::numeric_limits<double>::min();
202 double maxY = maxX;
203
204 // as Clipper/SHAPE_POLY_SET uses ints we first need to upscale to a reasonably large size
205 // (in integer coordinates) to avoid losing accuracy.
206 const double convert_scale = 1000000000.0;
207
208 for( IMPORTED_POLYGON* path : aPaths )
209 {
210 for( VECTOR2D& v : path->Vertices() )
211 {
212 minX = std::min( minX, v.x );
213 minY = std::min( minY, v.y );
214 maxX = std::max( maxX, v.x );
215 maxY = std::max( maxY, v.y );
216 }
217 }
218
219 double origW = ( maxX - minX );
220 double origH = ( maxY - minY );
221 double upscaledW, upscaledH;
222
223 wxCHECK( origH && origW, /* void */ );
224
225 if( origW > origH )
226 {
227 upscaledW = convert_scale;
228 upscaledH = ( origH == 0.0f ? 0.0 : origH * convert_scale / origW );
229 }
230 else
231 {
232 upscaledH = convert_scale;
233 upscaledW = ( origW == 0.0f ? 0.0 : origW * convert_scale / origH );
234 }
235
236 std::vector<IMPORTED_POLYGON*> openPaths;
237 std::vector<SHAPE_LINE_CHAIN> upscaledPaths;
238
239 for( IMPORTED_POLYGON* path : aPaths )
240 {
241 if( path->Vertices().size() < 3 )
242 {
243 openPaths.push_back( path );
244 continue;
245 }
246
248
249 for( VECTOR2D& v : path->Vertices() )
250 {
251 int xp = KiROUND( ( v.x - minX ) * ( upscaledW / origW ) );
252 int yp = KiROUND( ( v.y - minY ) * ( upscaledH / origH ) );
253 lc.Append( xp, yp );
254 }
255
256 lc.SetClosed( true );
257 upscaledPaths.push_back( lc );
258 }
259
260 SHAPE_POLY_SET result;
261 result.BuildPolysetFromOrientedPaths( upscaledPaths,
262 aFillRule == GRAPHICS_IMPORTER::PF_EVEN_ODD );
263 result.Fracture();
264
265 for( int outl = 0; outl < result.OutlineCount(); outl++ )
266 {
267 const SHAPE_LINE_CHAIN& ro = result.COutline( outl );
268 std::vector<VECTOR2D> pts;
269
270 for( int i = 0; i < ro.PointCount(); i++ )
271 {
272 double xp = (double) ro.CPoint( i ).x * ( origW / upscaledW ) + minX;
273 double yp = (double) ro.CPoint( i ).y * ( origH / upscaledH ) + minY;
274 pts.emplace_back( VECTOR2D( xp, yp ) );
275 }
276
277 aShapes.push_back( std::make_unique<IMPORTED_POLYGON>( pts, aStroke, aFilled, aFillColor ) );
278 }
279
280 for( IMPORTED_POLYGON* openPath : openPaths )
281 aShapes.push_back( std::make_unique<IMPORTED_POLYGON>( *openPath ) );
282}
283
284
286{
287 int curShapeIdx = -1;
288 IMPORTED_STROKE lastStroke;
289 bool lastFilled = false;
290 COLOR4D lastFillColor = COLOR4D::UNSPECIFIED;
291
292 std::list<std::unique_ptr<IMPORTED_SHAPE>> newShapes;
293 std::vector<IMPORTED_POLYGON*> polypaths;
294
295 for( std::unique_ptr<IMPORTED_SHAPE>& shape : m_shapes )
296 {
297 IMPORTED_POLYGON* poly = dynamic_cast<IMPORTED_POLYGON*>( shape.get() );
298
299 if( !poly || poly->GetParentShapeIndex() < 0 )
300 {
301 newShapes.push_back( shape->clone() );
302 continue;
303 }
304
305 int index = poly->GetParentShapeIndex();
306
307 if( index != curShapeIdx && curShapeIdx >= 0 )
308 {
309 convertPolygon( newShapes, polypaths, m_shapeFillRules[curShapeIdx], lastStroke,
310 lastFilled, lastFillColor );
311
312 polypaths.clear();
313 }
314
315 curShapeIdx = index;
316 lastStroke = poly->GetStroke();
317 lastFilled = poly->IsFilled();
318 lastFillColor = poly->GetFillColor();
319 polypaths.push_back( poly );
320 }
321
322 if( curShapeIdx >= 0 )
323 {
324 convertPolygon( newShapes, polypaths, m_shapeFillRules[curShapeIdx], lastStroke, lastFilled,
325 lastFillColor );
326 }
327
328 m_shapes.swap( newShapes );
329}
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition: box2.h:990
constexpr const Vec & GetPosition() const
Definition: box2.h:211
constexpr void SetOrigin(const Vec &pos)
Definition: box2.h:237
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition: box2.h:658
constexpr void SetSize(const SizeVec &size)
Definition: box2.h:248
constexpr coord_type GetLeft() const
Definition: box2.h:228
constexpr const Vec & GetOrigin() const
Definition: box2.h:210
constexpr bool IsValid() const
Definition: box2.h:909
constexpr coord_type GetRight() const
Definition: box2.h:217
constexpr const SizeVec & GetSize() const
Definition: box2.h:206
constexpr coord_type GetTop() const
Definition: box2.h:229
constexpr coord_type GetBottom() const
Definition: box2.h:222
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 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.
Interface that creates objects representing shapes for a given data model.
std::vector< POLY_FILL_RULE > m_shapeFillRules
void ReportMsg(const wxString &aMessage)
VECTOR2D GetScale() const
double GetMillimeterToIuFactor()
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
int GetParentShapeIndex() const
A clone of IMPORTED_STROKE, but with floating-point width.
A color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:104
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.
void Fracture()
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
void BuildPolysetFromOrientedPaths(const std::vector< SHAPE_LINE_CHAIN > &aPaths, bool aEvenOdd=false)
Build a SHAPE_POLY_SET from a bunch of outlines in provided in random order.
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
#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
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:694