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