From 8cfbc343673d77da6d7f8dc9c394465fd84ba6fe Mon Sep 17 00:00:00 2001 From: Amorilia <amorilia@users.sourceforge.net> Date: Wed, 21 Mar 2007 08:16:50 +0000 Subject: [PATCH] The SWIG wrapper is now functional again, although only a very limited number of objects are supported so far, to reduce the size of the wrapper. Details: * set property eolstyle:native on .cpp and .h files for better windows + linux support * added small cpp file for quick nif read and write test (for debug purposes) * fixed memory leak due to array of non-fixed sized objects: Header.copyright (caused segfaults under GCC) * disabled pragma warning on NvTriStripObjects.cpp * fixed undefined behaviour of map[...] = ... map.size() ... in niflib.cpp (caused segfaults under GCC) * cleaned up SConstruct file - if DEBUG is false, TUNE is assumed - added TEST option * workaround in pyniflib.h for ReadNifTree (need to find better solution!) * swig wrapper interface: NiNode, NiAVObject, and NiObjectNET are fully supported (nothing else, for now) * now using unittest for python test script(s) --- NvTriStrip/NvTriStripObjects.cpp | 2 +- SConstruct | 131 ++++++++++++++----------------- include/gen/Header.h | 2 +- niflib_test.cpp | 38 +++++++++ src/gen/Header.cpp | 2 +- src/niflib.cpp | 16 ++-- swig/SConscript | 6 ++ swig/niflib_test.py | 41 ++++++++++ swig/pyniflib.h | 6 ++ swig/pyniflib.i | 42 ++++++---- swig/pyniflib_test.py | 13 --- 11 files changed, 187 insertions(+), 112 deletions(-) create mode 100644 niflib_test.cpp create mode 100644 swig/niflib_test.py delete mode 100644 swig/pyniflib_test.py diff --git a/NvTriStrip/NvTriStripObjects.cpp b/NvTriStrip/NvTriStripObjects.cpp index d2c32478..6428bf4f 100644 --- a/NvTriStrip/NvTriStripObjects.cpp +++ b/NvTriStrip/NvTriStripObjects.cpp @@ -1,5 +1,5 @@ -#pragma warning( disable : 4786 ) +//#pragma warning( disable : 4786 ) #include <assert.h> #include <set> diff --git a/SConstruct b/SConstruct index ea454ee4..708b24e7 100644 --- a/SConstruct +++ b/SConstruct @@ -11,94 +11,79 @@ Help(""" ##Global Vars, should add cc, cxx, build, etc eventually... ##May want to turn Jobs down, we will try and detect an appropriate JOB rate for linux -JOBS ='1' -DETECT_JOBS = 'yes' #Set this to no, if you are setting JOBS!! -DEBUG ='no' -TUNE ='yes' -CFFLAGS_EXTRA_WARNING ='no' -PYWRAP = True # set to False if you do not want to build the python wrapper +JOBS = 1 # default number of jobs, if detection fails +DETECT_JOBS = True # set this to False, if you are setting JOBS +DEBUG = True # turn on debugging info? +CFFLAGS_EXTRA_WARNING = False # extra compiler warnings +PYWRAP = True # build the python wrapper? +TEST = True # build test scripts? # Setting up a basic default environment # Theory is it can be expanded for versatility, like swig doesn't seem to like jobs of 4 env = Environment(ENV = os.environ) - -# detect platform -if sys.platform == 'linux2' or sys.platform == 'linux-i386': - OS = 'linux' - python_lib = ['python%d.%d' % sys.version_info[0:2]] - python_libpath = [sysconfig.get_python_lib (0, 1) + '/config'] - python_include = [sysconfig.get_python_inc ()] - env.Append(CCFLAGS = ' -Iinclude -fPIC -Wall -pipe') -elif sys.platform == 'cygwin': - OS = 'linux' +# linux platform +if sys.platform in ['linux2', 'linux-i386', 'cygwin']: + # set python environment python_lib = ['python%d.%d' % sys.version_info[0:2]] python_libpath = [sysconfig.get_python_lib (0, 1) + '/config'] python_include = [sysconfig.get_python_inc ()] + # set compiler flags env.Append(CCFLAGS = ' -Wall') + if CFFLAGS_EXTRA_WARNING: + env.Append(CCFLAGS = ' -Wextra') + if sys.platform != 'cygwin': + env.Append(CCFLAGS = ' -fPIC -pipe') + # flags for debugging info + if DEBUG: + env.Append(CCFLAGS = ' -O0 -g3 -ggdb') + # flags for tuning (only if DEBUG is False) + else: + env.Append(CCFLAGS = ' -O3') + # architecture specific tuning + # x86, x86_64, or what? (better way?) + proc = commands.getoutput('uname -m') + if proc == 'x86_64': + arch = 'x86_64' + #elif exp.match(proc): # broken + elif proc in ['x86', 'i386', 'i486', 'i586', 'i686']: + arch = 'x86' + elif proc in ['Power Macintosh', 'ppc']: + arch = 'ppc' + else: + arch = None + # needs to be cleaned up and expanded to include Intel + if arch == 'x86_64': + if commands.getoutput('uname -i') == 'AuthenticAMD': + env.Append(CCFLAGS = ' -mtune=k8') + #elif commands.getoutput('uname -i') == 'GenuineIntel': + # env.Append(CCFLAGS = ' -mtune=nocona') + # detect the number of jobs + if DETECT_JOBS: + detected_jobs = int(commands.getoutput('cat /proc/cpuinfo | grep -c "^processor"')) + if detected_jobs >= 2: + JOBS = detected_jobs + +# windows platform elif sys.platform == 'win32': - OS = 'windows' python_include = [sysconfig.get_python_inc()] python_libpath = [sysconfig.get_python_lib(1, 1) + '/../libs'] python_lib = ['python24'] - env.Append(CCFLAGS = '/EHsc /O2 /GS /Zi /TP') + env.Append(CCFLAGS = '/EHsc /GS /TP') + if DEBUG: + env.Append(CCFLAGS = ' /Zi') + else: + env.Append(CCFLAGS = ' /O2') + +# other platforms else: print "Error: Platform %s not supported."%sys.platform Exit(1) - - -#Debug, Extra, 02 checks -if (sys.platform == 'linux2' or sys.platform == 'linux-i386') or sys.platform == 'cygwin': - if DEBUG == 'yes': - env.Append(CCFLAGS = ' -g3') - if CFFLAGS_EXTRA_WARNING == 'yes': - env.Append(CCFLAGS = ' -Wextra') - if TUNE == 'yes': - env.Append(CCFLAGS = ' -O2') - if TUNE == 'yes' and DEBUG == 'yes' : - print "TUNE and DEBUG may conflict!!" - - - -# Proc x86, x86_64, or what? Better way? -proc = commands.getoutput('uname -m') -if proc == 'x86_64': - arch = 'x86_64' -#elif exp.match(proc): # broken -elif proc == 'x86': - arch = 'x86' -elif proc == 'Power Macintosh' or proc == 'ppc': - arch = 'ppc' -else: - arch = 'cpu' - -#This is ugly, needs to be cleaned up and expanded to include Intel. -# -if arch == 'x86_64' and TUNE == 'yes': - if commands.getoutput('uname -i') == 'AuthenticAMD': - env.Append(CCFLAGS = ' -mtune=k8') -# if commands.getoutput('uname -i') == 'GenuineIntel': -# env.Append(CCFLAGS = ' -mtune=nocona') -#Should cover 64 bit versions of Intel chips, but don't have one and never bothered to look into it - - - -#We want to detect the number of jobs! Although some folks depending on sys load may want to set it more aggressively. -if DETECT_JOBS == 'yes' and OS == 'linux': - detected_jobs = commands.getoutput('cat /proc/cpuinfo | grep -c "^processor"') - if detected_jobs >= '1': - print "Detected Jobs: %s"%detected_jobs - JOBS = detected_jobs - print "Detected Jobs changing to %s"%JOBS - else: - print "Error Detecting jobs!" - - +# set number of jobs env.SetOption('num_jobs', JOBS) - # detect SWIG try: env['SWIG'] @@ -111,6 +96,7 @@ Please install SWIG to build the python wrapper.""" print "You can download SWIG from http://www.swig.org/\n" + gen_objfiles = Split(""" src/gen/ByteArray.cpp src/gen/Footer.cpp @@ -401,10 +387,9 @@ niflib = env.StaticLibrary('niflib', [core_objfiles, gen_objfiles, obj_objfiles, # build Python wrapper if PYWRAP: - SConscript('swig/SConscript' , exports=['env', 'python_lib', 'python_libpath', 'python_include', 'niflib']) - -# Here's how to compile niflyze: -#env.Program('niflyze', 'niflyze.cpp', LIBS=[niflib], LIBPATH=['.']) + SConscript('swig/SConscript' , exports=['env', 'python_lib', 'python_libpath', 'python_include', 'niflib', 'TEST']) # A test program: -#env.Program('test', 'test.cpp', LIBS=[niflib], LIBPATH=['.']) +if TEST: + env.Program('niflib_test', 'niflib_test.cpp', LIBS=[niflib], LIBPATH=['.']) + diff --git a/include/gen/Header.h b/include/gen/Header.h index efe36b96..8fef87f7 100644 --- a/include/gen/Header.h +++ b/include/gen/Header.h @@ -31,7 +31,7 @@ struct NIFLIB_API Header { /*! * Unknown. */ - array<3,LineString > copyright; + vector<LineString> copyright; /*! * The NIF version, in hexadecimal notation: 0x04000002, 0x0401000C, * 0x04020002, 0x04020100, 0x04020200, 0x0A000100, 0x0A010000, diff --git a/niflib_test.cpp b/niflib_test.cpp new file mode 100644 index 00000000..eecee5a2 --- /dev/null +++ b/niflib_test.cpp @@ -0,0 +1,38 @@ +#include <cassert> + +#include "include/niflib.h" +#include "include/obj/NiNode.h" + +using namespace Niflib; +using namespace std; + +void CreateSimpleNif() { + NiNodeRef root(new NiNode); + Matrix44 x(Vector3(2,3,4),Matrix33(0,0,1,0,1,0,-1,0,0),0.123); + root->SetLocalTransform(x); + NifInfo nifinfo(0x14000005); + nifinfo.userVersion = 11; + nifinfo.creator = "amorilia"; + WriteNifTree("test.nif", root, nifinfo); +}; + +void CheckSimpleNif() { + NifInfo nifinfo; + NiObjectRef root_obj = ReadNifTree("test.nif", &nifinfo); + NiNodeRef root = DynamicCast<NiNode>(root_obj); + assert(nifinfo.version == 0x14000005); + assert(nifinfo.userVersion == 11); + assert(nifinfo.creator == "amorilia"); + assert(root != NULL); + Matrix44 x(Vector3(2,3,4),Matrix33(0,0,1,0,1,0,-1,0,0),0.123); + Matrix44 y = root->GetLocalTransform(); + for (int i=0; i<4; i++) + for (int j=0; j<4; j++) + assert(abs(x[i][j]-y[i][j]) < 0.0001); +}; + +int main() { + CreateSimpleNif(); + CheckSimpleNif(); + return 0; +} diff --git a/src/gen/Header.cpp b/src/gen/Header.cpp index a861387d..28c13690 100644 --- a/src/gen/Header.cpp +++ b/src/gen/Header.cpp @@ -5,7 +5,7 @@ All rights reserved. Please see niflib.h for licence. */ using namespace Niflib; //Constructor -Header::Header() : version((unsigned int)0x04000002), endianType((byte)1), userVersion((unsigned int)0), numBlocks((unsigned int)0), userVersion2((unsigned int)0), numBlockTypes((unsigned short)0), unknownInt2((unsigned int)0) {}; +Header::Header() : copyright(3), version((unsigned int)0x04000002), endianType((byte)1), userVersion((unsigned int)0), numBlocks((unsigned int)0), userVersion2((unsigned int)0), numBlockTypes((unsigned short)0), unknownInt2((unsigned int)0) {}; //Copy Constructor Header::Header( const Header & src ) { diff --git a/src/niflib.cpp b/src/niflib.cpp index c4b5a82d..273f9a57 100644 --- a/src/niflib.cpp +++ b/src/niflib.cpp @@ -138,10 +138,9 @@ vector<NiObjectRef> ReadNifList( string const & file_name, NifInfo * info ) { //--Open File--// ifstream in( file_name.c_str(), ifstream::binary ); - - return ReadNifList( in, info ); - + vector<NiObjectRef> ret = ReadNifList( in, info ); in.close(); + return ret; } vector<NiObjectRef> ReadNifList( istream & in, NifInfo * info ) { @@ -354,7 +353,7 @@ vector<NiObjectRef> ReadNifList( istream & in, NifInfo * info ) { for ( map<unsigned,NiObjectRef>::iterator it = objects.begin(); it != objects.end(); ++it ) { #ifdef DEBUG_LINK_PHASE - cout << i << ": " << blocks[i] << endl; + cout << it->first << ": " << it->second << endl; #endif //Fix links & other pre-processing it->second->FixLinks( objects, link_stack, header.version, header.userVersion ); @@ -505,7 +504,8 @@ void EnumerateObjects( NiObject * root, map<Type*,unsigned int> & type_map, map< //Add this object type to the map if it isn't there already if ( type_map.find( (Type*)&(root->GetType()) ) == type_map.end() ) { //The type has not yet been registered, so register it - type_map[ (Type*)&(root->GetType()) ] = (unsigned int)(type_map.size()); + unsigned int n = type_map.size(); + type_map[ (Type*)&(root->GetType()) ] = n; } // Oblivion has very rigid requirements about block ordering and the bhkRigidBody @@ -522,7 +522,8 @@ void EnumerateObjects( NiObject * root, map<Type*,unsigned int> & type_map, map< // If reverse is set then add the link after children otherwise add it before if (!reverse) { - link_map[root] = (unsigned int)(link_map.size()); + unsigned int n = link_map.size(); + link_map[root] = n; } //Call this function on all links of this object @@ -534,7 +535,8 @@ void EnumerateObjects( NiObject * root, map<Type*,unsigned int> & type_map, map< } if (reverse) { - link_map[root] = (unsigned int)(link_map.size()); + unsigned int n = link_map.size(); + link_map[root] = n; } } diff --git a/swig/SConscript b/swig/SConscript index 095959e3..18550b79 100644 --- a/swig/SConscript +++ b/swig/SConscript @@ -1,3 +1,9 @@ Import('*') niflib_python = env.SharedLibrary('_niflib', 'pyniflib.i', LIBS=[niflib] + python_lib, LIBPATH = python_libpath, SWIGFLAGS = '-c++ -python', CPPPATH = ['.'] + python_include, SHLIBPREFIX='') + +if TEST: + import unittest + import niflib_test + unittest.main(niflib_test) + diff --git a/swig/niflib_test.py b/swig/niflib_test.py new file mode 100644 index 00000000..30fb606d --- /dev/null +++ b/swig/niflib_test.py @@ -0,0 +1,41 @@ +# quick test of the niflib library in python + +import unittest +import os +from niflib import * + +class TestNiNode(unittest.TestCase): + def setUp(self): + # (TODO: use python's temporary file creation method) + + # create nif file + root = CreateNiNode() + self.x = Matrix44(Vector3(2,3,4),Matrix33(0,0,1,0,1,0,-1,0,0),0.123) + root.SetLocalTransform(self.x) + self.nifinfo = NifInfo() + self.nifinfo.version = 0x14000005 + self.nifinfo.creator = "amorilia" + self.nifinfo.userVersion = 11 + WriteNifTree("test.nif", root.Ptr(), self.nifinfo) + + # read nif file + self.nifinfo_in = NifInfo() + root_obj = ReadNifFile("test.nif", self.nifinfo_in) + root_in = DynamicCastToNiNode(root_obj.Ptr()) + + self.x_in = root_in.GetLocalTransform() + + # delete nif file + os.remove("test.nif") + + def test(self): + self.failIf(self.nifinfo.version != self.nifinfo_in.version) + self.failIf(self.nifinfo.userVersion != self.nifinfo_in.userVersion) + self.failIf(self.nifinfo.creator != self.nifinfo_in.creator) + for i in range(4): + for j in range(4): + self.assertAlmostEqual(self.x[i][j], self.x_in[i][j], 5) + +if __name__ == '__main__': + unittest.main() + diff --git a/swig/pyniflib.h b/swig/pyniflib.h index c6e5e53f..3dbd281b 100644 --- a/swig/pyniflib.h +++ b/swig/pyniflib.h @@ -1,6 +1,8 @@ #ifndef _PYNIFLIB_H_ #define _PYNIFLIB_H_ +#include "../include/niflib.h" +#include "../include/obj/NiObject.h" #include "../include/obj/NiNode.h" namespace Niflib { @@ -9,6 +11,10 @@ using namespace std; Niflib::NiNodeRef CreateNiNode() { return Niflib::NiNodeRef(new Niflib::NiNode); } +// SWIG does not like ReadNifTree; so far I have no idea why. +// As a workaround, you can use this wrapper function. +Niflib::NiObjectRef ReadNifFile( string name, Niflib::NifInfo * nifinfo) { return Niflib::ReadNifTree(name, nifinfo);} + }; #endif diff --git a/swig/pyniflib.i b/swig/pyniflib.i index c11d15e8..0f7a60a3 100644 --- a/swig/pyniflib.i +++ b/swig/pyniflib.i @@ -76,20 +76,21 @@ POSSIBILITY OF SUCH DAMAGE. */ // essential for NIF exporting and importing scripts. // This will reduce the size of the wrapper file. -/* - // various function ignores (might be put pack in final version) %ignore asString; %ignore FixLinks; %ignore Read; %ignore Write; %ignore operator=; +%ignore operator[]; %ignore TypeConst; %ignore GetIDString; %ignore IsSameType; %ignore IsDerivedType; %ignore NumObjectsInMemory; +/* + // vector ignores (will be unignored again once wrapper is functional) %ignore iterator; %ignore pop; @@ -117,7 +118,9 @@ POSSIBILITY OF SUCH DAMAGE. */ */ // ignores objects python does not need to know of anyway -//ignore Ptr; +//%ignore Ptr; + +/* // ignore all base objects: we only need access via the Ref objects %ignore NiObject; @@ -343,6 +346,8 @@ POSSIBILITY OF SUCH DAMAGE. */ %ignore NiZBufferProperty; %ignore RootCollisionNode; +*/ + // Import the symbols from these but do not include them in the wrapper %import "../include/gen/obj_defines.h" %import "../include/NIF_IO.h" @@ -359,10 +364,18 @@ POSSIBILITY OF SUCH DAMAGE. */ %{ #include "../include/niflib.h" +%} + +// SWIG is very picky about namespaces. Using namespace Niflib throughout +// ensures that SWIG wraps all types in the right way. +%{ +using namespace Niflib; +%} + +%{ #include "../include/Ref.h" #include "../include/Type.h" #include "../include/nif_math.h" - #include "../include/obj/NiObject.h" #include "../include/obj/AKeyedData.h" #include "../include/obj/AParticleModifier.h" @@ -617,11 +630,6 @@ POSSIBILITY OF SUCH DAMAGE. */ #include "../include/gen/LimitedHingeDescriptor.h" %} -// This resolves an issue with SWIG and namespace? -%{ -using namespace Niflib; -%} - // Extra Python interface functions are defined next %include "pyniflib.h" %{ @@ -630,7 +638,7 @@ using namespace Niflib; %} // Data structures - +/* %include "../include/gen/ByteArray.h" %include "../include/gen/Footer.h" %include "../include/gen/LODRange.h" @@ -693,18 +701,20 @@ using namespace Niflib; %template(vector_MatchGroup) std::vector<Niflib::MatchGroup>; %template(pair_int_float) std::pair<int, float>; %template(map_int_float) std::map<int, float>; +*/ // NIF file blocks %include "../include/obj/NiObject.h" -//%ignore NiObjectRef; +%template(NiObjectRef) Niflib::Ref<Niflib::NiObject>; %include "../include/obj/NiObjectNET.h"; -//%ignore NiObjectNetRef; -//template(NiObjectNETRef) Niflib::Ref<Niflib::NiObjectNET>; -//template(DynamicCastToNiObjectNET) Niflib::DynamicCast<Niflib::NiObjectNET>; -//template(StaticCastToNiObjectNET) Niflib::StaticCast<Niflib::NiObjectNET>; +%template(NiObjectNETRef) Niflib::Ref<Niflib::NiObjectNET>; +%template(DynamicCastToNiObjectNET) Niflib::DynamicCast<Niflib::NiObjectNET>; +%template(StaticCastToNiObjectNET) Niflib::StaticCast<Niflib::NiObjectNET>; %include "../include/obj/NiAVObject.h"; -//%ignore NiAVObjectRef; +%template(NiAVObjectRef) Niflib::Ref<Niflib::NiAVObject>; +%template(DynamicCastToNiAVObject) Niflib::DynamicCast<Niflib::NiAVObject>; +%template(StaticCastToNiAVObject) Niflib::StaticCast<Niflib::NiAVObject>; %include "../include/obj/NiNode.h" %template(NiNodeRef) Niflib::Ref<Niflib::NiNode>; %template(DynamicCastToNiNode) Niflib::DynamicCast<Niflib::NiNode>; diff --git a/swig/pyniflib_test.py b/swig/pyniflib_test.py deleted file mode 100644 index 225190fa..00000000 --- a/swig/pyniflib_test.py +++ /dev/null @@ -1,13 +0,0 @@ -# quick test of the niflib library in python - -from niflib import * - -root = CreateNiNode() -x = Matrix44(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16) -root.SetLocalTransform(x) - -nifinfo = NifInfo() -nifinfo.version = 0x14000005 -nifinfo.creator = "amorilia" -WriteNifTree( "test.nif", root.Ptr(), nifinfo ) - -- GitLab