Skip to content

Commit

Permalink
Fixed wrong Property-Handling of olefile-package.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jens M. Plonka committed Dec 6, 2024
1 parent 5de56ab commit 1339fc5
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 18 deletions.
202 changes: 200 additions & 2 deletions Import_IPT.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,29 @@
from importerSAT import importModel, convertModel
from Acis import setReader
from PySide.QtGui import QMessageBox
from struct import unpack

VT_EMPTY=0; VT_NULL=1; VT_I2=2; VT_I4=3; VT_R4=4; VT_R8=5; VT_CY=6;
VT_DATE=7; VT_BSTR=8; VT_DISPATCH=9; VT_ERROR=10; VT_BOOL=11;
VT_VARIANT=12; VT_UNKNOWN=13; VT_DECIMAL=14; VT_I1=16; VT_UI1=17;
VT_UI2=18; VT_UI4=19; VT_I8=20; VT_UI8=21; VT_INT=22; VT_UINT=23;
VT_VOID=24; VT_HRESULT=25; VT_PTR=26; VT_SAFEARRAY=27; VT_CARRAY=28;
VT_USERDEFINED=29; VT_LPSTR=30; VT_LPWSTR=31; VT_FILETIME=64;
VT_BLOB=65; VT_STREAM=66; VT_STORAGE=67; VT_STREAMED_OBJECT=68;
VT_STORED_OBJECT=69; VT_BLOB_OBJECT=70; VT_CF=71; VT_CLSID=72;
VT_VECTOR=0x1000;

VT = dict([(v, k) for k, v in list(vars().items()) if k[:3] == "VT_"])

# For Python 3.x, need to redefine long as int:
if str is not bytes:
long = int

# [PL]: Defect levels to classify parsing errors - see OleFileIO._raise_defect()
DEFECT_UNSURE = 10 # a case which looks weird, but not sure it's a defect
DEFECT_POTENTIAL = 20 # a potential defect
DEFECT_INCORRECT = 30 # an error according to specifications, but parsing can go on
DEFECT_FATAL = 40 # an error which cannot be ignored, parsing is impossible

def ReadIgnorable(fname):
logInfo(u" IGNORED: '%s'" %(fname[-1]))
Expand Down Expand Up @@ -171,7 +194,7 @@ def read(ole):
# getModel().UFRxDoc = importerUFRxDoc.read(ufrxDoc)
handled[PrintableName(fname)] = True
elif (name.startswith('\x05')):
props = ole.getproperties(fname, convert_time=True)
props = GetProperties(ole, fname)
if (name == '\x05Aaalpg0m0wzvuhc41dwauxbwJc'):
ReadOtherProperties(props, fname, Inventor_Document_Summary_Information)
setCompany(getProperty(props, KEY_DOC_SUM_INFO_COMPANY))
Expand Down Expand Up @@ -272,9 +295,184 @@ def create3dModel(root, doc):
else:
brep = getModel().getBRep()
for asm in brep.AcisList:
setReader(asm.get('SAT'))
setReader(asm.SAT)
if (strategy == STRATEGY_SAT):
importModel(root)
elif (strategy == STRATEGY_STEP):
convertModel(root, doc.Name)
return

def GetProperties(ole, filename):
streampath = filename
if not isinstance(streampath, str):
streampath = '/'.join(streampath)
fp = ole.openstream(filename)
data = {}
try:
# header
s = fp.read(28)
clsid, i = getUUID(s, 8)
# format id
s = fp.read(20)
fmtid, i = getUUID(s, 0)
offset, i = getUInt32(s, i)
fp.seek(offset)
# get section
b_size = fp.read(4)
size = UINT32(b_size, 0)
s = b_size + fp.read(size-4)
# number of properties:
num_props = UINT32(s, 4)
except BaseException as exc:
# catch exception while parsing property header, and only raise
# a DEFECT_INCORRECT then return an empty dict, because this is not
# a fatal error when parsing the whole file
msg = f"Error while parsing properties header in stream {repr(streampath)}: {exc}"
raise Exception(DEFECT_INCORRECT, msg, type(exc))
return data
# clamp num_props based on the data length
num_props = min(num_props, int(len(s) / 8))
for i in range(num_props):
property_id = 0 # just in case of an exception
try:
property_id, j = getUInt32(s, 8+i*8)
offset, j = getUInt32(s, j)
property_type, j = getUInt32(s, offset)

vt_name = VT.get(property_type, 'UNKNOWN')
logInfo('property id=%d: type=%d/%s offset=%X' % (property_id, property_type, vt_name, offset))

value = _parse_property(s, offset+4, property_id, property_type)
data[property_id] = value
except BaseException as exc:
# catch exception while parsing each property, and only raise
# a DEFECT_INCORRECT, because parsing can go on
msg = 'Error while parsing property id %d in stream %s: %s' % (property_id, repr(streampath), exc)
raise Exception(msg, type(exc))

return data

def _parse_property(s, offset, property_id, property_type):
v = None
if property_type <= VT_BLOB or property_type in (VT_CLSID, VT_CF):
v, _ = _parse_property_basic(s, offset, property_id, property_type)
elif property_type == VT_VECTOR | VT_VARIANT:
logWarning('property_type == VT_VECTOR | VT_VARIANT')
off = 4
count = UINT32(s, offset)
values = []
for _ in range(count):
property_type = UINT32(s, offset + off)
v, sz = _parse_property_basic(s, offset + off + 4, property_id, property_type)
values.append(v)
off = sz + 4
v = values

elif property_type & VT_VECTOR:
property_type_base = property_type & ~VT_VECTOR
logWarning('property_type == VT_VECTOR | %s' % VT.get(property_type_base, 'UNKNOWN'))
off = 4
count = UINT32(s, offset)
values = []
for _ in range(count):
v, off = _parse_property_basic(s, offset + off, property_id, property_type & ~VT_VECTOR)
values.append(v)
v = values
else:
logWarning('property id=%d: type=%d not implemented in parser yet' % (property_id, property_type))
return v

def Property_VT_NULL(s, offset):
return None, offset
def Property_VT_EMPTY(s, offset):
return None, offset
def Property_VT_I1(s, offset): # 8-bit signed integer
return getSInt8(s, offset)
def Property_VT_I2(s, offset): # 16-bit signed integer
return getSInt16(s, offset)
def Property_VT_UI22(s, offset): # 2-byte unsigned integer
return getUInt16(s, offset)
def Property_VT_I4(s, offset):# VT_I4: 32-bit signed integer
return getSInt32(s, offset)
def Property_VT_INT(s, offset):
return getSInt32(s, offset)
def Property_VT_ERROR(s, offset): # VT_ERROR: HRESULT, similar to 32-bit signed integer
# see https://msdn.microsoft.com/en-us/library/cc230330.aspx
return getUInt32(s, offset)
def Property_VT_I8(s, offset): # 8-byte signed integer
return getSInt64(s, offset)
def Property_VT_UI8(s, offset): # 8-byte unsigned integer
return getUInt64(s, offset)
def Property_VT_UI4(s, offset):
return getUInt32(s, offset)
def Property_VT_UINT2(s, offset): # 4-byte unsigned integer
return getUInt32(s, offset)
def Property_VT_BSTR(s, offset): # CodePageString, see https://msdn.microsoft.com/en-us/library/dd942354.aspx
# size is a 32 bits integer, including the null terminator, and
# possibly trailing or embedded null chars
# TODO: if codepage is unicode, the string should be converted as such
value, i = getLen32Text8(s, offset)
return value.replace(b'\x00', b''), i
def Property_VT_LPSTR(s, offset):
return Property_VT_LPSTR(s, offset)
def Property_VT_BLOB(s, offset): # binary large object (BLOB)
# see https://msdn.microsoft.com/en-us/library/dd942282.aspx
count, i = getUInt32(s, offset)
return getUInt8A(s, i, count)
def Property_VT_LPWSTR(s, offset): # UnicodeString
# see https://msdn.microsoft.com/en-us/library/dd942313.aspx
# "the string should NOT contain embedded or additional trailing null characters."
return getLen32Text16(s, offset)
def Property_VT_FILETIME(s, offset): # FILETIME is a 64-bit int: "number of 100ns periods since Jan 1,1601".
value, i = getUInt64(s, offset)
logWarning('Converting property VT_FILETIME2 to python datetime, value=%d=%fs' %(value, float(value)/10000000))
# convert FILETIME to Python datetime.datetime
# inspired from https://code.activestate.com/recipes/511425-filetime-to-datetime/
_FILETIME_null_date = datetime.datetime(1601, 1, 1, 0, 0, 0)
logInfo('timedelta days=%d' % (value//(10*1000000*3600*24)))
return _FILETIME_null_date + datetime.timedelta(microseconds=value//10), i
def Property_VT_UI1(s, offset): # 1-byte unsigned integer
return getUInt8(s, offset)
def Property_VT_CLSID(s, offset):
return getUUID(s, offset)
def Property_VT_CF(s, offset): # PropertyIdentifier or ClipboardData??
# see https://msdn.microsoft.com/en-us/library/dd941945.aspx
cnt, i = getUInt32(s, offset)
fmt, i = getUInt32(s, i)
dat = s[i:i+cnt-4]
return (fmt, dat), i+cnt-4
def Property_VT_BOOL(s, offset): # VARIANT_BOOL, 16 bits bool, 0x0000=Fals, 0xFFFF=True
# see https://msdn.microsoft.com/en-us/library/cc237864.aspx
value, i = getUInt16(s, offset)
return bool(value), i
def Property_VT_R4(s, offset): # 32 bit single precision
return getFloat32(s, offset)
def Property_VT_R8(s, offset): # 64 bit double precision
return getFloat64(s, offset)
def Property_VT_CY(s, offset): # 8 Byte Currency
value, i = getSInt64(s, offset)
return value / 1000.0, i
def Property_VT_DECIMAL(s, offset): # 96 bit Decimal
reserved, i = getSInt16(s, offset)
scale, i = getUInt8(s, i)
sign, i = getUInt8(s, i)
hi32, i = getUInt32(s, i)
hi64, i = getUInt64(s, i)
value = (hi32 << 64 + hi64) * scale
if (sign):
value *= -1;
return value, i
def Property_VT_DATE(s, offset): # B byte floating point
return getFloat64(s, offset) # FIXME convert to datetime!


def _parse_property_basic(s, offset, property_id, property_type):
# test for common types first (should perhaps use
# a dictionary instead?)
fkt_name = f"Property_{VT.get(property_type, 'UNKNOWN')}"
fkt = getattr(sys.modules[__name__], fkt_name, None)
if (fkt is None):
logError('property id=%d: type=%d not implemented in parser yet' % (property_id, property_type))
# see https://msdn.microsoft.com/en-us/library/dd942033.aspx
return None, offset
return fkt(s, offset)
2 changes: 1 addition & 1 deletion importerSAT.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def readText(fileName):

result = False
setDumpFolder(fileName)
with open(fileName, 'rU') as file:
with open(fileName, 'r') as file:
reader = AcisReader(file)
reader.name, trash = os.path.splitext(os.path.basename(fileName))
result = reader.readText()
Expand Down
31 changes: 16 additions & 15 deletions importerUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@

__strategy__ = __prmPrefIL__.GetInt("strategy", STRATEGY_SAT)

IS_CELL_REF = re.compile('^[a-z](\d+)?$', re.IGNORECASE)
IS_BETA = re.compile('^.* Beta(\d+) .*$', re.IGNORECASE)
IS_CELL_REF = re.compile('^[a-z](\\d+)?$', re.IGNORECASE)
IS_BETA = re.compile('^.* Beta(\\d+) .*$', re.IGNORECASE)
_author = ''
_company = ''
_comment = ''
Expand Down Expand Up @@ -379,17 +379,17 @@ def readData(self, filename):
self.setIconData(f.read())
self.type = filename[-3:].upper()
def setData(self, data):
# skip thumbnail class header (-1, -1, 3, 0, bpp, width, height, 0)
buffer = data[0x10:]
self.width, i = getUInt16(data, 0x0A)
# skip thumbnail class header (3, 0, bpp, width, height, 0)
self.bpp, i = getUInt16(data, 4)
self.width, i = getUInt16(data, i)
self.height, i = getUInt16(data, i)

buffer = data[i+2:]
if (buffer[0x1:0x4] == b'PNG'):
self.type = 'PNG'
self.data_ = buffer
else: # it's old BMP => rebuild header
self.type = 'BMP'
fmt, dummy = getUInt32(buffer, 0x12)
fmt, dummy = getUInt32(buffer, 0x0E)
if (fmt == 3):
offset = 0x50
elif (fmt == 5):
Expand All @@ -410,7 +410,7 @@ def writeThumbnail(data):
global _thumbnail
if (data):
thumbnail = Thumbnail()
thumbnail.setData(data)
thumbnail.setData(data[1])
dumpFolder = getDumpFolder()
if ((not (dumpFolder is None)) and ParamGet("User parameter:BaseApp/Preferences/Mod/InventorLoader").GetBool('Others.DumpThumbnails', True)):
with open(u"%s/_.%s" %(dumpFolder, thumbnail.type.lower()), 'wb') as file:
Expand Down Expand Up @@ -923,10 +923,11 @@ def PrintableName(fname):
return repr('/'.join(fname))

def decode(filename, utf=False):
if (isinstance(filename, unicode)):
# workaround since ifcopenshell currently can't handle unicode filenames
encoding = ENCODING_FS if (utf) else sys.getfilesystemencoding()
filename = filename.encode(encoding).decode("utf-8")
if (sys.version_info.major < 3):
if (isinstance(filename, unicode)):
# workaround since ifcopenshell currently can't handle unicode filenames
encoding = ENCODING_FS if (utf) else sys.getfilesystemencoding()
filename = filename.encode(encoding).decode("utf-8")
return filename

def isEmbeddings(names):
Expand Down Expand Up @@ -1070,8 +1071,8 @@ def setInventorFile(file):

def isString(value):
if (type(value) is str): return True
if (sys.version_info.major < 3):
if (type(value) is unicode): return True
if (sys.version_info.major < 3) and (type(value) is unicode):
return True
return False

class Color(object):
Expand Down Expand Up @@ -1125,7 +1126,7 @@ def setTableValue(table, col, row, val):
if (type(val) == str):
table.set(getCellRef(col, row), val)
else:
if ((sys.version_info.major <= 2) and (type(val) == unicode)):
if ((sys.version_info.major < 3) and (type(val) == unicode)):
table.set(getCellRef(col, row), "%s" %(val.encode("utf8")))
else:
table.set(getCellRef(col, row), str(val))
Expand Down

0 comments on commit 1339fc5

Please sign in to comment.