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
111 boundingBox.SetOrigin( boundingBox.GetPosition().x * aImporter.GetScale().x,
112 boundingBox.GetPosition().y * aImporter.GetScale().y );
113 boundingBox.SetSize( boundingBox.GetSize().x * aImporter.GetScale().x,
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()
119 || boundingBox.GetSize().y * aImporter.GetMillimeterToIuFactor()
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 double iuFactor = aImporter.GetMillimeterToIuFactor();
135
136 if( boundingBox.GetRight() * iuFactor > std::numeric_limits<int>::max()
137 || boundingBox.GetBottom() * iuFactor > std::numeric_limits<int>::max()
138 || boundingBox.GetLeft() * iuFactor < std::numeric_limits<int>::min()
139 || boundingBox.GetTop() * iuFactor < std::numeric_limits<int>::min() )
140 {
141 VECTOR2D offset = boundingBox.GetOrigin();
142 aImporter.SetImportOffsetMM( -offset );
143 }
144 }
145 else
146 {
147 double total_scale_x = aImporter.GetScale().x * aImporter.GetMillimeterToIuFactor();
148 double total_scale_y = aImporter.GetScale().y * aImporter.GetMillimeterToIuFactor();
149
150 double max_offset_x = ( aImporter.GetImportOffsetMM().x + boundingBox.GetRight() ) * total_scale_x;
151 double max_offset_y = ( aImporter.GetImportOffsetMM().y + boundingBox.GetBottom() ) * total_scale_y;
152 double min_offset_x = ( aImporter.GetImportOffsetMM().x + boundingBox.GetLeft() ) * total_scale_x;
153 double min_offset_y = ( aImporter.GetImportOffsetMM().y + boundingBox.GetTop() ) * total_scale_y;
154
155 VECTOR2D newOffset = aImporter.GetImportOffsetMM();
156 bool needsAdjustment = false;
157
158 if( max_offset_x >= std::numeric_limits<int>::max() )
159 {
160 newOffset.x -= ( max_offset_x - std::numeric_limits<int>::max() + 100.0 ) / total_scale_x;
161 needsAdjustment = true;
162 }
163 else if( min_offset_x <= std::numeric_limits<int>::min() )
164 {
165 newOffset.x -= ( min_offset_x - std::numeric_limits<int>::min() - 100 ) / total_scale_x;
166 needsAdjustment = true;
167 }
168
169 if( max_offset_y >= std::numeric_limits<int>::max() )
170 {
171 newOffset.y -= ( max_offset_y - std::numeric_limits<int>::max() + 100 ) / total_scale_y;
172 needsAdjustment = true;
173 }
174 else if( min_offset_y <= std::numeric_limits<int>::min() )
175 {
176 newOffset.y -= ( min_offset_y - std::numeric_limits<int>::min() - 100 ) / total_scale_y;
177 needsAdjustment = true;
178 }
179
180 if( needsAdjustment )
181 {
182 aImporter.ReportMsg( wxString::Format( _( "Import offset adjusted to (%f, %f) to fit "
183 "within numeric limits" ),
184 newOffset.x, newOffset.y ) );
185 aImporter.SetImportOffsetMM( newOffset );
186 }
187 }
188
189 for( std::unique_ptr<IMPORTED_SHAPE>& shape : m_shapes )
190 shape->ImportTo( aImporter );
191}
192
193// converts a single SVG-style polygon (multiple outlines, hole detection based on orientation,
194// custom fill rule) to a format that can be digested by KiCad (single outline, fractured).
195static void convertPolygon( std::list<std::unique_ptr<IMPORTED_SHAPE>>& aShapes,
196 std::vector<IMPORTED_POLYGON*>& aPaths,
198 const IMPORTED_STROKE& aStroke, bool aFilled,
199 const COLOR4D& aFillColor )
200{
201 double minX = std::numeric_limits<double>::max();
202 double minY = minX;
203 double maxX = std::numeric_limits<double>::min();
204 double maxY = maxX;
205
206 // as Clipper/SHAPE_POLY_SET uses ints we first need to upscale to a reasonably large size
207 // (in integer coordinates) to avoid losing accuracy.
208 const double convert_scale = 1000000000.0;
209
210 for( IMPORTED_POLYGON* path : aPaths )
211 {
212 for( VECTOR2D& v : path->Vertices() )
213 {
214 minX = std::min( minX, v.x );
215 minY = std::min( minY, v.y );
216 maxX = std::max( maxX, v.x );
217 maxY = std::max( maxY, v.y );
218 }
219 }
220
221 double origW = ( maxX - minX );
222 double origH = ( maxY - minY );
223 double upscaledW, upscaledH;
224
225 wxCHECK( origH && origW, /* void */ );
226
227 if( origW > origH )
228 {
229 upscaledW = convert_scale;
230 upscaledH = ( origH == 0.0f ? 0.0 : origH * convert_scale / origW );
231 }
232 else
233 {
234 upscaledH = convert_scale;
235 upscaledW = ( origW == 0.0f ? 0.0 : origW * convert_scale / origH );
236 }
237
238 std::vector<IMPORTED_POLYGON*> openPaths;
239 std::vector<SHAPE_LINE_CHAIN> upscaledPaths;
240
241 for( IMPORTED_POLYGON* path : aPaths )
242 {
243 if( path->Vertices().size() < 3 )
244 {
245 openPaths.push_back( path );
246 continue;
247 }
248
250
251 for( VECTOR2D& v : path->Vertices() )
252 {
253 int xp = KiROUND( ( v.x - minX ) * ( upscaledW / origW ) );
254 int yp = KiROUND( ( v.y - minY ) * ( upscaledH / origH ) );
255 lc.Append( xp, yp );
256 }
257
258 lc.SetClosed( true );
259 upscaledPaths.push_back( lc );
260 }
261
263 result.BuildPolysetFromOrientedPaths( upscaledPaths,
264 aFillRule == GRAPHICS_IMPORTER::PF_EVEN_ODD );
265 result.Fracture();
266
267 for( int outl = 0; outl < result.OutlineCount(); outl++ )
268 {
269 const SHAPE_LINE_CHAIN& ro = result.COutline( outl );
270 std::vector<VECTOR2D> pts;
271
272 for( int i = 0; i < ro.PointCount(); i++ )
273 {
274 double xp = (double) ro.CPoint( i ).x * ( origW / upscaledW ) + minX;
275 double yp = (double) ro.CPoint( i ).y * ( origH / upscaledH ) + minY;
276 pts.emplace_back( VECTOR2D( xp, yp ) );
277 }
278
279 aShapes.push_back( std::make_unique<IMPORTED_POLYGON>( pts, aStroke, aFilled, aFillColor ) );
280 }
281
282 for( IMPORTED_POLYGON* openPath : openPaths )
283 aShapes.push_back( std::make_unique<IMPORTED_POLYGON>( *openPath ) );
284}
285
286
288{
289 int curShapeIdx = -1;
290 IMPORTED_STROKE lastStroke;
291 bool lastFilled = false;
292 COLOR4D lastFillColor = COLOR4D::UNSPECIFIED;
293
294 std::list<std::unique_ptr<IMPORTED_SHAPE>> newShapes;
295 std::vector<IMPORTED_POLYGON*> polypaths;
296
297 for( std::unique_ptr<IMPORTED_SHAPE>& shape : m_shapes )
298 {
299 IMPORTED_POLYGON* poly = dynamic_cast<IMPORTED_POLYGON*>( shape.get() );
300
301 if( !poly || poly->GetParentShapeIndex() < 0 )
302 {
303 newShapes.push_back( shape->clone() );
304 continue;
305 }
306
307 int index = poly->GetParentShapeIndex();
308
309 if( index != curShapeIdx && curShapeIdx >= 0 )
310 {
311 convertPolygon( newShapes, polypaths, m_shapeFillRules[curShapeIdx], lastStroke,
312 lastFilled, lastFillColor );
313
314 polypaths.clear();
315 }
316
317 curShapeIdx = index;
318 lastStroke = poly->GetStroke();
319 lastFilled = poly->IsFilled();
320 lastFillColor = poly->GetFillColor();
321 polypaths.push_back( poly );
322 }
323
324 if( curShapeIdx >= 0 )
325 {
326 convertPolygon( newShapes, polypaths, m_shapeFillRules[curShapeIdx], lastStroke, lastFilled,
327 lastFillColor );
328 }
329
330 m_shapes.swap( newShapes );
331}
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 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.
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:694