You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1587 lines
53 KiB

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