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