From 249460892710b656ec9adde40deb036c0d2221c7 Mon Sep 17 00:00:00 2001 From: Bill Roeske Date: Mon, 3 Jun 2013 10:38:30 +0200 Subject: [PATCH] [pyassimp] Fix py3.3 + 64bits issues 64-bit Compatibility: The first four characters of a String material property would be cut off. A String's length is defined in structs.py as a c_size_t variable, which is 8 bytes wide on 64-bit Python. However, when an aiString is used as an aiMaterial property in C/C++, the length is truncated down to a 4-byte value on 64-bit machines (see MaterialSystem.cpp aiMaterial::AddProperty() for details). A new struct was declared in structs.py (MaterialPropertyString) and used in core._get_properties(). Python 3.3 Compatibility: The built-in function hasattr() changed in Python 3.2 to not trap exceptions, which means a NULL pointer ValueException now escaped when checking if a pointer was valid (hasattr(obj, 'contents') in core.call_init()) (see http://bugs.python.org/issue9666 for details). A new helper function was defined that preserves the legacy functionality of trapping the exceptions (helper.hasattr_silent()) and used throughout the code as a replacement for hasattr(). String objects would import as "bytes" rather than as a string. This was most noticeable in the key names for material properties, where the trailing ' of a bytes object would remain after it was converted to a string. The solution was to call decode() on the bytes object using utf-8 decoding. This applies to various parts of core.py. Closes #35 --- port/PyAssimp/pyassimp/core.py | 16 ++++++++-------- port/PyAssimp/pyassimp/helper.py | 14 ++++++++++++-- port/PyAssimp/pyassimp/structs.py | 22 +++++++++++++++++++++- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/port/PyAssimp/pyassimp/core.py b/port/PyAssimp/pyassimp/core.py index 6df31be8e..22670431d 100644 --- a/port/PyAssimp/pyassimp/core.py +++ b/port/PyAssimp/pyassimp/core.py @@ -59,12 +59,12 @@ def make_tuple(ai_obj, type = None): def call_init(obj, caller = None): # init children - if hasattr(obj, '_init'): + if helper.hasattr_silent(obj, '_init'): obj._init(parent = caller) # pointers - elif hasattr(obj, 'contents'): - if hasattr(obj.contents, '_init'): + elif helper.hasattr_silent(obj, 'contents'): + if helper.hasattr_silent(obj.contents, '_init'): obj.contents._init(target = obj, parent = caller) @@ -76,7 +76,7 @@ def _init(self, target = None, parent = None): :param target: set the object which receive the added methods. Useful when manipulating pointers, to skip the intermediate 'contents' deferencing. """ - if hasattr(self, '_is_init'): + if helper.hasattr_silent(self, '_is_init'): return self self._is_init = True @@ -108,7 +108,7 @@ def _init(self, target = None, parent = None): if isinstance(obj, structs.String): - setattr(target, 'name', str(obj.data)) + setattr(target, 'name', obj.data.decode("utf-8")) setattr(target.__class__, '__repr__', lambda x: str(x.__class__) + "(" + x.name + ")") setattr(target.__class__, '__str__', lambda x: x.name) continue @@ -121,7 +121,7 @@ def _init(self, target = None, parent = None): logger.debug("Added a parent as self." + name) continue - if hasattr(self, 'mNum' + m[1:]): + if helper.hasattr_silent(self, 'mNum' + m[1:]): length = getattr(self, 'mNum' + m[1:]) @@ -346,7 +346,7 @@ def _get_properties(properties, length): for p in [properties[i] for i in range(length)]: #the name p = p.contents - key = str(p.mKey.data).split('.')[1] + key = str(p.mKey.data.decode("utf-8")).split('.')[1] #the data from ctypes import POINTER, cast, c_int, c_float, sizeof @@ -354,7 +354,7 @@ def _get_properties(properties, length): arr = cast(p.mData, POINTER(c_float * int(p.mDataLength/sizeof(c_float)) )).contents value = [x for x in arr] elif p.mType == 3: #string can't be an array - value = cast(p.mData, POINTER(structs.String)).contents.data + value = cast(p.mData, POINTER(structs.MaterialPropertyString)).contents.data.decode("utf-8") elif p.mType == 4: arr = cast(p.mData, POINTER(c_int * int(p.mDataLength/sizeof(c_int)) )).contents value = [x for x in arr] diff --git a/port/PyAssimp/pyassimp/helper.py b/port/PyAssimp/pyassimp/helper.py index 6a6339d33..2e1b41954 100644 --- a/port/PyAssimp/pyassimp/helper.py +++ b/port/PyAssimp/pyassimp/helper.py @@ -154,5 +154,15 @@ def search_library(): # XXX: take version postfix of the .so on linux? return res[1:] - - +def hasattr_silent(object, name): + """ + Calls hasttr() with the given parameters and preserves the legacy (pre-Python 3.2) + functionality of silently catching exceptions. + + Returns the result of hasatter() or False if an exception was raised. + """ + + try: + return hasattr(object, name) + except: + return False diff --git a/port/PyAssimp/pyassimp/structs.py b/port/PyAssimp/pyassimp/structs.py index fb98ee53d..9d8f5a5a1 100644 --- a/port/PyAssimp/pyassimp/structs.py +++ b/port/PyAssimp/pyassimp/structs.py @@ -1,6 +1,6 @@ #-*- coding: UTF-8 -*- -from ctypes import POINTER, c_void_p, c_int, c_uint, c_char, c_float, Structure, c_char_p, c_double, c_ubyte, c_size_t +from ctypes import POINTER, c_void_p, c_int, c_uint, c_char, c_float, Structure, c_char_p, c_double, c_ubyte, c_size_t, c_uint32 class Vector2D(Structure): @@ -82,6 +82,26 @@ class String(Structure): ("data", c_char*MAXLEN), ] +class MaterialPropertyString(Structure): + """ + See 'aiTypes.h' for details. + + The size of length is truncated to 4 bytes on 64-bit platforms when used as a + material property (see MaterialSystem.cpp aiMaterial::AddProperty() for details). + """ + + MAXLEN = 1024 + + _fields_ = [ + # Binary length of the string excluding the terminal 0. This is NOT the + # logical length of strings containing UTF-8 multibyte sequences! It's + # the number of bytes from the beginning of the string to its end. + ("length", c_uint32), + + # String buffer. Size limit is MAXLEN + ("data", c_char*MAXLEN), + ] + class MemoryInfo(Structure): """ See 'aiTypes.h' for details.