KiCad PCB EDA Suite
Loading...
Searching...
No Matches
opengl/create_scene.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) 2015-2016 Mario Luzeiro <[email protected]>
5 * Copyright (C) 2023 CERN
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
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 "render_3d_opengl.h"
28#include <board.h>
29#include <footprint.h>
30#include <layer_range.h>
31#include <pcb_track.h>
32#include "../../3d_math.h"
34#include <lset.h>
35#include <trigo.h>
36#include <project.h>
37#include <core/profile.h> // To use GetRunningMicroSecs or another profiling utility
39#include <eda_3d_viewer_frame.h>
40#include <project_pcb.h>
42
43
45 TRIANGLE_DISPLAY_LIST* aDstLayer, float aZtop,
46 float aZbot )
47{
48 const SFVEC2F& center = aCircle->GetCenter();
49 const float radius = aCircle->GetRadius() * 2.0f; // Double because the render triangle
50
51 // This is a small adjustment to the circle texture
52 const float texture_factor = ( 8.0f / (float) SIZE_OF_CIRCLE_TEXTURE ) + 1.0f;
53 const float f = ( sqrtf( 2.0f ) / 2.0f ) * radius * texture_factor;
54
55 // Top and Bot segments ends are just triangle semi-circles, so need to add it in duplicated.
56 aDstLayer->m_layer_top_segment_ends->AddTriangle( SFVEC3F( center.x + f, center.y, aZtop ),
57 SFVEC3F( center.x - f, center.y, aZtop ),
58 SFVEC3F( center.x, center.y - f, aZtop ) );
59
60 aDstLayer->m_layer_top_segment_ends->AddTriangle( SFVEC3F( center.x - f, center.y, aZtop ),
61 SFVEC3F( center.x + f, center.y, aZtop ),
62 SFVEC3F( center.x, center.y + f, aZtop ) );
63
64 aDstLayer->m_layer_bot_segment_ends->AddTriangle( SFVEC3F( center.x - f, center.y, aZbot ),
65 SFVEC3F( center.x + f, center.y, aZbot ),
66 SFVEC3F( center.x, center.y - f, aZbot ) );
67
68 aDstLayer->m_layer_bot_segment_ends->AddTriangle( SFVEC3F( center.x + f, center.y, aZbot ),
69 SFVEC3F( center.x - f, center.y, aZbot ),
70 SFVEC3F( center.x, center.y + f, aZbot ) );
71}
72
73
75 TRIANGLE_DISPLAY_LIST* aDstLayer,
76 float aZtop, float aZbot )
77{
78 const SFVEC2F& v0 = aPoly->GetV0();
79 const SFVEC2F& v1 = aPoly->GetV1();
80 const SFVEC2F& v2 = aPoly->GetV2();
81 const SFVEC2F& v3 = aPoly->GetV3();
82
83 addTopAndBottomTriangles( aDstLayer, v0, v2, v1, aZtop, aZbot );
84 addTopAndBottomTriangles( aDstLayer, v2, v0, v3, aZtop, aZbot );
85}
86
87
88void RENDER_3D_OPENGL::generateRing( const SFVEC2F& aCenter, float aInnerRadius,
89 float aOuterRadius, unsigned int aNr_sides_per_circle,
90 std::vector< SFVEC2F >& aInnerContourResult,
91 std::vector< SFVEC2F >& aOuterContourResult,
92 bool aInvertOrder )
93{
94 aInnerContourResult.clear();
95 aInnerContourResult.reserve( aNr_sides_per_circle + 2 );
96
97 aOuterContourResult.clear();
98 aOuterContourResult.reserve( aNr_sides_per_circle + 2 );
99
100 const int delta = 3600 / aNr_sides_per_circle;
101
102 for( int ii = 0; ii < 3600; ii += delta )
103 {
104 float angle = (float)( aInvertOrder ? ( 3600 - ii ) : ii )
105 * 2.0f * glm::pi<float>() / 3600.0f;
106 const SFVEC2F rotatedDir = SFVEC2F( cos( angle ), sin( angle ) );
107
108 aInnerContourResult.emplace_back( aCenter.x + rotatedDir.x * aInnerRadius,
109 aCenter.y + rotatedDir.y * aInnerRadius );
110
111 aOuterContourResult.emplace_back( aCenter.x + rotatedDir.x * aOuterRadius,
112 aCenter.y + rotatedDir.y * aOuterRadius );
113 }
114
115 aInnerContourResult.push_back( aInnerContourResult[0] );
116 aOuterContourResult.push_back( aOuterContourResult[0] );
117
118 wxASSERT( aInnerContourResult.size() == aOuterContourResult.size() );
119}
120
121
123 float aZtop, float aZbot )
124{
125 const SFVEC2F& center = aRing->GetCenter();
126 const float inner = aRing->GetInnerRadius();
127 const float outer = aRing->GetOuterRadius();
128
129 std::vector< SFVEC2F > innerContour;
130 std::vector< SFVEC2F > outerContour;
131
132 generateRing( center, inner, outer, m_boardAdapter.GetCircleSegmentCount( outer * 2.0f ),
133 innerContour, outerContour, false );
134
135 // This will add the top and bot quads that will form the approximated ring
136 for( unsigned int i = 0; i < ( innerContour.size() - 1 ); ++i )
137 {
138 const SFVEC2F& vi0 = innerContour[i + 0];
139 const SFVEC2F& vi1 = innerContour[i + 1];
140 const SFVEC2F& vo0 = outerContour[i + 0];
141 const SFVEC2F& vo1 = outerContour[i + 1];
142
143 aDstLayer->m_layer_top_triangles->AddQuad( SFVEC3F( vi1.x, vi1.y, aZtop ),
144 SFVEC3F( vi0.x, vi0.y, aZtop ),
145 SFVEC3F( vo0.x, vo0.y, aZtop ),
146 SFVEC3F( vo1.x, vo1.y, aZtop ) );
147
148 aDstLayer->m_layer_bot_triangles->AddQuad( SFVEC3F( vi1.x, vi1.y, aZbot ),
149 SFVEC3F( vo1.x, vo1.y, aZbot ),
150 SFVEC3F( vo0.x, vo0.y, aZbot ),
151 SFVEC3F( vi0.x, vi0.y, aZbot ) );
152 }
153}
154
155
157 float aZtop, float aZbot )
158{
159 const SFVEC2F& v1 = aTri->GetP1();
160 const SFVEC2F& v2 = aTri->GetP2();
161 const SFVEC2F& v3 = aTri->GetP3();
162
163 addTopAndBottomTriangles( aDstLayer, v1, v2, v3, aZtop, aZbot );
164}
165
166
168 TRIANGLE_DISPLAY_LIST* aDstLayer,
169 float aZtop, float aZbot )
170{
171 const SFVEC2F& leftStart = aSeg->GetLeftStar();
172 const SFVEC2F& leftEnd = aSeg->GetLeftEnd();
173 const SFVEC2F& leftDir = aSeg->GetLeftDir();
174
175 const SFVEC2F& rightStart = aSeg->GetRightStar();
176 const SFVEC2F& rightEnd = aSeg->GetRightEnd();
177 const SFVEC2F& rightDir = aSeg->GetRightDir();
178 const float radius = aSeg->GetRadius();
179
180 const SFVEC2F& start = aSeg->GetStart();
181 const SFVEC2F& end = aSeg->GetEnd();
182
183 const float texture_factor = ( 12.0f / (float) SIZE_OF_CIRCLE_TEXTURE ) + 1.0f;
184 const float texture_factorF = ( 6.0f / (float) SIZE_OF_CIRCLE_TEXTURE ) + 1.0f;
185
186 const float radius_of_the_square = sqrtf( aSeg->GetRadiusSquared() * 2.0f );
187 const float radius_triangle_factor = ( radius_of_the_square - radius ) / radius;
188
189 const SFVEC2F factorS = SFVEC2F( -rightDir.y * radius * radius_triangle_factor,
190 rightDir.x * radius * radius_triangle_factor );
191
192 const SFVEC2F factorE = SFVEC2F( -leftDir.y * radius * radius_triangle_factor,
193 leftDir.x * radius * radius_triangle_factor );
194
195 // Top end segment triangles (semi-circles)
197 SFVEC3F( rightEnd.x + texture_factor * factorS.x,
198 rightEnd.y + texture_factor * factorS.y,
199 aZtop ),
200 SFVEC3F( leftStart.x + texture_factor * factorE.x,
201 leftStart.y + texture_factor * factorE.y,
202 aZtop ),
203 SFVEC3F( start.x - texture_factorF * leftDir.x * radius * sqrtf( 2.0f ),
204 start.y - texture_factorF * leftDir.y * radius * sqrtf( 2.0f ),
205 aZtop ) );
206
208 SFVEC3F( leftEnd.x + texture_factor * factorE.x,
209 leftEnd.y + texture_factor * factorE.y, aZtop ),
210 SFVEC3F( rightStart.x + texture_factor * factorS.x,
211 rightStart.y + texture_factor * factorS.y, aZtop ),
212 SFVEC3F( end.x - texture_factorF * rightDir.x * radius * sqrtf( 2.0f ),
213 end.y - texture_factorF * rightDir.y * radius * sqrtf( 2.0f ),
214 aZtop ) );
215
216 // Bot end segment triangles (semi-circles)
218 SFVEC3F( leftStart.x + texture_factor * factorE.x,
219 leftStart.y + texture_factor * factorE.y,
220 aZbot ),
221 SFVEC3F( rightEnd.x + texture_factor * factorS.x,
222 rightEnd.y + texture_factor * factorS.y,
223 aZbot ),
224 SFVEC3F( start.x - texture_factorF * leftDir.x * radius * sqrtf( 2.0f ),
225 start.y - texture_factorF * leftDir.y * radius * sqrtf( 2.0f ),
226 aZbot ) );
227
229 SFVEC3F( rightStart.x + texture_factor * factorS.x,
230 rightStart.y + texture_factor * factorS.y, aZbot ),
231 SFVEC3F( leftEnd.x + texture_factor * factorE.x,
232 leftEnd.y + texture_factor * factorE.y, aZbot ),
233 SFVEC3F( end.x - texture_factorF * rightDir.x * radius * sqrtf( 2.0f ),
234 end.y - texture_factorF * rightDir.y * radius * sqrtf( 2.0f ),
235 aZbot ) );
236
237 // Segment top and bot planes
238 aDstLayer->m_layer_top_triangles->AddQuad(
239 SFVEC3F( rightEnd.x, rightEnd.y, aZtop ),
240 SFVEC3F( rightStart.x, rightStart.y, aZtop ),
241 SFVEC3F( leftEnd.x, leftEnd.y, aZtop ),
242 SFVEC3F( leftStart.x, leftStart.y, aZtop ) );
243
244 aDstLayer->m_layer_bot_triangles->AddQuad(
245 SFVEC3F( rightEnd.x, rightEnd.y, aZbot ),
246 SFVEC3F( leftStart.x, leftStart.y, aZbot ),
247 SFVEC3F( leftEnd.x, leftEnd.y, aZbot ),
248 SFVEC3F( rightStart.x, rightStart.y, aZbot ) );
249}
250
251
253 const SHAPE_POLY_SET& aPoly, float aZtop, float aZbot,
254 bool aInvertFaces, const BVH_CONTAINER_2D* aThroughHoles )
255{
256 if( aListHolesObject2d.size() == 0 )
257 return nullptr;
258
259 OPENGL_RENDER_LIST* ret = nullptr;
260 TRIANGLE_DISPLAY_LIST* layerTriangles = new TRIANGLE_DISPLAY_LIST( aListHolesObject2d.size() * 2 );
261
262 // Convert the list of objects(filled circles) to triangle layer structure
263 for( const OBJECT_2D* object2d : aListHolesObject2d )
264 {
265 switch( object2d->GetObjectType() )
266 {
268 addObjectTriangles( static_cast<const FILLED_CIRCLE_2D*>( object2d ), layerTriangles, aZtop, aZbot );
269 break;
270
272 addObjectTriangles( static_cast<const ROUND_SEGMENT_2D*>( object2d ), layerTriangles, aZtop, aZbot );
273 break;
274
275 default:
276 wxFAIL_MSG( wxT( "RENDER_3D_OPENGL::generateHoles: Object type not implemented" ) );
277 break;
278 }
279 }
280
281 // Note: he can have a aListHolesObject2d with holes but without contours
282 // eg: when there are only NPTH on the list and the contours were not added
283 if( aPoly.OutlineCount() > 0 )
284 {
285 layerTriangles->AddToMiddleContours( aPoly, aZbot, aZtop, m_boardAdapter.BiuTo3dUnits(), aInvertFaces,
286 aThroughHoles );
287 }
288
289 ret = new OPENGL_RENDER_LIST( *layerTriangles, m_circleTexture, aZbot, aZtop );
290
291 delete layerTriangles;
292
293 return ret;
294}
295
296
298 const SHAPE_POLY_SET* aPolyList, PCB_LAYER_ID aLayer,
299 const BVH_CONTAINER_2D* aThroughHoles )
300{
301 if( aContainer == nullptr )
302 return nullptr;
303
304 const LIST_OBJECT2D& listObject2d = aContainer->GetList();
305
306 if( listObject2d.size() == 0 )
307 return nullptr;
308
309 float zBot = 0.0f;
310 float zTop = 0.0f;
311
312 getLayerZPos( aLayer, zTop, zBot );
313
314 // Calculate an estimation for the nr of triangles based on the nr of objects
315 unsigned int nrTrianglesEstimation = listObject2d.size() * 8;
316
317 TRIANGLE_DISPLAY_LIST* layerTriangles = new TRIANGLE_DISPLAY_LIST( nrTrianglesEstimation );
318
319 // store in a list so it will be latter deleted
320 m_triangles.push_back( layerTriangles );
321
322 // Load the 2D (X,Y axis) component of shapes
323 for( const OBJECT_2D* object2d : listObject2d )
324 {
325 switch( object2d->GetObjectType() )
326 {
328 addObjectTriangles( static_cast<const FILLED_CIRCLE_2D*>( object2d ), layerTriangles, zTop, zBot );
329 break;
330
332 addObjectTriangles( static_cast<const POLYGON_4PT_2D*>( object2d ), layerTriangles, zTop, zBot );
333 break;
334
336 addObjectTriangles( static_cast<const RING_2D*>( object2d ), layerTriangles, zTop, zBot );
337 break;
338
340 addObjectTriangles( static_cast<const TRIANGLE_2D*>( object2d ), layerTriangles, zTop, zBot );
341 break;
342
344 addObjectTriangles( static_cast<const ROUND_SEGMENT_2D*>( object2d ), layerTriangles, zTop, zBot );
345 break;
346
347 default:
348 wxFAIL_MSG( wxT( "RENDER_3D_OPENGL: Object type is not implemented" ) );
349 break;
350 }
351 }
352
353 if( aPolyList && aPolyList->OutlineCount() > 0 )
354 {
355 layerTriangles->AddToMiddleContours( *aPolyList, zBot, zTop, m_boardAdapter.BiuTo3dUnits(), false,
356 aThroughHoles );
357 }
358
359 // Create display list
360 return new OPENGL_RENDER_LIST( *layerTriangles, m_circleTexture, zBot, zTop );
361}
362
363
365{
366 float layer_z_bot = 0.0f;
367 float layer_z_top = 0.0f;
368
369 getLayerZPos( aLayer, layer_z_top, layer_z_bot );
370
371 TRIANGLE_DISPLAY_LIST* layerTriangles = new TRIANGLE_DISPLAY_LIST( 1 );
372
373 // store in a list so it will be latter deleted
374 m_triangles.push_back( layerTriangles );
375
376 return new OPENGL_RENDER_LIST( *layerTriangles, m_circleTexture, layer_z_bot, layer_z_top );
377}
378
379
381 const BVH_CONTAINER_2D* aThroughHoles )
382{
383 OPENGL_RENDER_LIST* dispLists = nullptr;
384 CONTAINER_2D boardContainer;
385
386 ConvertPolygonToTriangles( aBoardPoly, boardContainer, m_boardAdapter.BiuTo3dUnits(),
387 (const BOARD_ITEM &)*m_boardAdapter.GetBoard() );
388
389 const LIST_OBJECT2D& listBoardObject2d = boardContainer.GetList();
390
391 if( listBoardObject2d.size() > 0 )
392 {
393 // We will set a unitary Z so it will in future used with transformations since the
394 // board poly will be used not only to draw itself but also the solder mask layers.
395 const float layer_z_top = 1.0f;
396 const float layer_z_bot = 0.0f;
397
398 TRIANGLE_DISPLAY_LIST* layerTriangles =
399 new TRIANGLE_DISPLAY_LIST( listBoardObject2d.size() );
400
401 // Convert the list of objects(triangles) to triangle layer structure
402 for( const OBJECT_2D* itemOnLayer : listBoardObject2d )
403 {
404 const OBJECT_2D* object2d_A = itemOnLayer;
405
406 wxASSERT( object2d_A->GetObjectType() == OBJECT_2D_TYPE::TRIANGLE );
407
408 const TRIANGLE_2D* tri = static_cast<const TRIANGLE_2D*>( object2d_A );
409
410 const SFVEC2F& v1 = tri->GetP1();
411 const SFVEC2F& v2 = tri->GetP2();
412 const SFVEC2F& v3 = tri->GetP3();
413
414 addTopAndBottomTriangles( layerTriangles, v1, v2, v3, layer_z_top, layer_z_bot );
415 }
416
417 if( aBoardPoly.OutlineCount() > 0 )
418 {
419 layerTriangles->AddToMiddleContours( aBoardPoly, layer_z_bot, layer_z_top,
420 m_boardAdapter.BiuTo3dUnits(), false,
421 aThroughHoles );
422
423 dispLists = new OPENGL_RENDER_LIST( *layerTriangles, m_circleTexture,
424 layer_z_top, layer_z_top );
425 }
426
427 delete layerTriangles;
428 }
429
430 return dispLists;
431}
432
433
435{
436 if( !m_boardAdapter.GetBoard() )
437 return;
438
439 const int copperLayerCount = m_boardAdapter.GetBoard()->GetCopperLayerCount();
440 const float unitScale = m_boardAdapter.BiuTo3dUnits();
441 const int platingThickness = m_boardAdapter.GetHolePlatingThickness();
442 const float boardBodyThickness = m_boardAdapter.GetBoardBodyThickness();
443
444 // We use the same unit z range as the board (0 to 1) and apply scaling when rendering
445 const float boardZTop = 1.0f; // Top of board body
446 const float boardZBot = 0.0f; // Bottom of board body
447
448 // Helper to convert layer Z position to normalized 0-1 range
449 auto normalizeZ = [&]( float absZ ) -> float
450 {
451 float boardTop = m_boardAdapter.GetLayerBottomZPos( F_Cu );
452 float boardBot = m_boardAdapter.GetLayerBottomZPos( B_Cu );
453 float boardThick = boardTop - boardBot;
454
455 if( boardThick <= 0 )
456 return 0.5f;
457
458 // Map absolute Z to 0-1 range where 0 = B_Cu and 1 = F_Cu
459 return ( absZ - boardBot ) / boardThick;
460 };
461
462 // We'll accumulate all plug geometry into a single triangle list
463 TRIANGLE_DISPLAY_LIST* plugTriangles = new TRIANGLE_DISPLAY_LIST( 1024 );
464
465 // Process vias for backdrill and post-machining plugs
466 for( const PCB_TRACK* track : m_boardAdapter.GetBoard()->Tracks() )
467 {
468 if( track->Type() != PCB_VIA_T )
469 continue;
470
471 const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
472
473 const float holeDiameter = via->GetDrillValue() * unitScale;
474 const float holeInnerRadius = holeDiameter / 2.0f;
475 const float holeOuterRadius = holeInnerRadius + platingThickness * unitScale;
476 const SFVEC2F center( via->GetStart().x * unitScale, -via->GetStart().y * unitScale );
477 const int nrSegments = m_boardAdapter.GetCircleSegmentCount( via->GetDrillValue() );
478
479 PCB_LAYER_ID topLayer, bottomLayer;
480 via->LayerPair( &topLayer, &bottomLayer );
481
482 float viaZTop, viaZBot, dummy;
483 getLayerZPos( topLayer, viaZTop, dummy );
484 getLayerZPos( bottomLayer, dummy, viaZBot );
485
486 // Handle backdrill plugs
487 const float secondaryDrillRadius = via->GetSecondaryDrillSize().value_or( 0 ) * 0.5f * unitScale;
488 const float tertiaryDrillRadius = via->GetTertiaryDrillSize().value_or( 0 ) * 0.5f * unitScale;
489
490 if( secondaryDrillRadius > holeOuterRadius || tertiaryDrillRadius > holeOuterRadius )
491 {
492 PCB_LAYER_ID plug_start_layer = F_Cu;
493 PCB_LAYER_ID plug_end_layer = B_Cu;
494
495 // Case 1: secondary drill exists, so we need to adjust the plug_end_layer
496 if( secondaryDrillRadius > holeOuterRadius )
497 {
498 plug_end_layer = via->GetSecondaryDrillEndLayer();
499 }
500 // Case 2: tertiary drill exists, so we need to adjust the plug_start_layer
501 if( tertiaryDrillRadius > holeOuterRadius )
502 {
503 plug_start_layer = via->GetTertiaryDrillStartLayer();
504 }
505
506 // Calculate where the backdrill ends and plug should start
507 float plugZTop, plugZBot, temp;
508 getLayerZPos( plug_end_layer, temp, plugZBot );
509 getLayerZPos( plug_start_layer, plugZTop, temp );
510
511 // Create a ring from holeOuterRadius to backdrillRadius
512 generateCylinder( center, holeOuterRadius, std::max( secondaryDrillRadius, tertiaryDrillRadius ),
513 plugZTop, plugZBot, nrSegments, plugTriangles );
514 }
515
516 // Handle front post-machining plugs
517 const auto frontMode = via->GetFrontPostMachining();
518
519 if( frontMode.has_value()
521 && frontMode.value() != PAD_DRILL_POST_MACHINING_MODE::UNKNOWN )
522 {
523 const float frontRadius = via->GetFrontPostMachiningSize() * 0.5f * unitScale;
524 const float frontDepth = via->GetFrontPostMachiningDepth() * unitScale;
525
526 if( frontRadius > holeOuterRadius && frontDepth > 0 )
527 {
528 // Plug goes from bottom of post-machining to bottom of via
529 float pmBottomZ = normalizeZ( viaZTop - frontDepth );
530 float plugZBot = normalizeZ( viaZBot );
531
532 if( pmBottomZ > plugZBot )
533 {
534 if( frontMode.value() == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK )
535 {
536 EDA_ANGLE angle( via->GetFrontPostMachiningAngle(), TENTHS_OF_A_DEGREE_T );
537 generateInvCone( center, holeOuterRadius, frontRadius,
538 pmBottomZ, plugZBot, nrSegments, plugTriangles, angle );
539 }
540 else
541 {
542 generateCylinder( center, holeOuterRadius, frontRadius,
543 pmBottomZ, plugZBot, nrSegments, plugTriangles );
544 }
545 }
546 }
547 }
548
549 // Handle back post-machining plugs
550 const auto backMode = via->GetBackPostMachining();
551
552 if( backMode.has_value()
554 && backMode.value() != PAD_DRILL_POST_MACHINING_MODE::UNKNOWN )
555 {
556 const float backRadius = via->GetBackPostMachiningSize() * 0.5f * unitScale;
557 const float backDepth = via->GetBackPostMachiningDepth() * unitScale;
558
559 if( backRadius > holeOuterRadius && backDepth > 0 )
560 {
561 // Plug goes from top of via to top of post-machining
562 float plugZTop = normalizeZ( viaZTop );
563 float pmTopZ = normalizeZ( viaZBot + backDepth );
564
565 if( plugZTop > pmTopZ )
566 {
567 if( backMode.value() == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK )
568 {
569 EDA_ANGLE angle( via->GetBackPostMachiningAngle(), TENTHS_OF_A_DEGREE_T );
570 generateInvCone( center, holeOuterRadius, backRadius,
571 plugZTop, pmTopZ, nrSegments, plugTriangles, angle );
572 }
573 else
574 {
575 generateCylinder( center, holeOuterRadius, backRadius,
576 plugZTop, pmTopZ, nrSegments, plugTriangles );
577 }
578 }
579 }
580 }
581 }
582
583 // Process pads for post-machining plugs
584 for( const FOOTPRINT* footprint : m_boardAdapter.GetBoard()->Footprints() )
585 {
586 for( const PAD* pad : footprint->Pads() )
587 {
588 if( !pad->HasHole() )
589 continue;
590
591 if( pad->GetDrillShape() != PAD_DRILL_SHAPE::CIRCLE )
592 continue;
593
594 const SFVEC2F padCenter( pad->GetPosition().x * unitScale,
595 -pad->GetPosition().y * unitScale );
596 const float holeInnerRadius = pad->GetDrillSize().x * 0.5f * unitScale;
597 const float holeOuterRadius = holeInnerRadius + platingThickness * unitScale;
598 const int nrSegments = m_boardAdapter.GetCircleSegmentCount( pad->GetDrillSize().x );
599
600 float padZTop, padZBot, padDummy;
601 getLayerZPos( F_Cu, padZTop, padDummy );
602 getLayerZPos( B_Cu, padDummy, padZBot );
603
604 // Handle front post-machining plugs for pads
605 const auto frontMode = pad->GetFrontPostMachining();
606
607 if( frontMode.has_value()
609 && frontMode.value() != PAD_DRILL_POST_MACHINING_MODE::UNKNOWN )
610 {
611 const float frontRadius = pad->GetFrontPostMachiningSize() * 0.5f * unitScale;
612 const float frontDepth = pad->GetFrontPostMachiningDepth() * unitScale;
613
614 if( frontRadius > holeOuterRadius && frontDepth > 0 )
615 {
616 float pmBottomZ = normalizeZ( padZTop - frontDepth );
617 float plugZBot = normalizeZ( padZBot );
618
619 if( pmBottomZ > plugZBot )
620 {
621 if( frontMode.value() == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK )
622 {
623 EDA_ANGLE angle( pad->GetFrontPostMachiningAngle(), TENTHS_OF_A_DEGREE_T );
624 generateInvCone( padCenter, holeOuterRadius, frontRadius,
625 pmBottomZ, plugZBot, nrSegments, plugTriangles, angle );
626 }
627 else
628 {
629 generateCylinder( padCenter, holeOuterRadius, frontRadius,
630 pmBottomZ, plugZBot, nrSegments, plugTriangles );
631 }
632 }
633 }
634 }
635
636 // Handle back post-machining plugs for pads
637 const auto backMode = pad->GetBackPostMachining();
638
639 if( backMode.has_value()
641 && backMode.value() != PAD_DRILL_POST_MACHINING_MODE::UNKNOWN )
642 {
643 const float backRadius = pad->GetBackPostMachiningSize() * 0.5f * unitScale;
644 const float backDepth = pad->GetBackPostMachiningDepth() * unitScale;
645
646 if( backRadius > holeOuterRadius && backDepth > 0 )
647 {
648 float plugZTop = normalizeZ( padZTop );
649 float pmTopZ = normalizeZ( padZBot + backDepth );
650
651 if( plugZTop > pmTopZ )
652 {
653 if( backMode.value() == PAD_DRILL_POST_MACHINING_MODE::COUNTERSINK )
654 {
655 EDA_ANGLE angle( pad->GetBackPostMachiningAngle(), TENTHS_OF_A_DEGREE_T );
656 generateInvCone( padCenter, holeOuterRadius, backRadius,
657 plugZTop, pmTopZ, nrSegments, plugTriangles, angle );
658 }
659 else
660 {
661 generateCylinder( padCenter, holeOuterRadius, backRadius,
662 plugZTop, pmTopZ, nrSegments, plugTriangles );
663 }
664 }
665 }
666 }
667 }
668 }
669
670 // If we have any plug geometry, create a render list for it
671 if( plugTriangles->m_layer_top_triangles->GetVertexSize() > 0
672 || plugTriangles->m_layer_bot_triangles->GetVertexSize() > 0
673 || plugTriangles->m_layer_middle_contours_quads->GetVertexSize() > 0 )
674 {
675 // Store the triangles for later cleanup
676 m_triangles.push_back( plugTriangles );
677
678 // Create a render list for the plugs using the same Z range as the board
679 // This will be scaled and drawn alongside m_boardWithHoles in renderBoardBody()
681 boardZTop, boardZTop );
682 }
683 else
684 {
685 delete plugTriangles;
686 }
687}
688
689
691{
692 if( !m_boardAdapter.GetBoard() )
693 return;
694
695 const float biuTo3d = m_boardAdapter.BiuTo3dUnits();
696
697 for( FOOTPRINT* fp : m_boardAdapter.GetBoard()->Footprints() )
698 {
699 if( !fp->HasExtrudedBody() )
700 continue;
701
702 const EXTRUDED_3D_BODY* body = fp->GetExtrudedBody();
703
704 if( !body->m_show || !m_boardAdapter.IsFootprintShown( fp ) )
705 continue;
706
707 SHAPE_POLY_SET outline;
708
709 if( !GetExtrusionOutline( fp, outline ) )
710 continue;
711
712 if( outline.OutlineCount() == 0 )
713 continue;
714
715 outline.Simplify();
716
717 VECTOR2I fpPos = fp->GetPosition();
718 ApplyExtrusionTransform( outline, body, fpPos );
719
720 bool isBack = fp->IsFlipped();
721 float boardSurfaceZ = m_boardAdapter.GetFootprintZPos( isBack );
722 float standoff3d = body->m_standoff * biuTo3d;
723 float bodyThickness = ( body->m_height - body->m_standoff ) * biuTo3d * body->m_scale.z;
724 float zOffset3d = pcbIUScale.mmToIU( body->m_offset.z ) * biuTo3d;
725
726 float zBot, zTop;
727
728 if( !isBack )
729 {
730 zBot = boardSurfaceZ + standoff3d + zOffset3d;
731 zTop = zBot + bodyThickness;
732 }
733 else
734 {
735 zTop = boardSurfaceZ - standoff3d - zOffset3d;
736 zBot = zTop - bodyThickness;
737 }
738
739 CONTAINER_2D triContainer;
740 ConvertPolygonToTriangles( outline, triContainer, biuTo3d, *fp );
741
742 const LIST_OBJECT2D& triList = triContainer.GetList();
743
744 if( triList.empty() )
745 continue;
746
747 TRIANGLE_DISPLAY_LIST* layerTri = new TRIANGLE_DISPLAY_LIST( triList.size() );
748
749 for( const OBJECT_2D* obj : triList )
750 {
751 const TRIANGLE_2D* tri = static_cast<const TRIANGLE_2D*>( obj );
752 addTopAndBottomTriangles( layerTri, tri->GetP1(), tri->GetP2(), tri->GetP3(), zTop, zBot );
753 }
754
755 layerTri->AddToMiddleContours( outline, zBot, zTop, biuTo3d, false );
756
757 OPENGL_RENDER_LIST* renderList = new OPENGL_RENDER_LIST( *layerTri, m_circleTexture, zTop, zBot );
758
759 m_triangles.push_back( layerTri );
760 m_extrudedBodyLists[fp] = renderList;
761
762 // Create metallic pin extrusions for THT pads
763 // Start from opposite board side to standoff height
764 if( standoff3d > 0.0f )
765 {
766 SHAPE_POLY_SET pinPoly;
767
768 if( GetExtrusionPinOutline( fp, pinPoly ) )
769 {
770 ApplyExtrusionTransform( pinPoly, body, fpPos );
771
772 float oppositeSurfaceZ = m_boardAdapter.GetFootprintZPos( !isBack );
773 float protrusion = 1.0f * pcbIUScale.IU_PER_MM * biuTo3d;
774 float pinZBot, pinZTop;
775
776 if( !isBack )
777 {
778 pinZBot = oppositeSurfaceZ - protrusion;
779 pinZTop = boardSurfaceZ + standoff3d;
780 }
781 else
782 {
783 pinZTop = oppositeSurfaceZ + protrusion;
784 pinZBot = boardSurfaceZ - standoff3d;
785 }
786
787 CONTAINER_2D pinTriContainer;
788 ConvertPolygonToTriangles( pinPoly, pinTriContainer, biuTo3d, *fp );
789
790 const LIST_OBJECT2D& pinTriList = pinTriContainer.GetList();
791
792 if( !pinTriList.empty() )
793 {
794 TRIANGLE_DISPLAY_LIST* pinLayerTri = new TRIANGLE_DISPLAY_LIST( pinTriList.size() );
795
796 for( const OBJECT_2D* obj : pinTriList )
797 {
798 const TRIANGLE_2D* tri = static_cast<const TRIANGLE_2D*>( obj );
799 addTopAndBottomTriangles( pinLayerTri, tri->GetP1(), tri->GetP2(), tri->GetP3(), pinZTop,
800 pinZBot );
801 }
802
803 pinLayerTri->AddToMiddleContours( pinPoly, pinZBot, pinZTop, biuTo3d, false );
804
805 OPENGL_RENDER_LIST* pinRenderList =
806 new OPENGL_RENDER_LIST( *pinLayerTri, m_circleTexture, pinZTop, pinZBot );
807
808 m_triangles.push_back( pinLayerTri );
809 m_extrudedPadLists[fp] = pinRenderList;
810 }
811 }
812 }
813 }
814}
815
816
817void RENDER_3D_OPENGL::reload( REPORTER* aStatusReporter, REPORTER* aWarningReporter )
818{
819 m_reloadRequested = false;
820
821 freeAllLists();
823
825
826 int64_t stats_startReloadTime = GetRunningMicroSecs();
827
828 m_boardAdapter.InitSettings( aStatusReporter, aWarningReporter );
829
830 SFVEC3F camera_pos = m_boardAdapter.GetBoardCenter();
831 m_camera.SetBoardLookAtPos( camera_pos );
832
833 if( aStatusReporter )
834 aStatusReporter->Report( _( "Load OpenGL: board" ) );
835
836 // Create Board
837 m_board = createBoard( m_boardAdapter.GetBoardPoly(), &m_boardAdapter.GetTH_IDs() );
838
839 m_antiBoardPolys.RemoveAllContours();
840 m_antiBoardPolys.NewOutline();
841 m_antiBoardPolys.Append( VECTOR2I( -INT_MAX/2, -INT_MAX/2 ) );
842 m_antiBoardPolys.Append( VECTOR2I( INT_MAX/2, -INT_MAX/2 ) );
843 m_antiBoardPolys.Append( VECTOR2I( INT_MAX/2, INT_MAX/2 ) );
844 m_antiBoardPolys.Append( VECTOR2I( -INT_MAX/2, INT_MAX/2 ) );
845 m_antiBoardPolys.Outline( 0 ).SetClosed( true );
846
847 m_antiBoardPolys.BooleanSubtract( m_boardAdapter.GetBoardPoly() );
849
850 SHAPE_POLY_SET board_poly_with_holes = m_boardAdapter.GetBoardPoly().CloneDropTriangulation();
851 board_poly_with_holes.BooleanSubtract( m_boardAdapter.GetTH_ODPolys() );
852 board_poly_with_holes.BooleanSubtract( m_boardAdapter.GetNPTH_ODPolys() );
853
854 // Also subtract counterbore, countersink, and backdrill polygons from the board
855 if( m_boardAdapter.GetFrontCounterborePolys().OutlineCount() > 0 )
856 board_poly_with_holes.BooleanSubtract( m_boardAdapter.GetFrontCounterborePolys() );
857
858 if( m_boardAdapter.GetBackCounterborePolys().OutlineCount() > 0 )
859 board_poly_with_holes.BooleanSubtract( m_boardAdapter.GetBackCounterborePolys() );
860
861 if( m_boardAdapter.GetFrontCountersinkPolys().OutlineCount() > 0 )
862 board_poly_with_holes.BooleanSubtract( m_boardAdapter.GetFrontCountersinkPolys() );
863
864 if( m_boardAdapter.GetBackCountersinkPolys().OutlineCount() > 0 )
865 board_poly_with_holes.BooleanSubtract( m_boardAdapter.GetBackCountersinkPolys() );
866
867 if( m_boardAdapter.GetBackdrillPolys().OutlineCount() > 0 )
868 board_poly_with_holes.BooleanSubtract( m_boardAdapter.GetBackdrillPolys() );
869
870 if( m_boardAdapter.GetTertiarydrillPolys().OutlineCount() > 0 )
871 board_poly_with_holes.BooleanSubtract( m_boardAdapter.GetTertiarydrillPolys() );
872
873
874 m_boardWithHoles = createBoard( board_poly_with_holes, &m_boardAdapter.GetTH_IDs() );
875
876 // Create plugs for backdrilled and post-machined areas
878
879 if( m_antiBoard )
880 m_antiBoard->SetItIsTransparent( true );
881
882 // Create Through Holes and vias
883 if( aStatusReporter )
884 aStatusReporter->Report( _( "Load OpenGL: holes and vias" ) );
885
886 SHAPE_POLY_SET outerPolyTHT = m_boardAdapter.GetTH_ODPolys().CloneDropTriangulation();
887
888 // Include NPTH polygons so their barrel walls are also generated
889 if( m_boardAdapter.GetNPTH_ODPolys().OutlineCount() > 0 )
890 outerPolyTHT.BooleanAdd( m_boardAdapter.GetNPTH_ODPolys() );
891
892 outerPolyTHT.BooleanIntersection( m_boardAdapter.GetBoardPoly() );
893
894 m_outerThroughHoles = generateHoles( m_boardAdapter.GetTH_ODs().GetList(), outerPolyTHT,
895 1.0f, 0.0f, false, &m_boardAdapter.GetTH_IDs() );
896
897 m_outerViaThroughHoles = generateHoles( m_boardAdapter.GetViaTH_ODs().GetList(),
898 m_boardAdapter.GetViaTH_ODPolys(), 1.0f, 0.0f, false );
899
900 if( m_boardAdapter.m_Cfg->m_Render.clip_silk_on_via_annuli )
901 {
902 m_outerThroughHoleRings = generateHoles( m_boardAdapter.GetViaAnnuli().GetList(),
903 m_boardAdapter.GetViaAnnuliPolys(),
904 1.0f, 0.0f, false );
905 }
906
907 const MAP_POLY& innerMapHoles = m_boardAdapter.GetHoleIdPolysMap();
908 const MAP_POLY& outerMapHoles = m_boardAdapter.GetHoleOdPolysMap();
909
910 wxASSERT( innerMapHoles.size() == outerMapHoles.size() );
911
912 const MAP_CONTAINER_2D_BASE& map_holes = m_boardAdapter.GetLayerHoleMap();
913
914 if( outerMapHoles.size() > 0 )
915 {
916 float layer_z_bot = 0.0f;
917 float layer_z_top = 0.0f;
918
919 for( const auto& [ layer, poly ] : outerMapHoles )
920 {
921 getLayerZPos( layer, layer_z_top, layer_z_bot );
922
923 m_outerLayerHoles[layer] = generateHoles( map_holes.at( layer )->GetList(), *poly,
924 layer_z_top, layer_z_bot, false );
925 }
926
927 for( const auto& [ layer, poly ] : innerMapHoles )
928 {
929 getLayerZPos( layer, layer_z_top, layer_z_bot );
930
931 m_innerLayerHoles[layer] = generateHoles( map_holes.at( layer )->GetList(), *poly,
932 layer_z_top, layer_z_bot, false );
933 }
934 }
935
936 // Generate vertical cylinders of vias and pads (copper)
938
939 // Add layers maps
940 if( aStatusReporter )
941 aStatusReporter->Report( _( "Load OpenGL: layers" ) );
942
943 std::bitset<LAYER_3D_END> visibilityFlags = m_boardAdapter.GetVisibleLayers();
944 const MAP_POLY& map_poly = m_boardAdapter.GetPolyMap();
945 wxString msg;
946
947 for( const auto& [ layer, container2d ] : m_boardAdapter.GetLayerMap() )
948 {
949 if( !m_boardAdapter.Is3dLayerEnabled( layer, visibilityFlags ) )
950 continue;
951
952 if( aStatusReporter )
953 {
954 msg = m_boardAdapter.GetBoard()->GetLayerName( layer );
955 aStatusReporter->Report( wxString::Format( _( "Load OpenGL layer %s" ), msg ) );
956 }
957
958 SHAPE_POLY_SET polyListSubtracted;
959 SHAPE_POLY_SET* polyList = nullptr;
960
961 // Load the vertical (Z axis) component of shapes
962
963 if( m_boardAdapter.m_Cfg->m_Render.opengl_copper_thickness )
964 {
965 if( map_poly.contains( layer ) )
966 {
967 polyListSubtracted = *map_poly.at( layer );
968
969 if( LSET::PhysicalLayersMask().test( layer ) )
970 {
971 polyListSubtracted.BooleanIntersection( m_boardAdapter.GetBoardPoly() );
972 }
973
974 if( layer != B_Mask && layer != F_Mask )
975 {
976 polyListSubtracted.BooleanSubtract( m_boardAdapter.GetTH_ODPolys() );
977 polyListSubtracted.BooleanSubtract( m_boardAdapter.GetNPTH_ODPolys() );
978
979 // Subtract counterbore/countersink cutouts from copper layers
980 if( layer == F_Cu )
981 {
982 polyListSubtracted.BooleanSubtract( m_boardAdapter.GetFrontCounterborePolys() );
983 polyListSubtracted.BooleanSubtract( m_boardAdapter.GetFrontCountersinkPolys() );
984 }
985 else if( layer == B_Cu )
986 {
987 polyListSubtracted.BooleanSubtract( m_boardAdapter.GetBackCounterborePolys() );
988 polyListSubtracted.BooleanSubtract( m_boardAdapter.GetBackCountersinkPolys() );
989 }
990 }
991
992 if( m_boardAdapter.m_Cfg->m_Render.subtract_mask_from_silk )
993 {
994 if( layer == B_SilkS && map_poly.contains( B_Mask ) )
995 {
996 polyListSubtracted.BooleanSubtract( *map_poly.at( B_Mask ) );
997 }
998 else if( layer == F_SilkS && map_poly.contains( F_Mask ) )
999 {
1000 polyListSubtracted.BooleanSubtract( *map_poly.at( F_Mask ) );
1001 }
1002 }
1003
1004 polyList = &polyListSubtracted;
1005 }
1006 }
1007
1008 OPENGL_RENDER_LIST* oglList = generateLayerList( container2d, polyList, layer,
1009 &m_boardAdapter.GetTH_IDs() );
1010
1011 if( oglList != nullptr )
1012 m_layers[layer] = oglList;
1013 }
1014
1015 if( m_boardAdapter.m_Cfg->m_Render.DifferentiatePlatedCopper() )
1016 {
1017 const SHAPE_POLY_SET* frontPlatedCopperPolys = m_boardAdapter.GetFrontPlatedCopperPolys();
1018 const SHAPE_POLY_SET* backPlatedCopperPolys = m_boardAdapter.GetBackPlatedCopperPolys();
1019
1020 if( frontPlatedCopperPolys )
1021 {
1022 SHAPE_POLY_SET poly = frontPlatedCopperPolys->CloneDropTriangulation();
1023 poly.BooleanIntersection( m_boardAdapter.GetBoardPoly() );
1024 poly.BooleanSubtract( m_boardAdapter.GetTH_ODPolys() );
1025 poly.BooleanSubtract( m_boardAdapter.GetNPTH_ODPolys() );
1026 poly.BooleanSubtract( m_boardAdapter.GetFrontCounterborePolys() );
1027 poly.BooleanSubtract( m_boardAdapter.GetFrontCountersinkPolys() );
1028 poly.BooleanSubtract( m_boardAdapter.GetTertiarydrillPolys() );
1029
1030 m_platedPadsFront = generateLayerList( m_boardAdapter.GetPlatedPadsFront(), &poly,
1031 F_Cu );
1032
1033 // An entry for F_Cu must exist in m_layers or we'll never look at m_platedPadsFront
1034 if( m_layers.count( F_Cu ) == 0 )
1036 }
1037
1038 if( backPlatedCopperPolys )
1039 {
1040 SHAPE_POLY_SET poly = backPlatedCopperPolys->CloneDropTriangulation();
1041 poly.BooleanIntersection( m_boardAdapter.GetBoardPoly() );
1042 poly.BooleanSubtract( m_boardAdapter.GetTH_ODPolys() );
1043 poly.BooleanSubtract( m_boardAdapter.GetNPTH_ODPolys() );
1044 poly.BooleanSubtract( m_boardAdapter.GetBackCounterborePolys() );
1045 poly.BooleanSubtract( m_boardAdapter.GetBackCountersinkPolys() );
1046 poly.BooleanSubtract( m_boardAdapter.GetBackdrillPolys() );
1047
1048 m_platedPadsBack = generateLayerList( m_boardAdapter.GetPlatedPadsBack(), &poly, B_Cu );
1049
1050 // An entry for B_Cu must exist in m_layers or we'll never look at m_platedPadsBack
1051 if( m_layers.count( B_Cu ) == 0 )
1053 }
1054 }
1055
1056 if( m_boardAdapter.m_Cfg->m_Render.show_off_board_silk )
1057 {
1058 if( const BVH_CONTAINER_2D* padsFront = m_boardAdapter.GetOffboardPadsFront() )
1059 m_offboardPadsFront = generateLayerList( padsFront, nullptr, F_Cu );
1060
1061 if( const BVH_CONTAINER_2D* padsBack = m_boardAdapter.GetOffboardPadsBack() )
1062 m_offboardPadsBack = generateLayerList( padsBack, nullptr, B_Cu );
1063 }
1064
1065 // Load 3D models
1066 if( aStatusReporter )
1067 aStatusReporter->Report( _( "Loading 3D models..." ) );
1068
1069 load3dModels( aStatusReporter );
1070
1072
1073 if( aStatusReporter )
1074 {
1075 // Calculation time in seconds
1076 double calculation_time = (double)( GetRunningMicroSecs() - stats_startReloadTime) / 1e6;
1077
1078 aStatusReporter->Report( wxString::Format( _( "Reload time %.3f s" ), calculation_time ) );
1079 }
1080}
1081
1082
1084 const SFVEC2F& v1, const SFVEC2F& v2, float top,
1085 float bot )
1086{
1087 aDst->m_layer_bot_triangles->AddTriangle( SFVEC3F( v0.x, v0.y, bot ),
1088 SFVEC3F( v1.x, v1.y, bot ),
1089 SFVEC3F( v2.x, v2.y, bot ) );
1090
1092 SFVEC3F( v1.x, v1.y, top ),
1093 SFVEC3F( v0.x, v0.y, top ) );
1094}
1095
1096
1097void RENDER_3D_OPENGL::getLayerZPos( PCB_LAYER_ID aLayer, float& aOutZtop, float& aOutZbot ) const
1098{
1099 aOutZbot = m_boardAdapter.GetLayerBottomZPos( aLayer );
1100 aOutZtop = m_boardAdapter.GetLayerTopZPos( aLayer );
1101
1102 if( aOutZtop < aOutZbot )
1103 {
1104 float tmpFloat = aOutZbot;
1105 aOutZbot = aOutZtop;
1106 aOutZtop = tmpFloat;
1107 }
1108}
1109
1110
1111void RENDER_3D_OPENGL::generateCylinder( const SFVEC2F& aCenter, float aInnerRadius,
1112 float aOuterRadius, float aZtop, float aZbot,
1113 unsigned int aNr_sides_per_circle,
1114 TRIANGLE_DISPLAY_LIST* aDstLayer )
1115{
1116 std::vector< SFVEC2F > innerContour;
1117 std::vector< SFVEC2F > outerContour;
1118
1119 generateRing( aCenter, aInnerRadius, aOuterRadius, aNr_sides_per_circle, innerContour,
1120 outerContour, false );
1121
1122 for( unsigned int i = 0; i < ( innerContour.size() - 1 ); ++i )
1123 {
1124 const SFVEC2F& vi0 = innerContour[i + 0];
1125 const SFVEC2F& vi1 = innerContour[i + 1];
1126 const SFVEC2F& vo0 = outerContour[i + 0];
1127 const SFVEC2F& vo1 = outerContour[i + 1];
1128
1129 aDstLayer->m_layer_top_triangles->AddQuad( SFVEC3F( vi1.x, vi1.y, aZtop ),
1130 SFVEC3F( vi0.x, vi0.y, aZtop ),
1131 SFVEC3F( vo0.x, vo0.y, aZtop ),
1132 SFVEC3F( vo1.x, vo1.y, aZtop ) );
1133
1134 aDstLayer->m_layer_bot_triangles->AddQuad( SFVEC3F( vi1.x, vi1.y, aZbot ),
1135 SFVEC3F( vo1.x, vo1.y, aZbot ),
1136 SFVEC3F( vo0.x, vo0.y, aZbot ),
1137 SFVEC3F( vi0.x, vi0.y, aZbot ) );
1138 }
1139
1140 aDstLayer->AddToMiddleContours( outerContour, aZbot, aZtop, true );
1141 aDstLayer->AddToMiddleContours( innerContour, aZbot, aZtop, false );
1142}
1143
1144
1145void RENDER_3D_OPENGL::generateInvCone( const SFVEC2F& aCenter, float aInnerRadius,
1146 float aOuterRadius, float aZtop, float aZbot,
1147 unsigned int aNr_sides_per_circle,
1148 TRIANGLE_DISPLAY_LIST* aDstLayer, EDA_ANGLE aAngle )
1149{
1150 // For a countersink cone:
1151 // - The outer contour goes from aZbot to aZtop (full height)
1152 // - The inner contour goes from aZbot to aZbot + innerHeight
1153 // - The top surface is conical, sloping from inner top to outer top
1154 // - aAngle is the half-angle of the cone (in decidegrees from the vertical)
1155
1156 // Calculate the inner contour height based on the cone angle
1157 // tan(angle) = (outerRadius - innerRadius) / innerHeight
1158 // innerHeight = (outerRadius - innerRadius) / tan(angle)
1159 float radialDiff = aOuterRadius - aInnerRadius;
1160 float angleRad = aAngle.AsRadians();
1161
1162 // Clamp angle to avoid division by zero or negative heights
1163 if( angleRad < 0.01f )
1164 angleRad = 0.01f;
1165
1166 float innerHeight = radialDiff / tanf( angleRad );
1167 float totalHeight = aZtop - aZbot;
1168
1169 // Clamp inner height to not exceed total height
1170 if( innerHeight > totalHeight )
1171 innerHeight = totalHeight;
1172
1173 float zInnerTop = aZbot + innerHeight;
1174
1175 std::vector< SFVEC2F > innerContour;
1176 std::vector< SFVEC2F > outerContour;
1177
1178 generateRing( aCenter, aInnerRadius, aOuterRadius, aNr_sides_per_circle, innerContour,
1179 outerContour, false );
1180
1181 for( unsigned int i = 0; i < ( innerContour.size() - 1 ); ++i )
1182 {
1183 const SFVEC2F& vi0 = innerContour[i + 0];
1184 const SFVEC2F& vi1 = innerContour[i + 1];
1185 const SFVEC2F& vo0 = outerContour[i + 0];
1186 const SFVEC2F& vo1 = outerContour[i + 1];
1187
1188 // Conical top surface: from inner contour at zInnerTop to outer contour at aZtop
1189 aDstLayer->m_layer_top_triangles->AddQuad( SFVEC3F( vi1.x, vi1.y, zInnerTop ),
1190 SFVEC3F( vi0.x, vi0.y, zInnerTop ),
1191 SFVEC3F( vo0.x, vo0.y, aZtop ),
1192 SFVEC3F( vo1.x, vo1.y, aZtop ) );
1193
1194 // Flat bottom surface
1195 aDstLayer->m_layer_bot_triangles->AddQuad( SFVEC3F( vi1.x, vi1.y, aZbot ),
1196 SFVEC3F( vo1.x, vo1.y, aZbot ),
1197 SFVEC3F( vo0.x, vo0.y, aZbot ),
1198 SFVEC3F( vi0.x, vi0.y, aZbot ) );
1199 }
1200
1201 // Outer contour wall goes full height
1202 aDstLayer->AddToMiddleContours( outerContour, aZbot, aZtop, true );
1203 // Inner contour wall only goes up to zInnerTop
1204 aDstLayer->AddToMiddleContours( innerContour, aZbot, zInnerTop, false );
1205}
1206
1207
1208void RENDER_3D_OPENGL::generateDisk( const SFVEC2F& aCenter, float aRadius, float aZ,
1209 unsigned int aNr_sides_per_circle, TRIANGLE_DISPLAY_LIST* aDstLayer,
1210 bool aTop )
1211{
1212 const float delta = 2.0f * glm::pi<float>() / (float) aNr_sides_per_circle;
1213
1214 for( unsigned int i = 0; i < aNr_sides_per_circle; ++i )
1215 {
1216 float a0 = delta * i;
1217 float a1 = delta * ( i + 1 );
1218 const SFVEC3F p0( aCenter.x + cosf( a0 ) * aRadius,
1219 aCenter.y + sinf( a0 ) * aRadius, aZ );
1220 const SFVEC3F p1( aCenter.x + cosf( a1 ) * aRadius,
1221 aCenter.y + sinf( a1 ) * aRadius, aZ );
1222 const SFVEC3F c( aCenter.x, aCenter.y, aZ );
1223
1224 if( aTop )
1225 aDstLayer->m_layer_top_triangles->AddTriangle( p1, p0, c );
1226 else
1227 aDstLayer->m_layer_bot_triangles->AddTriangle( p0, p1, c );
1228 }
1229}
1230
1231
1232void RENDER_3D_OPENGL::generateDimple( const SFVEC2F& aCenter, float aRadius, float aZ,
1233 float aDepth, unsigned int aNr_sides_per_circle,
1234 TRIANGLE_DISPLAY_LIST* aDstLayer, bool aTop )
1235{
1236 const float delta = 2.0f * glm::pi<float>() / (float) aNr_sides_per_circle;
1237 const SFVEC3F c( aCenter.x, aCenter.y, aTop ? aZ - aDepth : aZ + aDepth );
1238
1239 for( unsigned int i = 0; i < aNr_sides_per_circle; ++i )
1240 {
1241 float a0 = delta * i;
1242 float a1 = delta * ( i + 1 );
1243 const SFVEC3F p0( aCenter.x + cosf( a0 ) * aRadius,
1244 aCenter.y + sinf( a0 ) * aRadius, aZ );
1245 const SFVEC3F p1( aCenter.x + cosf( a1 ) * aRadius,
1246 aCenter.y + sinf( a1 ) * aRadius, aZ );
1247
1248 if( aTop )
1249 aDstLayer->m_layer_top_triangles->AddTriangle( p0, p1, c );
1250 else
1251 aDstLayer->m_layer_bot_triangles->AddTriangle( p1, p0, c );
1252 }
1253}
1254
1255
1257 const SFVEC2F& aHoleCenter,
1259 int aSizeIU,
1260 int aDepthIU,
1261 float aHoleInnerRadius,
1262 float aZSurface,
1263 bool aIsFront,
1264 float aPlatingThickness3d,
1265 float aUnitScale,
1266 float* aZEnd )
1267{
1268 if( !m_boardAdapter.m_Cfg->m_Render.show_plated_barrels )
1269 return false;
1270
1271 if( !aDstLayer || aPlatingThickness3d <= 0.0f || aHoleInnerRadius <= 0.0f )
1272 return false;
1273
1276 {
1277 return false;
1278 }
1279
1280 if( aSizeIU <= 0 || aDepthIU <= 0 )
1281 return false;
1282
1283 const float radius = aSizeIU * 0.5f * aUnitScale;
1284 const float depth = aDepthIU * aUnitScale;
1285
1286 if( radius <= aHoleInnerRadius || depth <= 0.0f )
1287 return false;
1288
1289 float zEnd = aIsFront ? ( aZSurface - depth ) : ( aZSurface + depth );
1290
1291 if( aZEnd )
1292 *aZEnd = zEnd;
1293
1294 const float zTop = std::max( aZSurface, zEnd );
1295 const float zBot = std::min( aZSurface, zEnd );
1296
1297 const int diameterBIU = std::max( aSizeIU,
1298 std::max( 1,
1299 (int) ( ( aHoleInnerRadius * 2.0f )
1300 / aUnitScale ) ) );
1301 const unsigned int nrSegments =
1302 std::max( 12u, m_boardAdapter.GetCircleSegmentCount( diameterBIU ) );
1303
1305 {
1306 generateCylinder( aHoleCenter, radius, radius + aPlatingThickness3d, zTop, zBot,
1307 nrSegments, aDstLayer );
1308 return true;
1309 }
1310
1311 float csTopRadius = radius;
1312 float csBotRadius = aHoleInnerRadius;
1313
1314 std::vector< SFVEC2F > innerContourTop, outerContourTop;
1315 std::vector< SFVEC2F > innerContourBot, outerContourBot;
1316
1317 generateRing( aHoleCenter, csTopRadius, csTopRadius + aPlatingThickness3d, nrSegments,
1318 innerContourTop, outerContourTop, false );
1319 generateRing( aHoleCenter, csBotRadius, csBotRadius + aPlatingThickness3d, nrSegments,
1320 innerContourBot, outerContourBot, false );
1321
1322 for( unsigned int i = 0; i < ( innerContourTop.size() - 1 ); ++i )
1323 {
1324 const SFVEC2F& vi0_top = innerContourTop[i + 0];
1325 const SFVEC2F& vi1_top = innerContourTop[i + 1];
1326 const SFVEC2F& vo0_top = outerContourTop[i + 0];
1327 const SFVEC2F& vo1_top = outerContourTop[i + 1];
1328
1329 const SFVEC2F& vi0_bot = innerContourBot[i + 0];
1330 const SFVEC2F& vi1_bot = innerContourBot[i + 1];
1331 const SFVEC2F& vo0_bot = outerContourBot[i + 0];
1332 const SFVEC2F& vo1_bot = outerContourBot[i + 1];
1333
1335 SFVEC3F( vi1_top.x, vi1_top.y, zTop ),
1336 SFVEC3F( vi0_top.x, vi0_top.y, zTop ),
1337 SFVEC3F( vi0_bot.x, vi0_bot.y, zBot ),
1338 SFVEC3F( vi1_bot.x, vi1_bot.y, zBot ) );
1339
1341 SFVEC3F( vo1_top.x, vo1_top.y, zTop ),
1342 SFVEC3F( vo0_top.x, vo0_top.y, zTop ),
1343 SFVEC3F( vo0_bot.x, vo0_bot.y, zBot ),
1344 SFVEC3F( vo1_bot.x, vo1_bot.y, zBot ) );
1345 }
1346
1347 return true;
1348}
1349
1350
1351void RENDER_3D_OPENGL::generateViaBarrels( float aPlatingThickness3d, float aUnitScale )
1352{
1353 if( !m_boardAdapter.GetBoard() || m_boardAdapter.GetViaCount() <= 0 )
1354 return;
1355
1356 if( !m_boardAdapter.m_Cfg->m_Render.show_plated_barrels )
1357 return;
1358
1359 float averageDiameter = m_boardAdapter.GetAverageViaHoleDiameter();
1360 unsigned int averageSegCount = m_boardAdapter.GetCircleSegmentCount( averageDiameter );
1361 unsigned int trianglesEstimate = averageSegCount * 8 * m_boardAdapter.GetViaCount();
1362
1363 TRIANGLE_DISPLAY_LIST* layerTriangleVIA = new TRIANGLE_DISPLAY_LIST( trianglesEstimate );
1364
1365 for( const PCB_TRACK* track : m_boardAdapter.GetBoard()->Tracks() )
1366 {
1367 if( track->Type() != PCB_VIA_T )
1368 continue;
1369
1370 const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
1371 bool isBackdrilled = via->GetSecondaryDrillSize().has_value();
1372 bool isTertiarydrilled = via->GetTertiaryDrillSize().has_value();
1373 bool hasFrontPostMachining = via->GetFrontPostMachining().value_or( PAD_DRILL_POST_MACHINING_MODE::UNKNOWN )
1375 && via->GetFrontPostMachining().value_or( PAD_DRILL_POST_MACHINING_MODE::UNKNOWN )
1377 bool hasBackPostMachining = via->GetBackPostMachining().value_or( PAD_DRILL_POST_MACHINING_MODE::UNKNOWN )
1379 && via->GetBackPostMachining().value_or( PAD_DRILL_POST_MACHINING_MODE::UNKNOWN )
1381
1382 if( via->GetViaType() == VIATYPE::THROUGH && !isBackdrilled
1383 && !hasFrontPostMachining && !hasBackPostMachining )
1384 {
1385 continue;
1386 }
1387
1388 const float holediameter = via->GetDrillValue() * aUnitScale;
1389 const int nrSegments = m_boardAdapter.GetCircleSegmentCount( via->GetDrillValue() );
1390 const float hole_inner_radius = holediameter / 2.0f;
1391
1392 const SFVEC2F via_center( via->GetStart().x * aUnitScale,
1393 -via->GetStart().y * aUnitScale );
1394
1395 PCB_LAYER_ID top_layer, bottom_layer;
1396 via->LayerPair( &top_layer, &bottom_layer );
1397
1398 float ztop, zbot, dummy;
1399
1400 getLayerZPos( top_layer, ztop, dummy );
1401 getLayerZPos( bottom_layer, dummy, zbot );
1402
1403 wxASSERT( zbot < ztop );
1404
1405 float ztop_plated = ztop;
1406 float zbot_plated = zbot;
1407
1408 if( isBackdrilled )
1409 {
1410 PCB_LAYER_ID secEnd = via->GetSecondaryDrillEndLayer();
1411 float secZEnd;
1412
1413 // Backdrill goes from the back surface up to secEnd, so get the top of that layer
1414 // as the bottom of the plated barrel
1415 getLayerZPos( secEnd, secZEnd, dummy );
1416 zbot_plated = std::max( zbot_plated, secZEnd );
1417 }
1418
1419 if( isTertiarydrilled )
1420 {
1421 PCB_LAYER_ID terEnd = via->GetTertiaryDrillEndLayer();
1422 float terZEnd;
1423
1424 // Tertiary drill goes from the front surface down to terEnd, so get the bottom of that layer
1425 // as the top of the plated barrel
1426 getLayerZPos( terEnd, dummy, terZEnd );
1427 ztop_plated = std::min( ztop_plated, terZEnd );
1428 }
1429
1430 auto applyViaPostMachining = [&]( bool isFront )
1431 {
1432 auto modeOpt = isFront ? via->GetFrontPostMachining()
1433 : via->GetBackPostMachining();
1434
1435 if( !modeOpt
1437 {
1438 return;
1439 }
1440
1441 int sizeIU = isFront ? via->GetFrontPostMachiningSize()
1442 : via->GetBackPostMachiningSize();
1443 int depthIU = isFront ? via->GetFrontPostMachiningDepth()
1444 : via->GetBackPostMachiningDepth();
1445 float zSurface = isFront ? ztop : zbot;
1446 float zEnd = 0.0f;
1447
1448 if( appendPostMachiningGeometry( layerTriangleVIA, via_center, modeOpt.value(),
1449 sizeIU, depthIU, hole_inner_radius, zSurface,
1450 isFront, aPlatingThickness3d, aUnitScale, &zEnd ) )
1451 {
1452 if( isFront )
1453 ztop_plated = std::min( ztop_plated, zEnd );
1454 else
1455 zbot_plated = std::max( zbot_plated, zEnd );
1456 }
1457 };
1458
1459 if( hasFrontPostMachining )
1460 applyViaPostMachining( true );
1461 if( hasBackPostMachining )
1462 applyViaPostMachining( false );
1463
1464 generateCylinder( via_center, hole_inner_radius, hole_inner_radius + aPlatingThickness3d, ztop_plated,
1465 zbot_plated, nrSegments, layerTriangleVIA );
1466 }
1467
1468 const float padFrontSurface =
1469 m_boardAdapter.GetLayerBottomZPos( F_Cu )
1470 + m_boardAdapter.GetFrontCopperThickness() * 0.99f;
1471 const float padBackSurface =
1472 m_boardAdapter.GetLayerBottomZPos( B_Cu )
1473 - m_boardAdapter.GetBackCopperThickness() * 0.99f;
1474
1475 for( const FOOTPRINT* footprint : m_boardAdapter.GetBoard()->Footprints() )
1476 {
1477 for( const PAD* pad : footprint->Pads() )
1478 {
1479 if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
1480 continue;
1481
1482 if( !pad->HasHole() )
1483 continue;
1484
1485 if( pad->GetDrillShape() != PAD_DRILL_SHAPE::CIRCLE )
1486 continue;
1487
1488 const SFVEC2F padCenter( pad->GetPosition().x * aUnitScale,
1489 -pad->GetPosition().y * aUnitScale );
1490 const float holeInnerRadius =
1491 pad->GetDrillSize().x * 0.5f * aUnitScale;
1492
1493 auto emitPadPostMachining = [&]( bool isFront )
1494 {
1495 auto modeOpt = isFront ? pad->GetFrontPostMachining()
1496 : pad->GetBackPostMachining();
1497
1498 if( !modeOpt
1500 {
1501 return;
1502 }
1503
1504 int sizeIU = isFront ? pad->GetFrontPostMachiningSize()
1505 : pad->GetBackPostMachiningSize();
1506 int depthIU = isFront ? pad->GetFrontPostMachiningDepth()
1507 : pad->GetBackPostMachiningDepth();
1508 float zSurface = isFront ? padFrontSurface : padBackSurface;
1509
1510 appendPostMachiningGeometry( layerTriangleVIA, padCenter, modeOpt.value(),
1511 sizeIU, depthIU, holeInnerRadius, zSurface,
1512 isFront, aPlatingThickness3d, aUnitScale,
1513 nullptr );
1514 };
1515
1516 emitPadPostMachining( true );
1517 emitPadPostMachining( false );
1518 }
1519 }
1520
1521 m_microviaHoles = new OPENGL_RENDER_LIST( *layerTriangleVIA, 0, 0.0f, 0.0f );
1522
1523 delete layerTriangleVIA;
1524}
1525
1526
1527void RENDER_3D_OPENGL::generatePlatedHoleShells( int aPlatingThickness, float aUnitScale )
1528{
1529 if( !m_boardAdapter.GetBoard() )
1530 return;
1531
1532 if( m_boardAdapter.GetHoleCount() <= 0 && m_boardAdapter.GetViaCount() <= 0 )
1533 return;
1534
1535 SHAPE_POLY_SET tht_outer_holes_poly; // Stores the outer poly of the copper holes
1536 SHAPE_POLY_SET tht_inner_holes_poly; // Stores the inner poly of the copper holes
1537
1538 tht_outer_holes_poly.RemoveAllContours();
1539 tht_inner_holes_poly.RemoveAllContours();
1540
1541 for( const PCB_TRACK* track : m_boardAdapter.GetBoard()->Tracks() )
1542 {
1543 if( track->Type() != PCB_VIA_T )
1544 continue;
1545
1546 const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
1547
1548 if( via->GetViaType() == VIATYPE::THROUGH )
1549 {
1550 TransformCircleToPolygon( tht_outer_holes_poly, via->GetPosition(),
1551 via->GetDrill() / 2 + aPlatingThickness,
1552 via->GetMaxError(), ERROR_INSIDE );
1553
1554 TransformCircleToPolygon( tht_inner_holes_poly, via->GetPosition(), via->GetDrill() / 2,
1555 via->GetMaxError(), ERROR_INSIDE );
1556 }
1557 }
1558
1559 for( const FOOTPRINT* footprint : m_boardAdapter.GetBoard()->Footprints() )
1560 {
1561 for( const PAD* pad : footprint->Pads() )
1562 {
1563 if( pad->GetAttribute() != PAD_ATTRIB::NPTH )
1564 {
1565 if( !pad->HasHole() )
1566 continue;
1567
1568 pad->TransformHoleToPolygon( tht_outer_holes_poly, aPlatingThickness,
1569 pad->GetMaxError(), ERROR_INSIDE );
1570 pad->TransformHoleToPolygon( tht_inner_holes_poly, 0,
1571 pad->GetMaxError(), ERROR_INSIDE );
1572 }
1573 }
1574 }
1575
1576 tht_outer_holes_poly.BooleanSubtract( tht_inner_holes_poly );
1577
1578 tht_outer_holes_poly.BooleanSubtract( m_antiBoardPolys );
1579
1580 CONTAINER_2D holesContainer;
1581
1582 ConvertPolygonToTriangles( tht_outer_holes_poly, holesContainer,
1583 aUnitScale, *m_boardAdapter.GetBoard() );
1584
1585 const LIST_OBJECT2D& holes2D = holesContainer.GetList();
1586
1587 if( holes2D.size() > 0 && m_boardAdapter.m_Cfg->m_Render.show_plated_barrels )
1588 {
1589 float layer_z_top, layer_z_bot, dummy;
1590
1591 getLayerZPos( F_Cu, layer_z_top, dummy );
1592 getLayerZPos( B_Cu, dummy, layer_z_bot );
1593
1594 TRIANGLE_DISPLAY_LIST* layerTriangles = new TRIANGLE_DISPLAY_LIST( holes2D.size() );
1595
1596 for( const OBJECT_2D* itemOnLayer : holes2D )
1597 {
1598 const OBJECT_2D* object2d_A = itemOnLayer;
1599
1600 wxASSERT( object2d_A->GetObjectType() == OBJECT_2D_TYPE::TRIANGLE );
1601
1602 const TRIANGLE_2D* tri = static_cast<const TRIANGLE_2D*>( object2d_A );
1603
1604 const SFVEC2F& v1 = tri->GetP1();
1605 const SFVEC2F& v2 = tri->GetP2();
1606 const SFVEC2F& v3 = tri->GetP3();
1607
1608 addTopAndBottomTriangles( layerTriangles, v1, v2, v3, layer_z_top, layer_z_bot );
1609 }
1610
1611 wxASSERT( tht_outer_holes_poly.OutlineCount() > 0 );
1612
1613 if( tht_outer_holes_poly.OutlineCount() > 0 )
1614 {
1615 layerTriangles->AddToMiddleContours( tht_outer_holes_poly,
1616 layer_z_bot, layer_z_top,
1617 aUnitScale, false );
1618
1619 m_padHoles = new OPENGL_RENDER_LIST( *layerTriangles, m_circleTexture,
1620 layer_z_top, layer_z_top );
1621 }
1622
1623 delete layerTriangles;
1624 }
1625}
1626
1627
1628void RENDER_3D_OPENGL::generateViaCovers( float aPlatingThickness3d, float aUnitScale )
1629{
1630 if( !m_boardAdapter.GetBoard() || m_boardAdapter.GetViaCount() <= 0 )
1631 return;
1632
1633 TRIANGLE_DISPLAY_LIST* frontCover = new TRIANGLE_DISPLAY_LIST( m_boardAdapter.GetViaCount() );
1634 TRIANGLE_DISPLAY_LIST* backCover = new TRIANGLE_DISPLAY_LIST( m_boardAdapter.GetViaCount() );
1635
1636 for( const PCB_TRACK* track : m_boardAdapter.GetBoard()->Tracks() )
1637 {
1638 if( track->Type() != PCB_VIA_T )
1639 continue;
1640
1641 const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
1642
1643 const float holediameter = via->GetDrillValue() * aUnitScale;
1644 const float hole_radius = holediameter / 2.0f + 2.0f * aPlatingThickness3d;
1645 const SFVEC2F center( via->GetStart().x * aUnitScale,
1646 -via->GetStart().y * aUnitScale );
1647 unsigned int seg = m_boardAdapter.GetCircleSegmentCount( via->GetDrillValue() );
1648
1649 PCB_LAYER_ID top_layer, bottom_layer;
1650 via->LayerPair( &top_layer, &bottom_layer );
1651 float ztop, zbot, dummy;
1652 getLayerZPos( top_layer, ztop, dummy );
1653 getLayerZPos( bottom_layer, dummy, zbot );
1654
1655 bool frontCovering = via->GetFrontCoveringMode() == COVERING_MODE::COVERED
1656 || via->IsTented( F_Mask );
1657 bool backCovering = via->GetBackCoveringMode() == COVERING_MODE::COVERED
1658 || via->IsTented( B_Mask );
1659 bool frontPlugged = via->GetFrontPluggingMode() == PLUGGING_MODE::PLUGGED;
1660 bool backPlugged = via->GetBackPluggingMode() == PLUGGING_MODE::PLUGGED;
1661 bool filled = via->GetFillingMode() == FILLING_MODE::FILLED
1662 || via->GetCappingMode() == CAPPING_MODE::CAPPED;
1663
1664 const auto frontPostMachining =
1665 via->GetFrontPostMachining().value_or( PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED );
1666 const auto backPostMachining =
1667 via->GetBackPostMachining().value_or( PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED );
1668
1669 bool hasFrontPostMachining = frontPostMachining != PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED
1670 && frontPostMachining != PAD_DRILL_POST_MACHINING_MODE::UNKNOWN;
1671 bool hasBackPostMachining = backPostMachining != PAD_DRILL_POST_MACHINING_MODE::NOT_POST_MACHINED
1672 && backPostMachining != PAD_DRILL_POST_MACHINING_MODE::UNKNOWN;
1673
1674 bool hasFrontBackdrill = via->GetSecondaryDrillStartLayer() == F_Cu;
1675 bool hasBackBackdrill = via->GetSecondaryDrillStartLayer() == B_Cu;
1676
1677 const float depth = hole_radius * 0.3f;
1678
1679 if( frontCovering && !hasFrontPostMachining && !hasFrontBackdrill )
1680 {
1681 if( filled || !frontPlugged )
1682 generateDisk( center, hole_radius, ztop, seg, frontCover, true );
1683 else
1684 generateDimple( center, hole_radius, ztop, depth, seg, frontCover, true );
1685 }
1686
1687 if( backCovering && !hasBackPostMachining && !hasBackBackdrill )
1688 {
1689 if( filled || !backPlugged )
1690 generateDisk( center, hole_radius, zbot, seg, backCover, false );
1691 else
1692 generateDimple( center, hole_radius, zbot, depth, seg, backCover, false );
1693 }
1694 }
1695
1696 if( frontCover->m_layer_top_triangles->GetVertexSize() > 0 )
1697 m_viaFrontCover = new OPENGL_RENDER_LIST( *frontCover, 0, 0.0f, 0.0f );
1698
1699 if( backCover->m_layer_bot_triangles->GetVertexSize() > 0 )
1700 m_viaBackCover = new OPENGL_RENDER_LIST( *backCover, 0, 0.0f, 0.0f );
1701
1702 delete frontCover;
1703 delete backCover;
1704}
1705
1706
1708{
1709 if( !m_boardAdapter.GetBoard() )
1710 return;
1711
1712 const int platingThickness = m_boardAdapter.GetHolePlatingThickness();
1713 const float unitScale = m_boardAdapter.BiuTo3dUnits();
1714 const float platingThickness3d = platingThickness * unitScale;
1715
1716 generateViaBarrels( platingThickness3d, unitScale );
1717 generatePlatedHoleShells( platingThickness, unitScale );
1718 generateViaCovers( platingThickness3d, unitScale );
1719}
1720
1721
1723{
1724 if( m_3dModelMap.size() > 0 )
1725 return;
1726
1727 if( wxFrame* frame = dynamic_cast<wxFrame*>( m_canvas->GetParent() ) )
1728 {
1729 STATUSBAR_REPORTER activityReporter( frame->GetStatusBar(),
1731 load3dModels( &activityReporter );
1732 }
1733 else
1734 {
1735 load3dModels( nullptr );
1736 }
1737}
1738
1739
1741{
1742 if( !m_boardAdapter.GetBoard() )
1743 return;
1744
1745 // Building the 3D models late crashes on recent versions of macOS
1746 // Unclear the exact mechanism, but as a workaround, just build them
1747 // all the time. See https://gitlab.com/kicad/code/kicad/-/issues/17198
1748#ifndef __WXMAC__
1749 if( !m_boardAdapter.m_IsPreviewer
1750 && !m_boardAdapter.m_Cfg->m_Render.show_footprints_normal
1751 && !m_boardAdapter.m_Cfg->m_Render.show_footprints_insert
1752 && !m_boardAdapter.m_Cfg->m_Render.show_footprints_virtual )
1753 {
1754 return;
1755 }
1756#endif
1757
1758 S3D_CACHE* cacheMgr = m_boardAdapter.Get3dCacheManager();
1759
1760 // Go for all footprints
1761 for( const FOOTPRINT* footprint : m_boardAdapter.GetBoard()->Footprints() )
1762 {
1763 wxString libraryName = footprint->GetFPID().GetLibNickname();
1764 wxString footprintBasePath = wxEmptyString;
1765
1766 if( m_boardAdapter.GetBoard()->GetProject() )
1767 {
1768 try
1769 {
1770 // FindRow() can throw an exception
1771 std::optional<LIBRARY_TABLE_ROW*> fpRow =
1772 PROJECT_PCB::FootprintLibAdapter( m_boardAdapter.GetBoard()->GetProject() )
1773 ->GetRow( libraryName );
1774
1775 if( fpRow )
1776 footprintBasePath = LIBRARY_MANAGER::GetFullURI( *fpRow, true );
1777 }
1778 catch( ... )
1779 {
1780 // Do nothing if the libraryName is not found in lib table
1781 }
1782 }
1783
1784 for( const FP_3DMODEL& fp_model : footprint->Models() )
1785 {
1786 if( fp_model.m_Show && !fp_model.m_Filename.empty() )
1787 {
1788 if( aStatusReporter )
1789 {
1790 // Display the short filename of the 3D fp_model loaded:
1791 // (the full name is usually too long to be displayed)
1792 wxFileName fn( fp_model.m_Filename );
1793 aStatusReporter->Report( wxString::Format( _( "Loading %s..." ),
1794 fn.GetFullName() ) );
1795 }
1796
1797 // Check if the fp_model is not present in our cache map
1798 // (Not already loaded in memory)
1799 if( !m_3dModelMap.contains( fp_model.m_Filename ) )
1800 {
1801 // It is not present, try get it from cache
1802 std::vector<const EMBEDDED_FILES*> embeddedFilesStack;
1803 embeddedFilesStack.push_back( footprint->GetEmbeddedFiles() );
1804 embeddedFilesStack.push_back( m_boardAdapter.GetBoard()->GetEmbeddedFiles() );
1805
1806 const S3DMODEL* modelPtr = cacheMgr->GetModel( fp_model.m_Filename, footprintBasePath,
1807 std::move( embeddedFilesStack ) );
1808
1809 // only add it if the return is not NULL
1810 if( modelPtr )
1811 {
1812 MATERIAL_MODE materialMode = m_boardAdapter.m_Cfg->m_Render.material_mode;
1813 MODEL_3D* model = new MODEL_3D( *modelPtr, materialMode );
1814
1815 m_3dModelMap[ fp_model.m_Filename ] = model;
1816 }
1817 }
1818 }
1819 }
1820 }
1821}
MATERIAL_MODE
Render 3d model shape materials mode.
Definition 3d_enums.h:71
Defines math related functions.
void ApplyExtrusionTransform(SHAPE_POLY_SET &aOutline, const EXTRUDED_3D_BODY *aBody, const VECTOR2I &aFpPos)
Apply 2D extrusion transforms (rotation, scale, offset) to an outline.
bool GetExtrusionPinOutline(const FOOTPRINT *aFootprint, SHAPE_POLY_SET &aPinPoly)
Get the pin outline polygons for extruded THT pin rendering.
bool GetExtrusionOutline(const FOOTPRINT *aFootprint, SHAPE_POLY_SET &aOutline, PCB_LAYER_ID aLayerOverride)
Get the extrusion outline polygon for a footprint in board coordinates.
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:125
std::map< PCB_LAYER_ID, SHAPE_POLY_SET * > MAP_POLY
A type that stores polysets for each layer id.
std::map< PCB_LAYER_ID, BVH_CONTAINER_2D * > MAP_CONTAINER_2D_BASE
A type that stores a container of 2d objects for each layer id.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
const LIST_OBJECT2D & GetList() const
double AsRadians() const
Definition eda_angle.h:120
VECTOR3D m_offset
Definition footprint.h:116
VECTOR3D m_scale
Definition footprint.h:114
const SFVEC2F & GetCenter() const
float GetRadius() const
std::optional< LIBRARY_TABLE_ROW * > GetRow(const wxString &aNickname, LIBRARY_TABLE_SCOPE aScope=LIBRARY_TABLE_SCOPE::BOTH) const
Like LIBRARY_MANAGER::GetRow but filtered to the LIBRARY_TABLE_TYPE of this adapter.
std::optional< wxString > GetFullURI(LIBRARY_TABLE_TYPE aType, const wxString &aNickname, bool aSubstituted=false)
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
static const LSET & PhysicalLayersMask()
Return a mask holding all layers which are physically realized.
Definition lset.cpp:697
static OBJECT_2D_STATS & Instance()
Definition object_2d.h:137
void ResetStats()
Definition object_2d.h:122
OBJECT_2D_TYPE GetObjectType() const
Definition object_2d.h:107
Store the OpenGL display lists to related with a layer.
Definition pad.h:55
Simple non-intersecting polygon with 4 points.
const SFVEC2F & GetV3() const
const SFVEC2F & GetV0() const
const SFVEC2F & GetV1() const
const SFVEC2F & GetV2() const
static FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
BOARD_ADAPTER & m_boardAdapter
Settings reference in use for this render.
OPENGL_RENDER_LIST * m_board
void reload(REPORTER *aStatusReporter, REPORTER *aWarningReporter)
OPENGL_RENDER_LIST * generateHoles(const LIST_OBJECT2D &aListHolesObject2d, const SHAPE_POLY_SET &aPoly, float aZtop, float aZbot, bool aInvertFaces, const BVH_CONTAINER_2D *aThroughHoles=nullptr)
void generateCylinder(const SFVEC2F &aCenter, float aInnerRadius, float aOuterRadius, float aZtop, float aZbot, unsigned int aNr_sides_per_circle, TRIANGLE_DISPLAY_LIST *aDstLayer)
OPENGL_RENDER_LIST * m_outerThroughHoleRings
OPENGL_RENDER_LIST * m_offboardPadsFront
void generateRing(const SFVEC2F &aCenter, float aInnerRadius, float aOuterRadius, unsigned int aNr_sides_per_circle, std::vector< SFVEC2F > &aInnerContourResult, std::vector< SFVEC2F > &aOuterContourResult, bool aInvertOrder)
SHAPE_POLY_SET m_antiBoardPolys
The negative polygon representation of the board outline.
void load3dModels(REPORTER *aStatusReporter)
Load footprint models from the cache and load it to openGL lists in the form of MODEL_3D objects.
OPENGL_RENDER_LIST * generateLayerList(const BVH_CONTAINER_2D *aContainer, const SHAPE_POLY_SET *aPolyList, PCB_LAYER_ID aLayer, const BVH_CONTAINER_2D *aThroughHoles=nullptr)
OPENGL_RENDER_LIST * m_microviaHoles
OPENGL_RENDER_LIST * createBoard(const SHAPE_POLY_SET &aBoardPoly, const BVH_CONTAINER_2D *aThroughHoles=nullptr)
void Load3dModelsIfNeeded()
Load footprint models if they are not already loaded, i.e.
std::map< const FOOTPRINT *, OPENGL_RENDER_LIST * > m_extrudedPadLists
void addObjectTriangles(const RING_2D *aRing, TRIANGLE_DISPLAY_LIST *aDstLayer, float aZtop, float aZbot)
MAP_OGL_DISP_LISTS m_layers
MAP_OGL_DISP_LISTS m_innerLayerHoles
OPENGL_RENDER_LIST * m_boardWithHoles
void generateInvCone(const SFVEC2F &aCenter, float aInnerRadius, float aOuterRadius, float aZtop, float aZbot, unsigned int aNr_sides_per_circle, TRIANGLE_DISPLAY_LIST *aDstLayer, EDA_ANGLE aAngle)
MAP_OGL_DISP_LISTS m_outerLayerHoles
OPENGL_RENDER_LIST * m_offboardPadsBack
std::map< wxString, MODEL_3D * > m_3dModelMap
OPENGL_RENDER_LIST * m_viaBackCover
OPENGL_RENDER_LIST * m_viaFrontCover
OPENGL_RENDER_LIST * generateEmptyLayerList(PCB_LAYER_ID aLayer)
LIST_TRIANGLES m_triangles
store pointers so can be deleted latter
OPENGL_RENDER_LIST * m_outerViaThroughHoles
OPENGL_RENDER_LIST * m_outerThroughHoles
void generateViaCovers(float aPlatingThickness3d, float aUnitScale)
OPENGL_RENDER_LIST * m_platedPadsFront
void generatePlatedHoleShells(int aPlatingThickness, float aUnitScale)
std::map< const FOOTPRINT *, OPENGL_RENDER_LIST * > m_extrudedBodyLists
void generateDisk(const SFVEC2F &aCenter, float aRadius, float aZ, unsigned int aNr_sides_per_circle, TRIANGLE_DISPLAY_LIST *aDstLayer, bool aTop)
OPENGL_RENDER_LIST * m_postMachinePlugs
Board material plugs for backdrill/counterbore/countersink.
bool appendPostMachiningGeometry(TRIANGLE_DISPLAY_LIST *aDstLayer, const SFVEC2F &aHoleCenter, PAD_DRILL_POST_MACHINING_MODE aMode, int aSizeIU, int aDepthIU, float aHoleInnerRadius, float aZSurface, bool aIsFront, float aPlatingThickness3d, float aUnitScale, float *aZEnd)
OPENGL_RENDER_LIST * m_antiBoard
void getLayerZPos(PCB_LAYER_ID aLayerID, float &aOutZtop, float &aOutZbot) const
EDA_3D_CANVAS * m_canvas
OPENGL_RENDER_LIST * m_padHoles
void addTopAndBottomTriangles(TRIANGLE_DISPLAY_LIST *aDst, const SFVEC2F &v0, const SFVEC2F &v1, const SFVEC2F &v2, float top, float bot)
void generateViaBarrels(float aPlatingThickness3d, float aUnitScale)
OPENGL_RENDER_LIST * m_platedPadsBack
void generateDimple(const SFVEC2F &aCenter, float aRadius, float aZ, float aDepth, unsigned int aNr_sides_per_circle, TRIANGLE_DISPLAY_LIST *aDstLayer, bool aTop)
void backfillPostMachine()
Create ring-shaped plugs for holes that have backdrill or post-machining.
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:75
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:104
float GetOuterRadius() const
Definition ring_2d.h:48
float GetInnerRadius() const
Definition ring_2d.h:47
const SFVEC2F & GetCenter() const
Definition ring_2d.h:46
const SFVEC2F & GetLeftEnd() const
const SFVEC2F & GetRightEnd() const
const SFVEC2F & GetLeftStar() const
const SFVEC2F & GetLeftDir() const
const SFVEC2F & GetEnd() const
float GetRadius() const
float GetRadiusSquared() const
const SFVEC2F & GetStart() const
const SFVEC2F & GetRightDir() const
const SFVEC2F & GetRightStar() const
Cache for storing the 3D shapes.
Definition 3d_cache.h:57
S3DMODEL * GetModel(const wxString &aModelFileName, const wxString &aBasePath, std::vector< const EMBEDDED_FILES * > aEmbeddedFilesStack)
Attempt to load the scene data for a model and to translate it into an S3D_MODEL structure for displa...
Definition 3d_cache.cpp:583
Represent a set of closed polygons.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
int OutlineCount() const
Return the number of outlines in the set.
SHAPE_POLY_SET CloneDropTriangulation() const
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
A wrapper for reporting to a specific text location in a statusbar.
Definition reporter.h:361
const SFVEC2F & GetP2() const
Definition triangle_2d.h:44
const SFVEC2F & GetP3() const
Definition triangle_2d.h:45
const SFVEC2F & GetP1() const
Definition triangle_2d.h:43
Store arrays of triangles to be used to create display lists.
TRIANGLE_LIST * m_layer_bot_segment_ends
void AddToMiddleContours(const SHAPE_LINE_CHAIN &outlinePath, float zBot, float zTop, double aBiuTo3Du, bool aInvertFaceDirection, const BVH_CONTAINER_2D *aThroughHoles=nullptr)
TRIANGLE_LIST * m_layer_middle_contours_quads
TRIANGLE_LIST * m_layer_top_segment_ends
TRIANGLE_LIST * m_layer_bot_triangles
TRIANGLE_LIST * m_layer_top_triangles
void AddTriangle(const SFVEC3F &aV1, const SFVEC3F &aV2, const SFVEC3F &aV3)
void AddQuad(const SFVEC3F &aV1, const SFVEC3F &aV2, const SFVEC3F &aV3, const SFVEC3F &aV4)
unsigned int GetVertexSize() const
std::list< OBJECT_2D * > LIST_OBJECT2D
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.
#define _(s)
Declaration of the eda_3d_viewer class.
@ TENTHS_OF_A_DEGREE_T
Definition eda_angle.h:30
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ B_Mask
Definition layer_ids.h:98
@ B_Cu
Definition layer_ids.h:65
@ F_Mask
Definition layer_ids.h:97
@ F_SilkS
Definition layer_ids.h:100
@ B_SilkS
Definition layer_ids.h:101
@ F_Cu
Definition layer_ids.h:64
PAD_DRILL_POST_MACHINING_MODE
Definition padstack.h:76
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
int64_t GetRunningMicroSecs()
An alternate way to calculate an elapsed time (in microsecondes) to class PROF_COUNTER.
#define SIZE_OF_CIRCLE_TEXTURE
std::vector< FAB_LAYER_COLOR > dummy
Store the a model based on meshes and materials.
Definition c3dmodel.h:95
KIBIS_MODEL * model
KIBIS top(path, &reporter)
VECTOR3I v1(5, 5, 5)
VECTOR2I center
int radius
VECTOR2I end
VECTOR2I v2(1, 0)
VECTOR2I v3(-2, 1)
int delta
void ConvertPolygonToTriangles(const SHAPE_POLY_SET &aPolyList, CONTAINER_2D_BASE &aDstContainer, float aBiuTo3dUnitsScale, const BOARD_ITEM &aBoardItem)
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:94
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687
glm::vec2 SFVEC2F
Definition xv3d_types.h:42
glm::vec3 SFVEC3F
Definition xv3d_types.h:44