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