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