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