/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2015 Cirilo Bernardo * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include <3d_model_viewer/c3d_model_viewer.h> #include <3d_rendering/3d_render_ogl_legacy/c_ogl_3dmodel.h> #include #include #include #include #include #include #include #include #include #include #include "3d_cache.h" #include "3d_info.h" #include "3d_filename_resolver.h" #include "plugins/3dapi/ifsg_api.h" #include "panel_prev_model.h" #include "dialog_select_3dmodel.h" #include "3d_cache_dialogs.h" // ensure -360 < rotation < 360 static void checkRotation( double& rot ) { if( rot >= 360.0 ) { int n = rot / 360.0; rot -= 360.0 * (double)n; } else if( rot <= -360.0 ) { int n = -rot / 360.0; rot += 360.0 * (double)n; } return; } enum { ID_SET_DIR = wxID_LAST + 1, ID_CFG_PATHS, ID_SCALEX, ID_SCALEY, ID_SCALEZ, ID_ROTX, ID_ROTY, ID_ROTZ, ID_OFFX, ID_OFFY, ID_OFFZ, ID_3D_ISO, ID_3D_UPDATE, ID_3D_LEFT, ID_3D_RIGHT, ID_3D_FRONT, ID_3D_BACK, ID_3D_TOP, ID_3D_BOTTOM }; wxBEGIN_EVENT_TABLE( PANEL_PREV_3D, wxPanel) EVT_CHOICE( ID_SET_DIR, PANEL_PREV_3D::SetRootDir ) EVT_TEXT_ENTER( ID_SCALEX, PANEL_PREV_3D::updateOrientation ) EVT_TEXT_ENTER( ID_SCALEY, PANEL_PREV_3D::updateOrientation ) EVT_TEXT_ENTER( ID_SCALEZ, PANEL_PREV_3D::updateOrientation ) EVT_TEXT_ENTER( ID_ROTX, PANEL_PREV_3D::updateOrientation ) EVT_TEXT_ENTER( ID_ROTY, PANEL_PREV_3D::updateOrientation ) EVT_TEXT_ENTER( ID_ROTZ, PANEL_PREV_3D::updateOrientation ) EVT_TEXT_ENTER( ID_OFFX, PANEL_PREV_3D::updateOrientation ) EVT_TEXT_ENTER( ID_OFFY, PANEL_PREV_3D::updateOrientation ) EVT_TEXT_ENTER( ID_OFFZ, PANEL_PREV_3D::updateOrientation ) EVT_BUTTON( ID_CFG_PATHS, PANEL_PREV_3D::Cfg3DPaths ) EVT_BUTTON( ID_3D_ISO, PANEL_PREV_3D::View3DISO ) EVT_BUTTON( ID_3D_UPDATE, PANEL_PREV_3D::View3DUpdate ) EVT_BUTTON( ID_3D_LEFT, PANEL_PREV_3D::View3DLeft ) EVT_BUTTON( ID_3D_RIGHT, PANEL_PREV_3D::View3DRight ) EVT_BUTTON( ID_3D_FRONT, PANEL_PREV_3D::View3DFront ) EVT_BUTTON( ID_3D_BACK, PANEL_PREV_3D::View3DBack ) EVT_BUTTON( ID_3D_TOP, PANEL_PREV_3D::View3DTop ) EVT_BUTTON( ID_3D_BOTTOM, PANEL_PREV_3D::View3DBottom ) wxEND_EVENT_TABLE() PANEL_PREV_3D::PANEL_PREV_3D( wxWindow* aParent, bool hasFileSelector ) : wxPanel( aParent, -1 ) { m_ModelManager = NULL; m_FileDlg = NULL; canvas = NULL; model = NULL; xscale = NULL; yscale = NULL; zscale = NULL; xrot = NULL; yrot = NULL; zrot = NULL; xoff = NULL; yoff = NULL; zoff = NULL; wxBoxSizer* mainBox = new wxBoxSizer( wxVERTICAL ); wxStaticBoxSizer* vbox = new wxStaticBoxSizer( wxVERTICAL, this, _( "3D Model Orientation" ) ); wxBoxSizer* hboxDirChoice = NULL; dirChoices = NULL; if( hasFileSelector ) { hboxDirChoice = new wxBoxSizer( wxHORIZONTAL ); dirChoices = new wxChoice( this, ID_SET_DIR, wxDefaultPosition, wxSize( 320, 20 ) ); dirChoices->SetMinSize( wxSize( 320, 12 ) ); #ifdef _WIN32 // Note: On Win32 the native selector box will truncate text // if the text is too long. dirChoices->SetMinSize( wxSize( 450, -1 ) ); #endif wxStaticText* stDirChoice = new wxStaticText( this, -1, _( "Paths:" ) ); wxButton* cfgPaths = new wxButton( this, ID_CFG_PATHS, _( "Configure Paths" ) ); hboxDirChoice->Add( stDirChoice, 0, wxALL | wxCENTER, 5 ); hboxDirChoice->Add( dirChoices, 1, wxEXPAND | wxALL, 5 ); hboxDirChoice->Add( cfgPaths, 0, wxALL, 5 ); } wxFloatingPointValidator< float > valScale( 6 ); valScale.SetRange( 0.001, 100 ); wxFloatingPointValidator< float > valRotate( 2 ); valRotate.SetRange( -180.0, 180.0 ); wxFloatingPointValidator< float > valOffset( 6 ); wxStaticBoxSizer* vbScale = new wxStaticBoxSizer( wxVERTICAL, this, _( "Scale" ) ); wxStaticBoxSizer* vbRotate = new wxStaticBoxSizer( wxVERTICAL, this, _( "Rotation" ) ); wxStaticBoxSizer* vbOffset = new wxStaticBoxSizer( wxVERTICAL, this, _( "Offset (inches)" ) ); wxStaticBox* modScale = vbScale->GetStaticBox(); wxStaticBox* modRotate = vbRotate->GetStaticBox(); wxStaticBox* modOffset = vbOffset->GetStaticBox(); wxBoxSizer* hbS1 = new wxBoxSizer( wxHORIZONTAL ); wxBoxSizer* hbS2 = new wxBoxSizer( wxHORIZONTAL ); wxBoxSizer* hbS3 = new wxBoxSizer( wxHORIZONTAL ); wxStaticText* txtS1 = new wxStaticText( modScale, -1, wxT( "X:" ) ); wxStaticText* txtS2 = new wxStaticText( modScale, -1, wxT( "Y:" ) ); wxStaticText* txtS3 = new wxStaticText( modScale, -1, wxT( "Z:" ) ); xscale = new wxTextCtrl( modScale, ID_SCALEX, "1.0", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER, valScale ); yscale = new wxTextCtrl( modScale, ID_SCALEY, "1.0", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER, valScale ); zscale = new wxTextCtrl( modScale, ID_SCALEZ, "1.0", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER, valScale ); xscale->SetMaxLength( 9 ); yscale->SetMaxLength( 9 ); zscale->SetMaxLength( 9 ); hbS1->Add( txtS1, 0, wxALL, 2 ); hbS1->Add( xscale, 0, wxALL, 2 ); hbS2->Add( txtS2, 0, wxALL, 2 ); hbS2->Add( yscale, 0, wxALL, 2 ); hbS3->Add( txtS3, 0, wxALL, 2 ); hbS3->Add( zscale, 0, wxALL, 2 ); vbScale->Add( hbS1, 0, wxEXPAND | wxALL, 2 ); vbScale->Add( hbS2, 0, wxEXPAND | wxALL, 2 ); vbScale->Add( hbS3, 0, wxEXPAND | wxALL, 2 ); wxBoxSizer* hbR1 = new wxBoxSizer( wxHORIZONTAL ); wxBoxSizer* hbR2 = new wxBoxSizer( wxHORIZONTAL ); wxBoxSizer* hbR3 = new wxBoxSizer( wxHORIZONTAL ); wxStaticText* txtR1 = new wxStaticText( modRotate, ID_ROTX, wxT( "X:" ) ); wxStaticText* txtR2 = new wxStaticText( modRotate, ID_ROTY, wxT( "Y:" ) ); wxStaticText* txtR3 = new wxStaticText( modRotate, ID_ROTZ, wxT( "Z:" ) ); xrot = new wxTextCtrl( modRotate, ID_ROTX, "0.0", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER, valRotate ); yrot = new wxTextCtrl( modRotate, ID_ROTY, "0.0", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER, valRotate ); zrot = new wxTextCtrl( modRotate, ID_ROTZ, "0.0", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER, valRotate ); xrot->SetMaxLength( 9 ); yrot->SetMaxLength( 9 ); zrot->SetMaxLength( 9 ); hbR1->Add( txtR1, 0, wxALL, 2 ); hbR1->Add( xrot, 0, wxALL, 2 ); hbR2->Add( txtR2, 0, wxALL, 2 ); hbR2->Add( yrot, 0, wxALL, 2 ); hbR3->Add( txtR3, 0, wxALL, 2 ); hbR3->Add( zrot, 0, wxALL, 2 ); vbRotate->Add( hbR1, 0, wxEXPAND | wxALL, 2 ); vbRotate->Add( hbR2, 0, wxEXPAND | wxALL, 2 ); vbRotate->Add( hbR3, 0, wxEXPAND | wxALL, 2 ); wxBoxSizer* hbO1 = new wxBoxSizer( wxHORIZONTAL ); wxBoxSizer* hbO2 = new wxBoxSizer( wxHORIZONTAL ); wxBoxSizer* hbO3 = new wxBoxSizer( wxHORIZONTAL ); wxStaticText* txtO1 = new wxStaticText( modOffset, -1, wxT( "X:" ) ); wxStaticText* txtO2 = new wxStaticText( modOffset, -1, wxT( "Y:" ) ); wxStaticText* txtO3 = new wxStaticText( modOffset, -1, wxT( "Z:" ) ); xoff = new wxTextCtrl( modOffset, ID_OFFX, "0.0", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER, valOffset ); yoff = new wxTextCtrl( modOffset, ID_OFFY, "0.0", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER, valOffset ); zoff = new wxTextCtrl( modOffset, ID_OFFZ, "0.0", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER, valOffset ); xoff->SetMaxLength( 9 ); yoff->SetMaxLength( 9 ); zoff->SetMaxLength( 9 ); hbO1->Add( txtO1, 0, wxALL, 2 ); hbO1->Add( xoff, 0, wxALL, 2 ); hbO2->Add( txtO2, 0, wxALL, 2 ); hbO2->Add( yoff, 0, wxALL, 2 ); hbO3->Add( txtO3, 0, wxALL, 2 ); hbO3->Add( zoff, 0, wxALL, 2 ); vbOffset->Add( hbO1, 0, wxEXPAND | wxALL, 2 ); vbOffset->Add( hbO2, 0, wxEXPAND | wxALL, 2 ); vbOffset->Add( hbO3, 0, wxEXPAND | wxALL, 2 ); if( NULL != hboxDirChoice ) mainBox->Add( hboxDirChoice, 0, wxEXPAND ); // hbox holding orientation data and preview wxBoxSizer* hbox = new wxBoxSizer( wxHORIZONTAL ); // vbox holding orientation data wxBoxSizer* vboxOrient = new wxBoxSizer( wxVERTICAL ); // vbox holding the preview and view buttons wxBoxSizer* vboxPrev = new wxBoxSizer( wxVERTICAL ); vboxOrient->Add( vbScale, 0, wxALL, 5 ); vboxOrient->Add( vbRotate, 0, wxALL, 5 ); vboxOrient->Add( vbOffset, 0, wxALL, 5 ); vboxOrient->AddSpacer( 20 ); // add preview items preview = new wxPanel( this, -1 ); preview->SetMinSize( wxSize( 320, 200 ) ); preview->SetBackgroundColour( wxColor( 0, 0, 0 )); vboxPrev->Add( preview, 1, wxEXPAND | wxALIGN_CENTER | wxLEFT | wxRIGHT, 5 ); // buttons: wxButton* vFront = new wxButton( this, ID_3D_FRONT, wxT( "F" ) ); wxButton* vBack = new wxButton( this, ID_3D_BACK, wxT( "B" ) ); wxButton* vLeft = new wxButton( this, ID_3D_LEFT, wxT( "L" ) ); wxButton* vRight = new wxButton( this, ID_3D_RIGHT, wxT( "R" ) ); wxButton* vTop = new wxButton( this, ID_3D_TOP, wxT( "T" ) ); wxButton* vBottom = new wxButton( this, ID_3D_BOTTOM, wxT( "B" ) ); wxButton* vISO = new wxButton( this, ID_3D_ISO, wxT( "I" ) ); wxButton* vUpdate = new wxButton( this, ID_3D_UPDATE, wxT( "U" ) ); wxBoxSizer* hbBT = new wxBoxSizer( wxHORIZONTAL ); wxBoxSizer* hbBB = new wxBoxSizer( wxHORIZONTAL ); hbBT->Add( vISO, 0, wxCENTER | wxALL, 3 ); hbBT->Add( vLeft, 0, wxCENTER | wxALL, 3 ); hbBT->Add( vFront, 0, wxCENTER | wxALL, 3 ); hbBT->Add( vTop, 0, wxCENTER | wxALL, 3 ); hbBT->AddSpacer( 17 ); hbBB->Add( vUpdate, 0, wxCENTER | wxALL, 3 ); hbBB->Add( vRight, 0, wxCENTER | wxALL, 3 ); hbBB->Add( vBack, 0, wxCENTER | wxALL, 3 ); hbBB->Add( vBottom, 0, wxCENTER | wxALL, 3 ); hbBB->AddSpacer( 17 ); vboxPrev->AddSpacer( 7 ); vboxPrev->Add( hbBT, 0 ); vboxPrev->Add( hbBB, 0 ); // XXX - Suppress the buttons until the Renderer code is ready. // vboxPrev->Hide( preview, true ); vboxPrev->Hide( hbBT, true ); vboxPrev->Hide( hbBB, true ); hbox->Add( vboxOrient, 0, wxALL, 5 ); hbox->Add( vboxPrev, 1, wxEXPAND ); vbox->Add( hbox, 1, wxEXPAND ); mainBox->Add( vbox, 1, wxEXPAND | wxALL, 5 ); if( hasFileSelector ) { // NOTE: if/when the FIle Selector preview is implemented // we may need to hide the orientation boxes to ensure the // users have sufficient display area for the browser. // hbox->Hide( vboxOrient, true ); // XXX - // NOTE: for now we always suppress the preview and model orientation // panels while in the file selector mainBox->Hide( vbox, true ); } SetSizerAndFit( mainBox ); Centre(); return; } PANEL_PREV_3D::~PANEL_PREV_3D() { if( NULL != canvas ) { canvas->Clear3DModel(); canvas->Refresh(); canvas->Update(); } model = NULL; return; } void PANEL_PREV_3D::SetModelManager( S3D_CACHE* aModelManager ) { m_ModelManager = aModelManager; updateDirChoiceList(); return; } void PANEL_PREV_3D::SetFileSelectorDlg( wxFileDialog* aFileDlg ) { m_FileDlg = aFileDlg; updateDirChoiceList(); return; } void PANEL_PREV_3D::updateDirChoiceList( void ) { if( NULL == m_FileDlg || NULL == m_ModelManager || NULL == dirChoices ) return; std::list< S3D_ALIAS > const* md = m_ModelManager->GetResolver()->GetPaths(); std::list< S3D_ALIAS >::const_iterator sL = md->begin(); std::list< S3D_ALIAS >::const_iterator eL = md->end(); std::vector< wxString > cl; while( sL != eL ) { cl.push_back( sL->m_pathexp ); ++sL; } if( !cl.empty() ) { dirChoices->Clear(); dirChoices->Append( (int)cl.size(), &cl[0] ); dirChoices->Select( 0 ); } return; } void PANEL_PREV_3D::SetRootDir( wxCommandEvent& event ) { if( !m_FileDlg ) return; m_FileDlg->SetDirectory( dirChoices->GetString( dirChoices->GetSelection() ) ); return; } void PANEL_PREV_3D::Cfg3DPaths( wxCommandEvent& event ) { if( !m_FileDlg || !m_ModelManager ) return; if( S3D::Configure3DPaths( this, m_ModelManager->GetResolver() ) ) updateDirChoiceList(); return; } void PANEL_PREV_3D::View3DISO( wxCommandEvent& event ) { // XXX - TO BE IMPLEMENTED std::cout << "Switch to Isometric View\n"; return; } void PANEL_PREV_3D::View3DUpdate( wxCommandEvent& event ) { // XXX - TO BE IMPLEMENTED std::cout << "Update 3D View\n"; // update the model filename if appropriate if( NULL != m_FileDlg ) { wxString modelName = m_FileDlg->GetCurrentlySelectedFilename(); UpdateModelName( modelName ); } return; } void PANEL_PREV_3D::View3DLeft( wxCommandEvent& event ) { // XXX - TO BE IMPLEMENTED std::cout << "Switch to Left View\n"; return; } void PANEL_PREV_3D::View3DRight( wxCommandEvent& event ) { // XXX - TO BE IMPLEMENTED std::cout << "Switch to Right View\n"; return; } void PANEL_PREV_3D::View3DFront( wxCommandEvent& event ) { // XXX - TO BE IMPLEMENTED std::cout << "Switch to Front View\n"; return; } void PANEL_PREV_3D::View3DBack( wxCommandEvent& event ) { // XXX - TO BE IMPLEMENTED std::cout << "Switch to Back View\n"; return; } void PANEL_PREV_3D::View3DTop( wxCommandEvent& event ) { // XXX - TO BE IMPLEMENTED std::cout << "Switch to Top View\n"; return; } void PANEL_PREV_3D::View3DBottom( wxCommandEvent& event ) { // XXX - TO BE IMPLEMENTED std::cout << "Switch to Bottom View\n"; return; } void PANEL_PREV_3D::GetModelData( S3D_INFO* aModel ) { if( NULL == aModel ) return; SGPOINT scale; SGPOINT rotation; SGPOINT offset; getOrientationVars( scale, rotation, offset ); aModel->scale = scale; aModel->offset = offset; aModel->rotation = rotation; // return if we are not in file selection mode if( NULL == m_FileDlg ) return; // file selection mode: retrieve the filename and specify a // path relative to one of the config paths wxFileName fname = m_FileDlg->GetPath(); fname.Normalize(); aModel->filename = m_ModelManager->GetResolver()->ShortenPath( fname.GetFullPath() ); return; } void PANEL_PREV_3D::SetModelData( S3D_INFO const* aModel ) { xscale->ChangeValue( wxString::FromDouble( aModel->scale.x ) ); yscale->ChangeValue( wxString::FromDouble( aModel->scale.y ) ); zscale->ChangeValue( wxString::FromDouble( aModel->scale.z ) ); xrot->ChangeValue( wxString::FromDouble( aModel->rotation.x ) ); yrot->ChangeValue( wxString::FromDouble( aModel->rotation.y ) ); zrot->ChangeValue( wxString::FromDouble( aModel->rotation.z ) ); xoff->ChangeValue( wxString::FromDouble( aModel->offset.x ) ); yoff->ChangeValue( wxString::FromDouble( aModel->offset.y ) ); zoff->ChangeValue( wxString::FromDouble( aModel->offset.z ) ); modelInfo = *aModel; UpdateModelName( aModel->filename ); return; } void PANEL_PREV_3D::UpdateModelName( wxString const& aModelName ) { bool newModel = false; // if the model name is a directory simply clear the current model if( aModelName.empty() || wxFileName::DirExists( aModelName ) ) { currentModelFile.clear(); modelInfo.filename.clear(); } else { wxString newModelFile; if( m_ModelManager ) newModelFile = m_ModelManager->GetResolver()->ResolvePath( aModelName ); else if( wxFileName::FileExists( aModelName ) ) newModelFile = aModelName; if( newModelFile.empty() ) { #ifdef DEBUG std::cout << "[3dv] Update Model: (no such file) " << aModelName.ToUTF8() << "\n"; #endif } else if( newModelFile.Cmp( currentModelFile ) ) { newModel = true; #ifdef DEBUG std::cout << "[3dv] Update Model: " << newModelFile.ToUTF8() << "\n"; #endif } #ifdef DEBUG else { std::cout << "[3dv] Update Model: [model unchanged]\n"; } #endif currentModelFile = newModelFile; modelInfo.filename = currentModelFile; } if( currentModelFile.empty() || newModel ) { #ifdef DEBUG std::cout << "[3dv] Update Model: painting black\n"; #endif if( NULL != canvas ) { canvas->Clear3DModel(); canvas->Refresh(); canvas->Update(); } model = NULL; if( currentModelFile.empty() ) return; } model = m_ModelManager->GetModel( modelInfo.filename ); if( NULL == model ) { #ifdef DEBUG std::cout << "[3dv] Update Model: no model loaded\n"; #endif if( NULL != canvas ) { canvas->Refresh(); canvas->Update(); } return; } #ifdef DEBUG std::cout << "[3dv] Update Model: loading preview\n"; #endif if( NULL == canvas ) { #ifdef DEBUG std::cout << "[3dv] Update Model: creating canvas\n"; #endif canvas = new C3D_MODEL_VIEWER( preview, COGL_ATT_LIST::GetAttributesList( true ) ); #ifdef DEBUG if( NULL == canvas ) std::cout << "[3dv] Update Model: canvas creation FAILED\n"; #endif wxSizer* ws = new wxBoxSizer( wxHORIZONTAL ); canvas->Set3DModel( *model ); ws->Add( canvas, 1, wxEXPAND ); preview->SetSizer( ws ); preview->Layout(); ws->FitInside( preview ); return; } canvas->Set3DModel( *model ); canvas->Refresh(); canvas->Update(); return; } void PANEL_PREV_3D::UpdateWindowUI( long flags ) { /* XXX - NOTE: until we figure out how to ensure that a Paint Event is generated for the File Selector's UI, we cannot display any preview within the file browser. if( wxUPDATE_UI_RECURSE == flags && m_FileDlg && m_ModelManager ) { // check for a change in the current model file S3D_INFO info; modelInfo = info; UpdateModelName( m_FileDlg->GetCurrentlySelectedFilename() ); } // */ wxPanel::UpdateWindowUI( flags ); return; } void PANEL_PREV_3D::updateOrientation( wxCommandEvent &event ) { // note: process even if the canvas is NULL since the user may // edit the filename to provide a valid file SGPOINT scale; SGPOINT rotation; SGPOINT offset; getOrientationVars( scale, rotation, offset ); modelInfo.scale = scale; modelInfo.offset = offset; modelInfo.rotation = rotation; if( NULL == canvas ) return; canvas->Clear3DModel(); model = m_ModelManager->GetModel( modelInfo.filename ); if( model ) { canvas->Set3DModel( *model ); canvas->Refresh(); canvas->Update(); } event.Skip(); return; } void PANEL_PREV_3D::getOrientationVars( SGPOINT& scale, SGPOINT& rotation, SGPOINT& offset ) { if( NULL == xscale || NULL == yscale || NULL == zscale || NULL == xrot || NULL == yrot || NULL == zrot || NULL == xoff || NULL == yoff || NULL == zoff ) { return; } xscale->GetValue().ToDouble( &scale.x ); yscale->GetValue().ToDouble( &scale.y ); zscale->GetValue().ToDouble( &scale.z ); if( 0.001 > scale.x || 0.001 > scale.y || 0.001 > scale.z ) { wxString errmsg = _("[INFO] invalid scale values; setting all to 1.0"); wxLogMessage( "%s", errmsg.ToUTF8() ); scale.x = 1.0; scale.y = 1.0; scale.z = 1.0; } xrot->GetValue().ToDouble( &rotation.x ); yrot->GetValue().ToDouble( &rotation.y ); zrot->GetValue().ToDouble( &rotation.z ); checkRotation( rotation.x ); checkRotation( rotation.y ); checkRotation( rotation.z ); xoff->GetValue().ToDouble( &offset.x ); yoff->GetValue().ToDouble( &offset.y ); zoff->GetValue().ToDouble( &offset.z ); return; }