#!BPY """ Registration info for Blender menus Name: 'Google Earth (.kml / .kmz)...' Blender: 241 Group: 'Import' Tip: 'Import geometry of .kml or .kmz 3D models' """ __author__ = "Jean-Michel Soler (jms)" __version__ = "0.1.8, june, 25, 2006" __url__ = "Script's homepage, http://jmsoler.free.fr/didacticiel/blender/tutor/py_import_kml-kmz_en.htm" __bpydoc__ = """\ Use this to read 3d geometry from .kml and .kmz (zipped .kml) files. Caution: the geometry data of the Google Earth's files on the web is licensed and you can not load or use it in a personnal work. Be aware that the contents of the file you try to read must be free or legally yours. """ # -------------------------------------------------------------------------- # ***** BEGIN GPL LICENSE BLOCK ***** # # Copyright (C) 2006: jm soler, jmsoler_at_free.fr # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation # # The Zipfile functions (lines 45 to 560) are borrowed from # Zipfile.py Python libraries . # # -------------------------------------------------------------------------- import Blender from Blender import Window import sys "Read and write ZIP files." import struct, os, time import binascii try: import zlib # We may need its compression method except ImportError: zlib = None __all__ = ["BadZipfile", "error", "ZIP_STORED", "ZIP_DEFLATED", "is_zipfile", "ZipInfo", "ZipFile"] class BadZipfile(Exception): pass error = BadZipfile # The exception raised by this module # constants for Zip file compression methods ZIP_STORED = 0 ZIP_DEFLATED = 8 # Other ZIP compression methods not supported # Here are some struct module formats for reading headers structEndArchive = "<4s4H2lH" # 9 items, end of archive, 22 bytes stringEndArchive = "PK\005\006" # magic number for end of archive record structCentralDir = "<4s4B4HlLL5HLl"# 19 items, central directory, 46 bytes stringCentralDir = "PK\001\002" # magic number for central directory structFileHeader = "<4s2B4HlLL2H" # 12 items, file header record, 30 bytes stringFileHeader = "PK\003\004" # magic number for file header # indexes of entries in the central directory structure _CD_SIGNATURE = 0 _CD_CREATE_VERSION = 1 _CD_CREATE_SYSTEM = 2 _CD_EXTRACT_VERSION = 3 _CD_EXTRACT_SYSTEM = 4 # is this meaningful? _CD_FLAG_BITS = 5 _CD_COMPRESS_TYPE = 6 _CD_TIME = 7 _CD_DATE = 8 _CD_CRC = 9 _CD_COMPRESSED_SIZE = 10 _CD_UNCOMPRESSED_SIZE = 11 _CD_FILENAME_LENGTH = 12 _CD_EXTRA_FIELD_LENGTH = 13 _CD_COMMENT_LENGTH = 14 _CD_DISK_NUMBER_START = 15 _CD_INTERNAL_FILE_ATTRIBUTES = 16 _CD_EXTERNAL_FILE_ATTRIBUTES = 17 _CD_LOCAL_HEADER_OFFSET = 18 # indexes of entries in the local file header structure _FH_SIGNATURE = 0 _FH_EXTRACT_VERSION = 1 _FH_EXTRACT_SYSTEM = 2 # is this meaningful? _FH_GENERAL_PURPOSE_FLAG_BITS = 3 _FH_COMPRESSION_METHOD = 4 _FH_LAST_MOD_TIME = 5 _FH_LAST_MOD_DATE = 6 _FH_CRC = 7 _FH_COMPRESSED_SIZE = 8 _FH_UNCOMPRESSED_SIZE = 9 _FH_FILENAME_LENGTH = 10 _FH_EXTRA_FIELD_LENGTH = 11 def is_zipfile(filename): """Quickly see if file is a ZIP file by checking the magic number.""" try: fpin = open(filename, "rb") endrec = _EndRecData(fpin) fpin.close() if endrec: return True # file has correct magic number except IOError: pass return False def _EndRecData(fpin): """Return data from the "End of Central Directory" record, or None. The data is a list of the nine items in the ZIP "End of central dir" record followed by a tenth item, the file seek offset of this record.""" fpin.seek(-22, 2) # Assume no archive comment. filesize = fpin.tell() + 22 # Get file size data = fpin.read() if data[0:4] == stringEndArchive and data[-2:] == "\000\000": endrec = struct.unpack(structEndArchive, data) endrec = list(endrec) endrec.append("") # Append the archive comment endrec.append(filesize - 22) # Append the record start offset return endrec # Search the last END_BLOCK bytes of the file for the record signature. # The comment is appended to the ZIP file and has a 16 bit length. # So the comment may be up to 64K long. We limit the search for the # signature to a few Kbytes at the end of the file for efficiency. # also, the signature must not appear in the comment. END_BLOCK = min(filesize, 1024 * 4) fpin.seek(filesize - END_BLOCK, 0) data = fpin.read() start = data.rfind(stringEndArchive) if start >= 0: # Correct signature string was found endrec = struct.unpack(structEndArchive, data[start:start+22]) endrec = list(endrec) comment = data[start+22:] if endrec[7] == len(comment): # Comment length checks out # Append the archive comment and start offset endrec.append(comment) endrec.append(filesize - END_BLOCK + start) return endrec return # Error, return None class ZipInfo: """Class with attributes describing each file in the ZIP archive.""" def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): self.orig_filename = filename # Original file name in archive # Terminate the file name at the first null byte. Null bytes in file # names are used as tricks by viruses in archives. null_byte = filename.find(chr(0)) if null_byte >= 0: filename = filename[0:null_byte] # This is used to ensure paths in generated ZIP files always use # forward slashes as the directory separator, as required by the # ZIP format specification. if os.sep != "/": filename = filename.replace(os.sep, "/") self.filename = filename # Normalized file name self.date_time = date_time # year, month, day, hour, min, sec # Standard values: self.compress_type = ZIP_STORED # Type of compression for the file self.comment = "" # Comment for each file self.extra = "" # ZIP extra data self.create_system = 0 # System which created ZIP archive self.create_version = 20 # Version which created ZIP archive self.extract_version = 20 # Version needed to extract archive self.reserved = 0 # Must be zero self.flag_bits = 0 # ZIP flag bits self.volume = 0 # Volume number of file header self.internal_attr = 0 # Internal attributes self.external_attr = 0 # External file attributes # Other attributes are set by class ZipFile: # header_offset Byte offset to the file header # file_offset Byte offset to the start of the file data # CRC CRC-32 of the uncompressed file # compress_size Size of the compressed file # file_size Size of the uncompressed file def FileHeader(self): """Return the per-file header as a string.""" dt = self.date_time dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2) if self.flag_bits & 0x08: # Set these to zero because we write them after the file data CRC = compress_size = file_size = 0 else: CRC = self.CRC compress_size = self.compress_size file_size = self.file_size header = struct.pack(structFileHeader, stringFileHeader, self.extract_version, self.reserved, self.flag_bits, self.compress_type, dostime, dosdate, CRC, compress_size, file_size, len(self.filename), len(self.extra)) return header + self.filename + self.extra class ZipFile: """ Class with methods to open, read, write, close, list zip files. z = ZipFile(file, mode="r", compression=ZIP_STORED) file: Either the path to the file, or a file-like object. If it is a path, the file will be opened and closed by ZipFile. mode: The mode can be either read "r", write "w" or append "a". compression: ZIP_STORED (no compression) or ZIP_DEFLATED (requires zlib). """ fp = None # Set here since __del__ checks it def __init__(self, file, mode="r", compression=ZIP_STORED): """Open the ZIP file with mode read "r", write "w" or append "a".""" if compression == ZIP_STORED: pass elif compression == ZIP_DEFLATED: if not zlib: raise RuntimeError,\ "Compression requires the (missing) zlib module" else: raise RuntimeError, "That compression method is not supported" self.debug = 0 # Level of printing: 0 through 3 self.NameToInfo = {} # Find file info given name self.filelist = [] # List of ZipInfo instances for archive self.compression = compression # Method of compression self.mode = key = mode.replace('b', '')[0] # Check if we were passed a file-like object if isinstance(file, basestring): self._filePassed = 0 self.filename = file modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'} self.fp = open(file, modeDict[mode]) else: self._filePassed = 1 self.fp = file self.filename = getattr(file, 'name', None) if key == 'r': self._GetContents() elif key == 'w': pass elif key == 'a': try: # See if file is a zip file self._RealGetContents() # seek to start of directory and overwrite self.fp.seek(self.start_dir, 0) except BadZipfile: # file is not a zip file, just append self.fp.seek(0, 2) else: if not self._filePassed: self.fp.close() self.fp = None raise RuntimeError, 'Mode must be "r", "w" or "a"' def _GetContents(self): """Read the directory, making sure we close the file if the format is bad.""" try: self._RealGetContents() except BadZipfile: if not self._filePassed: self.fp.close() self.fp = None raise def _RealGetContents(self): """Read in the table of contents for the ZIP file.""" fp = self.fp endrec = _EndRecData(fp) if not endrec: raise BadZipfile, "File is not a zip file" if self.debug > 1: print endrec size_cd = endrec[5] # bytes in central directory offset_cd = endrec[6] # offset of central directory self.comment = endrec[8] # archive comment # endrec[9] is the offset of the "End of Central Dir" record x = endrec[9] - size_cd # "concat" is zero, unless zip was concatenated to another file concat = x - offset_cd if self.debug > 2: print "given, inferred, offset", offset_cd, x, concat # self.start_dir: Position of start of central directory self.start_dir = offset_cd + concat fp.seek(self.start_dir, 0) total = 0 while total < size_cd: centdir = fp.read(46) total = total + 46 if centdir[0:4] != stringCentralDir: raise BadZipfile, "Bad magic number for central directory" centdir = struct.unpack(structCentralDir, centdir) if self.debug > 2: print centdir filename = fp.read(centdir[_CD_FILENAME_LENGTH]) # Create ZipInfo instance to store file information x = ZipInfo(filename) x.extra = fp.read(centdir[_CD_EXTRA_FIELD_LENGTH]) x.comment = fp.read(centdir[_CD_COMMENT_LENGTH]) total = (total + centdir[_CD_FILENAME_LENGTH] + centdir[_CD_EXTRA_FIELD_LENGTH] + centdir[_CD_COMMENT_LENGTH]) x.header_offset = centdir[_CD_LOCAL_HEADER_OFFSET] + concat # file_offset must be computed below... (x.create_version, x.create_system, x.extract_version, x.reserved, x.flag_bits, x.compress_type, t, d, x.CRC, x.compress_size, x.file_size) = centdir[1:12] x.volume, x.internal_attr, x.external_attr = centdir[15:18] # Convert date/time code to (year, month, day, hour, min, sec) x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F, t>>11, (t>>5)&0x3F, (t&0x1F) * 2 ) self.filelist.append(x) self.NameToInfo[x.filename] = x if self.debug > 2: print "total", total for data in self.filelist: fp.seek(data.header_offset, 0) fheader = fp.read(30) if fheader[0:4] != stringFileHeader: raise BadZipfile, "Bad magic number for file header" fheader = struct.unpack(structFileHeader, fheader) # file_offset is computed here, since the extra field for # the central directory and for the local file header # refer to different fields, and they can have different # lengths data.file_offset = (data.header_offset + 30 + fheader[_FH_FILENAME_LENGTH] + fheader[_FH_EXTRA_FIELD_LENGTH]) fname = fp.read(fheader[_FH_FILENAME_LENGTH]) if fname != data.orig_filename: raise RuntimeError, \ 'File name in directory "%s" and header "%s" differ.' % ( data.orig_filename, fname) def namelist(self): """Return a list of file names in the archive.""" l = [] for data in self.filelist: l.append(data.filename) return l def infolist(self): """Return a list of class ZipInfo instances for files in the archive.""" return self.filelist def printdir(self): """Print a table of contents for the zip file.""" print "%-46s %19s %12s" % ("File Name", "Modified ", "Size") for zinfo in self.filelist: date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size) def testzip(self): """Read all the files and check the CRC.""" for zinfo in self.filelist: try: self.read(zinfo.filename) # Check CRC-32 except BadZipfile: return zinfo.filename def getinfo(self, name): """Return the instance of ZipInfo given 'name'.""" return self.NameToInfo[name] def read(self, name): """Return file bytes (as a string) for name.""" if self.mode not in ("r", "a"): raise RuntimeError, 'read() requires mode "r" or "a"' if not self.fp: raise RuntimeError, \ "Attempt to read ZIP archive that was already closed" zinfo = self.getinfo(name) filepos = self.fp.tell() self.fp.seek(zinfo.file_offset, 0) bytes = self.fp.read(zinfo.compress_size) self.fp.seek(filepos, 0) if zinfo.compress_type == ZIP_STORED: pass elif zinfo.compress_type == ZIP_DEFLATED: if not zlib: raise RuntimeError, \ "De-compression requires the (missing) zlib module" # zlib compress/decompress code by Jeremy Hylton of CNRI dc = zlib.decompressobj(-15) bytes = dc.decompress(bytes) # need to feed in unused pad byte so that zlib won't choke ex = dc.decompress('Z') + dc.flush() if ex: bytes = bytes + ex else: raise BadZipfile, \ "Unsupported compression method %d for file %s" % \ (zinfo.compress_type, name) crc = binascii.crc32(bytes) if crc != zinfo.CRC: raise BadZipfile, "Bad CRC-32 for file %s" % name return bytes def _writecheck(self, zinfo): """Check for errors before writing a file to the archive.""" if zinfo.filename in self.NameToInfo: if self.debug: # Warning for duplicate names print "Duplicate name:", zinfo.filename if self.mode not in ("w", "a"): raise RuntimeError, 'write() requires mode "w" or "a"' if not self.fp: raise RuntimeError, \ "Attempt to write ZIP archive that was already closed" if zinfo.compress_type == ZIP_DEFLATED and not zlib: raise RuntimeError, \ "Compression requires the (missing) zlib module" if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED): raise RuntimeError, \ "That compression method is not supported" def write(self, filename, arcname=None, compress_type=None): """Put the bytes from filename into the archive under the name arcname.""" st = os.stat(filename) mtime = time.localtime(st.st_mtime) date_time = mtime[0:6] # Create ZipInfo instance to store file information if arcname is None: zinfo = ZipInfo(filename, date_time) else: zinfo = ZipInfo(arcname, date_time) zinfo.external_attr = (st[0] & 0xFFFF) << 16L # Unix attributes if compress_type is None: zinfo.compress_type = self.compression else: zinfo.compress_type = compress_type self._writecheck(zinfo) fp = open(filename, "rb") zinfo.flag_bits = 0x00 zinfo.header_offset = self.fp.tell() # Start of header bytes # Must overwrite CRC and sizes with correct data later zinfo.CRC = CRC = 0 zinfo.compress_size = compress_size = 0 zinfo.file_size = file_size = 0 self.fp.write(zinfo.FileHeader()) zinfo.file_offset = self.fp.tell() # Start of file bytes if zinfo.compress_type == ZIP_DEFLATED: cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) else: cmpr = None while 1: buf = fp.read(1024 * 8) if not buf: break file_size = file_size + len(buf) CRC = binascii.crc32(buf, CRC) if cmpr: buf = cmpr.compress(buf) compress_size = compress_size + len(buf) self.fp.write(buf) fp.close() if cmpr: buf = cmpr.flush() compress_size = compress_size + len(buf) self.fp.write(buf) zinfo.compress_size = compress_size else: zinfo.compress_size = file_size zinfo.CRC = CRC zinfo.file_size = file_size # Seek backwards and write CRC and file sizes position = self.fp.tell() # Preserve current position in file self.fp.seek(zinfo.header_offset + 14, 0) self.fp.write(struct.pack("','') return t elif nom.upper().find('.KML')!=-1: tz=open(nom,'r') t = tz.read() tz.close() t=t.replace('
','') return t else : return None # .... # needed to global call # .... OB = None SC = None ME= None POLYGON_NUMBER=0 DOCUMENTORIGINE=[] UPDATE_V=[] UPDATE_F=[] POS=0 NUMBER=0 PLACEMARK=0 eps=0.0000001 npoly=0 gt1=Blender.sys.time() def cree_POLYGON(ME,TESSEL): global OB, npoly, UPDATE_V, UPDATE_F, POS npoly+=1 for T in TESSEL: del T[-1] if npoly %100 == 1 : print 'Pgon: ', npoly, 'verts:',[len(T) for T in TESSEL] if npoly %250 == 1 : Blender.Window.RedrawAll() g2= Blender.sys.time()-gt1 print int(g2/60),':',int(g2%60) if len(TESSEL)==1 and len(TESSEL[0]) in [3,4] : if UPDATE_F==[]: POS=len(ME.verts) for VE in TESSEL[0]: UPDATE_V.append(VE) if len(TESSEL[0])==3: UPDATE_F.append([POS,POS+1,POS+2]) POS+=3 else : UPDATE_F.append([POS,POS+1,POS+2,POS+3]) POS+=4 else : if UPDATE_V!=[] : ME.verts.extend(UPDATE_V) FACES=[] if UPDATE_F!=[]: for FE in UPDATE_F: if len(FE)==3: FACES.append([ME.verts[FE[0]],ME.verts[FE[1]],ME.verts[FE[2]]]) else : FACES.append([ME.verts[FE[0]],ME.verts[FE[1]],ME.verts[FE[2]],ME.verts[FE[3]]]) if FACES!=[]: ME.faces.extend(FACES) FACES=[] UPDATE_F=[] UPDATE_V=[] EDGES=[] for T in TESSEL: ME.verts.extend(T) for t in range(len(T),1,-1): ME.verts[-t].sel=1 EDGES.append([ME.verts[-t],ME.verts[-t+1]]) ME.verts[-1].sel=1 EDGES.append([ME.verts[-1],ME.verts[-len(T)]]) ME.edges.extend(EDGES) ME.fill() if npoly %500 == 1 : for v in ME.verts: v.sel=1 ME.remDoubles(0.0) for v in ME.verts: v.sel=0 TESSEL=[] return ME,TESSEL def cree_FORME(v,TESSEL): VE=[(v[0]-DOCUMENTORIGINE[0])* 85331.2, (v[1]-DOCUMENTORIGINE[1])* 110976.0, (v[2]-DOCUMENTORIGINE[2]) ] TESSEL.append(VE) def active_FORME(): global ME, UPDATE_V, UPDATE_F, POS, OB if len(UPDATE_V)>2 : #print UPDATE_V ME.verts.extend(UPDATE_V) FACES=[] #print UPDATE_F, len(UPDATE_F) for FE in UPDATE_F: #print FE if len(FE)<4: FACES.append([ME.verts[FE[0]],ME.verts[FE[1]],ME.verts[FE[2]]]) else : FACES.append([ME.verts[FE[0]],ME.verts[FE[1]],ME.verts[FE[2]],ME.verts[FE[3]]]) #if len(ME.faces)%200==1 : print len(ME.faces) if FACES: ME.faces.extend(FACES) UPDATE_V=[] UPDATE_F=[] POS=0 if len(ME.verts)>0: for v in ME.verts: v.sel=1 ME.remDoubles(0.0) def wash_DATA(ndata): if ndata!='': ndata=ndata.replace('\n',',') ndata=ndata.replace('\r','') while ndata[-1]==' ': ndata=ndata.replace(' ',' ') while ndata[0]==' ': ndata=ndata[1:] while ndata[-1]==' ': ndata=ndata[:-1] if ndata[0]==',':ndata=ndata[1:] if ndata[-1]==',':ndata=ndata[:-1] ndata=ndata.replace(',,',',') ndata=ndata.replace(' ',',') ndata=ndata.split(',') for n in ndata : if n=='' : ndata.remove(n) return ndata def collecte_ATTRIBUTS(data): data=data.replace(' ',' ') ELEM={'TYPE':data[1:data.find(' ')]} t1=len(data) t2=0 ct=data.count('="') while ct>0: t0=data.find('="',t2) t2=data.find(' ',t2)+1 id=data[t2:t0] t2=data.find('"',t0+2) if id!='d': exec "ELEM[id]=\"\"\"%s\"\"\""%(data[t0+2:t2].replace('\\','/')) else: exec "ELEM[id]=[%s,%s]"%(t0+2,t2) ct=data.count('="',t2) return ELEM def contruit_HIERARCHIE(t): global DOCUMENTORIGINE, OB , ME, SC global NUMBER, PLACEMARK, POLYGON_NUMBER vv=[] TESSEL=[] OB = Blender.Object.New('Mesh') SC.link(OB) ME= OB.getData(mesh=1) [O.select(0) for O in Blender.Object.Get()] OB.select(1) t=t.replace('\t',' ') while t.find(' ')!=-1: t=t.replace(' ',' ') n0=0 t0=t1=0 baliste=[] balisetype=['?','?','/','/','!','!'] BALISES=['D', #DECL_TEXTE', 'D', #DECL_TEXTE', 'F', #FERMANTE', 'E', #ELEM_VIDE', 'd', #DOC', 'R', #REMARQUES', 'C', #CONTENU', 'O' #OUVRANTE' ] TAGS = ['kml','Document','description','DocumentSource', 'DocumentOrigin','visibility','LookAt', 'Folder','name','heading','tilt','latitude', 'longitude','range','Style','LineStyle','color', 'Placemark','styleUrl','GeometryCollection', 'Polygon','LinearRing','outerBoundaryIs', 'altitudeMode','coordinates','LineString', 'fill','PolyStyle','outline','innerBoundaryIs', 'IconStyle', 'Icon', 'x','y' ,'w','href','h' ] STACK=[] latitude = float(t[t.find('')+len(''):t.find('')]) longitude = float(t[t.find('')+len(''):t.find('')]) DOCUMENTORIGINE=[longitude,latitude,0 ] GETMAT=0 MATERIALS=[M.getName() for M in Blender.Material.Get()] while t1-1 : t0=t.find('<',t0) t1=t.find('>',t0) ouvrante=0 if t0>-1 and t1>-1: if t[t0+1] in balisetype: b=balisetype.index(t[t0+1]) if t[t0+2]=='-': b=balisetype.index(t[t0+1])+1 balise=BALISES[b] #print STACK if b==2 and t[t0:t1].find(STACK[-1])>-1: parent=STACK.pop(-1) elif t[t1-1] in balisetype: balise=BALISES[balisetype.index(t[t1-1])+1] else: t2=t.find(' ',t0) if t2>t1: t2=t1 ouvrante=1 NOM=t[t0+1:t2] if t.find('-1: balise=BALISES[-1] else: balise=BALISES[-2] if balise=='E' or balise=='O': if NOM not in TAGS : if NOM not in ['a','b','table','tr','td','div','hr']: TAGS.append(NOM) else : t0=t.find('',t0) if t0==-1 and t1==-1: break if balise=='O' and NOM in TAGS: STACK.append(NOM) if not PLACEMARK : if NOM.find('Style')==0: proprietes=collecte_ATTRIBUTS(t[t0:t1+ouvrante]) print proprietes if NOM.find('PolyStyle')==0: GETMAT=1 if NOM.find('color')==0 and GETMAT: COLOR=t[t2+1:t.find('-1: VAL=t[t2+2:t.find('')+VAL.count('') if NUMBER and NOM.find('Placemark')>-1 : PLACEMARK=1 if t[t2:t.find('-1 and len(ME.verts)>0: active_FORME() OB.select(0) #[O.select(0) for O in Blender.Object.Get()] OB = Blender.Object.New('Mesh') # link mesh to an object SC = Blender.Scene.GetCurrent() # link object to current scene SC.link(OB) ME=OB.getData(mesh=1) OB.select(1) if NOM.find('styleUrl')>-1: material= t[t2+2:t.find('') print 'Number of Placemark : ', PLACEMARK_NUMBER if PLACEMARK_NUMBER!=POLYGON_NUMBER : NUMBER=1 PLACEMARK=0 if t!='false': contruit_HIERARCHIE(t) active_FORME() gt2=Blender.sys.time()-gt1 print int(gt2/60),':',int(gt2%60) Blender.Window.FileSelector (fonctionSELECT, 'SELECT a .KMZ FILE')