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, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <footprint.h>
23#include <pad.h>
24#include <board_item.h>
25#include <layer_ids.h>
26#include <base_units.h>
27#include <geometry/eda_angle.h>
29#include <pcb_shape.h>
32#include <wx/log.h>
33
35{
36 BOX2I localBox;
37 bool hasLocalBounds = false;
38
39 for( PAD* pad : aFootprint->Pads() )
40 {
41 VECTOR2I padPos = pad->GetFPRelativePosition();
42 VECTOR2I padSize = pad->GetSize( PADSTACK::ALL_LAYERS );
43
44 BOX2I padBox;
45 padBox.SetOrigin( padPos - padSize / 2 );
46 padBox.SetSize( padSize );
47
48 if( !hasLocalBounds )
49 {
50 localBox = padBox;
51 hasLocalBounds = true;
52 }
53 else
54 {
55 localBox.Merge( padBox );
56 }
57 }
58
59 if( !hasLocalBounds )
60 {
61 VECTOR2I fpPos = aFootprint->GetPosition();
62 EDA_ANGLE fpAngle = aFootprint->GetOrientation();
63
64 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
65 {
66 PCB_LAYER_ID layer = item->GetLayer();
67
68 if( layer == F_Fab || layer == B_Fab || layer == F_CrtYd || layer == B_CrtYd || layer == F_SilkS
69 || layer == B_SilkS )
70 {
71 BOX2I itemBox = item->GetBoundingBox();
72
73 VECTOR2I corners[4] = { itemBox.GetOrigin() - fpPos,
74 VECTOR2I( itemBox.GetRight(), itemBox.GetTop() ) - fpPos,
75 VECTOR2I( itemBox.GetRight(), itemBox.GetBottom() ) - fpPos,
76 VECTOR2I( itemBox.GetLeft(), itemBox.GetBottom() ) - fpPos };
77
78 BOX2I localItemBox;
79
80 for( int ci = 0; ci < 4; ++ci )
81 {
82 RotatePoint( corners[ci], -fpAngle );
83
84 if( ci == 0 )
85 {
86 localItemBox.SetOrigin( corners[ci] );
87 localItemBox.SetSize( VECTOR2I( 0, 0 ) );
88 }
89 else
90 {
91 localItemBox.Merge( BOX2I( corners[ci], VECTOR2I( 0, 0 ) ) );
92 }
93 }
94
95 if( !hasLocalBounds )
96 {
97 localBox = localItemBox;
98 hasLocalBounds = true;
99 }
100 else
101 {
102 localBox.Merge( localItemBox );
103 }
104 }
105 }
106 }
107
108 if( !hasLocalBounds )
109 {
110 BOX2I fpBox = aFootprint->GetBoundingBox( false );
111 int size = std::min( fpBox.GetWidth(), fpBox.GetHeight() );
112 localBox.SetOrigin( VECTOR2I( -size / 2, -size / 2 ) );
113 localBox.SetSize( VECTOR2I( size, size ) );
114 }
115
116 return localBox;
117}
118
119
127static bool buildFilledPolygonFromShapes( const FOOTPRINT* aFootprint, PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aOutline )
128{
129 std::vector<PCB_SHAPE*> closedShapes;
130 std::vector<PCB_SHAPE*> openShapes;
131
132 for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
133 {
134 if( item->GetLayer() == aLayer && item->Type() == PCB_SHAPE_T )
135 {
136 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
137
138 if( shape->GetShape() == SHAPE_T::CIRCLE || shape->GetShape() == SHAPE_T::RECTANGLE
139 || shape->GetShape() == SHAPE_T::POLY )
140 {
141 closedShapes.push_back( shape );
142 }
143 else
144 {
145 openShapes.push_back( shape );
146 }
147 }
148 }
149
150 if( closedShapes.empty() && openShapes.empty() )
151 return false;
152
153 int maxError = pcbIUScale.mmToIU( 0.005 );
154
155 if( !openShapes.empty() )
156 {
157 int chainingEpsilon = pcbIUScale.mmToIU( 0.02 );
158
159 if( ConvertOutlineToPolygon( openShapes, aOutline, maxError, chainingEpsilon, true, nullptr )
160 && aOutline.OutlineCount() > 0 )
161 {
162 aOutline.Simplify();
163 }
164 else
165 {
166 aOutline.RemoveAllContours();
167
168 SHAPE_POLY_SET strokes;
169
170 for( PCB_SHAPE* shape : openShapes )
171 shape->TransformShapeToPolygon( strokes, aLayer, 0, maxError, ERROR_INSIDE );
172
173 if( strokes.OutlineCount() > 0 )
174 {
175 strokes.Simplify();
176
177 for( int i = 0; i < strokes.OutlineCount(); i++ )
178 aOutline.AddOutline( strokes.COutline( i ) );
179 }
180 }
181 }
182
183 for( const PCB_SHAPE* shape : closedShapes )
184 {
185 switch( shape->GetShape() )
186 {
187 case SHAPE_T::CIRCLE:
188 {
189 TransformCircleToPolygon( aOutline, shape->GetCenter(), shape->GetRadius(), ARC_HIGH_DEF, ERROR_INSIDE );
190 break;
191 }
193 {
194 std::vector<VECTOR2I> corners = shape->GetRectCorners();
195
196 if( corners.size() == 4 )
197 {
198 aOutline.NewOutline();
199
200 for( const VECTOR2I& pt : corners )
201 aOutline.Append( pt );
202 }
203
204 break;
205 }
206 case SHAPE_T::POLY:
207 {
208 const SHAPE_POLY_SET& polyShape = shape->GetPolyShape();
209
210 if( polyShape.OutlineCount() > 0 )
211 aOutline.Append( polyShape );
212
213 break;
214 }
215 default: break;
216 }
217 }
218
219 if( aOutline.OutlineCount() > 0 )
220 {
221 aOutline.Simplify();
222 return true;
223 }
224
225 return false;
226}
227
228
229static bool buildPadBoundingBox( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aOutline )
230{
231 BOX2I padBox = CalcPlaceholderLocalBox( aFootprint );
232
233 if( padBox.GetWidth() <= 0 || padBox.GetHeight() <= 0 )
234 return false;
235
236 VECTOR2I fpPos = aFootprint->GetPosition();
237 EDA_ANGLE fpAngle = aFootprint->GetOrientation();
238
239 VECTOR2I corners[4] = { padBox.GetOrigin(), VECTOR2I( padBox.GetRight(), padBox.GetTop() ),
240 VECTOR2I( padBox.GetRight(), padBox.GetBottom() ),
241 VECTOR2I( padBox.GetLeft(), padBox.GetBottom() ) };
242
243 aOutline.NewOutline();
244
245 for( int i = 0; i < 4; ++i )
246 {
247 RotatePoint( corners[i], fpAngle );
248 corners[i] += fpPos;
249 aOutline.Append( corners[i] );
250 }
251
252 return true;
253}
254
255
256bool GetExtrusionOutline( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aOutline, PCB_LAYER_ID aLayerOverride )
257{
258 aOutline.RemoveAllContours();
259
260 const EXTRUDED_3D_BODY* body = aFootprint->GetExtrudedBody();
261 PCB_LAYER_ID extLayer =
262 ( aLayerOverride != UNDEFINED_LAYER ) ? aLayerOverride : ( body ? body->m_layer : UNDEFINED_LAYER );
263
264 // Explicit pin bounding box mode
265 if( extLayer == UNSELECTED_LAYER )
266 return buildPadBoundingBox( aFootprint, aOutline );
267
268 bool isBack = aFootprint->IsFlipped();
269
270 if( isBack && ( extLayer == F_CrtYd || extLayer == F_Fab || extLayer == F_SilkS ) )
271 extLayer = FlipLayer( extLayer );
272 else if( !isBack && ( extLayer == B_CrtYd || extLayer == B_Fab || extLayer == B_SilkS ) )
273 extLayer = FlipLayer( extLayer );
274
275 if( extLayer != UNDEFINED_LAYER )
276 {
277 if( extLayer == F_CrtYd || extLayer == B_CrtYd )
278 {
279 const SHAPE_POLY_SET& courtyard = aFootprint->GetCourtyard( extLayer );
280
281 if( courtyard.OutlineCount() > 0 )
282 {
283 aOutline.Append( courtyard );
284 aOutline.Simplify();
285 return true;
286 }
287 }
288 else if( extLayer == F_Fab || extLayer == B_Fab )
289 {
290 if( buildFilledPolygonFromShapes( aFootprint, extLayer, aOutline ) )
291 return true;
292 }
293 else if( extLayer == F_SilkS || extLayer == B_SilkS )
294 {
295 if( buildFilledPolygonFromShapes( aFootprint, extLayer, aOutline ) )
296 return true;
297 }
298
299 wxLogTrace( wxT( "KI_TRACE_3D_RENDER" ), wxT( "Extrusion: no shapes found on explicit layer for '%s'" ),
300 aFootprint->GetReference() );
301 return false;
302 }
303
304 // Auto mode is courtyard -> fab -> pad bbox
305 PCB_LAYER_ID courtyardLayer = isBack ? B_CrtYd : F_CrtYd;
306 PCB_LAYER_ID fabLayer = isBack ? B_Fab : F_Fab;
307
308 const SHAPE_POLY_SET& courtyard = aFootprint->GetCourtyard( courtyardLayer );
309
310 if( courtyard.OutlineCount() > 0 )
311 {
312 aOutline.Append( courtyard );
313 aOutline.Simplify();
314 return true;
315 }
316
317 wxLogTrace( wxT( "KI_TRACE_3D_RENDER" ), wxT( "Extrusion: courtyard outline failed for '%s', trying fab layer" ),
318 aFootprint->GetReference() );
319
320 if( buildFilledPolygonFromShapes( aFootprint, fabLayer, aOutline ) )
321 return true;
322
323 wxLogTrace( wxT( "KI_TRACE_3D_RENDER" ),
324 wxT( "Extrusion: fab layer outline failed for '%s', trying silkscreen" ),
325 aFootprint->GetReference() );
326
327 PCB_LAYER_ID silkLayer = isBack ? B_SilkS : F_SilkS;
328
329 if( buildFilledPolygonFromShapes( aFootprint, silkLayer, aOutline ) )
330 return true;
331
332 wxLogTrace( wxT( "KI_TRACE_3D_RENDER" ),
333 wxT( "Extrusion: silkscreen outline failed for '%s', trying pad bbox" ),
334 aFootprint->GetReference() );
335
336 if( buildPadBoundingBox( aFootprint, aOutline ) )
337 return true;
338
339 wxLogTrace( wxT( "KI_TRACE_3D_RENDER" ), wxT( "Extrusion: no outline could be generated for '%s'" ),
340 aFootprint->GetReference() );
341 return false;
342}
343
344bool GetExtrusionPinOutline( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aPinPoly )
345{
346 aPinPoly.RemoveAllContours();
347
348 for( PAD* pad : aFootprint->Pads() )
349 {
350 if( pad->HasHole() )
351 {
352 int shrink = -pad->GetDrillSize().x / 20; // ~90% of hole diameter
353 pad->TransformHoleToPolygon( aPinPoly, shrink, ARC_HIGH_DEF, ERROR_INSIDE );
354 }
355 }
356
357 if( aPinPoly.OutlineCount() == 0 )
358 return false;
359
360 aPinPoly.Simplify();
361 return true;
362}
363
364
366{
367 switch( aMaterial )
368 {
369 default:
370 case EXTRUSION_MATERIAL::PLASTIC: return { aDiffuse * 0.1f, SFVEC3F( 0.4f, 0.4f, 0.4f ), 0.3f * 128.0f };
371 case EXTRUSION_MATERIAL::MATTE: return { aDiffuse * 0.1f, SFVEC3F( 0.05f, 0.05f, 0.05f ), 0.05f * 128.0f };
373 return { aDiffuse * 0.15f, aDiffuse * 0.5f + SFVEC3F( 0.4f, 0.4f, 0.4f ), 0.5f * 128.0f };
375 return { aDiffuse * 0.1f, aDiffuse * 0.75f + SFVEC3F( 0.25f, 0.25f, 0.25f ), 0.4f * 128.0f };
376 }
377}
378
379
380void ApplyExtrusionTransform( SHAPE_POLY_SET& aOutline, const EXTRUDED_3D_BODY* aBody, const VECTOR2I& aFpPos )
381{
382 if( aBody->m_rotation.z != 0.0 )
383 aOutline.Rotate( EDA_ANGLE( aBody->m_rotation.z, DEGREES_T ), aFpPos );
384
385 if( aBody->m_scale.x != 1.0 || aBody->m_scale.y != 1.0 )
386 aOutline.Scale( aBody->m_scale.x, aBody->m_scale.y, aFpPos );
387
388 if( aBody->m_offset.x != 0.0 || aBody->m_offset.y != 0.0 )
389 aOutline.Move( VECTOR2I( pcbIUScale.mmToIU( aBody->m_offset.x ), pcbIUScale.mmToIU( aBody->m_offset.y ) ) );
390}
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:137
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
constexpr void SetOrigin(const Vec &pos)
Definition box2.h:233
constexpr size_type GetWidth() const
Definition box2.h:210
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:654
constexpr void SetSize(const SizeVec &size)
Definition box2.h:244
constexpr size_type GetHeight() const
Definition box2.h:211
constexpr coord_type GetLeft() const
Definition box2.h:224
constexpr const Vec & GetOrigin() const
Definition box2.h:206
constexpr coord_type GetRight() const
Definition box2.h:213
constexpr coord_type GetTop() const
Definition box2.h:225
constexpr coord_type GetBottom() const
Definition box2.h:218
SHAPE_T GetShape() const
Definition eda_shape.h:185
VECTOR3D m_offset
Definition footprint.h:114
PCB_LAYER_ID m_layer
Definition footprint.h:107
VECTOR3D m_rotation
Definition footprint.h:113
VECTOR3D m_scale
Definition footprint.h:112
EDA_ANGLE GetOrientation() const
Definition footprint.h:406
const EXTRUDED_3D_BODY * GetExtrudedBody() const
Definition footprint.h:396
std::deque< PAD * > & Pads()
Definition footprint.h:375
bool IsFlipped() const
Definition footprint.h:614
const wxString & GetReference() const
Definition footprint.h:841
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:403
DRAWINGS & GraphicalItems()
Definition footprint.h:378
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:61
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:47
EXTRUSION_MATERIAL
Definition footprint.h:93
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:56
@ F_CrtYd
Definition layer_ids.h:112
@ UNSELECTED_LAYER
Definition layer_ids.h:58
@ F_Fab
Definition layer_ids.h:115
@ F_SilkS
Definition layer_ids.h:96
@ B_CrtYd
Definition layer_ids.h:111
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ B_SilkS
Definition layer_ids.h:97
@ B_Fab
Definition layer_ids.h:114
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:225
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:81
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
glm::vec3 SFVEC3F
Definition xv3d_types.h:40