KiCad PCB EDA Suite
Loading...
Searching...
No Matches
3d_placeholder_utils.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
26#include <footprint.h>
27#include <pad.h>
28#include <board_item.h>
29#include <layer_ids.h>
30#include <base_units.h>
31#include <geometry/eda_angle.h>
33#include <pcb_shape.h>
36#include <wx/log.h>
37
39{
40 BOX2I localBox;
41 bool hasLocalBounds = false;
42
43 for( PAD* pad : aFootprint->Pads() )
44 {
45 VECTOR2I padPos = pad->GetFPRelativePosition();
46 VECTOR2I padSize = pad->GetSize( PADSTACK::ALL_LAYERS );
47
48 BOX2I padBox;
49 padBox.SetOrigin( padPos - padSize / 2 );
50 padBox.SetSize( padSize );
51
52 if( !hasLocalBounds )
53 {
54 localBox = padBox;
55 hasLocalBounds = true;
56 }
57 else
58 {
59 localBox.Merge( padBox );
60 }
61 }
62
63 if( !hasLocalBounds )
64 {
65 VECTOR2I fpPos = aFootprint->GetPosition();
66 EDA_ANGLE fpAngle = aFootprint->GetOrientation();
67
68 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
69 {
70 PCB_LAYER_ID layer = item->GetLayer();
71
72 if( layer == F_Fab || layer == B_Fab || layer == F_CrtYd || layer == B_CrtYd || layer == F_SilkS
73 || layer == B_SilkS )
74 {
75 BOX2I itemBox = item->GetBoundingBox();
76
77 VECTOR2I corners[4] = { itemBox.GetOrigin() - fpPos,
78 VECTOR2I( itemBox.GetRight(), itemBox.GetTop() ) - fpPos,
79 VECTOR2I( itemBox.GetRight(), itemBox.GetBottom() ) - fpPos,
80 VECTOR2I( itemBox.GetLeft(), itemBox.GetBottom() ) - fpPos };
81
82 BOX2I localItemBox;
83
84 for( int ci = 0; ci < 4; ++ci )
85 {
86 RotatePoint( corners[ci], -fpAngle );
87
88 if( ci == 0 )
89 {
90 localItemBox.SetOrigin( corners[ci] );
91 localItemBox.SetSize( VECTOR2I( 0, 0 ) );
92 }
93 else
94 {
95 localItemBox.Merge( BOX2I( corners[ci], VECTOR2I( 0, 0 ) ) );
96 }
97 }
98
99 if( !hasLocalBounds )
100 {
101 localBox = localItemBox;
102 hasLocalBounds = true;
103 }
104 else
105 {
106 localBox.Merge( localItemBox );
107 }
108 }
109 }
110 }
111
112 if( !hasLocalBounds )
113 {
114 BOX2I fpBox = aFootprint->GetBoundingBox( false );
115 int size = std::min( fpBox.GetWidth(), fpBox.GetHeight() );
116 localBox.SetOrigin( VECTOR2I( -size / 2, -size / 2 ) );
117 localBox.SetSize( VECTOR2I( size, size ) );
118 }
119
120 return localBox;
121}
122
123
131static bool buildFilledPolygonFromShapes( const FOOTPRINT* aFootprint, PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aOutline )
132{
133 std::vector<PCB_SHAPE*> closedShapes;
134 std::vector<PCB_SHAPE*> openShapes;
135
136 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
137 {
138 if( item->GetLayer() == aLayer && item->Type() == PCB_SHAPE_T )
139 {
140 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
141
142 if( shape->GetShape() == SHAPE_T::CIRCLE || shape->GetShape() == SHAPE_T::RECTANGLE
143 || shape->GetShape() == SHAPE_T::POLY )
144 {
145 closedShapes.push_back( shape );
146 }
147 else
148 {
149 openShapes.push_back( shape );
150 }
151 }
152 }
153
154 if( closedShapes.empty() && openShapes.empty() )
155 return false;
156
157 int maxError = pcbIUScale.mmToIU( 0.005 );
158
159 if( !openShapes.empty() )
160 {
161 int chainingEpsilon = pcbIUScale.mmToIU( 0.02 );
162
163 if( ConvertOutlineToPolygon( openShapes, aOutline, maxError, chainingEpsilon, true, nullptr )
164 && aOutline.OutlineCount() > 0 )
165 {
166 aOutline.Simplify();
167 }
168 else
169 {
170 aOutline.RemoveAllContours();
171
172 SHAPE_POLY_SET strokes;
173
174 for( PCB_SHAPE* shape : openShapes )
175 shape->TransformShapeToPolygon( strokes, aLayer, 0, maxError, ERROR_INSIDE );
176
177 if( strokes.OutlineCount() > 0 )
178 {
179 strokes.Simplify();
180
181 for( int i = 0; i < strokes.OutlineCount(); i++ )
182 aOutline.AddOutline( strokes.COutline( i ) );
183 }
184 }
185 }
186
187 for( const PCB_SHAPE* shape : closedShapes )
188 {
189 switch( shape->GetShape() )
190 {
191 case SHAPE_T::CIRCLE:
192 {
193 TransformCircleToPolygon( aOutline, shape->GetCenter(), shape->GetRadius(), ARC_HIGH_DEF, ERROR_INSIDE );
194 break;
195 }
197 {
198 std::vector<VECTOR2I> corners = shape->GetRectCorners();
199
200 if( corners.size() == 4 )
201 {
202 aOutline.NewOutline();
203
204 for( const VECTOR2I& pt : corners )
205 aOutline.Append( pt );
206 }
207
208 break;
209 }
210 case SHAPE_T::POLY:
211 {
212 const SHAPE_POLY_SET& polyShape = shape->GetPolyShape();
213
214 if( polyShape.OutlineCount() > 0 )
215 aOutline.Append( polyShape );
216
217 break;
218 }
219 default: break;
220 }
221 }
222
223 if( aOutline.OutlineCount() > 0 )
224 {
225 aOutline.Simplify();
226 return true;
227 }
228
229 return false;
230}
231
232
233static bool buildPadBoundingBox( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aOutline )
234{
235 BOX2I padBox = CalcPlaceholderLocalBox( aFootprint );
236
237 if( padBox.GetWidth() <= 0 || padBox.GetHeight() <= 0 )
238 return false;
239
240 VECTOR2I fpPos = aFootprint->GetPosition();
241 EDA_ANGLE fpAngle = aFootprint->GetOrientation();
242
243 VECTOR2I corners[4] = { padBox.GetOrigin(), VECTOR2I( padBox.GetRight(), padBox.GetTop() ),
244 VECTOR2I( padBox.GetRight(), padBox.GetBottom() ),
245 VECTOR2I( padBox.GetLeft(), padBox.GetBottom() ) };
246
247 aOutline.NewOutline();
248
249 for( int i = 0; i < 4; ++i )
250 {
251 RotatePoint( corners[i], fpAngle );
252 corners[i] += fpPos;
253 aOutline.Append( corners[i] );
254 }
255
256 return true;
257}
258
259
260bool GetExtrusionOutline( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aOutline, PCB_LAYER_ID aLayerOverride )
261{
262 aOutline.RemoveAllContours();
263
264 const EXTRUDED_3D_BODY* body = aFootprint->GetExtrudedBody();
265 PCB_LAYER_ID extLayer =
266 ( aLayerOverride != UNDEFINED_LAYER ) ? aLayerOverride : ( body ? body->m_layer : UNDEFINED_LAYER );
267
268 // Explicit pin bounding box mode
269 if( extLayer == UNSELECTED_LAYER )
270 return buildPadBoundingBox( aFootprint, aOutline );
271
272 bool isBack = aFootprint->IsFlipped();
273
274 if( isBack && ( extLayer == F_CrtYd || extLayer == F_Fab || extLayer == F_SilkS ) )
275 extLayer = FlipLayer( extLayer );
276 else if( !isBack && ( extLayer == B_CrtYd || extLayer == B_Fab || extLayer == B_SilkS ) )
277 extLayer = FlipLayer( extLayer );
278
279 if( extLayer != UNDEFINED_LAYER )
280 {
281 if( extLayer == F_CrtYd || extLayer == B_CrtYd )
282 {
283 const SHAPE_POLY_SET& courtyard = aFootprint->GetCourtyard( extLayer );
284
285 if( courtyard.OutlineCount() > 0 )
286 {
287 aOutline.Append( courtyard );
288 aOutline.Simplify();
289 return true;
290 }
291 }
292 else if( extLayer == F_Fab || extLayer == B_Fab )
293 {
294 if( buildFilledPolygonFromShapes( aFootprint, extLayer, aOutline ) )
295 return true;
296 }
297 else if( extLayer == F_SilkS || extLayer == B_SilkS )
298 {
299 if( buildFilledPolygonFromShapes( aFootprint, extLayer, aOutline ) )
300 return true;
301 }
302
303 wxLogTrace( wxT( "KI_TRACE_3D_RENDER" ), wxT( "Extrusion: no shapes found on explicit layer for '%s'" ),
304 aFootprint->GetReference() );
305 return false;
306 }
307
308 // Auto mode is courtyard -> fab -> pad bbox
309 PCB_LAYER_ID courtyardLayer = isBack ? B_CrtYd : F_CrtYd;
310 PCB_LAYER_ID fabLayer = isBack ? B_Fab : F_Fab;
311
312 const SHAPE_POLY_SET& courtyard = aFootprint->GetCourtyard( courtyardLayer );
313
314 if( courtyard.OutlineCount() > 0 )
315 {
316 aOutline.Append( courtyard );
317 aOutline.Simplify();
318 return true;
319 }
320
321 wxLogTrace( wxT( "KI_TRACE_3D_RENDER" ), wxT( "Extrusion: courtyard outline failed for '%s', trying fab layer" ),
322 aFootprint->GetReference() );
323
324 if( buildFilledPolygonFromShapes( aFootprint, fabLayer, aOutline ) )
325 return true;
326
327 wxLogTrace( wxT( "KI_TRACE_3D_RENDER" ),
328 wxT( "Extrusion: fab layer outline failed for '%s', trying silkscreen" ),
329 aFootprint->GetReference() );
330
331 PCB_LAYER_ID silkLayer = isBack ? B_SilkS : F_SilkS;
332
333 if( buildFilledPolygonFromShapes( aFootprint, silkLayer, aOutline ) )
334 return true;
335
336 wxLogTrace( wxT( "KI_TRACE_3D_RENDER" ),
337 wxT( "Extrusion: silkscreen outline failed for '%s', trying pad bbox" ),
338 aFootprint->GetReference() );
339
340 if( buildPadBoundingBox( aFootprint, aOutline ) )
341 return true;
342
343 wxLogTrace( wxT( "KI_TRACE_3D_RENDER" ), wxT( "Extrusion: no outline could be generated for '%s'" ),
344 aFootprint->GetReference() );
345 return false;
346}
347
348bool GetExtrusionPinOutline( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aPinPoly )
349{
350 aPinPoly.RemoveAllContours();
351
352 for( PAD* pad : aFootprint->Pads() )
353 {
354 if( pad->HasHole() )
355 {
356 int shrink = -pad->GetDrillSize().x / 20; // ~90% of hole diameter
357 pad->TransformHoleToPolygon( aPinPoly, shrink, ARC_HIGH_DEF, ERROR_INSIDE );
358 }
359 }
360
361 if( aPinPoly.OutlineCount() == 0 )
362 return false;
363
364 aPinPoly.Simplify();
365 return true;
366}
367
368
370{
371 switch( aMaterial )
372 {
373 default:
374 case EXTRUSION_MATERIAL::PLASTIC: return { aDiffuse * 0.1f, SFVEC3F( 0.4f, 0.4f, 0.4f ), 0.3f * 128.0f };
375 case EXTRUSION_MATERIAL::MATTE: return { aDiffuse * 0.1f, SFVEC3F( 0.05f, 0.05f, 0.05f ), 0.05f * 128.0f };
377 return { aDiffuse * 0.15f, aDiffuse * 0.5f + SFVEC3F( 0.4f, 0.4f, 0.4f ), 0.5f * 128.0f };
379 return { aDiffuse * 0.1f, aDiffuse * 0.75f + SFVEC3F( 0.25f, 0.25f, 0.25f ), 0.4f * 128.0f };
380 }
381}
382
383
384void ApplyExtrusionTransform( SHAPE_POLY_SET& aOutline, const EXTRUDED_3D_BODY* aBody, const VECTOR2I& aFpPos )
385{
386 if( aBody->m_rotation.z != 0.0 )
387 aOutline.Rotate( EDA_ANGLE( aBody->m_rotation.z, DEGREES_T ), aFpPos );
388
389 if( aBody->m_scale.x != 1.0 || aBody->m_scale.y != 1.0 )
390 aOutline.Scale( aBody->m_scale.x, aBody->m_scale.y, aFpPos );
391
392 if( aBody->m_offset.x != 0.0 || aBody->m_offset.y != 0.0 )
393 aOutline.Move( VECTOR2I( pcbIUScale.mmToIU( aBody->m_offset.x ), pcbIUScale.mmToIU( aBody->m_offset.y ) ) );
394}
void ApplyExtrusionTransform(SHAPE_POLY_SET &aOutline, const EXTRUDED_3D_BODY *aBody, const VECTOR2I &aFpPos)
Apply 2D extrusion transforms (rotation, scale, offset) to an outline.
EXTRUSION_MATERIAL_PROPS GetMaterialProps(EXTRUSION_MATERIAL aMaterial, const SFVEC3F &aDiffuse)
bool GetExtrusionPinOutline(const FOOTPRINT *aFootprint, SHAPE_POLY_SET &aPinPoly)
Get the pin outline polygons for extruded THT pin rendering.
static bool buildFilledPolygonFromShapes(const FOOTPRINT *aFootprint, PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aOutline)
Build a filled polygon from shapes on the given layer.
static bool buildPadBoundingBox(const FOOTPRINT *aFootprint, SHAPE_POLY_SET &aOutline)
bool GetExtrusionOutline(const FOOTPRINT *aFootprint, SHAPE_POLY_SET &aOutline, PCB_LAYER_ID aLayerOverride)
Get the extrusion outline polygon for a footprint in board coordinates.
BOX2I CalcPlaceholderLocalBox(const FOOTPRINT *aFootprint)
Calculate a local space bounding box for a placeholder 3D model.
@ ERROR_INSIDE
constexpr int ARC_HIGH_DEF
Definition base_units.h:141
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:125
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
constexpr void SetOrigin(const Vec &pos)
Definition box2.h:237
constexpr size_type GetWidth() const
Definition box2.h:214
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 size_type GetHeight() const
Definition box2.h:215
constexpr coord_type GetLeft() const
Definition box2.h:228
constexpr const Vec & GetOrigin() const
Definition box2.h:210
constexpr coord_type GetRight() const
Definition box2.h:217
constexpr coord_type GetTop() const
Definition box2.h:229
constexpr coord_type GetBottom() const
Definition box2.h:222
SHAPE_T GetShape() const
Definition eda_shape.h:185
VECTOR3D m_offset
Definition footprint.h:116
PCB_LAYER_ID m_layer
Definition footprint.h:109
VECTOR3D m_rotation
Definition footprint.h:115
VECTOR3D m_scale
Definition footprint.h:114
EDA_ANGLE GetOrientation() const
Definition footprint.h:408
const EXTRUDED_3D_BODY * GetExtrudedBody() const
Definition footprint.h:398
std::deque< PAD * > & Pads()
Definition footprint.h:377
bool IsFlipped() const
Definition footprint.h:602
const wxString & GetReference() const
Definition footprint.h:829
const SHAPE_POLY_SET & GetCourtyard(PCB_LAYER_ID aLayer) const
Used in DRC to test the courtyard area (a complex polygon).
VECTOR2I GetPosition() const override
Definition footprint.h:405
DRAWINGS & GraphicalItems()
Definition footprint.h:380
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
Definition pad.h:55
Represent a set of closed polygons.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
void Scale(double aScaleFactorX, double aScaleFactorY, const VECTOR2I &aCenter)
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
int NewOutline()
Creates a new empty polygon in the set and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
void Move(const VECTOR2I &aVector) override
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
bool ConvertOutlineToPolygon(std::vector< PCB_SHAPE * > &aShapeList, SHAPE_POLY_SET &aPolygons, int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint, OUTLINE_ERROR_HANDLER *aErrorHandler, bool aAllowUseArcsInPolygons)
Build a polygon set with holes from a PCB_SHAPE list.
@ DEGREES_T
Definition eda_angle.h:31
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:49
EXTRUSION_MATERIAL
Definition footprint.h:95
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayerId, int aCopperLayersCount)
Definition layer_id.cpp:173
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ F_CrtYd
Definition layer_ids.h:116
@ UNSELECTED_LAYER
Definition layer_ids.h:62
@ F_Fab
Definition layer_ids.h:119
@ F_SilkS
Definition layer_ids.h:100
@ B_CrtYd
Definition layer_ids.h:115
@ UNDEFINED_LAYER
Definition layer_ids.h:61
@ B_SilkS
Definition layer_ids.h:101
@ B_Fab
Definition layer_ids.h:118
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:229
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:85
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687
glm::vec3 SFVEC3F
Definition xv3d_types.h:44