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