diff --git a/include/wxPcbStruct.h b/include/wxPcbStruct.h index 4b01014e73..8ac0d3b20d 100644 --- a/include/wxPcbStruct.h +++ b/include/wxPcbStruct.h @@ -934,12 +934,15 @@ public: * @param aMMtoWRMLunit = the VRML scaling factor: * 1.0 to export in mm. 0.001 for meters * @param aExport3DFiles = true to copy 3D shapes in the subir a3D_Subdir - * @param a3D_Subdir = sub directory where 3D shapes files are copied - * used only when aExport3DFiles == true + * @param aUseRelativePaths set to true to use relative paths instead of absolute paths + * in the board VRML file URLs. + * @param a3D_Subdir = sub directory where 3D shapes files are copied. This is only used + * when aExport3DFiles == true * @return true if Ok. */ bool ExportVRML_File( const wxString & aFullFileName, double aMMtoWRMLunit, - bool aExport3DFiles, const wxString & a3D_Subdir ); + bool aExport3DFiles, bool aUseRelativePaths, + const wxString & a3D_Subdir ); /** * Function ExportToIDF3 diff --git a/pcbnew/dialogs/dialog_export_vrml.cpp b/pcbnew/dialogs/dialog_export_vrml.cpp index b47b9011d6..75598b3ecc 100644 --- a/pcbnew/dialogs/dialog_export_vrml.cpp +++ b/pcbnew/dialogs/dialog_export_vrml.cpp @@ -38,19 +38,19 @@ */ #include // the wxFormBuilder header file -#define OPTKEY_OUTPUT_UNIT wxT("VrmlExportUnit" ) -#define OPTKEY_3DFILES_OPT wxT("VrmlExport3DShapeFilesOpt" ) +#define OPTKEY_OUTPUT_UNIT wxT( "VrmlExportUnit" ) +#define OPTKEY_3DFILES_OPT wxT( "VrmlExportCopyFiles" ) +#define OPTKEY_USE_ABS_PATHS wxT( "VrmlUseRelativePaths" ) + class DIALOG_EXPORT_3DFILE : public DIALOG_EXPORT_3DFILE_BASE { private: PCB_EDIT_FRAME* m_parent; - wxConfigBase* m_config; - int m_unitsOpt; // to remember last option - int m_3DFilesOpt; // to remember last option - - void OnCancelClick( wxCommandEvent& event ){ EndModal( wxID_CANCEL ); } - void OnOkClick( wxCommandEvent& event ){ EndModal( wxID_OK ); } + wxConfigBase* m_config; + int m_unitsOpt; // Remember last units option + bool m_copy3DFilesOpt; // Remember last copy model files option + bool m_useRelativePathsOpt; // Remember last use absolut paths option public: DIALOG_EXPORT_3DFILE( PCB_EDIT_FRAME* parent ) : @@ -58,30 +58,42 @@ public: { m_parent = parent; m_config = Kiface().KifaceSettings(); - SetFocus(); + m_filePicker->SetFocus(); m_config->Read( OPTKEY_OUTPUT_UNIT, &m_unitsOpt ); - m_config->Read( OPTKEY_3DFILES_OPT, &m_3DFilesOpt ); - m_rbSelectUnits->SetSelection(m_unitsOpt); - m_rb3DFilesOption->SetSelection(m_3DFilesOpt); + m_config->Read( OPTKEY_3DFILES_OPT, &m_copy3DFilesOpt ); + m_config->Read( OPTKEY_USE_ABS_PATHS, &m_useRelativePathsOpt ); + m_rbSelectUnits->SetSelection( m_unitsOpt ); + m_cbCopyFiles->SetValue( m_copy3DFilesOpt ); + m_cbUseAbsolutePaths->SetValue( m_useRelativePathsOpt ); + wxButton* okButton = (wxButton*) FindWindowByLabel( wxT( "OK" ) ); + + if( okButton ) + SetDefaultItem( okButton ); + GetSizer()->SetSizeHints( this ); Centre(); + + Connect( ID_USE_ABS_PATH, wxEVT_UPDATE_UI, + wxUpdateUIEventHandler( DIALOG_EXPORT_3DFILE::OnUpdateUseAbsolutPath ) ); } + ~DIALOG_EXPORT_3DFILE() { - m_unitsOpt = GetUnits( ); - m_3DFilesOpt = Get3DFilesOption( ); + m_unitsOpt = GetUnits(); + m_copy3DFilesOpt = GetCopyFilesOption(); m_config->Write( OPTKEY_OUTPUT_UNIT, m_unitsOpt ); - m_config->Write( OPTKEY_3DFILES_OPT, m_3DFilesOpt ); + m_config->Write( OPTKEY_3DFILES_OPT, m_copy3DFilesOpt ); + m_config->Write( OPTKEY_USE_ABS_PATHS, m_useRelativePathsOpt ); }; void SetSubdir( const wxString & aDir ) { - m_SubdirNameCtrl->SetValue( aDir); + m_SubdirNameCtrl->SetValue( aDir ); } - wxString GetSubdir( ) + wxString GetSubdir() { - return m_SubdirNameCtrl->GetValue( ); + return m_SubdirNameCtrl->GetValue(); } wxFilePickerCtrl* FilePicker() @@ -89,26 +101,43 @@ public: return m_filePicker; } - int GetUnits( ) + int GetUnits() { return m_unitsOpt = m_rbSelectUnits->GetSelection(); } - int Get3DFilesOption( ) + bool GetCopyFilesOption() { - return m_3DFilesOpt = m_rb3DFilesOption->GetSelection(); + return m_copy3DFilesOpt = m_cbCopyFiles->GetValue(); + } + + bool GetUseAbsolutePathsOption() + { + return m_useRelativePathsOpt = m_cbUseAbsolutePaths->GetValue(); + } + + void OnUpdateUseAbsolutPath( wxUpdateUIEvent& event ) + { + // Making path relative or absolute has no meaning when VRML files are not copied. + event.Enable( m_cbCopyFiles->GetValue() ); } }; -/** - * Function OnExportVRML - * will export the current BOARD to a VRML file. - */ void PCB_EDIT_FRAME::OnExportVRML( wxCommandEvent& event ) { wxFileName fn; - static wxString subDirFor3Dshapes = wxT("shapes3D"); + wxString projectPath; + + if( !wxGetEnv( wxT( "KIPRJMOD" ), &projectPath ) ) + projectPath = wxFileName::GetCwd(); + + static wxString subDirFor3Dshapes; + + if( subDirFor3Dshapes.IsEmpty() ) + { + subDirFor3Dshapes = wxT( "shapes3D" ); + } // The general VRML scale factor // Assuming the VRML default unit is the mm @@ -116,9 +145,8 @@ void PCB_EDIT_FRAME::OnExportVRML( wxCommandEvent& event ) double scaleList[3] = { 1.0/25.4, 1, 0.001 }; // Build default file name - wxString ext = wxT( "wrl" ); fn = GetBoard()->GetFileName(); - fn.SetExt( ext ); + fn.SetExt( wxT( "wrl" ) ); DIALOG_EXPORT_3DFILE dlg( this ); dlg.FilePicker()->SetPath( fn.GetFullPath() ); @@ -127,18 +155,26 @@ void PCB_EDIT_FRAME::OnExportVRML( wxCommandEvent& event ) if( dlg.ShowModal() != wxID_OK ) return; - double scale = scaleList[dlg.GetUnits( )]; // final scale export - bool export3DFiles = dlg.Get3DFilesOption( ) == 0; - + double scale = scaleList[dlg.GetUnits()]; // final scale export + bool export3DFiles = dlg.GetCopyFilesOption(); + bool useRelativePaths = dlg.GetUseAbsolutePathsOption(); + wxString fullFilename = dlg.FilePicker()->GetPath(); + wxFileName modelPath = fullFilename; wxBusyCursor dummy; - wxString fullFilename = dlg.FilePicker()->GetPath(); + modelPath.AppendDir( dlg.GetSubdir() ); subDirFor3Dshapes = dlg.GetSubdir(); - if( export3DFiles && !wxDirExists( subDirFor3Dshapes ) ) - wxMkdir( subDirFor3Dshapes ); + wxLogDebug( wxT( "Exporting enabled=%d to %s." ), + export3DFiles, GetChars( subDirFor3Dshapes ) ); - if( ! ExportVRML_File( fullFilename, scale, export3DFiles, subDirFor3Dshapes ) ) + if( export3DFiles && !modelPath.DirExists() ) + { + modelPath.Mkdir(); + } + + if( !ExportVRML_File( fullFilename, scale, export3DFiles, useRelativePaths, + modelPath.GetPath() ) ) { wxString msg = _( "Unable to create " ) + fullFilename; wxMessageBox( msg ); diff --git a/pcbnew/dialogs/dialog_export_vrml_base.cpp b/pcbnew/dialogs/dialog_export_vrml_base.cpp index cbcbeb7330..c9bf5afbbd 100644 --- a/pcbnew/dialogs/dialog_export_vrml_base.cpp +++ b/pcbnew/dialogs/dialog_export_vrml_base.cpp @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Oct 8 2012) +// C++ code generated with wxFormBuilder (version Nov 6 2013) // http://www.wxformbuilder.org/ // // PLEASE DO "NOT" EDIT THIS FILE! @@ -19,15 +19,14 @@ DIALOG_EXPORT_3DFILE_BASE::DIALOG_EXPORT_3DFILE_BASE( wxWindow* parent, wxWindow wxBoxSizer* bUpperSizer; bUpperSizer = new wxBoxSizer( wxVERTICAL ); - bUpperSizer->SetMinSize( wxSize( 450,-1 ) ); - m_staticText1 = new wxStaticText( this, wxID_ANY, _("Vrml main file filename:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText1 = new wxStaticText( this, wxID_ANY, _("File Name:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText1->Wrap( -1 ); bUpperSizer->Add( m_staticText1, 0, wxTOP|wxRIGHT|wxLEFT, 5 ); m_filePicker = new wxFilePickerCtrl( this, wxID_ANY, wxEmptyString, _("Save VRML Board File"), wxT("*.wrl"), wxDefaultPosition, wxDefaultSize, wxFLP_SAVE|wxFLP_USE_TEXTCTRL ); bUpperSizer->Add( m_filePicker, 0, wxBOTTOM|wxRIGHT|wxLEFT|wxEXPAND, 5 ); - m_staticText3 = new wxStaticText( this, wxID_ANY, _("Vrml 3D footprints shapes subdir:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText3 = new wxStaticText( this, wxID_ANY, _("Footprint 3D model path:"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText3->Wrap( -1 ); bUpperSizer->Add( m_staticText3, 0, wxTOP|wxRIGHT|wxLEFT, 5 ); @@ -36,25 +35,33 @@ DIALOG_EXPORT_3DFILE_BASE::DIALOG_EXPORT_3DFILE_BASE( wxWindow* parent, wxWindow bUpperSizer->Add( m_SubdirNameCtrl, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); - bSizer1->Add( bUpperSizer, 0, wxEXPAND, 5 ); + bSizer1->Add( bUpperSizer, 0, wxALL|wxEXPAND, 5 ); wxBoxSizer* bLowerSizer; bLowerSizer = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer4; + bSizer4 = new wxBoxSizer( wxVERTICAL ); + + m_cbCopyFiles = new wxCheckBox( this, wxID_ANY, _("Copy 3D model files to 3D model path"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cbCopyFiles->SetValue(true); + bSizer4->Add( m_cbCopyFiles, 0, wxALL, 5 ); + + m_cbUseAbsolutePaths = new wxCheckBox( this, ID_USE_ABS_PATH, _("Use absolute paths to model files in board VRML file"), wxDefaultPosition, wxDefaultSize, 0 ); + m_cbUseAbsolutePaths->SetValue(true); + bSizer4->Add( m_cbUseAbsolutePaths, 0, wxALL, 5 ); + + + bLowerSizer->Add( bSizer4, 3, wxEXPAND, 5 ); + wxString m_rbSelectUnitsChoices[] = { _("Inch"), _("mm"), _("Meter") }; int m_rbSelectUnitsNChoices = sizeof( m_rbSelectUnitsChoices ) / sizeof( wxString ); m_rbSelectUnits = new wxRadioBox( this, wxID_ANY, _("Units:"), wxDefaultPosition, wxDefaultSize, m_rbSelectUnitsNChoices, m_rbSelectUnitsChoices, 1, wxRA_SPECIFY_COLS ); m_rbSelectUnits->SetSelection( 0 ); bLowerSizer->Add( m_rbSelectUnits, 1, wxALL|wxEXPAND, 5 ); - wxString m_rb3DFilesOptionChoices[] = { _("Copy 3D Shapes Files in Subdir"), _("Use Absolute Path in Vrml File ") }; - int m_rb3DFilesOptionNChoices = sizeof( m_rb3DFilesOptionChoices ) / sizeof( wxString ); - m_rb3DFilesOption = new wxRadioBox( this, wxID_ANY, _("3D Shapes Files Option:"), wxDefaultPosition, wxDefaultSize, m_rb3DFilesOptionNChoices, m_rb3DFilesOptionChoices, 1, wxRA_SPECIFY_COLS ); - m_rb3DFilesOption->SetSelection( 1 ); - bLowerSizer->Add( m_rb3DFilesOption, 1, wxALL|wxEXPAND, 5 ); - - bSizer1->Add( bLowerSizer, 1, wxEXPAND, 5 ); + bSizer1->Add( bLowerSizer, 1, wxBOTTOM|wxEXPAND|wxLEFT|wxRIGHT, 5 ); m_staticline1 = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); bSizer1->Add( m_staticline1, 0, wxEXPAND|wxRIGHT|wxLEFT, 5 ); @@ -71,6 +78,7 @@ DIALOG_EXPORT_3DFILE_BASE::DIALOG_EXPORT_3DFILE_BASE( wxWindow* parent, wxWindow this->SetSizer( bSizer1 ); this->Layout(); + bSizer1->Fit( this ); // Connect Events m_sdbSizer1Cancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_EXPORT_3DFILE_BASE::OnCancelClick ), NULL, this ); diff --git a/pcbnew/dialogs/dialog_export_vrml_base.fbp b/pcbnew/dialogs/dialog_export_vrml_base.fbp index 44715a9766..39c1b430d4 100644 --- a/pcbnew/dialogs/dialog_export_vrml_base.fbp +++ b/pcbnew/dialogs/dialog_export_vrml_base.fbp @@ -20,8 +20,10 @@ . 1 + 1 1 1 + UI 1 0 @@ -42,10 +44,10 @@ DIALOG_EXPORT_3DFILE_BASE - 370,252 + -1,-1 wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER DIALOG_SHIM; dialog_shim.h - Vrml Board Export Options: + VRML Export Options @@ -93,10 +95,10 @@ none 5 - wxEXPAND + wxALL|wxEXPAND 0 - 450,-1 + -1,-1 bUpperSizer wxVERTICAL none @@ -132,7 +134,7 @@ 0 0 wxID_ANY - Vrml main file filename: + File Name: 0 @@ -304,7 +306,7 @@ 0 0 wxID_ANY - Vrml 3D footprints shapes subdir: + Footprint 3D model path: 0 @@ -450,13 +452,200 @@ 5 - wxEXPAND + wxBOTTOM|wxEXPAND|wxLEFT|wxRIGHT 1 bLowerSizer wxHORIZONTAL none + + 5 + wxEXPAND + 3 + + + bSizer4 + wxVERTICAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Copy 3D model files to 3D model path + + 0 + + + 0 + + 1 + m_cbCopyFiles + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + ID_USE_ABS_PATH + Use absolute paths to model files in board VRML file + + 0 + + + 0 + + 1 + m_cbUseAbsolutePaths + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 wxALL|wxEXPAND @@ -547,96 +736,6 @@ - - 5 - wxALL|wxEXPAND - 1 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - "Copy 3D Shapes Files in Subdir" "Use Absolute Path in Vrml File " - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - - 0 - 0 - wxID_ANY - 3D Shapes Files Option: - 1 - - 0 - - - 0 - - 1 - m_rb3DFilesOption - 1 - - - protected - 1 - - Resizable - 1 - 1 - - wxRA_SPECIFY_COLS - - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pcbnew/dialogs/dialog_export_vrml_base.h b/pcbnew/dialogs/dialog_export_vrml_base.h index aae593e79a..e1af897a06 100644 --- a/pcbnew/dialogs/dialog_export_vrml_base.h +++ b/pcbnew/dialogs/dialog_export_vrml_base.h @@ -1,5 +1,5 @@ /////////////////////////////////////////////////////////////////////////// -// C++ code generated with wxFormBuilder (version Oct 8 2012) +// C++ code generated with wxFormBuilder (version Nov 6 2013) // http://www.wxformbuilder.org/ // // PLEASE DO "NOT" EDIT THIS FILE! @@ -23,6 +23,7 @@ class DIALOG_SHIM; #include #include #include +#include #include #include #include @@ -38,12 +39,18 @@ class DIALOG_EXPORT_3DFILE_BASE : public DIALOG_SHIM private: protected: + enum + { + ID_USE_ABS_PATH = 1000 + }; + wxStaticText* m_staticText1; wxFilePickerCtrl* m_filePicker; wxStaticText* m_staticText3; wxTextCtrl* m_SubdirNameCtrl; + wxCheckBox* m_cbCopyFiles; + wxCheckBox* m_cbUseAbsolutePaths; wxRadioBox* m_rbSelectUnits; - wxRadioBox* m_rb3DFilesOption; wxStaticLine* m_staticline1; wxStdDialogButtonSizer* m_sdbSizer1; wxButton* m_sdbSizer1OK; @@ -56,7 +63,7 @@ class DIALOG_EXPORT_3DFILE_BASE : public DIALOG_SHIM public: - DIALOG_EXPORT_3DFILE_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Vrml Board Export Options:"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 370,252 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); + DIALOG_EXPORT_3DFILE_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("VRML Export Options"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); ~DIALOG_EXPORT_3DFILE_BASE(); }; diff --git a/pcbnew/exporters/export_vrml.cpp b/pcbnew/exporters/export_vrml.cpp index 7dda477532..705962e6ca 100644 --- a/pcbnew/exporters/export_vrml.cpp +++ b/pcbnew/exporters/export_vrml.cpp @@ -59,11 +59,6 @@ // offset for art layers, mm (silk, paste, etc) #define ART_OFFSET 0.025 -/* helper function: - * some characters cannot be used in names, - * this function change them to "_" - */ -static void ChangeIllegalCharacters( wxString& aFileName, bool aDirSepIsIllegal ); struct VRML_COLOR { @@ -122,6 +117,7 @@ struct VRML_COLOR } }; + enum VRML_COLOR_INDEX { VRML_COLOR_PCB = 0, @@ -139,10 +135,9 @@ private: VRML_COLOR colors[VRML_COLOR_LAST]; int iMaxSeg; // max. sides to a small circle - double arcMinLen, arcMaxLen; // min and max lengths of an arc chord + double arcMinLen, arcMaxLen; // min and max lengths of an arc chord public: - VRML_LAYER holes; VRML_LAYER board; VRML_LAYER top_copper; @@ -185,7 +180,7 @@ public: colors[ VRML_COLOR_SILK ] = VRML_COLOR( .9, .9, .9, .9, .9, .9, 0, 0, 0, 1, 0, 0.2 ); // pad silver - colors[ VRML_COLOR_TIN ] = VRML_COLOR( .749, .756, .761, .749, .756, .761, + colors[ VRML_COLOR_TIN ] = VRML_COLOR( .749, .756, .761, .749, .756, .761, 0, 0, 0, 0.8, 0, 0.8 ); precision = 5; @@ -268,6 +263,7 @@ public: // static var. for dealing with text static MODEL_VRML* model_vrml; + // select the VRML layer object to draw on; return true if // a layer has been selected. static bool GetLayer( MODEL_VRML& aModel, LAYER_NUM layer, VRML_LAYER** vlayer ) @@ -299,8 +295,8 @@ static bool GetLayer( MODEL_VRML& aModel, LAYER_NUM layer, VRML_LAYER** vlayer ) static void write_triangle_bag( std::ofstream& output_file, VRML_COLOR& color, - VRML_LAYER* layer, bool plane, bool top, - double top_z, double bottom_z, int aPrecision ) + VRML_LAYER* layer, bool plane, bool top, + double top_z, double bottom_z, int aPrecision ) { /* A lot of nodes are not required, but blender sometimes chokes * without them */ @@ -405,27 +401,26 @@ static void write_layers( MODEL_VRML& aModel, std::ofstream& output_file, BOARD* double brdz = aModel.board_thickness / 2.0 - ( Millimeter2iu( ART_OFFSET / 2.0 ) ) * aModel.scale; write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_PCB ), - &aModel.board, false, false, brdz, -brdz, aModel.precision ); + &aModel.board, false, false, brdz, -brdz, aModel.precision ); // VRML_LAYER top_copper; aModel.top_copper.Tesselate( &aModel.holes ); write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_TRACK ), - &aModel.top_copper, true, true, - aModel.GetLayerZ( F_Cu ), 0, aModel.precision ); + &aModel.top_copper, true, true, + aModel.GetLayerZ( F_Cu ), 0, aModel.precision ); // VRML_LAYER top_tin; aModel.top_tin.Tesselate( &aModel.holes ); write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_TIN ), - &aModel.top_tin, true, true, - aModel.GetLayerZ( F_Cu ) - + Millimeter2iu( ART_OFFSET / 2.0 ) * aModel.scale, - 0, aModel.precision ); + &aModel.top_tin, true, true, + aModel.GetLayerZ( F_Cu ) + Millimeter2iu( ART_OFFSET / 2.0 ) * aModel.scale, + 0, aModel.precision ); // VRML_LAYER bot_copper; aModel.bot_copper.Tesselate( &aModel.holes ); write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_TRACK ), - &aModel.bot_copper, true, false, - aModel.GetLayerZ( B_Cu ), 0, aModel.precision ); + &aModel.bot_copper, true, false, + aModel.GetLayerZ( B_Cu ), 0, aModel.precision ); // VRML_LAYER bot_tin; aModel.bot_tin.Tesselate( &aModel.holes ); @@ -439,23 +434,19 @@ static void write_layers( MODEL_VRML& aModel, std::ofstream& output_file, BOARD* aModel.plated_holes.Tesselate( NULL, true ); write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_TIN ), &aModel.plated_holes, false, false, - aModel.GetLayerZ( F_Cu ) - + Millimeter2iu( ART_OFFSET / 2.0 ) * aModel.scale, - aModel.GetLayerZ( B_Cu ) - - Millimeter2iu( ART_OFFSET / 2.0 ) * aModel.scale, + aModel.GetLayerZ( F_Cu ) + Millimeter2iu( ART_OFFSET / 2.0 ) * aModel.scale, + aModel.GetLayerZ( B_Cu ) - Millimeter2iu( ART_OFFSET / 2.0 ) * aModel.scale, aModel.precision ); // VRML_LAYER top_silk; aModel.top_silk.Tesselate( &aModel.holes ); - write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_SILK ), - &aModel.top_silk, true, true, - aModel.GetLayerZ( F_SilkS ), 0, aModel.precision ); + write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_SILK ), &aModel.top_silk, + true, true, aModel.GetLayerZ( F_SilkS ), 0, aModel.precision ); // VRML_LAYER bot_silk; aModel.bot_silk.Tesselate( &aModel.holes ); - write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_SILK ), - &aModel.bot_silk, true, false, - aModel.GetLayerZ( B_SilkS ), 0, aModel.precision ); + write_triangle_bag( output_file, aModel.GetColor( VRML_COLOR_SILK ), &aModel.bot_silk, + true, false, aModel.GetLayerZ( B_SilkS ), 0, aModel.precision ); } @@ -498,8 +489,8 @@ static void compute_layer_Zs( MODEL_VRML& aModel, BOARD* pcb ) static void export_vrml_line( MODEL_VRML& aModel, LAYER_NUM layer, - double startx, double starty, - double endx, double endy, double width ) + double startx, double starty, + double endx, double endy, double width ) { VRML_LAYER* vlayer; @@ -523,8 +514,8 @@ static void export_vrml_line( MODEL_VRML& aModel, LAYER_NUM layer, static void export_vrml_circle( MODEL_VRML& aModel, LAYER_NUM layer, - double startx, double starty, - double endx, double endy, double width ) + double startx, double starty, + double endx, double endy, double width ) { VRML_LAYER* vlayer; @@ -554,9 +545,9 @@ static void export_vrml_circle( MODEL_VRML& aModel, LAYER_NUM layer, static void export_vrml_arc( MODEL_VRML& aModel, LAYER_NUM layer, - double centerx, double centery, - double arc_startx, double arc_starty, - double width, double arc_angle ) + double centerx, double centery, + double arc_startx, double arc_starty, + double width, double arc_angle ) { VRML_LAYER* vlayer; @@ -569,8 +560,7 @@ static void export_vrml_arc( MODEL_VRML& aModel, LAYER_NUM layer, centery = -centery; arc_starty = -arc_starty; - if( !vlayer->AddArc( centerx, centery, arc_startx, arc_starty, - width, arc_angle, false ) ) + if( !vlayer->AddArc( centerx, centery, arc_startx, arc_starty, width, arc_angle, false ) ) throw( std::runtime_error( vlayer->GetError() ) ); } @@ -593,11 +583,11 @@ static void export_vrml_drawsegment( MODEL_VRML& aModel, DRAWSEGMENT* drawseg ) { case S_ARC: export_vrml_arc( aModel, layer, - (double) drawseg->GetCenter().x, - (double) drawseg->GetCenter().y, - (double) drawseg->GetArcStart().x, - (double) drawseg->GetArcStart().y, - w, drawseg->GetAngle() / 10 ); + (double) drawseg->GetCenter().x, + (double) drawseg->GetCenter().y, + (double) drawseg->GetArcStart().x, + (double) drawseg->GetArcStart().y, + w, drawseg->GetAngle() / 10 ); break; case S_CIRCLE: @@ -620,9 +610,9 @@ static void vrml_text_callback( int x0, int y0, int xf, int yf ) double scale = model_vrml->scale; export_vrml_line( *model_vrml, s_text_layer, - x0 * scale, y0 * scale, - xf * scale, yf * scale, - s_text_width * scale ); + x0 * scale, y0 * scale, + xf * scale, yf * scale, + s_text_width * scale ); } @@ -708,8 +698,7 @@ static void export_vrml_board( MODEL_VRML& aModel, BOARD* pcb ) // Build a polygon from edge cut items wxString msg; - if( !pcb->GetBoardPolygonOutlines( bufferPcbOutlines, - allLayerHoles, &msg ) ) + if( !pcb->GetBoardPolygonOutlines( bufferPcbOutlines, allLayerHoles, &msg ) ) { msg << wxT( "\n\n" ) << _( "Unable to calculate the board outlines;\n" @@ -744,7 +733,7 @@ static void export_vrml_board( MODEL_VRML& aModel, BOARD* pcb ) break; aModel.board.AddVertex( seg, bufferPcbOutlines[i].x * scale, - -(bufferPcbOutlines[i].y * scale ) ); + -(bufferPcbOutlines[i].y * scale ) ); ++i; } @@ -776,7 +765,7 @@ static void export_vrml_board( MODEL_VRML& aModel, BOARD* pcb ) break; aModel.holes.AddVertex( seg, allLayerHoles[i].x * scale, - -(allLayerHoles[i].y * scale ) ); + -( allLayerHoles[i].y * scale ) ); ++i; } @@ -788,9 +777,9 @@ static void export_vrml_board( MODEL_VRML& aModel, BOARD* pcb ) static void export_round_padstack( MODEL_VRML& aModel, BOARD* pcb, - double x, double y, double r, - LAYER_NUM bottom_layer, LAYER_NUM top_layer, - double hole ) + double x, double y, double r, + LAYER_NUM bottom_layer, LAYER_NUM top_layer, + double hole ) { LAYER_NUM layer = top_layer; bool thru = true; @@ -809,7 +798,7 @@ static void export_round_padstack( MODEL_VRML& aModel, BOARD* pcb, aModel.bot_copper.AddCircle( x, -y, r ); if( hole > 0 && !thru ) - aModel.bot_copper.AddCircle( x, -y, hole, true ); + aModel.bot_copper.AddCircle( x, -y, hole, true ); } else if( layer == F_Cu ) @@ -859,11 +848,11 @@ static void export_vrml_tracks( MODEL_VRML& aModel, BOARD* pcb ) } else if( track->GetLayer() == B_Cu || track->GetLayer() == F_Cu ) export_vrml_line( aModel, track->GetLayer(), - track->GetStart().x * aModel.scale, - track->GetStart().y * aModel.scale, - track->GetEnd().x * aModel.scale, - track->GetEnd().y * aModel.scale, - track->GetWidth() * aModel.scale ); + track->GetStart().x * aModel.scale, + track->GetStart().y * aModel.scale, + track->GetEnd().x * aModel.scale, + track->GetEnd().y * aModel.scale, + track->GetWidth() * aModel.scale ); } } @@ -888,8 +877,8 @@ static void export_vrml_zones( MODEL_VRML& aModel, BOARD* aPcb ) zone->SetFillMode( 0 ); // use filled polygons zone->BuildFilledSolidAreasPolygons( aPcb ); } - const CPOLYGONS_LIST& poly = zone->GetFilledPolysList(); + const CPOLYGONS_LIST& poly = zone->GetFilledPolysList(); int nvert = poly.GetCornersCount(); int i = 0; @@ -941,11 +930,11 @@ static void export_vrml_text_module( TEXTE_MODULE* module ) model_vrml->s_text_width = module->GetThickness(); DrawGraphicText( NULL, NULL, module->GetTextPosition(), BLACK, - module->GetShownText(), module->GetDrawRotation(), size, - module->GetHorizJustify(), module->GetVertJustify(), - module->GetThickness(), module->IsItalic(), - true, - vrml_text_callback ); + module->GetShownText(), module->GetDrawRotation(), size, + module->GetHorizJustify(), module->GetVertJustify(), + module->GetThickness(), module->IsItalic(), + true, + vrml_text_callback ); } } @@ -1053,45 +1042,46 @@ static void export_vrml_padshape( MODEL_VRML& aModel, VRML_LAYER* aTinLayer, D_P pad_dy = 0; case PAD_TRAPEZOID: + { + double coord[8] = { - double coord[8] = - { - -pad_w + pad_dy, -pad_h - pad_dx, - -pad_w - pad_dy, pad_h + pad_dx, - +pad_w - pad_dy, -pad_h + pad_dx, - +pad_w + pad_dy, pad_h - pad_dx - }; + -pad_w + pad_dy, -pad_h - pad_dx, + -pad_w - pad_dy, pad_h + pad_dx, + +pad_w - pad_dy, -pad_h + pad_dx, + +pad_w + pad_dy, pad_h - pad_dx + }; - for( int i = 0; i < 4; i++ ) - { - RotatePoint( &coord[i * 2], &coord[i * 2 + 1], aPad->GetOrientation() ); - coord[i * 2] += pad_x; - coord[i * 2 + 1] += pad_y; - } - - int lines; - - lines = aTinLayer->NewContour(); - - if( lines < 0 ) - throw( std::runtime_error( aTinLayer->GetError() ) ); - - if( !aTinLayer->AddVertex( lines, coord[0], -coord[1] ) ) - throw( std::runtime_error( aTinLayer->GetError() ) ); - - if( !aTinLayer->AddVertex( lines, coord[4], -coord[5] ) ) - throw( std::runtime_error( aTinLayer->GetError() ) ); - - if( !aTinLayer->AddVertex( lines, coord[6], -coord[7] ) ) - throw( std::runtime_error( aTinLayer->GetError() ) ); - - if( !aTinLayer->AddVertex( lines, coord[2], -coord[3] ) ) - throw( std::runtime_error( aTinLayer->GetError() ) ); - - if( !aTinLayer->EnsureWinding( lines, false ) ) - throw( std::runtime_error( aTinLayer->GetError() ) ); + for( int i = 0; i < 4; i++ ) + { + RotatePoint( &coord[i * 2], &coord[i * 2 + 1], aPad->GetOrientation() ); + coord[i * 2] += pad_x; + coord[i * 2 + 1] += pad_y; } + + int lines; + + lines = aTinLayer->NewContour(); + + if( lines < 0 ) + throw( std::runtime_error( aTinLayer->GetError() ) ); + + if( !aTinLayer->AddVertex( lines, coord[0], -coord[1] ) ) + throw( std::runtime_error( aTinLayer->GetError() ) ); + + if( !aTinLayer->AddVertex( lines, coord[4], -coord[5] ) ) + throw( std::runtime_error( aTinLayer->GetError() ) ); + + if( !aTinLayer->AddVertex( lines, coord[6], -coord[7] ) ) + throw( std::runtime_error( aTinLayer->GetError() ) ); + + if( !aTinLayer->AddVertex( lines, coord[2], -coord[3] ) ) + throw( std::runtime_error( aTinLayer->GetError() ) ); + + if( !aTinLayer->EnsureWinding( lines, false ) ) + throw( std::runtime_error( aTinLayer->GetError() ) ); + break; + } default: break; @@ -1103,9 +1093,9 @@ static void export_vrml_pad( MODEL_VRML& aModel, BOARD* pcb, D_PAD* aPad ) { double hole_drill_w = (double) aPad->GetDrillSize().x * aModel.scale / 2.0; double hole_drill_h = (double) aPad->GetDrillSize().y * aModel.scale / 2.0; - double hole_drill = std::min( hole_drill_w, hole_drill_h ); - double hole_x = aPad->GetPosition().x * aModel.scale; - double hole_y = aPad->GetPosition().y * aModel.scale; + double hole_drill = std::min( hole_drill_w, hole_drill_h ); + double hole_x = aPad->GetPosition().x * aModel.scale; + double hole_y = aPad->GetPosition().y * aModel.scale; // Export the hole on the edge layer if( hole_drill > 0 ) @@ -1119,7 +1109,7 @@ static void export_vrml_pad( MODEL_VRML& aModel, BOARD* pcb, D_PAD* aPad ) { // Oblong hole (slot) aModel.holes.AddSlot( hole_x, -hole_y, hole_drill_w * 2.0, hole_drill_h * 2.0, - aPad->GetOrientation()/10.0, true, pth ); + aPad->GetOrientation()/10.0, true, pth ); if( pth ) aModel.plated_holes.AddSlot( hole_x, -hole_y, @@ -1194,9 +1184,9 @@ static void compose_quat( double q1[4], double q2[4], double qr[4] ) static void export_vrml_module( MODEL_VRML& aModel, BOARD* aPcb, MODULE* aModule, - std::ofstream& aOutputFile, - double aVRMLModelsToBiu, - bool aExport3DFiles, const wxString& a3D_Subdir ) + std::ofstream& aOutputFile, double aVRMLModelsToBiu, + bool aExport3DFiles, bool aUseRelativePaths, + const wxString& a3D_Subdir ) { // Reference and value if( aModule->Reference().IsVisible() ) @@ -1236,119 +1226,121 @@ static void export_vrml_module( MODEL_VRML& aModel, BOARD* aPcb, MODULE* aModule if( !vrmlm->Is3DType( S3D_MASTER::FILE3D_VRML ) ) continue; - wxString fname = vrmlm->GetShape3DFullFilename(); + wxFileName modelFileName = vrmlm->GetShape3DFullFilename(); + wxFileName destFileName( a3D_Subdir, modelFileName.GetName(), modelFileName.GetExt() ); - fname.Replace( wxT( "\\" ), wxT( "/" ) ); - wxString source_fname = fname; - - if( aExport3DFiles ) + // Only copy VRML files. + if( modelFileName.FileExists() && modelFileName.GetExt() == wxT( "wrl" ) ) { - // Change illegal characters in filenames - ChangeIllegalCharacters( fname, true ); - fname = a3D_Subdir + wxT( "/" ) + fname; - - if( !wxFileExists( fname ) ) - wxCopyFile( source_fname, fname ); - } - - /* Calculate 3D shape rotation: - * this is the rotation parameters, with an additional 180 deg rotation - * for footprints that are flipped - * When flipped, axis rotation is the horizontal axis (X axis) - */ - double rotx = -vrmlm->m_MatRotation.x; - double roty = -vrmlm->m_MatRotation.y; - double rotz = -vrmlm->m_MatRotation.z; - - if( isFlipped ) - { - rotx += 180.0; - NEGATE( roty ); - NEGATE( rotz ); - } - - // Do some quaternion munching - double q1[4], q2[4], rot[4]; - build_quat( 1, 0, 0, DEG2RAD( rotx ), q1 ); - build_quat( 0, 1, 0, DEG2RAD( roty ), q2 ); - compose_quat( q1, q2, q1 ); - build_quat( 0, 0, 1, DEG2RAD( rotz ), q2 ); - compose_quat( q1, q2, q1 ); - - // Note here aModule->GetOrientation() is in 0.1 degrees, - // so module rotation has to be converted to radians - build_quat( 0, 0, 1, DECIDEG2RAD( aModule->GetOrientation() ), q2 ); - compose_quat( q1, q2, q1 ); - from_quat( q1, rot ); - - aOutputFile << "Transform {\n"; - - // A null rotation would fail the acos! - if( rot[3] != 0.0 ) - { - aOutputFile << " rotation " << std::setprecision( 3 ); - aOutputFile << rot[0] << " " << rot[1] << " " << rot[2] << " " << rot[3] << "\n"; - } - - // adjust 3D shape local offset position - // they are given in inch, so they are converted in board IU. - double offsetx = vrmlm->m_MatPosition.x * IU_PER_MILS * 1000.0; - double offsety = vrmlm->m_MatPosition.y * IU_PER_MILS * 1000.0; - double offsetz = vrmlm->m_MatPosition.z * IU_PER_MILS * 1000.0; - - if( isFlipped ) - NEGATE( offsetz ); - else // In normal mode, Y axis is reversed in Pcbnew. - NEGATE( offsety ); - - RotatePoint( &offsetx, &offsety, aModule->GetOrientation() ); - - aOutputFile << " translation " << std::setprecision( aModel.precision ); - aOutputFile << (( offsetx + aModule->GetPosition().x) * aModel.scale + aModel.tx ) << " "; - aOutputFile << ( -(offsety + aModule->GetPosition().y) * aModel.scale - aModel.ty ) << " "; - aOutputFile << ( (offsetz * aModel.scale ) + aModel.GetLayerZ( aModule->GetLayer() ) ) << "\n"; - - aOutputFile << " scale "; - aOutputFile << ( vrmlm->m_MatScale.x * aVRMLModelsToBiu ) << " "; - aOutputFile << ( vrmlm->m_MatScale.y * aVRMLModelsToBiu ) << " "; - aOutputFile << ( vrmlm->m_MatScale.z * aVRMLModelsToBiu ) << "\n"; - - if( fname.EndsWith( wxT( "x3d" ) ) ) - { - X3D_MODEL_PARSER* parser = new X3D_MODEL_PARSER( vrmlm ); - - if( parser ) + if( aExport3DFiles ) { - // embed x3d model in vrml format - double vrml_to_x3d = aVRMLModelsToBiu; - parser->Load( fname, vrml_to_x3d ); + wxDateTime srcModTime = modelFileName.GetModificationTime(); + wxDateTime destModTime = srcModTime; - try + destModTime.SetToCurrent(); + + if( destFileName.FileExists() ) + destModTime = destFileName.GetModificationTime(); + + // Only copy the file if it doesn't exist or has been modified. This eliminates + // the redundant file copies. + if( srcModTime != destModTime ) { - aOutputFile << " children [\n "; - aOutputFile << TO_UTF8( parser->VRML2_representation() ) << " ]\n"; - aOutputFile << " }\n"; - } - catch( const std::exception& e ) - { - delete parser; - throw; + wxLogDebug( wxT( "Copying 3D model %s to %s." ), + GetChars( modelFileName.GetFullPath() ), + GetChars( destFileName.GetFullPath() ) ); + + if( !wxCopyFile( modelFileName.GetFullPath(), destFileName.GetFullPath() ) ) + continue; } } - } - else - { + + /* Calculate 3D shape rotation: + * this is the rotation parameters, with an additional 180 deg rotation + * for footprints that are flipped + * When flipped, axis rotation is the horizontal axis (X axis) + */ + double rotx = -vrmlm->m_MatRotation.x; + double roty = -vrmlm->m_MatRotation.y; + double rotz = -vrmlm->m_MatRotation.z; + + if( isFlipped ) + { + rotx += 180.0; + NEGATE( roty ); + NEGATE( rotz ); + } + + // Do some quaternion munching + double q1[4], q2[4], rot[4]; + build_quat( 1, 0, 0, DEG2RAD( rotx ), q1 ); + build_quat( 0, 1, 0, DEG2RAD( roty ), q2 ); + compose_quat( q1, q2, q1 ); + build_quat( 0, 0, 1, DEG2RAD( rotz ), q2 ); + compose_quat( q1, q2, q1 ); + + // Note here aModule->GetOrientation() is in 0.1 degrees, + // so module rotation has to be converted to radians + build_quat( 0, 0, 1, DECIDEG2RAD( aModule->GetOrientation() ), q2 ); + compose_quat( q1, q2, q1 ); + from_quat( q1, rot ); + + aOutputFile << "Transform {\n"; + + // A null rotation would fail the acos! + if( rot[3] != 0.0 ) + { + aOutputFile << " rotation " << std::setprecision( 3 ); + aOutputFile << rot[0] << " " << rot[1] << " " << rot[2] << " " << rot[3] << "\n"; + } + + // adjust 3D shape local offset position + // they are given in inch, so they are converted in board IU. + double offsetx = vrmlm->m_MatPosition.x * IU_PER_MILS * 1000.0; + double offsety = vrmlm->m_MatPosition.y * IU_PER_MILS * 1000.0; + double offsetz = vrmlm->m_MatPosition.z * IU_PER_MILS * 1000.0; + + if( isFlipped ) + NEGATE( offsetz ); + else // In normal mode, Y axis is reversed in Pcbnew. + NEGATE( offsety ); + + RotatePoint( &offsetx, &offsety, aModule->GetOrientation() ); + + aOutputFile << " translation " << std::setprecision( aModel.precision ); + aOutputFile << ( ( offsetx + aModule->GetPosition().x ) * + aModel.scale + aModel.tx ) << " "; + aOutputFile << ( -(offsety + aModule->GetPosition().y) * + aModel.scale - aModel.ty ) << " "; + aOutputFile << ( (offsetz * aModel.scale ) + + aModel.GetLayerZ( aModule->GetLayer() ) ) << "\n"; + aOutputFile << " scale "; + aOutputFile << ( vrmlm->m_MatScale.x * aVRMLModelsToBiu ) << " "; + aOutputFile << ( vrmlm->m_MatScale.y * aVRMLModelsToBiu ) << " "; + aOutputFile << ( vrmlm->m_MatScale.z * aVRMLModelsToBiu ) << "\n"; aOutputFile << " children [\n Inline {\n url \""; - aOutputFile << TO_UTF8( fname ) << "\"\n } ]\n"; + + if( aUseRelativePaths ) + { + wxFileName tmp = destFileName; + tmp.SetExt( wxT( "" ) ); + tmp.SetName( wxT( "" ) ); + tmp.RemoveLastDir(); + destFileName.MakeRelativeTo( tmp.GetPath() ); + } + + wxString fn = destFileName.GetFullPath(); + fn.Replace( wxT( "\\" ), wxT( "/" ) ); + aOutputFile << TO_UTF8( fn ) << "\"\n } ]\n"; aOutputFile << " }\n"; } } } -bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, - double aMMtoWRMLunit, bool aExport3DFiles, - const wxString& a3D_Subdir ) +bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, double aMMtoWRMLunit, + bool aExport3DFiles, bool aUseRelativePaths, + const wxString& a3D_Subdir ) { wxString msg; BOARD* pcb = GetBoard(); @@ -1368,14 +1360,11 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, SetLocaleTo_C_standard(); // Begin with the usual VRML boilerplate - wxString name = aFullFileName; - - name.Replace( wxT( "\\" ), wxT( "/" ) ); - ChangeIllegalCharacters( name, false ); - + wxString fn = aFullFileName; + fn.Replace( wxT( "\\" ), wxT( "/" ) ); output_file << "#VRML V2.0 utf8\n"; output_file << "WorldInfo {\n"; - output_file << " title \"" << TO_UTF8( name ) << " - Generated by Pcbnew\"\n"; + output_file << " title \"" << TO_UTF8( fn ) << " - Generated by Pcbnew\"\n"; output_file << "}\n"; // Set the VRML world scale factor @@ -1387,8 +1376,7 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, // XXX - NOTE: we should allow the user a GUI option to specify the offset EDA_RECT bbbox = pcb->ComputeBoundingBox(); - model3d.SetOffset( -model3d.scale * bbbox.Centre().x, - model3d.scale * bbbox.Centre().y ); + model3d.SetOffset( -model3d.scale * bbbox.Centre().x, model3d.scale * bbbox.Centre().y ); output_file << " children [\n"; @@ -1417,9 +1405,8 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, // Export footprints for( MODULE* module = pcb->m_Modules; module != 0; module = module->Next() ) - export_vrml_module( model3d, pcb, module, output_file, - wrml_3D_models_scaling_factor, - aExport3DFiles, a3D_Subdir ); + export_vrml_module( model3d, pcb, module, output_file, wrml_3D_models_scaling_factor, + aExport3DFiles, aUseRelativePaths, a3D_Subdir ); // write out the board and all layers write_layers( model3d, output_file, pcb ); @@ -1443,17 +1430,3 @@ bool PCB_EDIT_FRAME::ExportVRML_File( const wxString& aFullFileName, return ok; } - - -/* - * some characters cannot be used in filenames, - * this function change them to "_" - */ -static void ChangeIllegalCharacters( wxString& aFileName, bool aDirSepIsIllegal ) -{ - if( aDirSepIsIllegal ) - aFileName.Replace( wxT( "/" ), wxT( "_" ) ); - - aFileName.Replace( wxT( " " ), wxT( "_" ) ); - aFileName.Replace( wxT( ":" ), wxT( "_" ) ); -}