import StringIO, base64, logging, operator import dxfgrabber import Image, ImageDraw, ImageFont #PIL from homcoord import * def rint(x):return int(round(x)) class BBox: """bounding box""" def __init__(self,pt1=None,pt2=None): self._corner1=None self._corner2=None if pt1: self+=pt1 if pt2: self+=pt2 def __iadd__(self,pt): if isinstance(pt,BBox): self+=pt._corner1 self+=pt._corner2 else: if not self._corner1: self._corner1=pt else: self._corner1=Pt(map(min,zip(self._corner1.xy,pt.xy))) if not self._corner2: self._corner2=pt else: self._corner2=Pt(map(max,zip(self._corner2.xy,pt.xy))) return self def __repr__(self): return "BBox(%s,%s)"%(self._corner1,self._corner2) def __call__(self): """:return: list of flatten corners""" l=list(self._corner1.xy) l.extend(list(self._corner2.xy)) return l def size(self): """:return: Pt with xy sizes""" return self._corner2-self._corner1 def center(self): """:return: Pt center""" res=self._corner2+self._corner1 return res/2 def trans(self,trans): """ :param trans: Xform :return: BBox = self transformed by trans """ return BBox(trans(self._corner1),trans(self._corner2)) def cbox(c,r): """ bounding box of a circle :param c: Pt center :param r: float radius :return: BBox """ rr=Pt(r,r) return BBox(c+rr,c-rr) def Trans(scale=1, offset=[0,0], rotation=0): res=Xform([[scale,0,offset[0]],[0,scale,offset[1]],[0,0,1]]) if rotation: res=Xrotate(rotation*pi/180.)*res return res class DXF: def __init__(self, file, layers=None, ignore=[]): """reads a .dxf file :param file: string path to .dxf file to read :param layers: list or dictionary of layers to handle. Empty = all layers :param ignore: list of strings of entity types to ignore """ self.dxf=dxfgrabber.readfile(file) self.layers=layers self.ignore=ignore def entities(self,ent=None): """iterator over dxf or block entities""" if not ent: ent=self.dxf.entities for e in ent: if self.layers and e.layer not in self.layers: continue elif e.dxftype in self.ignore: continue else: yield e def bbox(self): """:return: :class:BBox dwg enclosing bounding box""" box=BBox() for e in self.entities(): if e.dxftype=='LINE': box+=Pt(e.start[:2]) box+=Pt(e.end[:2]) elif e.dxftype == 'CIRCLE': box+=cbox(Pt(e.center[:2]),e.radius) elif e.dxftype == 'ARC': c=Pt(e.center[:2]) a=e.endangle-e.startangle if a>0: start=e.startangle else: #arc goes clockwise (step will be negative) start=e.endangle n=rint(abs(a)/10.) # number of points each 10° approximately n=max(n,1) step=a/n #angle between 2 points, might be negative for i in range(n+1): box+=c.radial(e.radius,radians(start+i*step)) elif e.dxftype=='POLYLINE': for v in e.vertices: box+=Pt(v.location[:2]) elif e.dxftype=='BLOCK': pass #TODO ... elif e.dxftype in ['TEXT','INSERT']: box+=Pt(e.insert[:2]) else: logging.warning('Unknown entity %s'%e) return box def _draw(self,draw,entities,trans,pen="black"): for e in entities: if e.dxftype=='LINE': b=list(trans(Pt(e.start[:2])).xy) b.extend(list(trans(Pt(e.end[:2])).xy)) draw.line(b,fill=pen) elif e.dxftype=='CIRCLE': b=cbox(Pt(e.center[:2]),e.radius) b=b.trans(trans) draw.ellipse(b(),outline=pen) elif e.dxftype=='ARC': c=Pt(e.center[:2]) b=cbox(c,e.radius) b=b.trans(trans) b=map(rint,b()) startangle=degrees(trans.angle(radians(e.startangle))) endangle=degrees(trans.angle(radians(e.endangle))) startangle,endangle=endangle,startangle #swap start/end because of Y symmetry draw.arc(b,int(startangle),int(endangle),fill=pen) elif e.dxftype=='POLYLINE': b=[] for v in e.vertices: b.extend(list(trans(Pt(v.location[:2])).xy)) draw.line(b,fill=pen) elif e.dxftype=='TEXT': h=e.height*trans.mag() pt=Pt(e.insert[:2])+Pt(0,e.height) #ACAD places texts by top left point... font=None try: font = ImageFont.truetype("c:/windows/fonts/Courier New.ttf", rint(h)) except: pass if not font: h=h*1.4 #magic factor ... fh=[8,10,12,14,16,18,20,22,24,26,28,30,36,40,48,60] i,h=min(enumerate(fh), key=lambda x: abs(x[1]-h)) #http://stackoverflow.com/questions/9706041/finding-index-of-an-item-closest-to-the-value-in-a-list-thats-not-entirely-sort import os path=os.path.realpath(__file__) path=os.path.dirname(path) font = ImageFont.load(path+'\\base_pil\\72\\Courier New_%s_72.pil'%h) draw.text(trans(pt).xy,e.text,font=font,fill=pen) elif e.dxftype=='INSERT': t2=Trans(1,e.insert,e.rotation).compose(trans) self._draw(draw,self.entities(self.dxf.blocks[e.name]._entities),t2,pen) elif e.dxftype=='BLOCK': pass # block definition is automatically stored in dxf.blocks dictionary else: logging.warning('Unknown entity %s'%e) def img(self,size=[256,256],back="white",pen="black",border=5,antialias=1): """:result: :class:`PIL:Image` rasterized image""" box=self.bbox() from Goulib.math2 import product if not product(box.size().xy): # either x or y ==0 return None s=map(operator.div,[float(x-border)*antialias if x else 1E9 for x in size ],box.size().xy) trans=Trans(scale=min(s)) size=trans(box.size())+Pt(2*antialias*border,2*antialias*border) #add borders as an offset offset=size/2-trans(box.center()) #offset in pixel coordinates trans=trans*Trans(offset=offset.xy) trans=trans*Xscale(1,-1) #invert y axis trans=trans*Xlate(0,size.y) #origin is lower left corner img = Image.new("RGB", map(rint,size.xy), back) self._draw(ImageDraw.Draw(img), self.entities(), trans, pen) if antialias>1: size=size/antialias img=img.resize(map(rint,size.xy), Image.ANTIALIAS) return img def img2base64(img,fmt='PNG'): """ :param img: :class:`PIL:Image` :result: string base64 encoded image content in specified format :see: http://stackoverflow.com/questions/14348442/django-how-do-i-display-a-pil-image-object-in-a-template """ output = StringIO.StringIO() img.save(output, fmt) output.seek(0) output_s = output.read() return base64.b64encode(output_s) if __name__ == '__main__': dxf=DXF("GV_12.DXF") img=dxf.img(size=[1280,None],border=50) print(img2base64(img)) img.save('..\\tests\\out.png')