pcbjam/tests/3d-regression/native/board_adapter_test_impl.cpp
Istvan Matejcsok ce1473c9ab test(3d): screenshot-baseline TDD suite for the 3D viewer OpenGL->WebGL port — 47 native goldens + red-state WebGL harness
tests/3d-regression mirrors the gal-regression pattern at renderer scale:
shared C++ scenarios call real KiCad 3D-viewer code (opengl_utils, display
lists + DrawCulled stencil subtraction, MODEL_3D VBOs, private generators via
a rob-template accessor, and full reload()+Redraw() composites over a
synthetic BOARD_ADAPTER). A native macOS harness renders them on real OpenGL
into 47 committed goldens (bit-deterministic, FBO capture); the wasm harness
compiles the same TUs against wasm/stubs/gl_ffp_stub.c no-ops so every
scenario renders blank — the TDD red state (parity meter: 47/47 changed).
Comparisons use the CI pixelmatch engine via the new generic compare-dirs.ts
(floors.json levels; manifest.json cmp-guards registry drift).

Documents an upstream bug: appendPostMachiningGeometry's countersink path
adds middle quads without normals, silently erasing the walls of any
display list it is batched into (3d-post-machining.png keeps the lists
separate to record it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 15:45:58 +02:00

521 lines
20 KiB
C++

/**
* Test-harness definitions of BOARD_ADAPTER's out-of-line members.
*
* The real board_adapter.cpp / create_layer_items.cpp drag the whole pcbnew
* board model + settings machinery in — none of which exists in this harness.
* Instead WE define the declared members: an out-of-line definition of a
* declared member function has full private access, so this TU is both the
* stub layer and the synthetic-board-data injection seam (InitSettings).
*
* Bodies marked "verbatim" are copied from board_adapter.cpp and must behave
* identically; bodies marked "test" are simplified board-less variants (any
* behavioral drift shows up as a baseline change and is reviewed there).
*/
#include "kicad_stubs_3d.h"
#include "3d_canvas/board_adapter.h"
#include "3d_rendering/raytracing/shapes2D/filled_circle_2d.h"
#include "3d_rendering/raytracing/shapes2D/round_segment_2d.h"
#include "3d_viewer/eda_3d_viewer_settings.h"
#include <board_design_settings.h>
#include <convert_basic_shapes_to_polygon.h>
#include <geometry/geometry_utils.h> // GetArcToSegmentCount
// Same values as board_adapter.cpp:51-57 (defined there as TU-local macros).
#define DEFAULT_BOARD_THICKNESS pcbIUScale.mmToIU( 1.6 )
#define DEFAULT_COPPER_THICKNESS pcbIUScale.mmToIU( 0.035 )
#define DEFAULT_TECH_LAYER_THICKNESS pcbIUScale.mmToIU( 0.025 )
#define SOLDERPASTE_LAYER_THICKNESS pcbIUScale.mmToIU( 0.04 )
#include "../scenarios/test_board_data.h"
// Statics (board_adapter.cpp:60-89, verbatim).
CUSTOM_COLORS_LIST BOARD_ADAPTER::g_SilkColors;
CUSTOM_COLORS_LIST BOARD_ADAPTER::g_MaskColors;
CUSTOM_COLORS_LIST BOARD_ADAPTER::g_PasteColors;
CUSTOM_COLORS_LIST BOARD_ADAPTER::g_FinishColors;
CUSTOM_COLORS_LIST BOARD_ADAPTER::g_BoardColors;
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultBackgroundTop;
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultBackgroundBot;
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultSilkscreen;
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultSolderMask;
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultSolderPaste;
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultSurfaceFinish;
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultBoardBody;
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultComments;
KIGFX::COLOR4D BOARD_ADAPTER::g_DefaultECOs;
const wxChar* BOARD_ADAPTER::m_logTrace = wxT( "KI_TRACE_EDA_CINFO3D_VISU" );
// The raytracer bevel global lives in board_adapter.cpp too.
float g_BevelThickness3DU = 0.0f;
// test: same default block as the real ctor (board_adapter.cpp:92-157) minus
// ReloadColorSettings() (our version below is settings-free) and the custom
// stackup color tables (not needed — GetLayerColors below is default-based).
BOARD_ADAPTER::BOARD_ADAPTER() :
m_Cfg( nullptr ),
m_IsBoardView( true ),
m_MousewheelPanning( true ),
m_IsPreviewer( false ),
m_board( nullptr ),
m_3dModelManager( nullptr ),
m_layerZcoordTop(),
m_layerZcoordBottom()
{
m_boardPos = VECTOR2I();
m_boardSize = VECTOR2I();
m_boardCenter = SFVEC3F( 0.0f );
m_boardBoundingBox.Reset();
m_TH_IDs.Clear();
m_TH_ODs.Clear();
m_viaAnnuli.Clear();
m_copperLayersCount = 2;
m_biuTo3Dunits = 1.0;
m_boardBodyThickness3DU = DEFAULT_BOARD_THICKNESS * m_biuTo3Dunits;
m_frontCopperThickness3DU = DEFAULT_COPPER_THICKNESS * m_biuTo3Dunits;
m_backCopperThickness3DU = DEFAULT_COPPER_THICKNESS * m_biuTo3Dunits;
m_nonCopperLayerThickness3DU = DEFAULT_TECH_LAYER_THICKNESS * m_biuTo3Dunits;
m_frontMaskThickness3DU = DEFAULT_TECH_LAYER_THICKNESS * m_biuTo3Dunits;
m_backMaskThickness3DU = DEFAULT_TECH_LAYER_THICKNESS * m_biuTo3Dunits;
m_solderPasteLayerThickness3DU = SOLDERPASTE_LAYER_THICKNESS * m_biuTo3Dunits;
m_trackCount = 0;
m_viaCount = 0;
m_averageViaHoleDiameter = 0.0f;
m_holeCount = 0;
m_averageHoleDiameter = 0.0f;
m_averageTrackWidth = 0.0f;
m_BgColorBot = SFVEC4F( 0.4, 0.4, 0.5, 1.0 );
m_BgColorTop = SFVEC4F( 0.8, 0.8, 0.9, 1.0 );
m_BoardBodyColor = SFVEC4F( 0.4, 0.4, 0.5, 0.9 );
m_SolderMaskColorTop = SFVEC4F( 0.1, 0.2, 0.1, 0.83 );
m_SolderMaskColorBot = SFVEC4F( 0.1, 0.2, 0.1, 0.83 );
m_SolderPasteColor = SFVEC4F( 0.4, 0.4, 0.4, 1.0 );
m_SilkScreenColorTop = SFVEC4F( 0.9, 0.9, 0.9, 1.0 );
m_SilkScreenColorBot = SFVEC4F( 0.9, 0.9, 0.9, 1.0 );
m_CopperColor = SFVEC4F( 0.75, 0.61, 0.23, 1.0 );
m_UserDrawingsColor = SFVEC4F( 0.85, 0.85, 0.85, 1.0 );
m_UserCommentsColor = SFVEC4F( 0.85, 0.85, 0.85, 1.0 );
m_ECO1Color = SFVEC4F( 0.70, 0.10, 0.10, 1.0 );
m_ECO2Color = SFVEC4F( 0.70, 0.10, 0.10, 1.0 );
for( int ii = 0; ii < 45; ++ii )
m_UserDefinedLayerColor[ii] = SFVEC4F( 0.70, 0.10, 0.10, 1.0 );
m_platedPadsFront = nullptr;
m_platedPadsBack = nullptr;
m_offboardPadsFront = nullptr;
m_offboardPadsBack = nullptr;
m_frontPlatedCopperPolys = nullptr;
m_backPlatedCopperPolys = nullptr;
ReloadColorSettings();
g_DefaultBackgroundTop = COLOR4D( 0.80, 0.80, 0.90, 1.0 );
g_DefaultBackgroundBot = COLOR4D( 0.40, 0.40, 0.50, 1.0 );
g_DefaultSilkscreen = COLOR4D( 0.94, 0.94, 0.94, 1.0 );
g_DefaultSolderMask = COLOR4D( 0.08, 0.20, 0.14, 0.83 );
g_DefaultSolderPaste = COLOR4D( 0.50, 0.50, 0.50, 1.0 );
g_DefaultSurfaceFinish = COLOR4D( 0.75, 0.61, 0.23, 1.0 );
g_DefaultBoardBody = COLOR4D( 0.4, 0.4, 0.5, 0.9 );
g_DefaultComments = COLOR4D( 0.85, 0.85, 0.85, 1.0 );
g_DefaultECOs = COLOR4D( 0.70, 0.10, 0.10, 1.0 );
}
BOARD_ADAPTER::~BOARD_ADAPTER()
{
destroyLayers();
}
// test: frees exactly what our InitSettings allocates.
void BOARD_ADAPTER::destroyLayers()
{
for( auto& [layer, container] : m_layerMap )
delete container;
m_layerMap.clear();
for( auto& [layer, container] : m_layerHoleMap )
delete container;
m_layerHoleMap.clear();
for( auto& [layer, poly] : m_layers_poly )
delete poly;
m_layers_poly.clear();
for( auto& [layer, poly] : m_layerHoleOdPolys )
delete poly;
m_layerHoleOdPolys.clear();
for( auto& [layer, poly] : m_layerHoleIdPolys )
delete poly;
m_layerHoleIdPolys.clear();
delete m_platedPadsFront;
delete m_platedPadsBack;
delete m_offboardPadsFront;
delete m_offboardPadsBack;
m_platedPadsFront = m_platedPadsBack = nullptr;
m_offboardPadsFront = m_offboardPadsBack = nullptr;
delete m_frontPlatedCopperPolys;
delete m_backPlatedCopperPolys;
m_frontPlatedCopperPolys = nullptr;
m_backPlatedCopperPolys = nullptr;
m_TH_ODs.Clear();
m_TH_IDs.Clear();
m_viaAnnuli.Clear();
m_viaTH_ODs.Clear();
m_board_poly.RemoveAllContours();
m_TH_ODPolys.RemoveAllContours();
m_NPTH_ODPolys.RemoveAllContours();
m_viaTH_ODPolys.RemoveAllContours();
m_viaAnnuliPolys.RemoveAllContours();
}
// test: settings-free — the board-editor color table gets a neutral default
// (the real one loads COLOR_SETTINGS; scenarios don't use per-PCB-layer colors).
void BOARD_ADAPTER::ReloadColorSettings() noexcept
{
for( int layer = F_Cu; layer < PCB_LAYER_ID_COUNT; ++layer )
m_BoardEditorColors[layer] = COLOR4D( 0.75, 0.61, 0.23, 1.0 );
}
// verbatim (board_adapter.cpp:243-288) minus the m_board branches (no board).
bool BOARD_ADAPTER::Is3dLayerEnabled( PCB_LAYER_ID aLayer,
const std::bitset<LAYER_3D_END>& aVisibilityFlags ) const
{
wxASSERT( aLayer < PCB_LAYER_ID_COUNT );
switch( aLayer )
{
case B_Cu: return aVisibilityFlags.test( LAYER_3D_COPPER_BOTTOM );
case F_Cu: return aVisibilityFlags.test( LAYER_3D_COPPER_TOP );
case B_Adhes: return aVisibilityFlags.test( LAYER_3D_ADHESIVE );
case F_Adhes: return aVisibilityFlags.test( LAYER_3D_ADHESIVE );
case B_Paste: return aVisibilityFlags.test( LAYER_3D_SOLDERPASTE );
case F_Paste: return aVisibilityFlags.test( LAYER_3D_SOLDERPASTE );
case B_SilkS: return aVisibilityFlags.test( LAYER_3D_SILKSCREEN_BOTTOM );
case F_SilkS: return aVisibilityFlags.test( LAYER_3D_SILKSCREEN_TOP );
case B_Mask: return aVisibilityFlags.test( LAYER_3D_SOLDERMASK_BOTTOM );
case F_Mask: return aVisibilityFlags.test( LAYER_3D_SOLDERMASK_TOP );
case Dwgs_User: return aVisibilityFlags.test( LAYER_3D_USER_DRAWINGS );
case Cmts_User: return aVisibilityFlags.test( LAYER_3D_USER_COMMENTS );
case Eco1_User: return aVisibilityFlags.test( LAYER_3D_USER_ECO1 );
case Eco2_User: return aVisibilityFlags.test( LAYER_3D_USER_ECO2 );
default:
return false; // test: no board -> unmapped layers hidden
}
}
// test: previews/boards not modeled — footprints always "shown".
bool BOARD_ADAPTER::IsFootprintShown( const FOOTPRINT* aFootprint ) const
{
return aFootprint != nullptr;
}
// verbatim no-board branch (board_adapter.cpp:315-320).
int BOARD_ADAPTER::GetHolePlatingThickness() const noexcept
{
return DEFAULT_COPPER_THICKNESS;
}
// verbatim (board_adapter.cpp:322-328).
unsigned int BOARD_ADAPTER::GetCircleSegmentCount( float aDiameter3DU ) const
{
wxASSERT( aDiameter3DU > 0.0f );
return GetCircleSegmentCount( (int) ( aDiameter3DU / m_biuTo3Dunits ) );
}
// test: like board_adapter.cpp:330-336 with the BOARD_DESIGN_SETTINGS default
// max error (ARC_HIGH_DEF) instead of a live board's setting.
unsigned int BOARD_ADAPTER::GetCircleSegmentCount( int aDiameterBIU ) const
{
wxASSERT( aDiameterBIU > 0 );
return GetArcToSegmentCount( aDiameterBIU / 2, ARC_HIGH_DEF, FULL_CIRCLE );
}
// verbatim non-previewer path (board_adapter.cpp:808-905) minus FOLLOW_PCB
// (needs a board).
std::bitset<LAYER_3D_END> BOARD_ADAPTER::GetVisibleLayers() const
{
std::bitset<LAYER_3D_END> ret;
ret.set( LAYER_3D_BOARD, m_Cfg->m_Render.show_board_body );
ret.set( LAYER_3D_PLATED_BARRELS, m_Cfg->m_Render.show_plated_barrels );
ret.set( LAYER_3D_COPPER_TOP, m_Cfg->m_Render.show_copper_top );
ret.set( LAYER_3D_COPPER_BOTTOM, m_Cfg->m_Render.show_copper_bottom );
ret.set( LAYER_3D_SILKSCREEN_TOP, m_Cfg->m_Render.show_silkscreen_top );
ret.set( LAYER_3D_SILKSCREEN_BOTTOM, m_Cfg->m_Render.show_silkscreen_bottom );
ret.set( LAYER_3D_SOLDERMASK_TOP, m_Cfg->m_Render.show_soldermask_top );
ret.set( LAYER_3D_SOLDERMASK_BOTTOM, m_Cfg->m_Render.show_soldermask_bottom );
ret.set( LAYER_3D_SOLDERPASTE, m_Cfg->m_Render.show_solderpaste );
ret.set( LAYER_3D_ADHESIVE, m_Cfg->m_Render.show_adhesive );
ret.set( LAYER_3D_USER_COMMENTS, m_Cfg->m_Render.show_comments );
ret.set( LAYER_3D_USER_DRAWINGS, m_Cfg->m_Render.show_drawings );
ret.set( LAYER_3D_USER_ECO1, m_Cfg->m_Render.show_eco1 );
ret.set( LAYER_3D_USER_ECO2, m_Cfg->m_Render.show_eco2 );
for( int layer = LAYER_3D_USER_1; layer <= LAYER_3D_USER_45; ++layer )
ret.set( layer, m_Cfg->m_Render.show_user[layer - LAYER_3D_USER_1] );
ret.set( LAYER_FP_REFERENCES, m_Cfg->m_Render.show_fp_references );
ret.set( LAYER_FP_VALUES, m_Cfg->m_Render.show_fp_values );
ret.set( LAYER_FP_TEXT, m_Cfg->m_Render.show_fp_text );
ret.set( LAYER_3D_TH_MODELS, m_Cfg->m_Render.show_footprints_normal );
ret.set( LAYER_3D_SMD_MODELS, m_Cfg->m_Render.show_footprints_insert );
ret.set( LAYER_3D_VIRTUAL_MODELS, m_Cfg->m_Render.show_footprints_virtual );
ret.set( LAYER_3D_MODELS_NOT_IN_POS, m_Cfg->m_Render.show_footprints_not_in_posfile );
ret.set( LAYER_3D_MODELS_MARKED_DNP, m_Cfg->m_Render.show_footprints_dnp );
ret.set( LAYER_3D_BOUNDING_BOXES, m_Cfg->m_Render.show_model_bbox );
ret.set( LAYER_3D_OFF_BOARD_SILK, m_Cfg->m_Render.show_off_board_silk );
ret.set( LAYER_3D_NAVIGATOR, m_Cfg->m_Render.show_navigator );
return ret;
}
// test: no board -> board-editor copper colors never apply.
bool BOARD_ADAPTER::GetUseBoardEditorCopperLayerColors() const
{
return false;
}
// verbatim (board_adapter.cpp:1039-1053).
float BOARD_ADAPTER::GetFootprintZPos( bool aIsFlipped ) const
{
if( aIsFlipped )
{
if( auto it = m_layerZcoordBottom.find( B_Paste ); it != m_layerZcoordBottom.end() )
return it->second;
}
else
{
if( auto it = m_layerZcoordTop.find( F_Paste ); it != m_layerZcoordTop.end() )
return it->second;
}
return 0.0;
}
// verbatim (board_adapter.cpp:1056-1070); user-layer remap dropped (unused here).
SFVEC4F BOARD_ADAPTER::GetLayerColor( int aLayerId ) const
{
wxASSERT( aLayerId < PCB_LAYER_ID_COUNT );
return GetColor( m_BoardEditorColors.at( aLayerId ) );
}
SFVEC4F BOARD_ADAPTER::GetColor( const COLOR4D& aColor ) const
{
return SFVEC4F( aColor.r, aColor.g, aColor.b, aColor.a );
}
// test: default-color scheme only (no COLOR_SETTINGS machinery).
std::map<int, COLOR4D> BOARD_ADAPTER::GetDefaultColors() const
{
std::map<int, COLOR4D> colors;
colors[LAYER_3D_BACKGROUND_TOP] = g_DefaultBackgroundTop;
colors[LAYER_3D_BACKGROUND_BOTTOM] = g_DefaultBackgroundBot;
colors[LAYER_3D_BOARD] = g_DefaultBoardBody;
colors[LAYER_3D_COPPER_TOP] = g_DefaultSurfaceFinish;
colors[LAYER_3D_COPPER_BOTTOM] = g_DefaultSurfaceFinish;
colors[LAYER_3D_SILKSCREEN_TOP] = g_DefaultSilkscreen;
colors[LAYER_3D_SILKSCREEN_BOTTOM] = g_DefaultSilkscreen;
colors[LAYER_3D_SOLDERMASK_TOP] = g_DefaultSolderMask;
colors[LAYER_3D_SOLDERMASK_BOTTOM] = g_DefaultSolderMask;
colors[LAYER_3D_SOLDERPASTE] = g_DefaultSolderPaste;
colors[LAYER_3D_USER_DRAWINGS] = g_DefaultComments;
colors[LAYER_3D_USER_COMMENTS] = g_DefaultComments;
colors[LAYER_3D_USER_ECO1] = g_DefaultECOs;
colors[LAYER_3D_USER_ECO2] = g_DefaultECOs;
return colors;
}
std::map<int, COLOR4D> BOARD_ADAPTER::GetLayerColors() const
{
return GetDefaultColors();
}
// ---------------------------------------------------------------------------
// THE SEAM: synthetic test-board data instead of a real BOARD.
//
// A 40 x 30 mm two-layer board. Layer Z stacking follows the real
// InitSettings maths (board body centered on Z=0, copper plated on top/bottom,
// tech layers above copper). All values in BIU (nm) scaled by m_biuTo3Dunits.
// ---------------------------------------------------------------------------
void BOARD_ADAPTER::InitSettings( REPORTER* aStatusReporter, REPORTER* aWarningReporter )
{
(void) aStatusReporter;
(void) aWarningReporter;
destroyLayers();
const int boardW = pcbIUScale.mmToIU( 40 );
const int boardH = pcbIUScale.mmToIU( 30 );
m_boardSize = VECTOR2I( boardW, boardH );
m_boardPos = VECTOR2I( 0, 0 );
m_copperLayersCount = 2;
// Same scale maths as the real InitSettings (board_adapter.cpp:382-387):
// no BOARD -> the "footprint holder" zoom hack applies.
m_biuTo3Dunits = RANGE_SCALE_3D / std::max( m_boardSize.x, m_boardSize.y );
m_biuTo3Dunits *= 1.6f;
m_boardBodyThickness3DU = DEFAULT_BOARD_THICKNESS * m_biuTo3Dunits;
m_frontCopperThickness3DU = DEFAULT_COPPER_THICKNESS * m_biuTo3Dunits;
m_backCopperThickness3DU = DEFAULT_COPPER_THICKNESS * m_biuTo3Dunits;
m_nonCopperLayerThickness3DU = DEFAULT_TECH_LAYER_THICKNESS * m_biuTo3Dunits;
m_frontMaskThickness3DU = DEFAULT_TECH_LAYER_THICKNESS * m_biuTo3Dunits;
m_backMaskThickness3DU = DEFAULT_TECH_LAYER_THICKNESS * m_biuTo3Dunits;
m_solderPasteLayerThickness3DU = SOLDERPASTE_LAYER_THICKNESS * m_biuTo3Dunits;
// Layer Z coordinates (board body spans -body/2 .. +body/2).
const float bodyTop = m_boardBodyThickness3DU / 2.0f;
const float bodyBot = -m_boardBodyThickness3DU / 2.0f;
m_layerZcoordBottom[F_Cu] = bodyTop;
m_layerZcoordTop[F_Cu] = bodyTop + m_frontCopperThickness3DU;
m_layerZcoordBottom[B_Cu] = bodyBot;
m_layerZcoordTop[B_Cu] = bodyBot - m_backCopperThickness3DU;
m_layerZcoordBottom[F_Mask] = m_layerZcoordTop[F_Cu];
m_layerZcoordTop[F_Mask] = m_layerZcoordBottom[F_Mask] + m_frontMaskThickness3DU;
m_layerZcoordBottom[B_Mask] = m_layerZcoordTop[B_Cu];
m_layerZcoordTop[B_Mask] = m_layerZcoordBottom[B_Mask] - m_backMaskThickness3DU;
m_layerZcoordBottom[F_SilkS] = m_layerZcoordTop[F_Mask];
m_layerZcoordTop[F_SilkS] = m_layerZcoordBottom[F_SilkS] + m_nonCopperLayerThickness3DU;
m_layerZcoordBottom[B_SilkS] = m_layerZcoordTop[B_Mask];
m_layerZcoordTop[B_SilkS] = m_layerZcoordBottom[B_SilkS] - m_nonCopperLayerThickness3DU;
m_layerZcoordBottom[F_Paste] = m_layerZcoordTop[F_Cu];
m_layerZcoordTop[F_Paste] = m_layerZcoordBottom[F_Paste] + m_solderPasteLayerThickness3DU;
m_layerZcoordBottom[B_Paste] = m_layerZcoordTop[B_Cu];
m_layerZcoordTop[B_Paste] = m_layerZcoordBottom[B_Paste] - m_solderPasteLayerThickness3DU;
m_boardCenter = SFVEC3F( 0.0f, 0.0f, 0.0f );
m_boardBoundingBox.Set( SFVEC3F( -boardW / 2 * m_biuTo3Dunits, -boardH / 2 * m_biuTo3Dunits,
bodyBot ),
SFVEC3F( boardW / 2 * m_biuTo3Dunits, boardH / 2 * m_biuTo3Dunits,
bodyTop ) );
// Board outline: plain rectangle.
m_board_poly.RemoveAllContours();
m_board_poly.NewOutline();
m_board_poly.Append( -boardW / 2, -boardH / 2 );
m_board_poly.Append( boardW / 2, -boardH / 2 );
m_board_poly.Append( boardW / 2, boardH / 2 );
m_board_poly.Append( -boardW / 2, boardH / 2 );
m_board_poly.Outline( 0 ).SetClosed( true );
// Copper: a few tracks + round pads per side; silkscreen: a frame; the
// BVH containers own the objects (they delete them).
auto addTrack = [&]( BVH_CONTAINER_2D* aDst, double aX1mm, double aY1mm, double aX2mm,
double aY2mm, double aWidthMm )
{
aDst->Add( new ROUND_SEGMENT_2D(
SFVEC2F( pcbIUScale.mmToIU( aX1mm ) * m_biuTo3Dunits,
pcbIUScale.mmToIU( aY1mm ) * m_biuTo3Dunits ),
SFVEC2F( pcbIUScale.mmToIU( aX2mm ) * m_biuTo3Dunits,
pcbIUScale.mmToIU( aY2mm ) * m_biuTo3Dunits ),
pcbIUScale.mmToIU( aWidthMm ) * m_biuTo3Dunits, DummyBoardItem() ) );
};
auto addCircle = [&]( BVH_CONTAINER_2D* aDst, double aXmm, double aYmm, double aRmm )
{
aDst->Add( new FILLED_CIRCLE_2D( SFVEC2F( pcbIUScale.mmToIU( aXmm ) * m_biuTo3Dunits,
pcbIUScale.mmToIU( aYmm ) * m_biuTo3Dunits ),
pcbIUScale.mmToIU( aRmm ) * m_biuTo3Dunits,
DummyBoardItem() ) );
};
BVH_CONTAINER_2D* frontCu = new BVH_CONTAINER_2D;
addTrack( frontCu, -15, -10, 15, -10, 1.0 );
addTrack( frontCu, -15, -10, -15, 10, 1.0 );
addTrack( frontCu, -15, 10, 0, 10, 0.6 );
addTrack( frontCu, 0, 10, 8, 2, 0.6 );
addCircle( frontCu, -15, -10, 1.5 );
addCircle( frontCu, 15, -10, 1.5 );
addCircle( frontCu, 8, 2, 1.2 );
frontCu->BuildBVH();
m_layerMap[F_Cu] = frontCu;
BVH_CONTAINER_2D* backCu = new BVH_CONTAINER_2D;
addTrack( backCu, 15, -10, 15, 10, 1.2 );
addTrack( backCu, 15, 10, -8, 10, 1.2 );
addCircle( backCu, -15, -10, 1.5 );
addCircle( backCu, 15, -10, 1.5 );
backCu->BuildBVH();
m_layerMap[B_Cu] = backCu;
BVH_CONTAINER_2D* frontSilk = new BVH_CONTAINER_2D;
addTrack( frontSilk, -17, -13, 17, -13, 0.3 );
addTrack( frontSilk, 17, -13, 17, 13, 0.3 );
addTrack( frontSilk, 17, 13, -17, 13, 0.3 );
addTrack( frontSilk, -17, 13, -17, -13, 0.3 );
addCircle( frontSilk, -12, 6, 0.8 );
frontSilk->BuildBVH();
m_layerMap[F_SilkS] = frontSilk;
// Through holes: the two big pads are plated through.
auto addHolePoly = [&]( SHAPE_POLY_SET& aPolys, double aXmm, double aYmm, double aRmm )
{
TransformCircleToPolygon( aPolys,
VECTOR2I( pcbIUScale.mmToIU( aXmm ), pcbIUScale.mmToIU( aYmm ) ),
pcbIUScale.mmToIU( aRmm ), ARC_HIGH_DEF, ERROR_INSIDE );
};
addCircle( &m_TH_ODs, -15, -10, 0.8 );
addCircle( &m_TH_ODs, 15, -10, 0.8 );
m_TH_ODs.BuildBVH();
addCircle( &m_TH_IDs, -15, -10, 0.65 );
addCircle( &m_TH_IDs, 15, -10, 0.65 );
m_TH_IDs.BuildBVH();
addHolePoly( m_TH_ODPolys, -15, -10, 0.8 );
addHolePoly( m_TH_ODPolys, 15, -10, 0.8 );
m_TH_ODPolys.Simplify();
}