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