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