diff --git a/code/BaseImporter.h b/code/BaseImporter.h index a4b9ce299..a68aae2d9 100644 --- a/code/BaseImporter.h +++ b/code/BaseImporter.h @@ -217,12 +217,8 @@ public: * The function is a request to the importer to update its configuration * basing on the Importer's configuration property list. * @param pImp Importer instance - * @param ppFlags Post-processing steps to be executed on the data - * returned by the loaders. This value is provided to allow some - * internal optimizations. */ - virtual void SetupProperties(const Importer* pImp /*, - unsigned int ppFlags*/); + virtual void SetupProperties(const Importer* pImp); protected: diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 1ea385e99..4cdf9d456 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -192,6 +192,11 @@ SOURCE_GROUP( Obj FILES ObjTools.h ) +SOURCE_GROUP( Ogre FILES + OgreImporter.h + OgreImporter.cpp +) + SOURCE_GROUP( Ply FILES PlyLoader.cpp PlyLoader.h @@ -472,6 +477,8 @@ ADD_LIBRARY( assimp SHARED ObjFileParser.cpp ObjFileParser.h ObjTools.h + OgreImporter.h + OgreImporter.cpp OptimizeGraph.cpp OptimizeGraph.h OptimizeMeshes.cpp diff --git a/code/Importer.cpp b/code/Importer.cpp index b9430c454..3ba27d45d 100644 --- a/code/Importer.cpp +++ b/code/Importer.cpp @@ -158,6 +158,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_BUILD_NO_LWS_IMPORTER # include "LWSLoader.h" #endif +#ifndef AI_BUILD_NO_OGRE_IMPORTER +# include "OgreImporter.h" +#endif // ....................................................................................... // PostProcess-Steps @@ -360,6 +363,10 @@ Importer::Importer() #if (!defined AI_BUILD_NO_LWS_IMPORTER) pimpl->mImporter.push_back( new LWSImporter()); #endif +#if (!defined AI_BUILD_NO_OGRE_IMPORTER) + pimpl->mImporter.push_back( new Ogre::OgreImporter()); +#endif + // ---------------------------------------------------------------------------- // Add an instance of each post processing step here in the order diff --git a/code/OgreImporter.cpp b/code/OgreImporter.cpp new file mode 100644 index 000000000..cf80f2b32 --- /dev/null +++ b/code/OgreImporter.cpp @@ -0,0 +1,425 @@ +#include "AssimpPCH.h" + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + +#include +#include +using namespace std; + +#include "boost/format.hpp" +using namespace boost; + +#include "OgreImporter.h" +#include "irrXMLWrapper.h" + + + +namespace Assimp +{ +namespace Ogre +{ + + +bool OgreImporter::CanRead(const std::string &pFile, Assimp::IOSystem *pIOHandler, bool checkSig) const +{ + if(!checkSig)//Check File Extension + { + std::string extension("mesh.xml"); + int l=extension.length(); + return pFile.substr(pFile.length()-l, l)==extension; + } + else//Check file Header + { + const char* tokens[] = {""}; + return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1); + } +} + + + +void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Assimp::IOSystem *pIOHandler) +{ + m_CurrentFilename=pFile; + m_CurrentIOHandler=pIOHandler; + m_CurrentScene=pScene; + + //Open the File: + boost::scoped_ptr file(pIOHandler->Open(pFile)); + if( file.get() == NULL) + throw new ImportErrorException("Failed to open file "+pFile+"."); + + //Read the Mesh File: + boost::scoped_ptr mIOWrapper( new CIrrXML_IOStreamReader( file.get())); + XmlReader* MeshFile = irr::io::createIrrXMLReader(mIOWrapper.get()); + if(!MeshFile)//parse the xml file + throw new ImportErrorException("Failed to create XML Reader for "+pFile); + + + DefaultLogger::get()->info("Mesh File opened"); + + //Read root Node: + if(!(XmlRead(MeshFile) && string(MeshFile->getNodeName())=="mesh")) + { + throw new ImportErrorException("Root Node is not ! "+pFile+" "+MeshFile->getNodeName()); + } + + //Go to the submeshs: + if(!(XmlRead(MeshFile) && string(MeshFile->getNodeName())=="submeshes")) + { + throw new ImportErrorException("No node in node! "+pFile); + } + + + //-------------------Read all submeshs:----------------------- + XmlRead(MeshFile); + while(string(MeshFile->getNodeName())=="submesh")//read the index values (the faces): + { + SubMesh NewSubMesh; + NewSubMesh.MaterialName=GetAttribute(MeshFile, "material"); + DefaultLogger::get()->info("Loading Submehs with Material: "+NewSubMesh.MaterialName); + ReadSubMesh(NewSubMesh, MeshFile); + } + //_______________________________________________________________- + + + + //-----------------Read the skeleton:---------------------- + //Create the root node + pScene->mRootNode=new aiNode("root"); + + //link the mesh with the root node: + pScene->mRootNode->mMeshes=new unsigned int[1]; + pScene->mRootNode->mMeshes[0]=0; + pScene->mRootNode->mNumMeshes=1; + //_________________________________________________________ +} + + + +void OgreImporter::GetExtensionList(std::string &append) +{ + append+="*.mesh.xml"; +} + + +void OgreImporter::SetupProperties(const Importer* pImp) +{ + m_MaterialLibFilename=pImp->GetPropertyString(AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE, "Scene.material"); +} + +void OgreImporter::ReadSubMesh(SubMesh &theSubMesh, XmlReader *Reader) +{ + vector FaceList; + vector Positions; bool HasPositions=false; + vector Normals; bool HasNormals=false; + vector Uvs; unsigned int NumUvs=0;//nearly always 2d, but assimp has always 3d texcoords + + XmlRead(Reader); + //TODO: maybe we have alsways just 1 faces and 1 geometry and always in this order. this loop will only work korrekt, wenn the order + //of faces and geometry changed, and not if we habe more than one of one + while(Reader->getNodeName()==string("faces") || string(Reader->getNodeName())=="geometry") + { + if(string(Reader->getNodeName())=="faces")//Read the face list + { + //some info logging: + unsigned int NumFaces=GetAttribute(Reader, "count"); + stringstream ss; ss <<"Submesh has " << NumFaces << " Faces."; + DefaultLogger::get()->info(ss.str()); + + while(XmlRead(Reader) && Reader->getNodeName()==string("face")) + { + Face NewFace; + NewFace.VertexIndices[0]=GetAttribute(Reader, "v1"); + NewFace.VertexIndices[1]=GetAttribute(Reader, "v2"); + NewFace.VertexIndices[2]=GetAttribute(Reader, "v3"); + if(Reader->getAttributeValue("v4"))//this should be supported in the future + { + throw new ImportErrorException("Submesh has quads, only traingles are supported!"); + } + FaceList.push_back(NewFace); + } + + } + else if(string(Reader->getNodeName())=="geometry")//Read the vertexdata + { + //some info logging: + unsigned int NumVertices=GetAttribute(Reader, "vertexcount"); + stringstream ss; ss<<"VertexCount: "<info(ss.str()); + + //General Informations about vertices + XmlRead(Reader); + if(!(Reader->getNodeName()==string("vertexbuffer"))) + { + throw new ImportErrorException("vertexbuffer node is not first in geometry node!"); + } + HasPositions=GetAttribute(Reader, "positions"); + HasNormals=GetAttribute(Reader, "normals"); + NumUvs=GetAttribute(Reader, "texture_coords"); + if(NumUvs>1) + throw new ImportErrorException("too many texcoords (just 1 supported!)"); + + //read all the vertices: + XmlRead(Reader); + while(Reader->getNodeName()==string("vertex")) + { + //read all vertex attributes: + + //Position + if(HasPositions) + { + XmlRead(Reader); + aiVector3D NewPos; + NewPos.x=GetAttribute(Reader, "x"); + NewPos.y=GetAttribute(Reader, "y"); + NewPos.z=GetAttribute(Reader, "z"); + Positions.push_back(NewPos); + } + + //Normal + if(HasNormals) + { + XmlRead(Reader); + aiVector3D NewNormal; + NewNormal.x=GetAttribute(Reader, "x"); + NewNormal.y=GetAttribute(Reader, "y"); + NewNormal.z=GetAttribute(Reader, "z"); + Normals.push_back(NewNormal); + } + + //Uv: + if(1==NumUvs) + { + XmlRead(Reader); + aiVector3D NewUv; + NewUv.x=GetAttribute(Reader, "u"); + NewUv.y=GetAttribute(Reader, "v"); + Uvs.push_back(NewUv); + } + XmlRead(Reader); + } + + } + } + DefaultLogger::get()->info(str(format("Positionen: %1% Normale: %2% TexCoords: %3%") % Positions.size() % Normals.size() % Uvs.size())); + + + //Make all Vertexes unique: (this is required by assimp) + vector UniqueFaceList(FaceList.size()); + vector UniquePositions(FaceList.size()*3);//*3 because each face consits of 3 vertexes, because we only support triangles^^ + vector UniqueNormals(FaceList.size()*3); + vector UniqueUvs(FaceList.size()*3); + + for(unsigned int i=0; imNumMeshes!=0) + throw new ImportErrorException("Currently only one mesh per File is allowed!!"); + + //---------------------Create the aiMesh:----------------------- + aiMesh* NewAiMesh=new aiMesh(); + + //Positions + NewAiMesh->mVertices=new aiVector3D[UniquePositions.size()]; + memcpy(NewAiMesh->mVertices, &UniquePositions[0], UniquePositions.size()*sizeof(aiVector3D)); + NewAiMesh->mNumVertices=UniquePositions.size(); + + //Normals + NewAiMesh->mNormals=new aiVector3D[UniqueNormals.size()]; + memcpy(NewAiMesh->mNormals, &UniqueNormals[0], UniqueNormals.size()*sizeof(aiVector3D)); + + //Uvs + NewAiMesh->mNumUVComponents[0]=2; + //NewAiMesh->mTextureCoords=new aiVector3D*[1]; + NewAiMesh->mTextureCoords[0]= new aiVector3D[UniqueUvs.size()]; + memcpy(NewAiMesh->mTextureCoords[0], &UniqueUvs[0], UniqueUvs.size()*sizeof(aiVector3D)); + + //Faces + NewAiMesh->mFaces=new aiFace[UniqueFaceList.size()]; + for(unsigned int i=0; imFaces[i].mNumIndices=3; + NewAiMesh->mFaces[i].mIndices=new unsigned int[3]; + + NewAiMesh->mFaces[i].mIndices[0]=UniqueFaceList[i].VertexIndices[0]; + NewAiMesh->mFaces[i].mIndices[1]=UniqueFaceList[i].VertexIndices[1]; + NewAiMesh->mFaces[i].mIndices[2]=UniqueFaceList[i].VertexIndices[2]; + } + NewAiMesh->mNumFaces=UniqueFaceList.size(); + + //Set the Material: + NewAiMesh->mMaterialIndex=0; + if(m_CurrentScene->mMaterials) + throw new ImportErrorException("only 1 material supported at this time!"); + m_CurrentScene->mMaterials=new aiMaterial*[1]; + m_CurrentScene->mNumMaterials=1; + m_CurrentScene->mMaterials[0]=MeshMat; + //________________________________________________________________ + + + //Attach the mesh to the scene: + m_CurrentScene->mNumMeshes=1; + m_CurrentScene->mMeshes=new aiMesh*; + m_CurrentScene->mMeshes[0]=NewAiMesh; + + + //stringstream ss; ss <<"Last Node: <" << Reader->getNodeName() << ">"; + //throw new ImportErrorException(ss.str()); +} + +aiMaterial* OgreImporter::LoadMaterial(std::string MaterialName) +{ + MaterialHelper *NewMaterial=new MaterialHelper(); + NewMaterial->AddProperty(&aiString(MaterialName.c_str()), AI_MATKEY_NAME); + /*For bettetr understanding of the material parser, here is a material example file: + + material Sarg + { + receive_shadows on + technique + { + pass + { + ambient 0.500000 0.500000 0.500000 1.000000 + diffuse 0.640000 0.640000 0.640000 1.000000 + specular 0.500000 0.500000 0.500000 1.000000 12.500000 + emissive 0.000000 0.000000 0.000000 1.000000 + texture_unit + { + texture SargTextur.tga + tex_address_mode wrap + filtering linear linear none + } + } + } + } + + */ + + + string MaterialFileName=m_CurrentFilename.substr(0, m_CurrentFilename.find('.'))+".material"; + DefaultLogger::get()->info(str(format("Trying to load %1%") % MaterialFileName)); + + //Read the file into memory and put it in a stringstream + stringstream ss; + {// after this block, the temporarly loaded data will be released + IOStream* MatFilePtr=m_CurrentIOHandler->Open(MaterialFileName); + if(NULL==MatFilePtr) + { + MatFilePtr=m_CurrentIOHandler->Open(m_MaterialLibFilename); + if(NULL==MatFilePtr) + { + DefaultLogger::get()->error(m_MaterialLibFilename+" and "+MaterialFileName + " could not be opned, Material will not be loaded!"); + return NewMaterial; + } + } + scoped_ptr MaterialFile(MatFilePtr); + vector FileData(MaterialFile->FileSize()); + MaterialFile->Read(&FileData[0], MaterialFile->FileSize(), 1); + BaseImporter::ConvertToUTF8(FileData); + + ss << &FileData[0]; + } + + string Line; + ss >> Line; + unsigned int Level=0;//Hierarchielevels in the material file, like { } blocks into another + while(!ss.eof()) + { + if(Line=="material") + { + ss >> Line; + if(Line==MaterialName)//Load the next material + { + ss >> Line; + if(Line!="{") + throw new ImportErrorException("empty material!"); + + while(Line!="}")//read until the end of the material + { + //Proceed to the first technique + ss >> Line; + if(Line=="technique") + { + ss >> Line; + if(Line!="{") + throw new ImportErrorException("empty technique!"); + while(Line!="}")//read until the end of the technique + { + ss >> Line; + if(Line=="pass") + { + ss >> Line; + if(Line!="{") + throw new ImportErrorException("empty pass!"); + while(Line!="}")//read until the end of the pass + { + ss >> Line; + if(Line=="ambient") + { + //read the ambient light values: + } + else if(Line=="diffuse") + { + } + else if(Line=="specular") + { + } + else if(Line=="emmisive") + { + } + else if(Line=="texture_unit") + { + ss >> Line; + if(Line!="{") + throw new ImportErrorException("empty texture unit!"); + while(Line!="}")//read until the end of the texture_unit + { + ss >> Line; + if(Line=="texture") + { + ss >> Line; + NewMaterial->AddProperty(&aiString(Line.c_str()), AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); + } + }//end of texture unit + } + } + } + }//end of technique + } + + }//end of material + } + else {} //this is the wrong material, proceed the file until we reach the next material + } + ss >> Line; + } + + return NewMaterial; +} + +}//namespace Ogre +}//namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_OGRE_IMPORTER \ No newline at end of file diff --git a/code/OgreImporter.h b/code/OgreImporter.h new file mode 100644 index 000000000..afb449e82 --- /dev/null +++ b/code/OgreImporter.h @@ -0,0 +1,122 @@ +#include "BaseImporter.h" + +#include + +#include "irrXMLWrapper.h" +#include "fast_atof.h" + +namespace Assimp +{ +namespace Ogre +{ + +typedef irr::io::IrrXMLReader XmlReader; + +//Forward declarations: +struct Face; +struct SubMesh; + +///The Main Ogre Importer Class +class OgreImporter : public BaseImporter +{ +public: + virtual bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const; + virtual void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); + virtual void GetExtensionList(std::string& append); + virtual void SetupProperties(const Importer* pImp); +private: + + ///Helper Functions to read parts of the XML File + /** @param Filename We need this to check for a material File with the same name.*/ + void ReadSubMesh(SubMesh& theSubMesh, XmlReader* Reader); + aiMaterial* LoadMaterial(std::string MaterialName); + + //Now we don't have to give theses parameters to all functions + std::string m_CurrentFilename; + std::string m_MaterialLibFilename; + IOSystem* m_CurrentIOHandler; + aiScene *m_CurrentScene; +}; + + + +//------------Helper Funktion to Get a Attribute Save--------------- +template inline t GetAttribute(XmlReader* Reader, std::string Name) +{ + throw std::exception("unimplemented Funtcion used!"); + return t(); +} + +template<> inline int GetAttribute(XmlReader* Reader, std::string Name) +{ + const char* Value=Reader->getAttributeValue(Name.c_str()); + if(Value) + return atoi(Value); + else + throw ImportErrorException(std::string("Attribute "+Name+" does not exist in "+Reader->getNodeName()).c_str()); +} + +template<> inline float GetAttribute(XmlReader* Reader, std::string Name) +{ + const char* Value=Reader->getAttributeValue(Name.c_str()); + if(Value) + return fast_atof(Value); + else + throw ImportErrorException(std::string("Attribute "+Name+" does not exist in "+Reader->getNodeName()).c_str()); +} + +template<> inline std::string GetAttribute(XmlReader* Reader, std::string Name) +{ + const char* Value=Reader->getAttributeValue(Name.c_str()); + if(Value) + return std::string(Value); + else + throw new ImportErrorException(std::string("Attribute "+Name+" does not exist in "+Reader->getNodeName()).c_str()); +} + +template<> inline bool GetAttribute(XmlReader* Reader, std::string Name) +{ + const char* Value=Reader->getAttributeValue(Name.c_str()); + if(Value) + { + if(Value==std::string("true")) + return true; + else if(Value==std::string("false")) + return false; + else + throw new ImportErrorException(std::string("Bool value has invalid value: "+Name+" / "+Value+" / "+Reader->getNodeName())); + } + else + throw new ImportErrorException(std::string("Attribute "+Name+" does not exist in "+Reader->getNodeName()).c_str()); +} +//__________________________________________________________________ + +inline bool XmlRead(XmlReader* Reader) +{ + do + { + if(!Reader->read()) + return false; + } + while(Reader->getNodeType()!=irr::io::EXN_ELEMENT); + return true; +} + + + +///For the moment just triangles, no other polygon types! +struct Face +{ + unsigned int VertexIndices[3]; +}; + +/// Helper Class to describe a complete SubMesh +struct SubMesh +{ + std::string Name; + std::string MaterialName; + std::vector Faces; +}; + +}//namespace Ogre +}//namespace Assimp \ No newline at end of file diff --git a/doc/dox.h b/doc/dox.h index 40e3633a5..566bffbe2 100644 --- a/doc/dox.h +++ b/doc/dox.h @@ -49,8 +49,11 @@ that it has not yet been implemented, and some formats have not completely been Object File Format ( *.off ).
Terragen Terrain ( *.ter )
3D GameStudio Model ( *.mdl )
-3D GameStudio Terrain ( *.hmp )


+3D GameStudio Terrain ( *.hmp )
+Ogre (.mesh.xml, .skeleton.xml, .material)

+See the @link importer_notes Importer Notes Page @endlink for informations, what a specific importer can do and what not.
+ 3: These formats support animations, but ASSIMP doesn't yet support them (or they're buggy)

@@ -778,7 +781,8 @@ OK, that sounds too easy :-). The whole procedure for a new loader merely looks
  • Create a header (FormatNameImporter.h) and a unit (FormatNameImporter.cpp) in the <root>/code/ directory
  • -
  • Add them to the following workspaces: vc8, vc9, CMAKE
  • +
  • Add them to the following workspaces: vc8 and vc9 (the files are in the workspaces directory), CMAKE (code/CMakeLists.txt, create a new +source group for your importer and put them also to ADD_LIBRARY( assimp SHARED))
  • Include AssimpPCH.h - this is the PCH file, and it includes already most Assimp-internal stuff.
  • Open Importer.cpp and include your header just below the (include_new_importers_here) line, guarded by a #define @@ -789,7 +793,7 @@ guarded by a #define @endcode Wrap the same guard around your .cpp!
  • -
  • No advance to the (register_new_importers_here) line in the Importer.cpp and register your importer there - just like all the others do.
  • +
  • Now advance to the (register_new_importers_here) line in the Importer.cpp and register your importer there - just like all the others do.
  • Setup a suitable test environment (i.e. use AssimpView or your own application), make sure to enable the #aiProcess_ValidateDataStructure flag and enable verbose logging. That is, simply call before you import anything: @code @@ -814,6 +818,14 @@ Done! Please, share your loader that everyone can profit from it!
+@section properties Properties + +You can use properties to chance the behavior of you importer. In order to do so, you have to overide BaseImporter::SetupProperties, and specify +you custom properties in aiConfig.h. Just have a look to the other AI_CONFIG_IMPORT_* defines and you will understand, how it works. + +The properties can be set with Importer::SetProperty***() and can be accessed in your SetupProperties function with Importer::GetProperty***(). You can +store the properties as a member variable of your importer, they are thread safe. + @section tnote Notes for text importers
    @@ -865,6 +877,12 @@ MaterialHelper* mat = new MaterialHelper(); const float spec = 16.f; mat->AddProperty(&spec, 1, AI_MATKEY_SHININESS); + +//set the name of the material: +NewMaterial->AddProperty(&aiString(MaterialName.c_str()), AI_MATKEY_NAME);//MaterialName is a std::string + +//set the first diffuse texture +NewMaterial->AddProperty(&aiString(Texturename.c_str()), AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0));//again, Texturename is a std::string @endcode @section boost Boost @@ -1390,3 +1408,34 @@ Build: alles von CustomBuild + DirectX + MFC? +/** +@page importer_notes Importer Notes + +@section ogre Ogre +Ogre importer is WIP and optimized for the Blender Ogre exporter! + +XML Format: There is a binary and a XML mesh Format from Ogre. This loader can only +Handle xml files, but don't panic, there is a command line converter, which you can use +to create XML files from Binary Files. Just look on the Ogre page for it. + +Currently you can only load meshes. So you will need to import the *.mesh.xml file, the loader will +try to find the appendant material and skeleton file. + +The skeleton file must have the same name as the mesh file, e.g. fish.mesh.xml and fish.skeleton.xml. + +The material file can have the same name as the mesh file, or you can use +Importer::Importer::SetPropertyString(AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE, "materiafile.material") to specify +the name of the material file. This is especially usefull if multiply materials a stored in a single file. +The importer will first try to load the material with the same name as the mesh and only if this can't be open try +to load the alternate material file. The default material filename is "Scene.material". + +What will be loaded? + +Mesh: Faces, Positions, Normals and one Uv pair. The Materialname will be used to load the material + +Material: The right material in the file will be searched, the importer should work with materials who +have 1 technique and 1 pass in this technique. From there, the texturename will be loaded. Also, the +materialname will be set. + +Skeleton: Nothing, yet. +*/ \ No newline at end of file diff --git a/include/aiConfig.h b/include/aiConfig.h index 41bbb40e2..aec5bc6e6 100644 --- a/include/aiConfig.h +++ b/include/aiConfig.h @@ -552,6 +552,12 @@ enum aiComponent #define AI_CONFIG_IMPORT_IRR_ANIM_FPS \ "IMPORT_IRR_ANIM_FPS" +/// Ogre Importer will try to load this Materialfile +/** +Ogre Mehs contain only the MaterialName, not the MaterialFile. If there is no material file +with the same name as the material, Ogre Importer will try to load this file and search the material in it. +*/ +#define AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE "IMPORT_OGRE_MATERIAL_FILE" #endif // !! AI_CONFIG_H_INC diff --git a/workspaces/vc8/assimp.vcproj b/workspaces/vc8/assimp.vcproj index 871dc84c1..f7480f433 100644 --- a/workspaces/vc8/assimp.vcproj +++ b/workspaces/vc8/assimp.vcproj @@ -1935,6 +1935,18 @@ > + + + + + + diff --git a/workspaces/vc9/assimp.vcproj b/workspaces/vc9/assimp.vcproj index 7b965847e..c5d6af9ec 100644 --- a/workspaces/vc9/assimp.vcproj +++ b/workspaces/vc9/assimp.vcproj @@ -1943,6 +1943,18 @@ > + + + + + +