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
30#include "3d_cache/3d_cache.h"
31#include "3d_cache/3d_info.h"
32#include "board.h"
34#include "fp_shape.h"
35#include "footprint.h"
36#include "pad.h"
37#include "pcb_text.h"
38#include "pcb_track.h"
39#include "convert_to_biu.h"
40#include <core/arraydim.h>
41#include <filename_resolver.h>
43#include "streamwrapper.h"
44#include "vrml_layer.h"
45#include "pcb_edit_frame.h"
46
49#include <macros.h>
50
51#include <exporter_vrml.h>
52
54{
55 pcb_exporter = new EXPORTER_PCB_VRML( aBoard );
56}
57
58
59bool EXPORTER_VRML::ExportVRML_File( PROJECT* aProject, wxString *aMessages,
60 const wxString& aFullFileName, double aMMtoWRMLunit,
61 bool aExport3DFiles, bool aUseRelativePaths,
62 const wxString& a3D_Subdir,
63 double aXRef, double aYRef )
64{
65 return pcb_exporter->ExportVRML_File( aProject, aMessages,
66 aFullFileName, aMMtoWRMLunit,
67 aExport3DFiles, aUseRelativePaths,
68 a3D_Subdir, aXRef, aYRef );
69}
70
71
73{
74 delete pcb_exporter;
75}
76
77
78// The max error (in mm) to approximate arcs to segments:
79#define ERR_APPROX_MAX_MM 0.005
80
81
87
93
94static bool g_ColorsLoaded = false;
95
96
98 m_OutputPCB( nullptr )
99{
100 m_board = aBoard;
101 m_ReuseDef = true;
102 m_precision = 6;
103 m_WorldScale = 1.0;
104 m_Cache3Dmodels = nullptr;
107 m_BoardToVrmlScale = MM_PER_IU;
108
109 for( int ii = 0; ii < VRML_COLOR_LAST; ++ii )
110 m_sgmaterial[ii] = nullptr;
111
112 for( unsigned i = 0; i < arrayDim( m_layer_z ); ++i )
113 m_layer_z[i] = 0;
114
115 // this default only makes sense if the output is in mm
117
118 // TODO: figure out a way to share all these stackup color definitions...
120
127 COLOR4D boardBody( 0, 0, 0, 0 );
128
130
131 auto findColor =
132 []( const wxString& aColorName, const CUSTOM_COLORS_LIST& aColorSet )
133 {
134 if( aColorName.StartsWith( wxT( "#" ) ) )
135 {
136 return KIGFX::COLOR4D( aColorName );
137 }
138 else
139 {
140 for( const CUSTOM_COLOR_ITEM& color : aColorSet )
141 {
142 if( color.m_ColorName == aColorName )
143 return color.m_Color;
144 }
145 }
146
147 return KIGFX::COLOR4D();
148 };
149
150 for( const BOARD_STACKUP_ITEM* stackupItem : stackup.GetList() )
151 {
152 wxString colorName = stackupItem->GetColor();
153
154 switch( stackupItem->GetType() )
155 {
157 if( stackupItem->GetBrdLayerId() == F_SilkS )
158 topSilk = findColor( colorName, m_SilkscreenColors );
159 else
160 botSilk = findColor( colorName, m_SilkscreenColors );
161 break;
162
164 if( stackupItem->GetBrdLayerId() == F_Mask )
165 topMask = findColor( colorName, m_MaskColors );
166 else
167 botMask = findColor( colorName, m_MaskColors );
168
169 break;
170
172 {
173 KIGFX::COLOR4D layerColor = findColor( colorName, m_BoardColors );
174
175 if( boardBody == COLOR4D( 0, 0, 0, 0 ) )
176 boardBody = layerColor;
177 else
178 boardBody = boardBody.Mix( layerColor, 1.0 - layerColor.a );
179
180 boardBody.a += ( 1.0 - boardBody.a ) * layerColor.a / 2;
181 break;
182 }
183
184 default:
185 break;
186 }
187 }
188
189 if( boardBody == COLOR4D( 0, 0, 0, 0 ) )
190 boardBody = m_DefaultBoardBody;
191
192 const wxString& finishName = stackup.m_FinishType;
193
194 if( finishName.EndsWith( wxT( "OSP" ) ) )
195 {
196 finish = findColor( wxT( "Copper" ), m_FinishColors );
197 }
198 else if( finishName.EndsWith( wxT( "IG" ) )
199 || finishName.EndsWith( wxT( "gold" ) ) )
200 {
201 finish = findColor( wxT( "Gold" ), m_FinishColors );
202 }
203 else if( finishName.StartsWith( wxT( "HAL" ) )
204 || finishName.StartsWith( wxT( "HASL" ) )
205 || finishName.EndsWith( wxT( "tin" ) )
206 || finishName.EndsWith( wxT( "nickel" ) ) )
207 {
208 finish = findColor( wxT( "Tin" ), m_FinishColors );
209 }
210 else if( finishName.EndsWith( wxT( "silver" ) ) )
211 {
212 finish = findColor( wxT( "Silver" ), m_FinishColors );
213 }
214
215 auto toVRMLColor =
216 []( const COLOR4D& aColor, double aSpecular, double aAmbient, double aShiny )
217 {
218 COLOR4D diff = aColor;
219 COLOR4D spec = aColor.Brightened( aSpecular );
220
221 return VRML_COLOR( diff.r, diff.g, diff.b,
222 spec.r, spec.g, spec.b,
223 aAmbient, 1.0 - aColor.a, aShiny );
224 };
225
226 vrml_colors_list[VRML_COLOR_TOP_SILK] = toVRMLColor( topSilk, 0.1, 0.7, 0.02 );
227 vrml_colors_list[VRML_COLOR_BOT_SILK] = toVRMLColor( botSilk, 0.1, 0.7, 0.02 );
228 vrml_colors_list[VRML_COLOR_TOP_SOLDMASK] = toVRMLColor( topMask, 0.3, 0.8, 0.30 );
229 vrml_colors_list[VRML_COLOR_BOT_SOLDMASK] = toVRMLColor( botMask, 0.3, 0.8, 0.30 );
230 vrml_colors_list[VRML_COLOR_PASTE] = toVRMLColor( paste, 0.6, 0.7, 0.70 );
231 vrml_colors_list[VRML_COLOR_COPPER] = toVRMLColor( finish, 0.6, 0.7, 0.90 );
232 vrml_colors_list[VRML_COLOR_PCB] = toVRMLColor( boardBody, 0.1, 0.7, 0.01 );
233
234 SetOffset( 0.0, 0.0 );
235}
236
237
239{
240 // destroy any unassociated material appearances
241 for( int j = 0; j < VRML_COLOR_LAST; ++j )
242 {
243 if( m_sgmaterial[j] && nullptr == S3D::GetSGNodeParent( m_sgmaterial[j] ) )
245
246 m_sgmaterial[j] = nullptr;
247 }
248
249 if( !m_components.empty() )
250 {
251 IFSG_TRANSFORM tmp( false );
252
253 for( auto i : m_components )
254 {
255 tmp.Attach( i );
256 tmp.SetParent( nullptr );
257 }
258
259 m_components.clear();
261 }
262}
263
265{
266 // Initialize the list of colors used in VRML export, but only once.
267 // (The list is static)
268 if( g_ColorsLoaded )
269 return;
270
271#define ADD_COLOR( list, r, g, b, a, name ) \
272 list.emplace_back( r/255.0, g/255.0, b/255.0, a, name )
273
274 ADD_COLOR( m_SilkscreenColors, 245, 245, 245, 1.0, _HKI( "Not specified" ) ); // White
275 ADD_COLOR( m_SilkscreenColors, 20, 51, 36, 1.0, wxT( "Green" ) );
276 ADD_COLOR( m_SilkscreenColors, 181, 19, 21, 1.0, wxT( "Red" ) );
277 ADD_COLOR( m_SilkscreenColors, 2, 59, 162, 1.0, wxT( "Blue" ) );
278 ADD_COLOR( m_SilkscreenColors, 11, 11, 11, 1.0, wxT( "Black" ) );
279 ADD_COLOR( m_SilkscreenColors, 245, 245, 245, 1.0, wxT( "White" ) );
280 ADD_COLOR( m_SilkscreenColors, 32, 2, 53, 1.0, wxT( "Purple" ) );
281 ADD_COLOR( m_SilkscreenColors, 194, 195, 0, 1.0, wxT( "Yellow" ) );
282
283 ADD_COLOR( m_MaskColors, 20, 51, 36, 0.83, _HKI( "Not specified" ) ); // Green
284 ADD_COLOR( m_MaskColors, 20, 51, 36, 0.83, wxT( "Green" ) );
285 ADD_COLOR( m_MaskColors, 91, 168, 12, 0.83, wxT( "Light Green" ) );
286 ADD_COLOR( m_MaskColors, 13, 104, 11, 0.83, wxT( "Saturated Green" ) );
287 ADD_COLOR( m_MaskColors, 181, 19, 21, 0.83, wxT( "Red" ) );
288 ADD_COLOR( m_MaskColors, 210, 40, 14, 0.83, wxT( "Light Red" ) );
289 ADD_COLOR( m_MaskColors, 239, 53, 41, 0.83, wxT( "Red/Orange" ) );
290 ADD_COLOR( m_MaskColors, 2, 59, 162, 0.83, wxT( "Blue" ) );
291 ADD_COLOR( m_MaskColors, 54, 79, 116, 0.83, wxT( "Light Blue 1" ) );
292 ADD_COLOR( m_MaskColors, 61, 85, 130, 0.83, wxT( "Light Blue 2" ) );
293 ADD_COLOR( m_MaskColors, 21, 70, 80, 0.83, wxT( "Green/Blue" ) );
294 ADD_COLOR( m_MaskColors, 11, 11, 11, 0.83, wxT( "Black" ) );
295 ADD_COLOR( m_MaskColors, 245, 245, 245, 0.83, wxT( "White" ) );
296 ADD_COLOR( m_MaskColors, 32, 2, 53, 0.83, wxT( "Purple" ) );
297 ADD_COLOR( m_MaskColors, 119, 31, 91, 0.83, wxT( "Light Purple" ) );
298 ADD_COLOR( m_MaskColors, 194, 195, 0, 0.83, wxT( "Yellow" ) );
299
300 ADD_COLOR( m_PasteColors, 128, 128, 128, 1.0, wxT( "Grey" ) );
301 ADD_COLOR( m_PasteColors, 90, 90, 90, 1.0, wxT( "Dark Grey" ) );
302 ADD_COLOR( m_PasteColors, 213, 213, 213, 1.0, wxT( "Silver" ) );
303
304 ADD_COLOR( m_FinishColors, 184, 115, 50, 1.0, wxT( "Copper" ) );
305 ADD_COLOR( m_FinishColors, 178, 156, 0, 1.0, wxT( "Gold" ) );
306 ADD_COLOR( m_FinishColors, 213, 213, 213, 1.0, wxT( "Silver" ) );
307 ADD_COLOR( m_FinishColors, 160, 160, 160, 1.0, wxT( "Tin" ) );
308
309 ADD_COLOR( m_BoardColors, 51, 43, 22, 0.83, wxT( "FR4 natural, dark" ) );
310 ADD_COLOR( m_BoardColors, 109, 116, 75, 0.83, wxT( "FR4 natural" ) );
311 ADD_COLOR( m_BoardColors, 252, 252, 250, 0.90, wxT( "PTFE natural" ) );
312 ADD_COLOR( m_BoardColors, 205, 130, 0, 0.68, wxT( "Polyimide" ) );
313 ADD_COLOR( m_BoardColors, 92, 17, 6, 0.90, wxT( "Phenolic natural" ) );
314 ADD_COLOR( m_BoardColors, 146, 99, 47, 0.83, wxT( "Brown 1" ) );
315 ADD_COLOR( m_BoardColors, 160, 123, 54, 0.83, wxT( "Brown 2" ) );
316 ADD_COLOR( m_BoardColors, 146, 99, 47, 0.83, wxT( "Brown 3" ) );
317 ADD_COLOR( m_BoardColors, 213, 213, 213, 1.0, wxT( "Aluminum" ) );
318
319 m_DefaultSilkscreen = COLOR4D( 0.94, 0.94, 0.94, 1.0 );
320 m_DefaultSolderMask = COLOR4D( 0.08, 0.20, 0.14, 0.83 );
321 m_DefaultSolderPaste = COLOR4D( 0.50, 0.50, 0.50, 1.0 );
322 m_DefaultSurfaceFinish = COLOR4D( 0.75, 0.61, 0.23, 1.0 );
323 m_DefaultBoardBody = COLOR4D( 0.43, 0.45, 0.30, 0.90 );
324#undef ADD_COLOR
325
326 g_ColorsLoaded = true;
327}
328
329
330bool EXPORTER_PCB_VRML::SetScale( double aWorldScale )
331{
332 // set the scaling of the VRML world
333 if( aWorldScale < 0.001 || aWorldScale > 10.0 )
334 throw( std::runtime_error( "WorldScale out of range (valid range is 0.001 to 10.0)" ) );
335
336 m_OutputPCB.SetScale( aWorldScale * 2.54 );
337 m_WorldScale = aWorldScale * 2.54;
338
339 return true;
340}
341
342
343void EXPORTER_PCB_VRML::SetOffset( double aXoff, double aYoff )
344{
345 m_tx = aXoff;
346 m_ty = -aYoff;
347
348 m_holes.SetVertexOffsets( aXoff, aYoff );
349 m_3D_board.SetVertexOffsets( aXoff, aYoff );
350 m_top_copper.SetVertexOffsets( aXoff, aYoff );
351 m_bot_copper.SetVertexOffsets( aXoff, aYoff );
352 m_top_silk.SetVertexOffsets( aXoff, aYoff );
353 m_bot_silk.SetVertexOffsets( aXoff, aYoff );
354 m_top_paste.SetVertexOffsets( aXoff, aYoff );
355 m_bot_paste.SetVertexOffsets( aXoff, aYoff );
356 m_top_soldermask.SetVertexOffsets( aXoff, aYoff );
357 m_bot_soldermask.SetVertexOffsets( aXoff, aYoff );
358 m_plated_holes.SetVertexOffsets( aXoff, aYoff );
359}
360
361
362bool EXPORTER_PCB_VRML::GetLayer3D( int layer, VRML_LAYER** vlayer )
363{
364 // select the VRML layer object to draw on; return true if
365 // a layer has been selected.
366 switch( layer )
367 {
368 case B_Cu: *vlayer = &m_bot_copper; return true;
369 case F_Cu: *vlayer = &m_top_copper; return true;
370 case B_SilkS: *vlayer = &m_bot_silk; return true;
371 case F_SilkS: *vlayer = &m_top_silk; return true;
372 case B_Mask: *vlayer = &m_bot_soldermask; return true;
373 case F_Mask: *vlayer = &m_top_soldermask; return true;
374 case B_Paste: *vlayer = &m_bot_paste; return true;
375 case F_Paste: *vlayer = &m_top_paste; return true;
376 default: return false;
377 }
378}
379
381{
382 SHAPE_POLY_SET holes, outlines = m_pcbOutlines;
383
384 // holes is the solder mask opening.
385 // the actual shape is the negative shape of mask opening.
386 PCB_LAYER_ID pcb_layer = F_Mask;
387 VRML_LAYER* vrmllayer = &m_top_soldermask;
388
389 for( int lcnt = 0; lcnt < 2; lcnt++ )
390 {
391 holes.RemoveAllContours();
392 outlines.RemoveAllContours();
393 outlines = m_pcbOutlines;
394 m_board->ConvertBrdLayerToPolygonalContours( pcb_layer, holes );
395
396 outlines.BooleanSubtract( holes, SHAPE_POLY_SET::PM_FAST );
398 ExportVrmlPolygonSet( vrmllayer, outlines );
399
400 pcb_layer = B_Mask;
401 vrmllayer = &m_bot_soldermask;
402 }
403}
404
405
407{
408 SHAPE_POLY_SET outlines;
409
410 PCB_LAYER_ID pcb_layer[] =
411 {
413 };
414
415 VRML_LAYER* vrmllayer[] =
416 {
418 nullptr // Sentinel
419 };
420
421 for( int lcnt = 0; ; lcnt++ )
422 {
423 if( vrmllayer[lcnt] == nullptr )
424 break;
425
426 outlines.RemoveAllContours();
427 m_board->ConvertBrdLayerToPolygonalContours( pcb_layer[lcnt], outlines );
429
430 ExportVrmlPolygonSet( vrmllayer[lcnt], outlines );
431 }
432}
433
434
435void EXPORTER_PCB_VRML::write_triangle_bag( std::ostream& aOut_file, const VRML_COLOR& aColor,
436 VRML_LAYER* aLayer, bool aPlane, bool aTop,
437 double aTop_z, double aBottom_z )
438{
439 // A lot of nodes are not required, but blender sometimes chokes without them.
440 static const char* shape_boiler[] =
441 {
442 "Transform {\n",
443 " children [\n",
444 " Group {\n",
445 " children [\n",
446 " Shape {\n",
447 " appearance Appearance {\n",
448 " material Material {\n",
449 0, // Material marker
450 " }\n",
451 " }\n",
452 " geometry IndexedFaceSet {\n",
453 " solid TRUE\n",
454 " coord Coordinate {\n",
455 " point [\n",
456 0, // Coordinates marker
457 " ]\n",
458 " }\n",
459 " coordIndex [\n",
460 0, // Index marker
461 " ]\n",
462 " }\n",
463 " }\n",
464 " ]\n",
465 " }\n",
466 " ]\n",
467 "}\n",
468 0 // End marker
469 };
470
471 int marker_found = 0, lineno = 0;
472
473 while( marker_found < 4 )
474 {
475 if( shape_boiler[lineno] )
476 {
477 aOut_file << shape_boiler[lineno];
478 }
479 else
480 {
481 marker_found++;
482
483 switch( marker_found )
484 {
485 case 1: // Material marker
486 {
487 std::streamsize lastPrecision = aOut_file.precision();
488 aOut_file << " diffuseColor " << std::setprecision(3);
489 aOut_file << aColor.diffuse_red << " ";
490 aOut_file << aColor.diffuse_grn << " ";
491 aOut_file << aColor.diffuse_blu << "\n";
492
493 aOut_file << " specularColor ";
494 aOut_file << aColor.spec_red << " ";
495 aOut_file << aColor.spec_grn << " ";
496 aOut_file << aColor.spec_blu << "\n";
497
498 aOut_file << " emissiveColor ";
499 aOut_file << aColor.emit_red << " ";
500 aOut_file << aColor.emit_grn << " ";
501 aOut_file << aColor.emit_blu << "\n";
502
503 aOut_file << " ambientIntensity " << aColor.ambient << "\n";
504 aOut_file << " transparency " << aColor.transp << "\n";
505 aOut_file << " shininess " << aColor.shiny << "\n";
506 aOut_file.precision( lastPrecision );
507 }
508 break;
509
510 case 2:
511
512 if( aPlane )
513 aLayer->WriteVertices( aTop_z, aOut_file, m_precision );
514 else
515 aLayer->Write3DVertices( aTop_z, aBottom_z, aOut_file, m_precision );
516
517 aOut_file << "\n";
518 break;
519
520 case 3:
521
522 if( aPlane )
523 aLayer->WriteIndices( aTop, aOut_file );
524 else
525 aLayer->Write3DIndices( aOut_file );
526
527 aOut_file << "\n";
528 break;
529
530 default:
531 break;
532 }
533 }
534
535 lineno++;
536 }
537}
538
539
540void EXPORTER_PCB_VRML::writeLayers( const char* aFileName, OSTREAM* aOutputFile )
541{
542 // VRML_LAYER board;
543 m_3D_board.Tesselate( &m_holes );
544 double brdz = m_brd_thickness / 2.0
546
548 {
550 &m_3D_board, false, false, brdz, -brdz );
551 }
552 else
553 {
555 }
556
557 // VRML_LAYER m_top_copper;
558 m_top_copper.Tesselate( &m_holes );
559
561 {
563 &m_top_copper, true, true, GetLayerZ( F_Cu ), 0 );
564 }
565 else
566 {
568 GetLayerZ( F_Cu ), true );
569 }
570
571 // VRML_LAYER m_top_paste;
572 m_top_paste.Tesselate( &m_holes );
573
575 {
577 &m_top_paste, true, true,
578 GetLayerZ( F_Cu ) + Millimeter2iu( ART_OFFSET / 2.0 ) *
580 0 );
581 }
582 else
583 {
585 GetLayerZ( F_Cu ) + Millimeter2iu( ART_OFFSET / 2.0 ) *
587 true );
588 }
589
590 // VRML_LAYER m_top_soldermask;
591 m_top_soldermask.Tesselate( &m_holes );
592
594 {
596 &m_top_soldermask, true, true,
597 GetLayerZ( F_Cu ) + Millimeter2iu( ART_OFFSET / 2.0 ) *
599 0 );
600 }
601 else
602 {
604 GetLayerZ( F_Cu ) + Millimeter2iu( ART_OFFSET / 2.0 ) *
606 true );
607 }
608
609 // VRML_LAYER m_bot_copper;
610 m_bot_copper.Tesselate( &m_holes );
611
613 {
615 &m_bot_copper, true, false, GetLayerZ( B_Cu ), 0 );
616 }
617 else
618 {
620 GetLayerZ( B_Cu ), false );
621 }
622
623 // VRML_LAYER m_bot_paste;
624 m_bot_paste.Tesselate( &m_holes );
625
627 {
629 &m_bot_paste, true, false,
630 GetLayerZ( B_Cu )
632 0 );
633 }
634 else
635 {
637 GetLayerZ( B_Cu ) - Millimeter2iu( ART_OFFSET / 2.0 ) *
639 false );
640 }
641
642 // VRML_LAYER m_bot_mask:
643 m_bot_soldermask.Tesselate( &m_holes );
644
646 {
648 &m_bot_soldermask, true, false,
649 GetLayerZ( B_Cu ) - Millimeter2iu( ART_OFFSET / 2.0 ) *
651 0 );
652 }
653 else
654 {
656 GetLayerZ( B_Cu ) - Millimeter2iu( ART_OFFSET / 2.0 ) *
658 false );
659 }
660
661 // VRML_LAYER PTH;
662 m_plated_holes.Tesselate( nullptr, true );
663
665 {
667 &m_plated_holes, false, false,
668 GetLayerZ( F_Cu ) + Millimeter2iu( ART_OFFSET / 2.0 ) *
670 GetLayerZ( B_Cu ) - Millimeter2iu( ART_OFFSET / 2.0 ) *
672 }
673 else
674 {
676 GetLayerZ( F_Cu ) + Millimeter2iu( ART_OFFSET / 2.0 ) *
678 GetLayerZ( B_Cu ) - Millimeter2iu( ART_OFFSET / 2.0 ) *
680 }
681
682 // VRML_LAYER m_top_silk;
683 m_top_silk.Tesselate( &m_holes );
684
686 {
688 true, true, GetLayerZ( F_SilkS ), 0 );
689 }
690 else
691 {
693 GetLayerZ( F_SilkS ), true );
694 }
695
696 // VRML_LAYER m_bot_silk;
697 m_bot_silk.Tesselate( &m_holes );
698
700 {
702 true, false, GetLayerZ( B_SilkS ), 0 );
703 }
704 else
705 {
707 GetLayerZ( B_SilkS ), false );
708 }
709
711 S3D::WriteVRML( aFileName, true, m_OutputPCB.GetRawPtr(), true, true );
712}
713
714
716{
717 int copper_layers = m_board->GetCopperLayerCount();
718
719 // We call it 'layer' thickness, but it's the whole board thickness!
721 double half_thickness = m_brd_thickness / 2;
722
723 // Compute each layer's Z value, more or less like the 3d view
724 for( LSEQ seq = LSET::AllCuMask().Seq(); seq; ++seq )
725 {
726 PCB_LAYER_ID i = *seq;
727
728 if( i < copper_layers )
729 SetLayerZ( i, half_thickness - m_brd_thickness * i / (copper_layers - 1) );
730 else
731 SetLayerZ( i, - half_thickness ); // bottom layer
732 }
733
734 // To avoid rounding interference, we apply an epsilon to each successive layer
735 double epsilon_z = Millimeter2iu( ART_OFFSET ) * m_BoardToVrmlScale;
736 SetLayerZ( B_Paste, -half_thickness - epsilon_z );
737 SetLayerZ( B_Adhes, -half_thickness - epsilon_z );
738 SetLayerZ( B_SilkS, -half_thickness - epsilon_z * 3 );
739 SetLayerZ( B_Mask, -half_thickness - epsilon_z * 2 );
740 SetLayerZ( F_Mask, half_thickness + epsilon_z * 2 );
741 SetLayerZ( F_SilkS, half_thickness + epsilon_z * 3 );
742 SetLayerZ( F_Adhes, half_thickness + epsilon_z );
743 SetLayerZ( F_Paste, half_thickness + epsilon_z );
744 SetLayerZ( Dwgs_User, half_thickness + epsilon_z * 5 );
745 SetLayerZ( Cmts_User, half_thickness + epsilon_z * 6 );
746 SetLayerZ( Eco1_User, half_thickness + epsilon_z * 7 );
747 SetLayerZ( Eco2_User, half_thickness + epsilon_z * 8 );
748 SetLayerZ( Edge_Cuts, 0 );
749}
750
751
752void EXPORTER_PCB_VRML::ExportVrmlPolygonSet( VRML_LAYER* aVlayer, const SHAPE_POLY_SET& aOutlines )
753{
754 // Polygons in SHAPE_POLY_SET must be without hole, i.e. holes must be linked
755 // previously to their main outline.
756 for( int icnt = 0; icnt < aOutlines.OutlineCount(); icnt++ )
757 {
758 const SHAPE_LINE_CHAIN& outline = aOutlines.COutline( icnt );
759
760 int seg = aVlayer->NewContour();
761
762 for( int jj = 0; jj < outline.PointCount(); jj++ )
763 {
764 if( !aVlayer->AddVertex( seg, outline.CPoint( jj ).x * m_BoardToVrmlScale,
765 -outline.CPoint( jj ).y * m_BoardToVrmlScale ) )
766 throw( std::runtime_error( aVlayer->GetError() ) );
767 }
768
769 aVlayer->EnsureWinding( seg, false );
770 }
771}
772
773
775{
777 {
778 wxLogWarning( _( "Board outline is malformed. Run DRC for a full analysis." ) );
779 }
780
781 int seg;
782
783 for( int cnt = 0; cnt < m_pcbOutlines.OutlineCount(); cnt++ )
784 {
785 const SHAPE_LINE_CHAIN& outline = m_pcbOutlines.COutline( cnt );
786
787 seg = m_3D_board.NewContour();
788
789 for( int j = 0; j < outline.PointCount(); j++ )
790 {
791 m_3D_board.AddVertex( seg, (double)outline.CPoint(j).x * m_BoardToVrmlScale,
792 -((double)outline.CPoint(j).y * m_BoardToVrmlScale ) );
793
794 }
795
796 m_3D_board.EnsureWinding( seg, false );
797
798 // Generate board holes from outlines:
799 for( int ii = 0; ii < m_pcbOutlines.HoleCount( cnt ); ii++ )
800 {
801 const SHAPE_LINE_CHAIN& hole = m_pcbOutlines.Hole( cnt, ii );
802
803 seg = m_holes.NewContour();
804
805 if( seg < 0 )
806 {
807 wxLogError( _( "VRML Export Failed: Could not add holes to contours." ) );
808 return;
809 }
810
811 for( int j = 0; j < hole.PointCount(); j++ )
812 {
813 m_holes.AddVertex( seg, (double) hole.CPoint(j).x * m_BoardToVrmlScale,
814 -( (double) hole.CPoint(j).y * m_BoardToVrmlScale ) );
815 }
816
817 m_holes.EnsureWinding( seg, true );
818 }
819 }
820}
821
822
823
825{
826 PCB_LAYER_ID top_layer, bottom_layer;
827
828 for( PCB_TRACK* track : m_board->Tracks() )
829 {
830 if( track->Type() != PCB_VIA_T )
831 continue;
832
833 const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
834
835 via->LayerPair( &top_layer, &bottom_layer );
836
837 // do not render a buried via
838 if( top_layer != F_Cu && bottom_layer != B_Cu )
839 continue;
840
841 // Export all via holes to m_holes
842 double hole_radius = via->GetDrillValue() * m_BoardToVrmlScale / 2.0;
843
844 if( hole_radius <= 0 )
845 continue;
846
847 double x = via->GetStart().x * m_BoardToVrmlScale;
848 double y = via->GetStart().y * m_BoardToVrmlScale;
849
850 // Set the optimal number of segments to approximate a circle.
851 // SetArcParams needs a count max, and the minimal and maximal length
852 // of segments
853 double max_error = ERR_APPROX_MAX_MM;
854
856 max_error /= 2.54; // The board is exported with a size reduced by 2.54
857
858 int nsides = GetArcToSegmentCount( via->GetDrillValue(), Millimeter2iu( max_error ),
859 FULL_CIRCLE );
860
861 double minSegLength = M_PI * 2.0 * hole_radius / nsides;
862 double maxSegLength = minSegLength*2.0;
863
864 m_holes.SetArcParams( nsides*2, minSegLength, maxSegLength );
865 m_plated_holes.SetArcParams( nsides*2, minSegLength, maxSegLength );
866
867 m_holes.AddCircle( x, -y, hole_radius, true, true );
868 m_plated_holes.AddCircle( x, -y, hole_radius, true, false );
869
870 m_holes.ResetArcParams();
871 m_plated_holes.ResetArcParams();
872 }
873}
874
875
877{
878 double hole_drill_w = (double) aPad->GetDrillSize().x * m_BoardToVrmlScale / 2.0;
879 double hole_drill_h = (double) aPad->GetDrillSize().y * m_BoardToVrmlScale / 2.0;
880 double hole_drill = std::min( hole_drill_w, hole_drill_h );
881 double hole_x = aPad->GetPosition().x * m_BoardToVrmlScale;
882 double hole_y = aPad->GetPosition().y * m_BoardToVrmlScale;
883
884 // Export the hole on the edge layer
885 if( hole_drill > 0 )
886 {
887 double max_error = ERR_APPROX_MAX_MM;
888
890 max_error /= 2.54; // The board is exported with a size reduced by 2.54
891
892 int nsides = GetArcToSegmentCount( hole_drill, Millimeter2iu( max_error ),
893 FULL_CIRCLE );
894 double minSegLength = M_PI * hole_drill / nsides;
895 double maxSegLength = minSegLength*2.0;
896
897 m_holes.SetArcParams( nsides*2, minSegLength, maxSegLength );
898 m_plated_holes.SetArcParams( nsides*2, minSegLength, maxSegLength );
899
900 bool pth = false;
901
902 if( ( aPad->GetAttribute() != PAD_ATTRIB::NPTH ) )
903 pth = true;
904
906 {
907 // Oblong hole (slot)
908
909 if( pth )
910 {
911 m_holes.AddSlot( hole_x, -hole_y, hole_drill_w * 2.0 + PLATE_OFFSET,
912 hole_drill_h * 2.0 + PLATE_OFFSET,
913 aPad->GetOrientation().AsDegrees(), true, true );
914
915 m_plated_holes.AddSlot( hole_x, -hole_y,
916 hole_drill_w * 2.0, hole_drill_h * 2.0,
917 aPad->GetOrientation().AsDegrees(), true, false );
918 }
919 else
920 {
921 m_holes.AddSlot( hole_x, -hole_y, hole_drill_w * 2.0, hole_drill_h * 2.0,
922 aPad->GetOrientation().AsDegrees(), true, false );
923
924 }
925 }
926 else
927 {
928 // Drill a round hole
929 if( pth )
930 {
931 m_holes.AddCircle( hole_x, -hole_y, hole_drill + PLATE_OFFSET, true, true );
932 m_plated_holes.AddCircle( hole_x, -hole_y, hole_drill, true, false );
933 }
934 else
935 {
936 m_holes.AddCircle( hole_x, -hole_y, hole_drill, true, false );
937 }
938
939 }
940
941 m_holes.ResetArcParams();
942 m_plated_holes.ResetArcParams();
943 }
944}
945
946
947// From axis/rot to quaternion
948static void build_quat( double x, double y, double z, double a, double q[4] )
949{
950 double sina = sin( a / 2 );
951
952 q[0] = x * sina;
953 q[1] = y * sina;
954 q[2] = z * sina;
955 q[3] = cos( a / 2 );
956}
957
958
959// From quaternion to axis/rot
960static void from_quat( double q[4], double rot[4] )
961{
962 rot[3] = acos( q[3] ) * 2;
963
964 for( int i = 0; i < 3; i++ )
965 rot[i] = q[i] / sin( rot[3] / 2 );
966}
967
968
969// Quaternion composition
970static void compose_quat( double q1[4], double q2[4], double qr[4] )
971{
972 double tmp[4];
973
974 tmp[0] = q2[3] * q1[0] + q2[0] * q1[3] + q2[1] * q1[2] - q2[2] * q1[1];
975 tmp[1] = q2[3] * q1[1] + q2[1] * q1[3] + q2[2] * q1[0] - q2[0] * q1[2];
976 tmp[2] = q2[3] * q1[2] + q2[2] * q1[3] + q2[0] * q1[1] - q2[1] * q1[0];
977 tmp[3] = q2[3] * q1[3] - q2[0] * q1[0] - q2[1] * q1[1] - q2[2] * q1[2];
978
979 qr[0] = tmp[0];
980 qr[1] = tmp[1];
981 qr[2] = tmp[2];
982 qr[3] = tmp[3];
983}
984
985
986void EXPORTER_PCB_VRML::ExportVrmlFootprint( FOOTPRINT* aFootprint, std::ostream* aOutputFile )
987{
988 // Note: if m_UseInlineModelsInBrdfile is false, the 3D footprint shape is copied to
989 // the vrml board file, and aOutputFile is not used (can be nullptr)
990 // if m_UseInlineModelsInBrdfile is true, the 3D footprint shape is copied to
991 // aOutputFile (with the suitable rotation/translation/scale transform, and the vrml board
992 // file contains only the filename of 3D shapes to add to the full vrml scene
993 wxCHECK( aFootprint, /* void */ );
994
995 // Export pad holes
996 for( PAD* pad : aFootprint->Pads() )
998
999 bool isFlipped = aFootprint->GetLayer() == B_Cu;
1000
1001 // Export the object VRML model(s)
1002 auto sM = aFootprint->Models().begin();
1003 auto eM = aFootprint->Models().end();
1004
1005 wxFileName subdir( m_Subdir3DFpModels, wxT( "" ) );
1006
1007 while( sM != eM )
1008 {
1009 if( !sM->m_Show )
1010 {
1011 ++sM;
1012 continue;
1013 }
1014
1015 SGNODE* mod3d = (SGNODE*) m_Cache3Dmodels->Load( sM->m_Filename );
1016
1017 if( nullptr == mod3d )
1018 {
1019 ++sM;
1020 continue;
1021 }
1022
1023 /* Calculate 3D shape rotation:
1024 * this is the rotation parameters, with an additional 180 deg rotation
1025 * for footprints that are flipped
1026 * When flipped, axis rotation is the horizontal axis (X axis)
1027 */
1028 double rotx = -sM->m_Rotation.x;
1029 double roty = -sM->m_Rotation.y;
1030 double rotz = -sM->m_Rotation.z;
1031
1032 if( isFlipped )
1033 {
1034 rotx += 180.0;
1035 roty = -roty;
1036 rotz = -rotz;
1037 }
1038
1039 // Do some quaternion munching
1040 double q1[4], q2[4], rot[4];
1041 build_quat( 1, 0, 0, DEG2RAD( rotx ), q1 );
1042 build_quat( 0, 1, 0, DEG2RAD( roty ), q2 );
1043 compose_quat( q1, q2, q1 );
1044 build_quat( 0, 0, 1, DEG2RAD( rotz ), q2 );
1045 compose_quat( q1, q2, q1 );
1046
1047 // Note here aFootprint->GetOrientation() is in 0.1 degrees, so footprint rotation
1048 // has to be converted to radians
1049 build_quat( 0, 0, 1, aFootprint->GetOrientation().AsRadians(), q2 );
1050 compose_quat( q1, q2, q1 );
1051 from_quat( q1, rot );
1052
1053 double offsetFactor = 1000.0f * IU_PER_MILS / 25.4f;
1054
1055 // adjust 3D shape local offset position
1056 // they are given in mm, so they are converted in board IU.
1057 double offsetx = sM->m_Offset.x * offsetFactor;
1058 double offsety = sM->m_Offset.y * offsetFactor;
1059 double offsetz = sM->m_Offset.z * offsetFactor;
1060
1061 if( isFlipped )
1062 offsetz = -offsetz;
1063 else
1064 offsety = -offsety; // In normal mode, Y axis is reversed in Pcbnew.
1065
1066 RotatePoint( &offsetx, &offsety, aFootprint->GetOrientation() );
1067
1068 SGPOINT trans;
1069 trans.x = ( offsetx + aFootprint->GetPosition().x ) * m_BoardToVrmlScale + m_tx;
1070 trans.y = -( offsety + aFootprint->GetPosition().y) * m_BoardToVrmlScale - m_ty;
1071 trans.z = (offsetz * m_BoardToVrmlScale ) + GetLayerZ( aFootprint->GetLayer() );
1072
1074 {
1075 wxCHECK( aOutputFile, /* void */ );
1076
1077 int old_precision = aOutputFile->precision();
1078 aOutputFile->precision( m_precision );
1079
1080 wxFileName srcFile = m_Cache3Dmodels->GetResolver()->ResolvePath( sM->m_Filename );
1081 wxFileName dstFile;
1082 dstFile.SetPath( m_Subdir3DFpModels );
1083 dstFile.SetName( srcFile.GetName() );
1084 dstFile.SetExt( wxT( "wrl" ) );
1085
1086 // copy the file if necessary
1087 wxDateTime srcModTime = srcFile.GetModificationTime();
1088 wxDateTime destModTime = srcModTime;
1089
1090 destModTime.SetToCurrent();
1091
1092 if( dstFile.FileExists() )
1093 destModTime = dstFile.GetModificationTime();
1094
1095 if( srcModTime != destModTime )
1096 {
1097 wxString fileExt = srcFile.GetExt();
1098 fileExt.LowerCase();
1099
1100 // copy VRML models and use the scenegraph library to
1101 // translate other model types
1102 if( fileExt == wxT( "wrl" ) )
1103 {
1104 if( !wxCopyFile( srcFile.GetFullPath(), dstFile.GetFullPath() ) )
1105 continue;
1106 }
1107 else
1108 {
1109 if( !S3D::WriteVRML( dstFile.GetFullPath().ToUTF8(), true, mod3d, m_ReuseDef,
1110 true ) )
1111 continue;
1112 }
1113 }
1114
1115 (*aOutputFile) << "Transform {\n";
1116
1117 // only write a rotation if it is >= 0.1 deg
1118 if( std::abs( rot[3] ) > 0.0001745 )
1119 {
1120 (*aOutputFile) << " rotation ";
1121 (*aOutputFile) << rot[0] << " " << rot[1] << " " << rot[2] << " " << rot[3] << "\n";
1122 }
1123
1124 (*aOutputFile) << " translation ";
1125 (*aOutputFile) << trans.x << " ";
1126 (*aOutputFile) << trans.y << " ";
1127 (*aOutputFile) << trans.z << "\n";
1128
1129 (*aOutputFile) << " scale ";
1130 (*aOutputFile) << sM->m_Scale.x << " ";
1131 (*aOutputFile) << sM->m_Scale.y << " ";
1132 (*aOutputFile) << sM->m_Scale.z << "\n";
1133
1134 (*aOutputFile) << " children [\n Inline {\n url \"";
1135
1137 {
1138 wxFileName tmp = dstFile;
1139 tmp.SetExt( wxT( "" ) );
1140 tmp.SetName( wxT( "" ) );
1141 tmp.RemoveLastDir();
1142 dstFile.MakeRelativeTo( tmp.GetPath() );
1143 }
1144
1145 wxString fn = dstFile.GetFullPath();
1146 fn.Replace( wxT( "\\" ), wxT( "/" ) );
1147 (*aOutputFile) << TO_UTF8( fn ) << "\"\n } ]\n";
1148 (*aOutputFile) << " }\n";
1149
1150 aOutputFile->precision( old_precision );
1151 }
1152 else
1153 {
1154 IFSG_TRANSFORM* modelShape = new IFSG_TRANSFORM( m_OutputPCB.GetRawPtr() );
1155
1156 // only write a rotation if it is >= 0.1 deg
1157 if( std::abs( rot[3] ) > 0.0001745 )
1158 modelShape->SetRotation( SGVECTOR( rot[0], rot[1], rot[2] ), rot[3] );
1159
1160 modelShape->SetTranslation( trans );
1161 modelShape->SetScale( SGPOINT( sM->m_Scale.x, sM->m_Scale.y, sM->m_Scale.z ) );
1162
1163 if( nullptr == S3D::GetSGNodeParent( mod3d ) )
1164 {
1165 m_components.push_back( mod3d );
1166 modelShape->AddChildNode( mod3d );
1167 }
1168 else
1169 {
1170 modelShape->AddRefNode( mod3d );
1171 }
1172
1173 }
1174
1175 ++sM;
1176 }
1177}
1178
1179
1180
1181bool EXPORTER_PCB_VRML::ExportVRML_File( PROJECT* aProject, wxString *aMessages,
1182 const wxString& aFullFileName, double aMMtoWRMLunit,
1183 bool aExport3DFiles, bool aUseRelativePaths,
1184 const wxString& a3D_Subdir,
1185 double aXRef, double aYRef )
1186{
1187 if( aProject == nullptr )
1188 {
1189 if( aMessages )
1190 *aMessages = _( "No project when exporting the VRML file");
1191
1192 return false;
1193 }
1194
1195 SetScale( aMMtoWRMLunit );
1196 m_UseInlineModelsInBrdfile = aExport3DFiles;
1197 m_Subdir3DFpModels = a3D_Subdir;
1198 m_UseRelPathIn3DModelFilename = aUseRelativePaths;
1199 m_Cache3Dmodels = aProject->Get3DCacheManager();
1200
1201 // When 3D models are separate files, for historical reasons the VRML unit
1202 // is expected to be 0.1 inch (2.54mm) instead of 1mm, so we adjust the m_BoardToVrmlScale
1203 // to match the VRML scale of these external files.
1204 // Otherwise we use 1mm as VRML unit
1206 {
1207 m_BoardToVrmlScale = MM_PER_IU / 2.54;
1208 SetOffset( -aXRef / 2.54, aYRef / 2.54 );
1209 }
1210 else
1211 {
1212 m_BoardToVrmlScale = MM_PER_IU;
1213 SetOffset( -aXRef, aYRef );
1214 }
1215
1216 bool success = true;
1217
1218 try
1219 {
1220 // Preliminary computation: the z value for each layer
1222
1223 // board edges and cutouts
1225
1226 // Draw solder mask layer (negative layer)
1230
1232 {
1233 // Copy fp 3D models in a folder, and link these files in
1234 // the board .vrml file
1235 ExportFp3DModelsAsLinkedFile( aFullFileName );
1236 }
1237 else
1238 {
1239 // merge footprints in the .vrml board file
1240 for( FOOTPRINT* footprint : m_board->Footprints() )
1241 ExportVrmlFootprint( footprint, nullptr );
1242
1243 // write out the board and all layers
1244 writeLayers( TO_UTF8( aFullFileName ), nullptr );
1245 }
1246 }
1247 catch( const std::exception& e )
1248 {
1249 if( aMessages )
1250 *aMessages << _( "VRML Export Failed:\n" ) << FROM_UTF8( e.what() );
1251
1252 success = false;
1253 }
1254
1255 return success;
1256}
1257
1258bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, double aMMtoWRMLunit,
1259 bool aExport3DFiles, bool aUseRelativePaths,
1260 const wxString& a3D_Subdir,
1261 double aXRef, double aYRef )
1262{
1263 bool success;
1264 wxString msgs;
1265 EXPORTER_VRML model3d( GetBoard() );
1266
1267 success = model3d.ExportVRML_File( &Prj(), &msgs, aFullFileName, aMMtoWRMLunit,
1268 aExport3DFiles, aUseRelativePaths,
1269 a3D_Subdir, aXRef, aYRef );
1270
1271 if( !msgs.IsEmpty() )
1272 wxMessageBox( msgs );
1273
1274 return success;
1275}
1276
1277
1278void EXPORTER_PCB_VRML::ExportFp3DModelsAsLinkedFile( const wxString& aFullFileName )
1279{
1280 // check if the 3D Subdir exists - create if not
1281 wxFileName subdir( m_Subdir3DFpModels, wxT( "" ) );
1282
1283 if( ! subdir.DirExists() )
1284 {
1285 if( !wxDir::Make( subdir.GetFullPath() ) )
1286 throw( std::runtime_error( "Could not create 3D model subdirectory" ) );
1287 }
1288
1289 OPEN_OSTREAM( output_file, TO_UTF8( aFullFileName ) );
1290
1291 if( output_file.fail() )
1292 {
1293 std::ostringstream ostr;
1294 ostr << "Could not open file '" << TO_UTF8( aFullFileName ) << "'";
1295 throw( std::runtime_error( ostr.str().c_str() ) );
1296 }
1297
1298 output_file.imbue( std::locale::classic() );
1299
1300 // Begin with the usual VRML boilerplate
1301 wxString fn = aFullFileName;
1302 fn.Replace( wxT( "\\" ) , wxT( "/" ) );
1303 output_file << "#VRML V2.0 utf8\n";
1304 output_file << "WorldInfo {\n";
1305 output_file << " title \"" << TO_UTF8( fn ) << " - Generated by Pcbnew\"\n";
1306 output_file << "}\n";
1307 output_file << "Transform {\n";
1308 output_file << " scale " << std::setprecision( m_precision );
1309 output_file << m_WorldScale << " ";
1310 output_file << m_WorldScale << " ";
1311 output_file << m_WorldScale << "\n";
1312 output_file << " children [\n";
1313
1314 // Export footprints
1315 for( FOOTPRINT* footprint : m_board->Footprints() )
1316 ExportVrmlFootprint( footprint, &output_file );
1317
1318 // write out the board and all layers
1319 writeLayers( TO_UTF8( aFullFileName ), &output_file );
1320
1321 // Close the outer 'transform' node
1322 output_file << "]\n}\n";
1323
1324 CLOSE_STREAM( output_file );
1325}
1326
1328{
1329 if( colorIdx == -1 )
1330 colorIdx = VRML_COLOR_PCB;
1331 else if( colorIdx == VRML_COLOR_LAST )
1332 return nullptr;
1333
1334 if( m_sgmaterial[colorIdx] )
1335 return m_sgmaterial[colorIdx];
1336
1337 IFSG_APPEARANCE vcolor( (SGNODE*) nullptr );
1338 VRML_COLOR* cp = &vrml_colors_list[colorIdx];
1339
1340 vcolor.SetSpecular( cp->spec_red, cp->spec_grn, cp->spec_blu );
1341 vcolor.SetDiffuse( cp->diffuse_red, cp->diffuse_grn, cp->diffuse_blu );
1342 vcolor.SetShininess( cp->shiny );
1343 // NOTE: XXX - replace with a better equation; using this definition
1344 // of ambient will not yield the best results
1345 vcolor.SetAmbient( cp->ambient, cp->ambient, cp->ambient );
1346 vcolor.SetTransparency( cp->transp );
1347
1348 m_sgmaterial[colorIdx] = vcolor.GetRawPtr();
1349
1350 return m_sgmaterial[colorIdx];
1351}
1352
1353
1355 VRML_LAYER* layer, double top_z, bool aTopPlane )
1356{
1357 std::vector< double > vertices;
1358 std::vector< int > idxPlane;
1359
1360 if( !( *layer ).Get2DTriangles( vertices, idxPlane, top_z, aTopPlane ) )
1361 {
1362 return;
1363 }
1364
1365 if( ( idxPlane.size() % 3 ) )
1366 {
1367 throw( std::runtime_error( "[BUG] index lists are not a multiple of 3 (not a triangle "
1368 "list)" ) );
1369 }
1370
1371 std::vector< SGPOINT > vlist;
1372 size_t nvert = vertices.size() / 3;
1373 size_t j = 0;
1374
1375 for( size_t i = 0; i < nvert; ++i, j+= 3 )
1376 vlist.emplace_back( vertices[j], vertices[j+1], vertices[j+2] );
1377
1378 // create the intermediate scenegraph
1379 IFSG_TRANSFORM tx0( PcbOutput.GetRawPtr() ); // tx0 = Transform for this outline
1380 IFSG_SHAPE shape( tx0 ); // shape will hold (a) all vertices and (b) a local list of normals
1381 IFSG_FACESET face( shape ); // this face shall represent the top and bottom planes
1382 IFSG_COORDS cp( face ); // coordinates for all faces
1383 cp.SetCoordsList( nvert, &vlist[0] );
1384 IFSG_COORDINDEX coordIdx( face ); // coordinate indices for top and bottom planes only
1385 coordIdx.SetIndices( idxPlane.size(), &idxPlane[0] );
1386 IFSG_NORMALS norms( face ); // normals for the top and bottom planes
1387
1388 // set the normals
1389 if( aTopPlane )
1390 {
1391 for( size_t i = 0; i < nvert; ++i )
1392 norms.AddNormal( 0.0, 0.0, 1.0 );
1393 }
1394 else
1395 {
1396 for( size_t i = 0; i < nvert; ++i )
1397 norms.AddNormal( 0.0, 0.0, -1.0 );
1398 }
1399
1400 // assign a color from the palette
1401 SGNODE* modelColor = getSGColor( colorID );
1402
1403 if( nullptr != modelColor )
1404 {
1405 if( nullptr == S3D::GetSGNodeParent( modelColor ) )
1406 shape.AddChildNode( modelColor );
1407 else
1408 shape.AddRefNode( modelColor );
1409 }
1410}
1411
1412
1414 VRML_LAYER* layer, double top_z, double bottom_z )
1415{
1416 std::vector< double > vertices;
1417 std::vector< int > idxPlane;
1418 std::vector< int > idxSide;
1419
1420 if( top_z < bottom_z )
1421 {
1422 double tmp = top_z;
1423 top_z = bottom_z;
1424 bottom_z = tmp;
1425 }
1426
1427 if( !( *layer ).Get3DTriangles( vertices, idxPlane, idxSide, top_z, bottom_z )
1428 || idxPlane.empty() || idxSide.empty() )
1429 {
1430 return;
1431 }
1432
1433 if( ( idxPlane.size() % 3 ) || ( idxSide.size() % 3 ) )
1434 {
1435 throw( std::runtime_error( "[BUG] index lists are not a multiple of 3 (not a "
1436 "triangle list)" ) );
1437 }
1438
1439 std::vector< SGPOINT > vlist;
1440 size_t nvert = vertices.size() / 3;
1441 size_t j = 0;
1442
1443 for( size_t i = 0; i < nvert; ++i, j+= 3 )
1444 vlist.emplace_back( vertices[j], vertices[j+1], vertices[j+2] );
1445
1446 // create the intermediate scenegraph
1447 IFSG_TRANSFORM tx0( PcbOutput.GetRawPtr() ); // tx0 = Transform for this outline
1448 IFSG_SHAPE shape( tx0 ); // shape will hold (a) all vertices and (b) a local list of normals
1449 IFSG_FACESET face( shape ); // this face shall represent the top and bottom planes
1450 IFSG_COORDS cp( face ); // coordinates for all faces
1451 cp.SetCoordsList( nvert, &vlist[0] );
1452 IFSG_COORDINDEX coordIdx( face ); // coordinate indices for top and bottom planes only
1453 coordIdx.SetIndices( idxPlane.size(), &idxPlane[0] );
1454 IFSG_NORMALS norms( face ); // normals for the top and bottom planes
1455
1456 // number of TOP (and bottom) vertices
1457 j = nvert / 2;
1458
1459 // set the TOP normals
1460 for( size_t i = 0; i < j; ++i )
1461 norms.AddNormal( 0.0, 0.0, 1.0 );
1462
1463 // set the BOTTOM normals
1464 for( size_t i = 0; i < j; ++i )
1465 norms.AddNormal( 0.0, 0.0, -1.0 );
1466
1467 // assign a color from the palette
1468 SGNODE* modelColor = getSGColor( colorID );
1469
1470 if( nullptr != modelColor )
1471 {
1472 if( nullptr == S3D::GetSGNodeParent( modelColor ) )
1473 shape.AddChildNode( modelColor );
1474 else
1475 shape.AddRefNode( modelColor );
1476 }
1477
1478 // create a second shape describing the vertical walls of the extrusion
1479 // using per-vertex-per-face-normals
1480 shape.NewNode( tx0 );
1481 shape.AddRefNode( modelColor ); // set the color to be the same as the top/bottom
1482 face.NewNode( shape );
1483 cp.NewNode( face ); // new vertex list
1484 norms.NewNode( face ); // new normals list
1485 coordIdx.NewNode( face ); // new index list
1486
1487 // populate the new per-face vertex list and its indices and normals
1488 std::vector< int >::iterator sI = idxSide.begin();
1489 std::vector< int >::iterator eI = idxSide.end();
1490
1491 size_t sidx = 0; // index to the new coord set
1492 SGPOINT p1, p2, p3;
1493 SGVECTOR vnorm;
1494
1495 while( sI != eI )
1496 {
1497 p1 = vlist[*sI];
1498 cp.AddCoord( p1 );
1499 ++sI;
1500
1501 p2 = vlist[*sI];
1502 cp.AddCoord( p2 );
1503 ++sI;
1504
1505 p3 = vlist[*sI];
1506 cp.AddCoord( p3 );
1507 ++sI;
1508
1509 vnorm.SetVector( S3D::CalcTriNorm( p1, p2, p3 ) );
1510 norms.AddNormal( vnorm );
1511 norms.AddNormal( vnorm );
1512 norms.AddNormal( vnorm );
1513
1514 coordIdx.AddIndex( (int)sidx );
1515 ++sidx;
1516 coordIdx.AddIndex( (int)sidx );
1517 ++sidx;
1518 coordIdx.AddIndex( (int)sidx );
1519 ++sidx;
1520 }
1521}
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
@ 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()
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition: board_item.h:170
Manage one layer needed to make a physical board.
Definition: board_stackup.h:90
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:240
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:2240
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:1939
FOOTPRINTS & Footprints()
Definition: board.h:282
int GetCopperLayerCount() const
Definition: board.cpp:486
TRACKS & Tracks()
Definition: board.h:279
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:615
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)
Determines the full path of the given file name.
EDA_ANGLE GetOrientation() const
Definition: footprint.h:196
PADS & Pads()
Definition: footprint.h:175
std::vector< FP_3DMODEL > & Models()
Definition: footprint.h:189
VECTOR2I GetPosition() const override
Definition: footprint.h:193
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.
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition: layer_ids.h:490
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:360
const VECTOR2I & GetDrillSize() const
Definition: pad.h:249
PAD_ATTRIB GetAttribute() const
Definition: pad.h:377
VECTOR2I GetPosition() const override
Definition: pad.h:184
EDA_ANGLE GetOrientation() const
Return the rotation angle of the pad.
Definition: pad.h:347
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
SCENEGRAPH * Load(const wxString &aModelFile)
Attempt to load the scene data for a model.
Definition: 3d_cache.cpp:285
FILENAME_RESOLVER * GetResolver() noexcept
Definition: 3d_cache.cpp:606
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:421
#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:412
@ NPTH
like PAD_PTH, but not plated
@ PAD_DRILL_SHAPE_OBLONG
Definition: pad_shapes.h:71
#define IU_PER_MILS
Definition: plotter.cpp:129
#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.
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
static constexpr int Millimeter2iu(double mm)
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:104