KiCad PCB EDA Suite
exporter_vrml.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) 2009-2013 Lorenzo Mercantonio
5 * Copyright (C) 2014-2017 Cirilo Bernardo
6 * Copyright (C) 2018 Jean-Pierre Charras jp.charras at wanadoo.fr
7 * Copyright (C) 2004-2022 KiCad Developers, see AUTHORS.txt for contributors.
8 *
9 * This program is free software: you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License as published by the
11 * Free Software Foundation, either version 3 of the License, or (at your
12 * option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23#include <exception>
24#include <fstream>
25#include <iomanip>
26#include <vector>
27#include <wx/dir.h>
28#include <wx/msgdlg.h>
29#include <wx/wfstream.h>
30#include <wx/zstream.h>
31
32#include "3d_cache/3d_cache.h"
33#include "3d_cache/3d_info.h"
34#include "board.h"
36#include <fp_lib_table.h>
37#include "fp_shape.h"
38#include "footprint.h"
39#include "pad.h"
40#include "pcb_text.h"
41#include "pcb_track.h"
42#include <core/arraydim.h>
43#include <filename_resolver.h>
45#include "streamwrapper.h"
46#include "vrml_layer.h"
47#include "pcb_edit_frame.h"
48
51#include <macros.h>
52
53#include <exporter_vrml.h>
54
56{
57 pcb_exporter = new EXPORTER_PCB_VRML( aBoard );
58}
59
60
61bool EXPORTER_VRML::ExportVRML_File( PROJECT* aProject, wxString *aMessages,
62 const wxString& aFullFileName, double aMMtoWRMLunit,
63 bool aExport3DFiles, bool aUseRelativePaths,
64 const wxString& a3D_Subdir,
65 double aXRef, double aYRef )
66{
67 return pcb_exporter->ExportVRML_File( aProject, aMessages,
68 aFullFileName, aMMtoWRMLunit,
69 aExport3DFiles, aUseRelativePaths,
70 a3D_Subdir, aXRef, aYRef );
71}
72
73
75{
76 delete pcb_exporter;
77}
78
79
80// The max error (in mm) to approximate arcs to segments:
81#define ERR_APPROX_MAX_MM 0.005
82
83
89
95
96static bool g_ColorsLoaded = false;
97
98
100 m_OutputPCB( nullptr )
101{
102 m_board = aBoard;
103 m_ReuseDef = true;
104 m_precision = 6;
105 m_WorldScale = 1.0;
106 m_Cache3Dmodels = nullptr;
110
111 for( int ii = 0; ii < VRML_COLOR_LAST; ++ii )
112 m_sgmaterial[ii] = nullptr;
113
114 for( unsigned i = 0; i < arrayDim( m_layer_z ); ++i )
115 m_layer_z[i] = 0;
116
117 // this default only makes sense if the output is in mm
119
120 // TODO: figure out a way to share all these stackup color definitions...
122
129 COLOR4D boardBody( 0, 0, 0, 0 );
130
132
133 auto findColor =
134 []( const wxString& aColorName, const CUSTOM_COLORS_LIST& aColorSet )
135 {
136 if( aColorName.StartsWith( wxT( "#" ) ) )
137 {
138 return KIGFX::COLOR4D( aColorName );
139 }
140 else
141 {
142 for( const CUSTOM_COLOR_ITEM& color : aColorSet )
143 {
144 if( color.m_ColorName == aColorName )
145 return color.m_Color;
146 }
147 }
148
149 return KIGFX::COLOR4D();
150 };
151
152 for( const BOARD_STACKUP_ITEM* stackupItem : stackup.GetList() )
153 {
154 wxString colorName = stackupItem->GetColor();
155
156 switch( stackupItem->GetType() )
157 {
159 if( stackupItem->GetBrdLayerId() == F_SilkS )
160 topSilk = findColor( colorName, m_SilkscreenColors );
161 else
162 botSilk = findColor( colorName, m_SilkscreenColors );
163 break;
164
166 if( stackupItem->GetBrdLayerId() == F_Mask )
167 topMask = findColor( colorName, m_MaskColors );
168 else
169 botMask = findColor( colorName, m_MaskColors );
170
171 break;
172
174 {
175 KIGFX::COLOR4D layerColor = findColor( colorName, m_BoardColors );
176
177 if( boardBody == COLOR4D( 0, 0, 0, 0 ) )
178 boardBody = layerColor;
179 else
180 boardBody = boardBody.Mix( layerColor, 1.0 - layerColor.a );
181
182 boardBody.a += ( 1.0 - boardBody.a ) * layerColor.a / 2;
183 break;
184 }
185
186 default:
187 break;
188 }
189 }
190
191 if( boardBody == COLOR4D( 0, 0, 0, 0 ) )
192 boardBody = m_DefaultBoardBody;
193
194 const wxString& finishName = stackup.m_FinishType;
195
196 if( finishName.EndsWith( wxT( "OSP" ) ) )
197 {
198 finish = findColor( wxT( "Copper" ), m_FinishColors );
199 }
200 else if( finishName.EndsWith( wxT( "IG" ) )
201 || finishName.EndsWith( wxT( "gold" ) ) )
202 {
203 finish = findColor( wxT( "Gold" ), m_FinishColors );
204 }
205 else if( finishName.StartsWith( wxT( "HAL" ) )
206 || finishName.StartsWith( wxT( "HASL" ) )
207 || finishName.EndsWith( wxT( "tin" ) )
208 || finishName.EndsWith( wxT( "nickel" ) ) )
209 {
210 finish = findColor( wxT( "Tin" ), m_FinishColors );
211 }
212 else if( finishName.EndsWith( wxT( "silver" ) ) )
213 {
214 finish = findColor( wxT( "Silver" ), m_FinishColors );
215 }
216
217 auto toVRMLColor =
218 []( const COLOR4D& aColor, double aSpecular, double aAmbient, double aShiny )
219 {
220 COLOR4D diff = aColor;
221 COLOR4D spec = aColor.Brightened( aSpecular );
222
223 return VRML_COLOR( diff.r, diff.g, diff.b,
224 spec.r, spec.g, spec.b,
225 aAmbient, 1.0 - aColor.a, aShiny );
226 };
227
228 vrml_colors_list[VRML_COLOR_TOP_SILK] = toVRMLColor( topSilk, 0.1, 0.7, 0.02 );
229 vrml_colors_list[VRML_COLOR_BOT_SILK] = toVRMLColor( botSilk, 0.1, 0.7, 0.02 );
230 vrml_colors_list[VRML_COLOR_TOP_SOLDMASK] = toVRMLColor( topMask, 0.3, 0.8, 0.30 );
231 vrml_colors_list[VRML_COLOR_BOT_SOLDMASK] = toVRMLColor( botMask, 0.3, 0.8, 0.30 );
232 vrml_colors_list[VRML_COLOR_PASTE] = toVRMLColor( paste, 0.6, 0.7, 0.70 );
233 vrml_colors_list[VRML_COLOR_COPPER] = toVRMLColor( finish, 0.6, 0.7, 0.90 );
234 vrml_colors_list[VRML_COLOR_PCB] = toVRMLColor( boardBody, 0.1, 0.7, 0.01 );
235
236 SetOffset( 0.0, 0.0 );
237}
238
239
241{
242 // destroy any unassociated material appearances
243 for( int j = 0; j < VRML_COLOR_LAST; ++j )
244 {
245 if( m_sgmaterial[j] && nullptr == S3D::GetSGNodeParent( m_sgmaterial[j] ) )
247
248 m_sgmaterial[j] = nullptr;
249 }
250
251 if( !m_components.empty() )
252 {
253 IFSG_TRANSFORM tmp( false );
254
255 for( auto i : m_components )
256 {
257 tmp.Attach( i );
258 tmp.SetParent( nullptr );
259 }
260
261 m_components.clear();
263 }
264}
265
267{
268 // Initialize the list of colors used in VRML export, but only once.
269 // (The list is static)
270 if( g_ColorsLoaded )
271 return;
272
273#define ADD_COLOR( list, r, g, b, a, name ) \
274 list.emplace_back( r/255.0, g/255.0, b/255.0, a, name )
275
276 ADD_COLOR( m_SilkscreenColors, 245, 245, 245, 1.0, _HKI( "Not specified" ) ); // White
277 ADD_COLOR( m_SilkscreenColors, 20, 51, 36, 1.0, wxT( "Green" ) );
278 ADD_COLOR( m_SilkscreenColors, 181, 19, 21, 1.0, wxT( "Red" ) );
279 ADD_COLOR( m_SilkscreenColors, 2, 59, 162, 1.0, wxT( "Blue" ) );
280 ADD_COLOR( m_SilkscreenColors, 11, 11, 11, 1.0, wxT( "Black" ) );
281 ADD_COLOR( m_SilkscreenColors, 245, 245, 245, 1.0, wxT( "White" ) );
282 ADD_COLOR( m_SilkscreenColors, 32, 2, 53, 1.0, wxT( "Purple" ) );
283 ADD_COLOR( m_SilkscreenColors, 194, 195, 0, 1.0, wxT( "Yellow" ) );
284
285 ADD_COLOR( m_MaskColors, 20, 51, 36, 0.83, _HKI( "Not specified" ) ); // Green
286 ADD_COLOR( m_MaskColors, 20, 51, 36, 0.83, wxT( "Green" ) );
287 ADD_COLOR( m_MaskColors, 91, 168, 12, 0.83, wxT( "Light Green" ) );
288 ADD_COLOR( m_MaskColors, 13, 104, 11, 0.83, wxT( "Saturated Green" ) );
289 ADD_COLOR( m_MaskColors, 181, 19, 21, 0.83, wxT( "Red" ) );
290 ADD_COLOR( m_MaskColors, 210, 40, 14, 0.83, wxT( "Light Red" ) );
291 ADD_COLOR( m_MaskColors, 239, 53, 41, 0.83, wxT( "Red/Orange" ) );
292 ADD_COLOR( m_MaskColors, 2, 59, 162, 0.83, wxT( "Blue" ) );
293 ADD_COLOR( m_MaskColors, 54, 79, 116, 0.83, wxT( "Light Blue 1" ) );
294 ADD_COLOR( m_MaskColors, 61, 85, 130, 0.83, wxT( "Light Blue 2" ) );
295 ADD_COLOR( m_MaskColors, 21, 70, 80, 0.83, wxT( "Green/Blue" ) );
296 ADD_COLOR( m_MaskColors, 11, 11, 11, 0.83, wxT( "Black" ) );
297 ADD_COLOR( m_MaskColors, 245, 245, 245, 0.83, wxT( "White" ) );
298 ADD_COLOR( m_MaskColors, 32, 2, 53, 0.83, wxT( "Purple" ) );
299 ADD_COLOR( m_MaskColors, 119, 31, 91, 0.83, wxT( "Light Purple" ) );
300 ADD_COLOR( m_MaskColors, 194, 195, 0, 0.83, wxT( "Yellow" ) );
301
302 ADD_COLOR( m_PasteColors, 128, 128, 128, 1.0, wxT( "Grey" ) );
303 ADD_COLOR( m_PasteColors, 90, 90, 90, 1.0, wxT( "Dark Grey" ) );
304 ADD_COLOR( m_PasteColors, 213, 213, 213, 1.0, wxT( "Silver" ) );
305
306 ADD_COLOR( m_FinishColors, 184, 115, 50, 1.0, wxT( "Copper" ) );
307 ADD_COLOR( m_FinishColors, 178, 156, 0, 1.0, wxT( "Gold" ) );
308 ADD_COLOR( m_FinishColors, 213, 213, 213, 1.0, wxT( "Silver" ) );
309 ADD_COLOR( m_FinishColors, 160, 160, 160, 1.0, wxT( "Tin" ) );
310
311 ADD_COLOR( m_BoardColors, 51, 43, 22, 0.83, wxT( "FR4 natural, dark" ) );
312 ADD_COLOR( m_BoardColors, 109, 116, 75, 0.83, wxT( "FR4 natural" ) );
313 ADD_COLOR( m_BoardColors, 252, 252, 250, 0.90, wxT( "PTFE natural" ) );
314 ADD_COLOR( m_BoardColors, 205, 130, 0, 0.68, wxT( "Polyimide" ) );
315 ADD_COLOR( m_BoardColors, 92, 17, 6, 0.90, wxT( "Phenolic natural" ) );
316 ADD_COLOR( m_BoardColors, 146, 99, 47, 0.83, wxT( "Brown 1" ) );
317 ADD_COLOR( m_BoardColors, 160, 123, 54, 0.83, wxT( "Brown 2" ) );
318 ADD_COLOR( m_BoardColors, 146, 99, 47, 0.83, wxT( "Brown 3" ) );
319 ADD_COLOR( m_BoardColors, 213, 213, 213, 1.0, wxT( "Aluminum" ) );
320
321 m_DefaultSilkscreen = COLOR4D( 0.94, 0.94, 0.94, 1.0 );
322 m_DefaultSolderMask = COLOR4D( 0.08, 0.20, 0.14, 0.83 );
323 m_DefaultSolderPaste = COLOR4D( 0.50, 0.50, 0.50, 1.0 );
324 m_DefaultSurfaceFinish = COLOR4D( 0.75, 0.61, 0.23, 1.0 );
325 m_DefaultBoardBody = COLOR4D( 0.43, 0.45, 0.30, 0.90 );
326#undef ADD_COLOR
327
328 g_ColorsLoaded = true;
329}
330
331
332bool EXPORTER_PCB_VRML::SetScale( double aWorldScale )
333{
334 // set the scaling of the VRML world
335 if( aWorldScale < 0.001 || aWorldScale > 10.0 )
336 throw( std::runtime_error( "WorldScale out of range (valid range is 0.001 to 10.0)" ) );
337
338 m_OutputPCB.SetScale( aWorldScale * 2.54 );
339 m_WorldScale = aWorldScale * 2.54;
340
341 return true;
342}
343
344
345void EXPORTER_PCB_VRML::SetOffset( double aXoff, double aYoff )
346{
347 m_tx = aXoff;
348 m_ty = -aYoff;
349
350 m_holes.SetVertexOffsets( aXoff, aYoff );
351 m_3D_board.SetVertexOffsets( aXoff, aYoff );
352 m_top_copper.SetVertexOffsets( aXoff, aYoff );
353 m_bot_copper.SetVertexOffsets( aXoff, aYoff );
354 m_top_silk.SetVertexOffsets( aXoff, aYoff );
355 m_bot_silk.SetVertexOffsets( aXoff, aYoff );
356 m_top_paste.SetVertexOffsets( aXoff, aYoff );
357 m_bot_paste.SetVertexOffsets( aXoff, aYoff );
358 m_top_soldermask.SetVertexOffsets( aXoff, aYoff );
359 m_bot_soldermask.SetVertexOffsets( aXoff, aYoff );
360 m_plated_holes.SetVertexOffsets( aXoff, aYoff );
361}
362
363
364bool EXPORTER_PCB_VRML::GetLayer3D( int layer, VRML_LAYER** vlayer )
365{
366 // select the VRML layer object to draw on; return true if
367 // a layer has been selected.
368 switch( layer )
369 {
370 case B_Cu: *vlayer = &m_bot_copper; return true;
371 case F_Cu: *vlayer = &m_top_copper; return true;
372 case B_SilkS: *vlayer = &m_bot_silk; return true;
373 case F_SilkS: *vlayer = &m_top_silk; return true;
374 case B_Mask: *vlayer = &m_bot_soldermask; return true;
375 case F_Mask: *vlayer = &m_top_soldermask; return true;
376 case B_Paste: *vlayer = &m_bot_paste; return true;
377 case F_Paste: *vlayer = &m_top_paste; return true;
378 default: return false;
379 }
380}
381
383{
384 SHAPE_POLY_SET holes, outlines = m_pcbOutlines;
385
386 // holes is the solder mask opening.
387 // the actual shape is the negative shape of mask opening.
388 PCB_LAYER_ID pcb_layer = F_Mask;
389 VRML_LAYER* vrmllayer = &m_top_soldermask;
390
391 for( int lcnt = 0; lcnt < 2; lcnt++ )
392 {
393 holes.RemoveAllContours();
394 outlines.RemoveAllContours();
395 outlines = m_pcbOutlines;
396 m_board->ConvertBrdLayerToPolygonalContours( pcb_layer, holes );
397
398 outlines.BooleanSubtract( holes, SHAPE_POLY_SET::PM_FAST );
400 ExportVrmlPolygonSet( vrmllayer, outlines );
401
402 pcb_layer = B_Mask;
403 vrmllayer = &m_bot_soldermask;
404 }
405}
406
407
409{
410 SHAPE_POLY_SET outlines;
411
412 PCB_LAYER_ID pcb_layer[] =
413 {
415 };
416
417 VRML_LAYER* vrmllayer[] =
418 {
420 nullptr // Sentinel
421 };
422
423 for( int lcnt = 0; ; lcnt++ )
424 {
425 if( vrmllayer[lcnt] == nullptr )
426 break;
427
428 outlines.RemoveAllContours();
429 m_board->ConvertBrdLayerToPolygonalContours( pcb_layer[lcnt], outlines );
431
432 ExportVrmlPolygonSet( vrmllayer[lcnt], outlines );
433 }
434}
435
436
437void EXPORTER_PCB_VRML::write_triangle_bag( std::ostream& aOut_file, const VRML_COLOR& aColor,
438 VRML_LAYER* aLayer, bool aPlane, bool aTop,
439 double aTop_z, double aBottom_z )
440{
441 // A lot of nodes are not required, but blender sometimes chokes without them.
442 static const char* shape_boiler[] =
443 {
444 "Transform {\n",
445 " children [\n",
446 " Group {\n",
447 " children [\n",
448 " Shape {\n",
449 " appearance Appearance {\n",
450 " material Material {\n",
451 0, // Material marker
452 " }\n",
453 " }\n",
454 " geometry IndexedFaceSet {\n",
455 " solid TRUE\n",
456 " coord Coordinate {\n",
457 " point [\n",
458 0, // Coordinates marker
459 " ]\n",
460 " }\n",
461 " coordIndex [\n",
462 0, // Index marker
463 " ]\n",
464 " }\n",
465 " }\n",
466 " ]\n",
467 " }\n",
468 " ]\n",
469 "}\n",
470 0 // End marker
471 };
472
473 int marker_found = 0, lineno = 0;
474
475 while( marker_found < 4 )
476 {
477 if( shape_boiler[lineno] )
478 {
479 aOut_file << shape_boiler[lineno];
480 }
481 else
482 {
483 marker_found++;
484
485 switch( marker_found )
486 {
487 case 1: // Material marker
488 {
489 std::streamsize lastPrecision = aOut_file.precision();
490 aOut_file << " diffuseColor " << std::setprecision(3);
491 aOut_file << aColor.diffuse_red << " ";
492 aOut_file << aColor.diffuse_grn << " ";
493 aOut_file << aColor.diffuse_blu << "\n";
494
495 aOut_file << " specularColor ";
496 aOut_file << aColor.spec_red << " ";
497 aOut_file << aColor.spec_grn << " ";
498 aOut_file << aColor.spec_blu << "\n";
499
500 aOut_file << " emissiveColor ";
501 aOut_file << aColor.emit_red << " ";
502 aOut_file << aColor.emit_grn << " ";
503 aOut_file << aColor.emit_blu << "\n";
504
505 aOut_file << " ambientIntensity " << aColor.ambient << "\n";
506 aOut_file << " transparency " << aColor.transp << "\n";
507 aOut_file << " shininess " << aColor.shiny << "\n";
508 aOut_file.precision( lastPrecision );
509 }
510 break;
511
512 case 2:
513
514 if( aPlane )
515 aLayer->WriteVertices( aTop_z, aOut_file, m_precision );
516 else
517 aLayer->Write3DVertices( aTop_z, aBottom_z, aOut_file, m_precision );
518
519 aOut_file << "\n";
520 break;
521
522 case 3:
523
524 if( aPlane )
525 aLayer->WriteIndices( aTop, aOut_file );
526 else
527 aLayer->Write3DIndices( aOut_file );
528
529 aOut_file << "\n";
530 break;
531
532 default:
533 break;
534 }
535 }
536
537 lineno++;
538 }
539}
540
541
542void EXPORTER_PCB_VRML::writeLayers( const char* aFileName, OSTREAM* aOutputFile )
543{
544 // VRML_LAYER board;
545 m_3D_board.Tesselate( &m_holes );
546 double brdz = m_brd_thickness / 2.0
548
550 {
552 &m_3D_board, false, false, brdz, -brdz );
553 }
554 else
555 {
557 }
558
559 // VRML_LAYER m_top_copper;
560 m_top_copper.Tesselate( &m_holes );
561
563 {
565 &m_top_copper, true, true, GetLayerZ( F_Cu ), 0 );
566 }
567 else
568 {
570 GetLayerZ( F_Cu ), true );
571 }
572
573 // VRML_LAYER m_top_paste;
574 m_top_paste.Tesselate( &m_holes );
575
577 {
579 &m_top_paste, true, true,
582 0 );
583 }
584 else
585 {
589 true );
590 }
591
592 // VRML_LAYER m_top_soldermask;
593 m_top_soldermask.Tesselate( &m_holes );
594
596 {
598 &m_top_soldermask, true, true,
601 0 );
602 }
603 else
604 {
608 true );
609 }
610
611 // VRML_LAYER m_bot_copper;
612 m_bot_copper.Tesselate( &m_holes );
613
615 {
617 &m_bot_copper, true, false, GetLayerZ( B_Cu ), 0 );
618 }
619 else
620 {
622 GetLayerZ( B_Cu ), false );
623 }
624
625 // VRML_LAYER m_bot_paste;
626 m_bot_paste.Tesselate( &m_holes );
627
629 {
631 &m_bot_paste, true, false,
632 GetLayerZ( B_Cu )
634 0 );
635 }
636 else
637 {
641 false );
642 }
643
644 // VRML_LAYER m_bot_mask:
645 m_bot_soldermask.Tesselate( &m_holes );
646
648 {
650 &m_bot_soldermask, true, false,
653 0 );
654 }
655 else
656 {
660 false );
661 }
662
663 // VRML_LAYER PTH;
664 m_plated_holes.Tesselate( nullptr, true );
665
667 {
669 &m_plated_holes, false, false,
674 }
675 else
676 {
682 }
683
684 // VRML_LAYER m_top_silk;
685 m_top_silk.Tesselate( &m_holes );
686
688 {
690 true, true, GetLayerZ( F_SilkS ), 0 );
691 }
692 else
693 {
695 GetLayerZ( F_SilkS ), true );
696 }
697
698 // VRML_LAYER m_bot_silk;
699 m_bot_silk.Tesselate( &m_holes );
700
702 {
704 true, false, GetLayerZ( B_SilkS ), 0 );
705 }
706 else
707 {
709 GetLayerZ( B_SilkS ), false );
710 }
711
713 S3D::WriteVRML( aFileName, true, m_OutputPCB.GetRawPtr(), true, true );
714}
715
716
718{
719 int copper_layers = m_board->GetCopperLayerCount();
720
721 // We call it 'layer' thickness, but it's the whole board thickness!
723 double half_thickness = m_brd_thickness / 2;
724
725 // Compute each layer's Z value, more or less like the 3d view
726 for( LSEQ seq = LSET::AllCuMask().Seq(); seq; ++seq )
727 {
728 PCB_LAYER_ID i = *seq;
729
730 if( i < copper_layers )
731 SetLayerZ( i, half_thickness - m_brd_thickness * i / (copper_layers - 1) );
732 else
733 SetLayerZ( i, - half_thickness ); // bottom layer
734 }
735
736 // To avoid rounding interference, we apply an epsilon to each successive layer
737 double epsilon_z = pcbIUScale.mmToIU( ART_OFFSET ) * m_BoardToVrmlScale;
738 SetLayerZ( B_Paste, -half_thickness - epsilon_z );
739 SetLayerZ( B_Adhes, -half_thickness - epsilon_z );
740 SetLayerZ( B_SilkS, -half_thickness - epsilon_z * 3 );
741 SetLayerZ( B_Mask, -half_thickness - epsilon_z * 2 );
742 SetLayerZ( F_Mask, half_thickness + epsilon_z * 2 );
743 SetLayerZ( F_SilkS, half_thickness + epsilon_z * 3 );
744 SetLayerZ( F_Adhes, half_thickness + epsilon_z );
745 SetLayerZ( F_Paste, half_thickness + epsilon_z );
746 SetLayerZ( Dwgs_User, half_thickness + epsilon_z * 5 );
747 SetLayerZ( Cmts_User, half_thickness + epsilon_z * 6 );
748 SetLayerZ( Eco1_User, half_thickness + epsilon_z * 7 );
749 SetLayerZ( Eco2_User, half_thickness + epsilon_z * 8 );
750 SetLayerZ( Edge_Cuts, 0 );
751}
752
753
754void EXPORTER_PCB_VRML::ExportVrmlPolygonSet( VRML_LAYER* aVlayer, const SHAPE_POLY_SET& aOutlines )
755{
756 // Polygons in SHAPE_POLY_SET must be without hole, i.e. holes must be linked
757 // previously to their main outline.
758 for( int icnt = 0; icnt < aOutlines.OutlineCount(); icnt++ )
759 {
760 const SHAPE_LINE_CHAIN& outline = aOutlines.COutline( icnt );
761
762 int seg = aVlayer->NewContour();
763
764 for( int jj = 0; jj < outline.PointCount(); jj++ )
765 {
766 if( !aVlayer->AddVertex( seg, outline.CPoint( jj ).x * m_BoardToVrmlScale,
767 -outline.CPoint( jj ).y * m_BoardToVrmlScale ) )
768 throw( std::runtime_error( aVlayer->GetError() ) );
769 }
770
771 aVlayer->EnsureWinding( seg, false );
772 }
773}
774
775
777{
779 {
780 wxLogWarning( _( "Board outline is malformed. Run DRC for a full analysis." ) );
781 }
782
783 int seg;
784
785 for( int cnt = 0; cnt < m_pcbOutlines.OutlineCount(); cnt++ )
786 {
787 const SHAPE_LINE_CHAIN& outline = m_pcbOutlines.COutline( cnt );
788
789 seg = m_3D_board.NewContour();
790
791 for( int j = 0; j < outline.PointCount(); j++ )
792 {
793 m_3D_board.AddVertex( seg, (double)outline.CPoint(j).x * m_BoardToVrmlScale,
794 -((double)outline.CPoint(j).y * m_BoardToVrmlScale ) );
795
796 }
797
798 m_3D_board.EnsureWinding( seg, false );
799
800 // Generate board holes from outlines:
801 for( int ii = 0; ii < m_pcbOutlines.HoleCount( cnt ); ii++ )
802 {
803 const SHAPE_LINE_CHAIN& hole = m_pcbOutlines.Hole( cnt, ii );
804
805 seg = m_holes.NewContour();
806
807 if( seg < 0 )
808 {
809 wxLogError( _( "VRML Export Failed: Could not add holes to contours." ) );
810 return;
811 }
812
813 for( int j = 0; j < hole.PointCount(); j++ )
814 {
815 m_holes.AddVertex( seg, (double) hole.CPoint(j).x * m_BoardToVrmlScale,
816 -( (double) hole.CPoint(j).y * m_BoardToVrmlScale ) );
817 }
818
819 m_holes.EnsureWinding( seg, true );
820 }
821 }
822}
823
824
825
827{
828 PCB_LAYER_ID top_layer, bottom_layer;
829
830 for( PCB_TRACK* track : m_board->Tracks() )
831 {
832 if( track->Type() != PCB_VIA_T )
833 continue;
834
835 const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
836
837 via->LayerPair( &top_layer, &bottom_layer );
838
839 // do not render a buried via
840 if( top_layer != F_Cu && bottom_layer != B_Cu )
841 continue;
842
843 // Export all via holes to m_holes
844 double hole_radius = via->GetDrillValue() * m_BoardToVrmlScale / 2.0;
845
846 if( hole_radius <= 0 )
847 continue;
848
849 double x = via->GetStart().x * m_BoardToVrmlScale;
850 double y = via->GetStart().y * m_BoardToVrmlScale;
851
852 // Set the optimal number of segments to approximate a circle.
853 // SetArcParams needs a count max, and the minimal and maximal length
854 // of segments
855 double max_error = ERR_APPROX_MAX_MM;
856
858 max_error /= 2.54; // The board is exported with a size reduced by 2.54
859
860 int nsides = GetArcToSegmentCount( via->GetDrillValue(), pcbIUScale.mmToIU( max_error ),
861 FULL_CIRCLE );
862
863 double minSegLength = M_PI * 2.0 * hole_radius / nsides;
864 double maxSegLength = minSegLength*2.0;
865
866 m_holes.SetArcParams( nsides*2, minSegLength, maxSegLength );
867 m_plated_holes.SetArcParams( nsides*2, minSegLength, maxSegLength );
868
869 m_holes.AddCircle( x, -y, hole_radius, true, true );
870 m_plated_holes.AddCircle( x, -y, hole_radius, true, false );
871
872 m_holes.ResetArcParams();
873 m_plated_holes.ResetArcParams();
874 }
875}
876
877
879{
880 double hole_drill_w = (double) aPad->GetDrillSize().x * m_BoardToVrmlScale / 2.0;
881 double hole_drill_h = (double) aPad->GetDrillSize().y * m_BoardToVrmlScale / 2.0;
882 double hole_drill = std::min( hole_drill_w, hole_drill_h );
883 double hole_x = aPad->GetPosition().x * m_BoardToVrmlScale;
884 double hole_y = aPad->GetPosition().y * m_BoardToVrmlScale;
885
886 // Export the hole on the edge layer
887 if( hole_drill > 0 )
888 {
889 double max_error = ERR_APPROX_MAX_MM;
890
892 max_error /= 2.54; // The board is exported with a size reduced by 2.54
893
894 int nsides = GetArcToSegmentCount( hole_drill, pcbIUScale.mmToIU( max_error ),
895 FULL_CIRCLE );
896 double minSegLength = M_PI * hole_drill / nsides;
897 double maxSegLength = minSegLength*2.0;
898
899 m_holes.SetArcParams( nsides*2, minSegLength, maxSegLength );
900 m_plated_holes.SetArcParams( nsides*2, minSegLength, maxSegLength );
901
902 bool pth = false;
903
904 if( ( aPad->GetAttribute() != PAD_ATTRIB::NPTH ) )
905 pth = true;
906
908 {
909 // Oblong hole (slot)
910
911 if( pth )
912 {
913 m_holes.AddSlot( hole_x, -hole_y, hole_drill_w * 2.0 + PLATE_OFFSET,
914 hole_drill_h * 2.0 + PLATE_OFFSET,
915 aPad->GetOrientation().AsDegrees(), true, true );
916
917 m_plated_holes.AddSlot( hole_x, -hole_y,
918 hole_drill_w * 2.0, hole_drill_h * 2.0,
919 aPad->GetOrientation().AsDegrees(), true, false );
920 }
921 else
922 {
923 m_holes.AddSlot( hole_x, -hole_y, hole_drill_w * 2.0, hole_drill_h * 2.0,
924 aPad->GetOrientation().AsDegrees(), true, false );
925
926 }
927 }
928 else
929 {
930 // Drill a round hole
931 if( pth )
932 {
933 m_holes.AddCircle( hole_x, -hole_y, hole_drill + PLATE_OFFSET, true, true );
934 m_plated_holes.AddCircle( hole_x, -hole_y, hole_drill, true, false );
935 }
936 else
937 {
938 m_holes.AddCircle( hole_x, -hole_y, hole_drill, true, false );
939 }
940
941 }
942
943 m_holes.ResetArcParams();
944 m_plated_holes.ResetArcParams();
945 }
946}
947
948
949// From axis/rot to quaternion
950static void build_quat( double x, double y, double z, double a, double q[4] )
951{
952 double sina = sin( a / 2 );
953
954 q[0] = x * sina;
955 q[1] = y * sina;
956 q[2] = z * sina;
957 q[3] = cos( a / 2 );
958}
959
960
961// From quaternion to axis/rot
962static void from_quat( double q[4], double rot[4] )
963{
964 rot[3] = acos( q[3] ) * 2;
965
966 for( int i = 0; i < 3; i++ )
967 rot[i] = q[i] / sin( rot[3] / 2 );
968}
969
970
971// Quaternion composition
972static void compose_quat( double q1[4], double q2[4], double qr[4] )
973{
974 double tmp[4];
975
976 tmp[0] = q2[3] * q1[0] + q2[0] * q1[3] + q2[1] * q1[2] - q2[2] * q1[1];
977 tmp[1] = q2[3] * q1[1] + q2[1] * q1[3] + q2[2] * q1[0] - q2[0] * q1[2];
978 tmp[2] = q2[3] * q1[2] + q2[2] * q1[3] + q2[0] * q1[1] - q2[1] * q1[0];
979 tmp[3] = q2[3] * q1[3] - q2[0] * q1[0] - q2[1] * q1[1] - q2[2] * q1[2];
980
981 qr[0] = tmp[0];
982 qr[1] = tmp[1];
983 qr[2] = tmp[2];
984 qr[3] = tmp[3];
985}
986
987
988void EXPORTER_PCB_VRML::ExportVrmlFootprint( FOOTPRINT* aFootprint, std::ostream* aOutputFile )
989{
990 // Note: if m_UseInlineModelsInBrdfile is false, the 3D footprint shape is copied to
991 // the vrml board file, and aOutputFile is not used (can be nullptr)
992 // if m_UseInlineModelsInBrdfile is true, the 3D footprint shape is copied to
993 // aOutputFile (with the suitable rotation/translation/scale transform, and the vrml board
994 // file contains only the filename of 3D shapes to add to the full vrml scene
995 wxCHECK( aFootprint, /* void */ );
996
997 wxString libraryName = aFootprint->GetFPID().GetLibNickname();
998 wxString footprintBasePath = wxEmptyString;
999
1000 if( m_board->GetProject() )
1001 {
1002 const FP_LIB_TABLE_ROW* fpRow = nullptr;
1003
1004 try
1005 {
1006 fpRow = m_board->GetProject()->PcbFootprintLibs()->FindRow( libraryName, false );
1007 }
1008 catch( ... )
1009 {
1010 // Not found: do nothing
1011 }
1012
1013 if( fpRow )
1014 footprintBasePath = fpRow->GetFullURI( true );
1015 }
1016
1017
1018 // Export pad holes
1019 for( PAD* pad : aFootprint->Pads() )
1021
1022 bool isFlipped = aFootprint->GetLayer() == B_Cu;
1023
1024 // Export the object VRML model(s)
1025 auto sM = aFootprint->Models().begin();
1026 auto eM = aFootprint->Models().end();
1027
1028 wxFileName subdir( m_Subdir3DFpModels, wxT( "" ) );
1029
1030 while( sM != eM )
1031 {
1032 if( !sM->m_Show )
1033 {
1034 ++sM;
1035 continue;
1036 }
1037
1038 SGNODE* mod3d = (SGNODE*) m_Cache3Dmodels->Load( sM->m_Filename, footprintBasePath );
1039
1040 if( nullptr == mod3d )
1041 {
1042 ++sM;
1043 continue;
1044 }
1045
1046 /* Calculate 3D shape rotation:
1047 * this is the rotation parameters, with an additional 180 deg rotation
1048 * for footprints that are flipped
1049 * When flipped, axis rotation is the horizontal axis (X axis)
1050 */
1051 double rotx = -sM->m_Rotation.x;
1052 double roty = -sM->m_Rotation.y;
1053 double rotz = -sM->m_Rotation.z;
1054
1055 if( isFlipped )
1056 {
1057 rotx += 180.0;
1058 roty = -roty;
1059 rotz = -rotz;
1060 }
1061
1062 // Do some quaternion munching
1063 double q1[4], q2[4], rot[4];
1064 build_quat( 1, 0, 0, DEG2RAD( rotx ), q1 );
1065 build_quat( 0, 1, 0, DEG2RAD( roty ), q2 );
1066 compose_quat( q1, q2, q1 );
1067 build_quat( 0, 0, 1, DEG2RAD( rotz ), q2 );
1068 compose_quat( q1, q2, q1 );
1069
1070 // Note here aFootprint->GetOrientation() is in 0.1 degrees, so footprint rotation
1071 // has to be converted to radians
1072 build_quat( 0, 0, 1, aFootprint->GetOrientation().AsRadians(), q2 );
1073 compose_quat( q1, q2, q1 );
1074 from_quat( q1, rot );
1075
1076 double offsetFactor = 1000.0f * pcbIUScale.IU_PER_MILS / 25.4f;
1077
1078 // adjust 3D shape local offset position
1079 // they are given in mm, so they are converted in board IU.
1080 double offsetx = sM->m_Offset.x * offsetFactor;
1081 double offsety = sM->m_Offset.y * offsetFactor;
1082 double offsetz = sM->m_Offset.z * offsetFactor;
1083
1084 if( isFlipped )
1085 offsetz = -offsetz;
1086 else
1087 offsety = -offsety; // In normal mode, Y axis is reversed in Pcbnew.
1088
1089 RotatePoint( &offsetx, &offsety, aFootprint->GetOrientation() );
1090
1091 SGPOINT trans;
1092 trans.x = ( offsetx + aFootprint->GetPosition().x ) * m_BoardToVrmlScale + m_tx;
1093 trans.y = -( offsety + aFootprint->GetPosition().y) * m_BoardToVrmlScale - m_ty;
1094 trans.z = (offsetz * m_BoardToVrmlScale ) + GetLayerZ( aFootprint->GetLayer() );
1095
1097 {
1098 wxCHECK( aOutputFile, /* void */ );
1099
1100 int old_precision = aOutputFile->precision();
1101 aOutputFile->precision( m_precision );
1102
1103 wxFileName srcFile =
1104 m_Cache3Dmodels->GetResolver()->ResolvePath( sM->m_Filename, wxEmptyString );
1105 wxFileName dstFile;
1106 dstFile.SetPath( m_Subdir3DFpModels );
1107 dstFile.SetName( srcFile.GetName() );
1108 dstFile.SetExt( wxT( "wrl" ) );
1109
1110 // copy the file if necessary
1111 wxDateTime srcModTime = srcFile.GetModificationTime();
1112 wxDateTime destModTime = srcModTime;
1113
1114 destModTime.SetToCurrent();
1115
1116 if( dstFile.FileExists() )
1117 destModTime = dstFile.GetModificationTime();
1118
1119 if( srcModTime != destModTime )
1120 {
1121 wxString fileExt = srcFile.GetExt();
1122 fileExt.LowerCase();
1123
1124 // copy VRML models and use the scenegraph library to
1125 // translate other model types
1126 if( fileExt == wxT( "wrl" ) )
1127 {
1128 if( !wxCopyFile( srcFile.GetFullPath(), dstFile.GetFullPath() ) )
1129 {
1130 ++sM;
1131 continue;
1132 }
1133 }
1134 else if( fileExt == wxT( "wrz" ) )
1135 {
1136 wxFileInputStream input_file_stream( srcFile.GetFullPath() );
1137 if( !input_file_stream.IsOk() || input_file_stream.GetSize() == wxInvalidSize )
1138 {
1139 ++sM;
1140 continue;
1141 }
1142
1143 wxZlibInputStream zlib_input_stream( input_file_stream, wxZLIB_GZIP );
1144 wxFFileOutputStream output_file_stream( dstFile.GetFullPath() );
1145 if( !zlib_input_stream.IsOk() || !output_file_stream.IsOk() )
1146 {
1147 output_file_stream.Close();
1148 ++sM;
1149 continue;
1150 }
1151
1152 output_file_stream.Write( zlib_input_stream );
1153 output_file_stream.Close();
1154 }
1155 else
1156 {
1157 if( !S3D::WriteVRML( dstFile.GetFullPath().ToUTF8(), true, mod3d, m_ReuseDef,
1158 true ) )
1159 {
1160 ++sM;
1161 continue;
1162 }
1163 }
1164 }
1165
1166 (*aOutputFile) << "Transform {\n";
1167
1168 // only write a rotation if it is >= 0.1 deg
1169 if( std::abs( rot[3] ) > 0.0001745 )
1170 {
1171 (*aOutputFile) << " rotation ";
1172 (*aOutputFile) << rot[0] << " " << rot[1] << " " << rot[2] << " " << rot[3] << "\n";
1173 }
1174
1175 (*aOutputFile) << " translation ";
1176 (*aOutputFile) << trans.x << " ";
1177 (*aOutputFile) << trans.y << " ";
1178 (*aOutputFile) << trans.z << "\n";
1179
1180 (*aOutputFile) << " scale ";
1181 (*aOutputFile) << sM->m_Scale.x << " ";
1182 (*aOutputFile) << sM->m_Scale.y << " ";
1183 (*aOutputFile) << sM->m_Scale.z << "\n";
1184
1185 (*aOutputFile) << " children [\n Inline {\n url \"";
1186
1188 {
1189 wxFileName tmp = dstFile;
1190 tmp.SetExt( wxT( "" ) );
1191 tmp.SetName( wxT( "" ) );
1192 tmp.RemoveLastDir();
1193 dstFile.MakeRelativeTo( tmp.GetPath() );
1194 }
1195
1196 wxString fn = dstFile.GetFullPath();
1197 fn.Replace( wxT( "\\" ), wxT( "/" ) );
1198 (*aOutputFile) << TO_UTF8( fn ) << "\"\n } ]\n";
1199 (*aOutputFile) << " }\n";
1200
1201 aOutputFile->precision( old_precision );
1202 }
1203 else
1204 {
1205 IFSG_TRANSFORM* modelShape = new IFSG_TRANSFORM( m_OutputPCB.GetRawPtr() );
1206
1207 // only write a rotation if it is >= 0.1 deg
1208 if( std::abs( rot[3] ) > 0.0001745 )
1209 modelShape->SetRotation( SGVECTOR( rot[0], rot[1], rot[2] ), rot[3] );
1210
1211 modelShape->SetTranslation( trans );
1212 modelShape->SetScale( SGPOINT( sM->m_Scale.x, sM->m_Scale.y, sM->m_Scale.z ) );
1213
1214 if( nullptr == S3D::GetSGNodeParent( mod3d ) )
1215 {
1216 m_components.push_back( mod3d );
1217 modelShape->AddChildNode( mod3d );
1218 }
1219 else
1220 {
1221 modelShape->AddRefNode( mod3d );
1222 }
1223
1224 }
1225
1226 ++sM;
1227 }
1228}
1229
1230
1231
1232bool EXPORTER_PCB_VRML::ExportVRML_File( PROJECT* aProject, wxString *aMessages,
1233 const wxString& aFullFileName, double aMMtoWRMLunit,
1234 bool aExport3DFiles, bool aUseRelativePaths,
1235 const wxString& a3D_Subdir,
1236 double aXRef, double aYRef )
1237{
1238 if( aProject == nullptr )
1239 {
1240 if( aMessages )
1241 *aMessages = _( "No project when exporting the VRML file");
1242
1243 return false;
1244 }
1245
1246 SetScale( aMMtoWRMLunit );
1247 m_UseInlineModelsInBrdfile = aExport3DFiles;
1248 m_Subdir3DFpModels = a3D_Subdir;
1249 m_UseRelPathIn3DModelFilename = aUseRelativePaths;
1250 m_Cache3Dmodels = aProject->Get3DCacheManager();
1251
1252 // When 3D models are separate files, for historical reasons the VRML unit
1253 // is expected to be 0.1 inch (2.54mm) instead of 1mm, so we adjust the m_BoardToVrmlScale
1254 // to match the VRML scale of these external files.
1255 // Otherwise we use 1mm as VRML unit
1257 {
1259 SetOffset( -aXRef / 2.54, aYRef / 2.54 );
1260 }
1261 else
1262 {
1264 SetOffset( -aXRef, aYRef );
1265 }
1266
1267 bool success = true;
1268
1269 try
1270 {
1271 // Preliminary computation: the z value for each layer
1273
1274 // board edges and cutouts
1276
1277 // Draw solder mask layer (negative layer)
1281
1283 {
1284 // Copy fp 3D models in a folder, and link these files in
1285 // the board .vrml file
1286 ExportFp3DModelsAsLinkedFile( aFullFileName );
1287 }
1288 else
1289 {
1290 // merge footprints in the .vrml board file
1291 for( FOOTPRINT* footprint : m_board->Footprints() )
1292 ExportVrmlFootprint( footprint, nullptr );
1293
1294 // write out the board and all layers
1295 writeLayers( TO_UTF8( aFullFileName ), nullptr );
1296 }
1297 }
1298 catch( const std::exception& e )
1299 {
1300 if( aMessages )
1301 *aMessages << _( "VRML Export Failed:\n" ) << FROM_UTF8( e.what() );
1302
1303 success = false;
1304 }
1305
1306 return success;
1307}
1308
1309bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, double aMMtoWRMLunit,
1310 bool aExport3DFiles, bool aUseRelativePaths,
1311 const wxString& a3D_Subdir,
1312 double aXRef, double aYRef )
1313{
1314 bool success;
1315 wxString msgs;
1316 EXPORTER_VRML model3d( GetBoard() );
1317
1318 success = model3d.ExportVRML_File( &Prj(), &msgs, aFullFileName, aMMtoWRMLunit,
1319 aExport3DFiles, aUseRelativePaths,
1320 a3D_Subdir, aXRef, aYRef );
1321
1322 if( !msgs.IsEmpty() )
1323 wxMessageBox( msgs );
1324
1325 return success;
1326}
1327
1328
1329void EXPORTER_PCB_VRML::ExportFp3DModelsAsLinkedFile( const wxString& aFullFileName )
1330{
1331 // check if the 3D Subdir exists - create if not
1332 wxFileName subdir( m_Subdir3DFpModels, wxT( "" ) );
1333
1334 if( ! subdir.DirExists() )
1335 {
1336 if( !wxDir::Make( subdir.GetFullPath() ) )
1337 throw( std::runtime_error( "Could not create 3D model subdirectory" ) );
1338 }
1339
1340 OPEN_OSTREAM( output_file, TO_UTF8( aFullFileName ) );
1341
1342 if( output_file.fail() )
1343 {
1344 std::ostringstream ostr;
1345 ostr << "Could not open file '" << TO_UTF8( aFullFileName ) << "'";
1346 throw( std::runtime_error( ostr.str().c_str() ) );
1347 }
1348
1349 output_file.imbue( std::locale::classic() );
1350
1351 // Begin with the usual VRML boilerplate
1352 wxString fn = aFullFileName;
1353 fn.Replace( wxT( "\\" ) , wxT( "/" ) );
1354 output_file << "#VRML V2.0 utf8\n";
1355 output_file << "WorldInfo {\n";
1356 output_file << " title \"" << TO_UTF8( fn ) << " - Generated by Pcbnew\"\n";
1357 output_file << "}\n";
1358 output_file << "Transform {\n";
1359 output_file << " scale " << std::setprecision( m_precision );
1360 output_file << m_WorldScale << " ";
1361 output_file << m_WorldScale << " ";
1362 output_file << m_WorldScale << "\n";
1363 output_file << " children [\n";
1364
1365 // Export footprints
1366 for( FOOTPRINT* footprint : m_board->Footprints() )
1367 ExportVrmlFootprint( footprint, &output_file );
1368
1369 // write out the board and all layers
1370 writeLayers( TO_UTF8( aFullFileName ), &output_file );
1371
1372 // Close the outer 'transform' node
1373 output_file << "]\n}\n";
1374
1375 CLOSE_STREAM( output_file );
1376}
1377
1379{
1380 if( colorIdx == -1 )
1381 colorIdx = VRML_COLOR_PCB;
1382 else if( colorIdx == VRML_COLOR_LAST )
1383 return nullptr;
1384
1385 if( m_sgmaterial[colorIdx] )
1386 return m_sgmaterial[colorIdx];
1387
1388 IFSG_APPEARANCE vcolor( (SGNODE*) nullptr );
1389 VRML_COLOR* cp = &vrml_colors_list[colorIdx];
1390
1391 vcolor.SetSpecular( cp->spec_red, cp->spec_grn, cp->spec_blu );
1392 vcolor.SetDiffuse( cp->diffuse_red, cp->diffuse_grn, cp->diffuse_blu );
1393 vcolor.SetShininess( cp->shiny );
1394 // NOTE: XXX - replace with a better equation; using this definition
1395 // of ambient will not yield the best results
1396 vcolor.SetAmbient( cp->ambient, cp->ambient, cp->ambient );
1397 vcolor.SetTransparency( cp->transp );
1398
1399 m_sgmaterial[colorIdx] = vcolor.GetRawPtr();
1400
1401 return m_sgmaterial[colorIdx];
1402}
1403
1404
1406 VRML_LAYER* layer, double top_z, bool aTopPlane )
1407{
1408 std::vector< double > vertices;
1409 std::vector< int > idxPlane;
1410
1411 if( !( *layer ).Get2DTriangles( vertices, idxPlane, top_z, aTopPlane ) )
1412 {
1413 return;
1414 }
1415
1416 if( ( idxPlane.size() % 3 ) )
1417 {
1418 throw( std::runtime_error( "[BUG] index lists are not a multiple of 3 (not a triangle "
1419 "list)" ) );
1420 }
1421
1422 std::vector< SGPOINT > vlist;
1423 size_t nvert = vertices.size() / 3;
1424 size_t j = 0;
1425
1426 for( size_t i = 0; i < nvert; ++i, j+= 3 )
1427 vlist.emplace_back( vertices[j], vertices[j+1], vertices[j+2] );
1428
1429 // create the intermediate scenegraph
1430 IFSG_TRANSFORM tx0( PcbOutput.GetRawPtr() ); // tx0 = Transform for this outline
1431 IFSG_SHAPE shape( tx0 ); // shape will hold (a) all vertices and (b) a local list of normals
1432 IFSG_FACESET face( shape ); // this face shall represent the top and bottom planes
1433 IFSG_COORDS cp( face ); // coordinates for all faces
1434 cp.SetCoordsList( nvert, &vlist[0] );
1435 IFSG_COORDINDEX coordIdx( face ); // coordinate indices for top and bottom planes only
1436 coordIdx.SetIndices( idxPlane.size(), &idxPlane[0] );
1437 IFSG_NORMALS norms( face ); // normals for the top and bottom planes
1438
1439 // set the normals
1440 if( aTopPlane )
1441 {
1442 for( size_t i = 0; i < nvert; ++i )
1443 norms.AddNormal( 0.0, 0.0, 1.0 );
1444 }
1445 else
1446 {
1447 for( size_t i = 0; i < nvert; ++i )
1448 norms.AddNormal( 0.0, 0.0, -1.0 );
1449 }
1450
1451 // assign a color from the palette
1452 SGNODE* modelColor = getSGColor( colorID );
1453
1454 if( nullptr != modelColor )
1455 {
1456 if( nullptr == S3D::GetSGNodeParent( modelColor ) )
1457 shape.AddChildNode( modelColor );
1458 else
1459 shape.AddRefNode( modelColor );
1460 }
1461}
1462
1463
1465 VRML_LAYER* layer, double top_z, double bottom_z )
1466{
1467 std::vector< double > vertices;
1468 std::vector< int > idxPlane;
1469 std::vector< int > idxSide;
1470
1471 if( top_z < bottom_z )
1472 {
1473 double tmp = top_z;
1474 top_z = bottom_z;
1475 bottom_z = tmp;
1476 }
1477
1478 if( !( *layer ).Get3DTriangles( vertices, idxPlane, idxSide, top_z, bottom_z )
1479 || idxPlane.empty() || idxSide.empty() )
1480 {
1481 return;
1482 }
1483
1484 if( ( idxPlane.size() % 3 ) || ( idxSide.size() % 3 ) )
1485 {
1486 throw( std::runtime_error( "[BUG] index lists are not a multiple of 3 (not a "
1487 "triangle list)" ) );
1488 }
1489
1490 std::vector< SGPOINT > vlist;
1491 size_t nvert = vertices.size() / 3;
1492 size_t j = 0;
1493
1494 for( size_t i = 0; i < nvert; ++i, j+= 3 )
1495 vlist.emplace_back( vertices[j], vertices[j+1], vertices[j+2] );
1496
1497 // create the intermediate scenegraph
1498 IFSG_TRANSFORM tx0( PcbOutput.GetRawPtr() ); // tx0 = Transform for this outline
1499 IFSG_SHAPE shape( tx0 ); // shape will hold (a) all vertices and (b) a local list of normals
1500 IFSG_FACESET face( shape ); // this face shall represent the top and bottom planes
1501 IFSG_COORDS cp( face ); // coordinates for all faces
1502 cp.SetCoordsList( nvert, &vlist[0] );
1503 IFSG_COORDINDEX coordIdx( face ); // coordinate indices for top and bottom planes only
1504 coordIdx.SetIndices( idxPlane.size(), &idxPlane[0] );
1505 IFSG_NORMALS norms( face ); // normals for the top and bottom planes
1506
1507 // number of TOP (and bottom) vertices
1508 j = nvert / 2;
1509
1510 // set the TOP normals
1511 for( size_t i = 0; i < j; ++i )
1512 norms.AddNormal( 0.0, 0.0, 1.0 );
1513
1514 // set the BOTTOM normals
1515 for( size_t i = 0; i < j; ++i )
1516 norms.AddNormal( 0.0, 0.0, -1.0 );
1517
1518 // assign a color from the palette
1519 SGNODE* modelColor = getSGColor( colorID );
1520
1521 if( nullptr != modelColor )
1522 {
1523 if( nullptr == S3D::GetSGNodeParent( modelColor ) )
1524 shape.AddChildNode( modelColor );
1525 else
1526 shape.AddRefNode( modelColor );
1527 }
1528
1529 // create a second shape describing the vertical walls of the extrusion
1530 // using per-vertex-per-face-normals
1531 shape.NewNode( tx0 );
1532 shape.AddRefNode( modelColor ); // set the color to be the same as the top/bottom
1533 face.NewNode( shape );
1534 cp.NewNode( face ); // new vertex list
1535 norms.NewNode( face ); // new normals list
1536 coordIdx.NewNode( face ); // new index list
1537
1538 // populate the new per-face vertex list and its indices and normals
1539 std::vector< int >::iterator sI = idxSide.begin();
1540 std::vector< int >::iterator eI = idxSide.end();
1541
1542 size_t sidx = 0; // index to the new coord set
1543 SGPOINT p1, p2, p3;
1544 SGVECTOR vnorm;
1545
1546 while( sI != eI )
1547 {
1548 p1 = vlist[*sI];
1549 cp.AddCoord( p1 );
1550 ++sI;
1551
1552 p2 = vlist[*sI];
1553 cp.AddCoord( p2 );
1554 ++sI;
1555
1556 p3 = vlist[*sI];
1557 cp.AddCoord( p3 );
1558 ++sI;
1559
1560 vnorm.SetVector( S3D::CalcTriNorm( p1, p2, p3 ) );
1561 norms.AddNormal( vnorm );
1562 norms.AddNormal( vnorm );
1563 norms.AddNormal( vnorm );
1564
1565 coordIdx.AddIndex( (int)sidx );
1566 ++sidx;
1567 coordIdx.AddIndex( (int)sidx );
1568 ++sidx;
1569 coordIdx.AddIndex( (int)sidx );
1570 ++sidx;
1571 }
1572}
defines the basic data associated with a single 3D model.
int color
Definition: DXF_plotter.cpp:57
constexpr std::size_t arrayDim(T const (&)[N]) noexcept
Returns # of elements in an array.
Definition: arraydim.h:31
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:109
@ BS_ITEM_TYPE_SILKSCREEN
Definition: board_stackup.h:49
@ BS_ITEM_TYPE_DIELECTRIC
Definition: board_stackup.h:44
@ BS_ITEM_TYPE_SOLDERMASK
Definition: board_stackup.h:47
int GetBoardThickness() const
The full thickness of the board including copper and masks.
BOARD_STACKUP & GetStackupDescriptor()
Manage one layer needed to make a physical board.
Definition: board_stackup.h:91
Manage layers needed to make a physical board.
const std::vector< BOARD_STACKUP_ITEM * > & GetList() const
wxString m_FinishType
The name of external copper finish.
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:265
void ConvertBrdLayerToPolygonalContours(PCB_LAYER_ID aLayer, SHAPE_POLY_SET &aOutlines) const
Build a set of polygons which are the outlines of copper items (pads, tracks, vias,...
Definition: board.cpp:2210
bool GetBoardPolygonOutlines(SHAPE_POLY_SET &aOutlines, OUTLINE_ERROR_HANDLER *aErrorHandler=nullptr)
Extract the board outlines and build a closed polygon from lines, arcs and circle items on edge cut l...
Definition: board.cpp:1911
FOOTPRINTS & Footprints()
Definition: board.h:307
int GetCopperLayerCount() const
Definition: board.cpp:502
TRACKS & Tracks()
Definition: board.h:304
PROJECT * GetProject() const
Definition: board.h:440
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:643
double AsDegrees() const
Definition: eda_angle.h:149
double AsRadians() const
Definition: eda_angle.h:153
VRML_COLOR & GetColor(VRML_COLOR_INDEX aIndex)
static CUSTOM_COLORS_LIST m_MaskColors
SGNODE * m_sgmaterial[VRML_COLOR_LAST]
static CUSTOM_COLORS_LIST m_PasteColors
static KIGFX::COLOR4D m_DefaultSolderPaste
VRML_LAYER m_top_copper
VRML_LAYER m_top_soldermask
bool GetLayer3D(int layer, VRML_LAYER **vlayer)
bool SetScale(double aWorldScale)
VRML_LAYER m_bot_paste
SGNODE * getSGColor(VRML_COLOR_INDEX colorIdx)
VRML_LAYER m_top_paste
EXPORTER_PCB_VRML(BOARD *aBoard)
VRML_LAYER m_bot_copper
SHAPE_POLY_SET m_pcbOutlines
VRML_LAYER m_plated_holes
void SetLayerZ(int aLayer, double aValue)
void ExportVrmlPolygonSet(VRML_LAYER *aVlayer, const SHAPE_POLY_SET &aOutlines)
void create_vrml_shell(IFSG_TRANSFORM &PcbOutput, VRML_COLOR_INDEX colorID, VRML_LAYER *layer, double top_z, double bottom_z)
void write_triangle_bag(std::ostream &aOut_file, const VRML_COLOR &aColor, VRML_LAYER *aLayer, bool aPlane, bool aTop, double aTop_z, double aBottom_z)
std::list< SGNODE * > m_components
static KIGFX::COLOR4D m_DefaultSilkscreen
static CUSTOM_COLORS_LIST m_SilkscreenColors
void SetOffset(double aXoff, double aYoff)
VRML_LAYER m_holes
double GetLayerZ(int aLayer)
void ExportVrmlPadHole(PAD *aPad)
static CUSTOM_COLORS_LIST m_BoardColors
void create_vrml_plane(IFSG_TRANSFORM &PcbOutput, VRML_COLOR_INDEX colorID, VRML_LAYER *layer, double aHeight, bool aTopPlane)
bool m_UseInlineModelsInBrdfile
wxString m_Subdir3DFpModels
static KIGFX::COLOR4D m_DefaultBoardBody
static CUSTOM_COLORS_LIST m_FinishColors
static KIGFX::COLOR4D m_DefaultSolderMask
double m_layer_z[PCB_LAYER_ID_COUNT]
bool m_UseRelPathIn3DModelFilename
void ExportVrmlFootprint(FOOTPRINT *aFootprint, std::ostream *aOutputFile)
bool ExportVRML_File(PROJECT *aProject, wxString *aMessages, const wxString &aFullFileName, double aMMtoWRMLunit, bool aExport3DFiles, bool aUseRelativePaths, const wxString &a3D_Subdir, double aXRef, double aYRef)
Export a VRML file image of the board.
void ExportFp3DModelsAsLinkedFile(const wxString &aFullFileName)
VRML_LAYER m_bot_soldermask
VRML_COLOR vrml_colors_list[VRML_COLOR_LAST]
VRML_LAYER m_3D_board
void writeLayers(const char *aFileName, OSTREAM *aOutputFile)
static KIGFX::COLOR4D m_DefaultSurfaceFinish
VRML_LAYER m_bot_silk
IFSG_TRANSFORM m_OutputPCB
VRML_LAYER m_top_silk
S3D_CACHE * m_Cache3Dmodels
Wrapper to expose an API for writing VRML files, without exposing all the many structures used in the...
Definition: export_vrml.h:33
EXPORTER_VRML(BOARD *aBoard)
EXPORTER_PCB_VRML * pcb_exporter
Definition: export_vrml.h:59
bool ExportVRML_File(PROJECT *aProject, wxString *aMessages, const wxString &aFullFileName, double aMMtoWRMLunit, bool aExport3DFiles, bool aUseRelativePaths, const wxString &a3D_Subdir, double aXRef, double aYRef)
Exports the board and its footprint shapes 3D (vrml files only) as a vrml file.
wxString ResolvePath(const wxString &aFileName, const wxString &aWorkingPath)
Determines the full path of the given file name.
EDA_ANGLE GetOrientation() const
Definition: footprint.h:191
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition: footprint.h:200
PADS & Pads()
Definition: footprint.h:170
const LIB_ID & GetFPID() const
Definition: footprint.h:212
std::vector< FP_3DMODEL > & Models()
Definition: footprint.h:184
VECTOR2I GetPosition() const override
Definition: footprint.h:188
Hold a record identifying a library accessed by the appropriate footprint library PLUGIN object in th...
Definition: fp_lib_table.h:41
const FP_LIB_TABLE_ROW * FindRow(const wxString &aNickName, bool aCheckIfEnabled=false)
Return an FP_LIB_TABLE_ROW if aNickName is found in this table or in any chained fall back table frag...
bool SetDiffuse(float aRVal, float aGVal, float aBVal)
bool SetSpecular(float aRVal, float aGVal, float aBVal)
bool SetShininess(float aShininess) noexcept
bool SetAmbient(float aRVal, float aGVal, float aBVal)
bool SetTransparency(float aTransparency) noexcept
IFSG_COORDINDEX is the wrapper for SGCOORDINDEX.
bool NewNode(SGNODE *aParent) override
Function NewNode creates a new node to associate with this wrapper.
IFSG_COORDS is the wrapper for SGCOORDS.
Definition: ifsg_coords.h:41
bool NewNode(SGNODE *aParent) override
Function NewNode creates a new node to associate with this wrapper.
bool SetCoordsList(size_t aListSize, const SGPOINT *aCoordsList)
bool AddCoord(double aXValue, double aYValue, double aZValue)
IFSG_FACESET is the wrapper for the SGFACESET class.
Definition: ifsg_faceset.h:41
bool NewNode(SGNODE *aParent) override
Function NewNode creates a new node to associate with this wrapper.
bool AddIndex(int aIndex)
Function AddIndex adds a single index to the list.
Definition: ifsg_index.cpp:57
bool SetIndices(size_t nIndices, int *aIndexList)
Function SetIndices sets the number of indices and creates a copy of the given index data.
Definition: ifsg_index.cpp:47
bool SetParent(SGNODE *aParent)
Function SetParent sets the parent SGNODE of this object.
Definition: ifsg_node.cpp:87
SGNODE * GetRawPtr(void) noexcept
Function GetRawPtr() returns the raw internal SGNODE pointer.
Definition: ifsg_node.cpp:65
void Destroy(void)
Function Destroy deletes the object held by this wrapper.
Definition: ifsg_node.cpp:55
bool AddChildNode(SGNODE *aNode)
Function AddChildNode adds a node as a child owned by this node.
Definition: ifsg_node.cpp:148
bool AddRefNode(SGNODE *aNode)
Function AddRefNode adds a reference to an existing node which is not owned by (not a child of) this ...
Definition: ifsg_node.cpp:128
IFSG_NORMALS is the wrapper for the SGNORMALS class.
Definition: ifsg_normals.h:41
bool NewNode(SGNODE *aParent) override
Function NewNode creates a new node to associate with this wrapper.
bool AddNormal(double aXValue, double aYValue, double aZValue)
IFSG_SHAPE is the wrapper for the SGSHAPE class.
Definition: ifsg_shape.h:41
bool NewNode(SGNODE *aParent) override
Function NewNode creates a new node to associate with this wrapper.
Definition: ifsg_shape.cpp:121
IFSG_TRANSFORM is the wrapper for the VRML compatible TRANSFORM block class SCENEGRAPH.
bool Attach(SGNODE *aNode) override
Function Attach associates a given SGNODE* with this wrapper.
bool SetTranslation(const SGPOINT &aTranslation) noexcept
bool SetRotation(const SGVECTOR &aRotationAxis, double aAngle)
bool SetScale(const SGPOINT &aScale) noexcept
A color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:104
double r
Red component.
Definition: color4d.h:384
double g
Green component.
Definition: color4d.h:385
double a
Alpha component.
Definition: color4d.h:387
COLOR4D Brightened(double aFactor) const
Return a color that is brighter by a given factor, without modifying object.
Definition: color4d.h:278
COLOR4D Mix(const COLOR4D &aColor, double aFactor) const
Return a color that is mixed with the input by a factor.
Definition: color4d.h:305
double b
Blue component.
Definition: color4d.h:386
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition: lib_id.h:87
const wxString GetFullURI(bool aSubstituted=false) const
Return the full location specifying URI for the LIB, either in original UI form or in environment var...
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition: layer_ids.h:491
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:773
Definition: pad.h:59
PAD_DRILL_SHAPE_T GetDrillShape() const
Definition: pad.h:378
const VECTOR2I & GetDrillSize() const
Definition: pad.h:262
PAD_ATTRIB GetAttribute() const
Definition: pad.h:395
VECTOR2I GetPosition() const override
Definition: pad.h:197
EDA_ANGLE GetOrientation() const
Return the rotation angle of the pad.
Definition: pad.h:365
BOARD * GetBoard() const
bool ExportVRML_File(const wxString &aFullFileName, double aMMtoWRMLunit, bool aExport3DFiles, bool aUseRelativePaths, const wxString &a3D_Subdir, double aXRef, double aYRef)
Create the file(s) exporting current BOARD to a VRML file.
Container for project specific data.
Definition: project.h:63
virtual FP_LIB_TABLE * PcbFootprintLibs(KIWAY &aKiway)
Return the table of footprint libraries.
Definition: project.cpp:319
FILENAME_RESOLVER * GetResolver() noexcept
Definition: 3d_cache.cpp:607
SCENEGRAPH * Load(const wxString &aModelFile, const wxString &aBasePath)
Attempt to load the scene data for a model.
Definition: 3d_cache.cpp:286
The base class of all Scene Graph nodes.
Definition: sg_node.h:75
double z
Definition: sg_base.h:72
double x
Definition: sg_base.h:70
double y
Definition: sg_base.h:71
void SetVector(double aXVal, double aYVal, double aZVal)
Definition: sg_base.cpp:233
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
Represent a set of closed polygons.
void BooleanSubtract(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset intersection For aFastMode meaning, see function booleanOp.
void Fracture(POLYGON_MODE aFastMode)
Convert a single outline slitted ("fractured") polygon into a set ouf outlines with holes.
int HoleCount(int aOutline) const
Return the reference to aIndex-th outline in the set.
SHAPE_LINE_CHAIN & Hole(int aOutline, int aHole)
Return the aIndex-th subpolygon in the set.
int OutlineCount() const
Return the number of vertices in a given outline/hole.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
std::vector< CUSTOM_COLOR_ITEM > CUSTOM_COLORS_LIST
#define _HKI(x)
#define _(s)
static constexpr EDA_ANGLE & FULL_CIRCLE
Definition: eda_angle.h:410
#define ADD_COLOR(list, r, g, b, a, name)
static void build_quat(double x, double y, double z, double a, double q[4])
static void compose_quat(double q1[4], double q2[4], double qr[4])
static void from_quat(double q[4], double rot[4])
static bool g_ColorsLoaded
#define ERR_APPROX_MAX_MM
VRML_COLOR_INDEX
Definition: exporter_vrml.h:33
@ VRML_COLOR_BOT_SILK
Definition: exporter_vrml.h:41
@ VRML_COLOR_LAST
Definition: exporter_vrml.h:42
@ VRML_COLOR_TOP_SILK
Definition: exporter_vrml.h:40
@ VRML_COLOR_TOP_SOLDMASK
Definition: exporter_vrml.h:37
@ VRML_COLOR_PCB
Definition: exporter_vrml.h:35
@ VRML_COLOR_PASTE
Definition: exporter_vrml.h:39
@ VRML_COLOR_COPPER
Definition: exporter_vrml.h:36
@ VRML_COLOR_BOT_SOLDMASK
Definition: exporter_vrml.h:38
#define PLATE_OFFSET
Definition: exporter_vrml.h:28
#define ART_OFFSET
Definition: exporter_vrml.h:26
a few functions useful in geometry calculations.
int GetArcToSegmentCount(int aRadius, int aErrorMax, const EDA_ANGLE &aArcAngle)
collects header files for all SG* wrappers and the API
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:59
@ B_Adhes
Definition: layer_ids.h:97
@ Edge_Cuts
Definition: layer_ids.h:113
@ Dwgs_User
Definition: layer_ids.h:109
@ F_Paste
Definition: layer_ids.h:101
@ Cmts_User
Definition: layer_ids.h:110
@ F_Adhes
Definition: layer_ids.h:98
@ B_Mask
Definition: layer_ids.h:106
@ B_Cu
Definition: layer_ids.h:95
@ Eco1_User
Definition: layer_ids.h:111
@ F_Mask
Definition: layer_ids.h:107
@ B_Paste
Definition: layer_ids.h:100
@ F_SilkS
Definition: layer_ids.h:104
@ Eco2_User
Definition: layer_ids.h:112
@ B_SilkS
Definition: layer_ids.h:103
@ F_Cu
Definition: layer_ids.h:64
This file contains miscellaneous commonly used macros and functions.
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: macros.h:96
static wxString FROM_UTF8(const char *cstring)
Convert a UTF8 encoded C string to a wxString for all wxWidgets build modes.
Definition: macros.h:110
SGLIB_API bool WriteVRML(const char *filename, bool overwrite, SGNODE *aTopNode, bool reuse, bool renameNodes)
Function WriteVRML writes out the given node and its subnodes to a VRML2 file.
Definition: ifsg_api.cpp:77
SGLIB_API SGNODE * GetSGNodeParent(SGNODE *aNode)
Definition: ifsg_api.cpp:494
SGLIB_API SGVECTOR CalcTriNorm(const SGPOINT &p1, const SGPOINT &p2, const SGPOINT &p3)
Function CalcTriNorm returns the normal vector of a triangle described by vertices p1,...
Definition: ifsg_api.cpp:464
SGLIB_API void DestroyNode(SGNODE *aNode) noexcept
Function DestroyNode deletes the given SG* class node.
Definition: ifsg_api.cpp:149
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:401
@ NPTH
like PAD_PTH, but not plated
@ PAD_DRILL_SHAPE_OBLONG
Definition: pad_shapes.h:71
#define OPEN_OSTREAM(var, name)
#define CLOSE_STREAM(var)
#define OSTREAM
A class to handle a custom color (predefined color) for the color picker dialog.
constexpr double IUTomm(int iu) const
Definition: base_units.h:87
const double IU_PER_MILS
Definition: base_units.h:78
const double MM_PER_IU
Definition: base_units.h:79
constexpr int mmToIU(double mm) const
Definition: base_units.h:89
float ambient
Definition: exporter_vrml.h:60
float emit_blu
Definition: exporter_vrml.h:58
float diffuse_red
Definition: exporter_vrml.h:48
float spec_blu
Definition: exporter_vrml.h:54
float diffuse_grn
Definition: exporter_vrml.h:49
float transp
Definition: exporter_vrml.h:61
float spec_grn
Definition: exporter_vrml.h:53
float emit_grn
Definition: exporter_vrml.h:57
float emit_red
Definition: exporter_vrml.h:56
float spec_red
Definition: exporter_vrml.h:52
float diffuse_blu
Definition: exporter_vrml.h:50
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Definition: trigo.cpp:183
double DEG2RAD(double deg)
Definition: trigo.h:195
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition: typeinfo.h:102