diff --git a/Import_IPT.py b/Import_IPT.py index 2366fab..d358019 100644 --- a/Import_IPT.py +++ b/Import_IPT.py @@ -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])) @@ -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)) @@ -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) diff --git a/importerSAT.py b/importerSAT.py index aa09805..b3f9f92 100644 --- a/importerSAT.py +++ b/importerSAT.py @@ -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() diff --git a/importerUtils.py b/importerUtils.py index d2309da..acbc042 100644 --- a/importerUtils.py +++ b/importerUtils.py @@ -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 = '' @@ -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): @@ -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: @@ -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): @@ -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): @@ -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))