bugfix and updates for DXF-Importer/Exporter scripts
[blender.git] / release / scripts / bpymodules / dxfLibrary.py
1 #dxfLibrary.py : provides functions for generating DXF files
2 # --------------------------------------------------------------------------
3 __version__ = "v1.31 - 2009.06.02"
4 __author__ = "Stani Michiels(Stani), Remigiusz Fiedler(migius)"
5 __license__ = "GPL"
6 __url__ = "http://wiki.blender.org/index.php/Scripts/Manual/Export/autodesk_dxf"
7 __bpydoc__ ="""The library to export geometry data to DXF format r12 version.
8
9 Copyright %s
10 Version %s
11 License %s
12 Homepage %s
13
14 See the homepage for documentation.
15 Dedicated thread on BlenderArtists: http://blenderartists.org/forum/showthread.php?t=136439
16
17 IDEAs:
18 -
19
20 TODO:
21 - add support for DXFr14 (needs extended file header)
22 - add support for SPLINEs (possible first in DXFr14 version)
23
24 History
25 v1.31 - 2009.06.02 by migius
26  - modif _Entity class: added paperspace,elevation
27 v1.30 - 2009.05.28 by migius
28  - bugfix 3dPOLYLINE/POLYFACE: VERTEX needs x,y,z coordinates, index starts with 1 not 0
29 v1.29 - 2008.12.28 by Yorik
30  - modif POLYLINE to support bulge segments
31 v1.28 - 2008.12.13 by Steeve/BlenderArtists
32  - bugfix for EXTMIN/EXTMAX to suit Cycas-CAD
33 v1.27 - 2008.10.07 by migius
34  - beautifying output code: keys whitespace prefix
35  - refactoring DXF-strings format: NewLine moved to the end of
36 v1.26 - 2008.10.05 by migius
37  - modif POLYLINE to support POLYFACE
38 v1.25 - 2008.09.28 by migius
39  - modif FACE class for r12
40 v1.24 - 2008.09.27 by migius
41  - modif POLYLINE class for r12
42  - changing output format from r9 to r12(AC1009)
43 v1.1 (20/6/2005) by www.stani.be/python/sdxf
44  - Python library to generate dxf drawings
45 ______________________________________________________________
46 """ % (__author__,__version__,__license__,__url__)
47
48 # --------------------------------------------------------------------------
49 # DXF Library: copyright (C) 2005 by Stani Michiels (AKA Stani)
50 #                       2008/2009 modif by Remigiusz Fiedler (AKA migius)
51 # --------------------------------------------------------------------------
52 # ***** BEGIN GPL LICENSE BLOCK *****
53 #
54 # This program is free software; you can redistribute it and/or
55 # modify it under the terms of the GNU General Public License
56 # as published by the Free Software Foundation; either version 2
57 # of the License, or (at your option) any later version.
58 #
59 # This program is distributed in the hope that it will be useful,
60 # but WITHOUT ANY WARRANTY; without even the implied warranty of
61 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
62 # GNU General Public License for more details.
63 #
64 # You should have received a copy of the GNU General Public License
65 # along with this program; if not, write to the Free Software Foundation,
66 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
67 #
68 # ***** END GPL LICENCE BLOCK *****
69
70
71 #import Blender
72 #from Blender import Mathutils, Window, Scene, sys, Draw
73 #import BPyMessages
74
75 try:
76         import copy
77         #from struct import pack
78 except:
79         copy = None
80
81 ####1) Private (only for developpers)
82 _HEADER_POINTS=['insbase','extmin','extmax']
83
84 #---helper functions-----------------------------------
85 def _point(x,index=0):
86         """Convert tuple to a dxf point"""
87         #print 'deb: _point=', x #-------------
88         return '\n'.join([' %s\n%s'%((i+1)*10+index,x[i]) for i in range(len(x))])
89
90 def _points(plist):
91         """Convert a list of tuples to dxf points"""
92         out = '\n'.join([_point(plist[i],i)for i in range(len(plist))])
93         return out
94
95 #---base classes----------------------------------------
96 class _Call:
97         """Makes a callable class."""
98         def copy(self):
99                 """Returns a copy."""
100                 return copy.deepcopy(self)
101
102         def __call__(self,**attrs):
103                 """Returns a copy with modified attributes."""
104                 copied=self.copy()
105                 for attr in attrs:setattr(copied,attr,attrs[attr])
106                 return copied
107
108 #-------------------------------------------------------
109 class _Entity(_Call):
110         """Base class for _common group codes for entities."""
111         def __init__(self,paperspace=None,color=None,layer='0',
112                                  lineType=None,lineTypeScale=None,lineWeight=None,
113                                  extrusion=None,elevation=None,thickness=None,
114                                  parent=None):
115                 """None values will be omitted."""
116                 self.paperspace   = paperspace
117                 self.color                = color
118                 self.layer                = layer
119                 self.lineType      = lineType
120                 self.lineTypeScale  = lineTypeScale
121                 self.lineWeight  = lineWeight
122                 self.extrusion    = extrusion
123                 self.elevation    = elevation
124                 self.thickness    = thickness
125                 #self.visible     = visible
126                 self.parent              = parent
127
128         def _common(self):
129                 """Return common group codes as a string."""
130                 if self.parent:parent=self.parent
131                 else:parent=self
132                 result =''
133                 if parent.paperspace==1: result+='  67\n1\n'
134                 if parent.layer!=None: result+='  8\n%s\n'%parent.layer
135                 if parent.color!=None: result+=' 62\n%s\n'%parent.color
136                 if parent.lineType!=None: result+='  6\n%s\n'%parent.lineType
137                 #TODO: if parent.lineWeight!=None: result+='370\n%s\n'%parent.lineWeight
138                 #TODO: if parent.visible!=None: result+='60\n%s\n'%parent.visible
139                 if parent.lineTypeScale!=None: result+=' 48\n%s\n'%parent.lineTypeScale
140                 if parent.elevation!=None: result+=' 38\n%s\n'%parent.elevation
141                 if parent.thickness!=None: result+=' 39\n%s\n'%parent.thickness
142                 if parent.extrusion!=None: result+='%s\n'%_point(parent.extrusion,200)
143                 return result
144
145 #--------------------------
146 class _Entities:
147         """Base class to deal with composed objects."""
148         def __dxf__(self):
149                 return []
150
151         def __str__(self):
152                 return ''.join([str(x) for x in self.__dxf__()])
153
154 #--------------------------
155 class _Collection(_Call):
156         """Base class to expose entities methods to main object."""
157         def __init__(self,entities=[]):
158                 self.entities=copy.copy(entities)
159                 #link entities methods to drawing
160                 for attr in dir(self.entities):
161                         if attr[0]!='_':
162                                 attrObject=getattr(self.entities,attr)
163                                 if callable(attrObject):
164                                         setattr(self,attr,attrObject)
165
166 ####2) Constants
167 #---color values
168 BYBLOCK=0
169 BYLAYER=256
170
171 #---block-type flags (bit coded values, may be combined):
172 ANONYMOUS =1  # This is an anonymous block generated by hatching, associative dimensioning, other internal operations, or an application
173 NON_CONSTANT_ATTRIBUTES =2  # This block has non-constant attribute definitions (this bit is not set if the block has any attribute definitions that are constant, or has no attribute definitions at all)
174 XREF =4  # This block is an external reference (xref)
175 XREF_OVERLAY =8  # This block is an xref overlay
176 EXTERNAL =16 # This block is externally dependent
177 RESOLVED =32 # This is a resolved external reference, or dependent of an external reference (ignored on input)
178 REFERENCED =64 # This definition is a referenced external reference (ignored on input)
179
180 #---mtext flags
181 #attachment point
182 TOP_LEFT = 1
183 TOP_CENTER = 2
184 TOP_RIGHT = 3
185 MIDDLE_LEFT = 4
186 MIDDLE_CENTER = 5
187 MIDDLE_RIGHT    = 6
188 BOTTOM_LEFT = 7
189 BOTTOM_CENTER = 8
190 BOTTOM_RIGHT = 9
191 #drawing direction
192 LEFT_RIGHT = 1
193 TOP_BOTTOM = 3
194 BY_STYLE = 5 #the flow direction is inherited from the associated text style
195 #line spacing style (optional):
196 AT_LEAST = 1 #taller characters will override
197 EXACT = 2 #taller characters will not override
198
199 #---polyline flags
200 CLOSED =1         # This is a closed polyline (or a polygon mesh closed in the M direction)
201 CURVE_FIT =2      # Curve-fit vertices have been added
202 SPLINE_FIT =4     # Spline-fit vertices have been added
203 POLYLINE_3D =8    # This is a 3D polyline
204 POLYGON_MESH =16         # This is a 3D polygon mesh
205 CLOSED_N =32     # The polygon mesh is closed in the N direction
206 POLYFACE_MESH =64        # The polyline is a polyface mesh
207 CONTINOUS_LINETYPE_PATTERN =128 # The linetype pattern is generated continuously around the vertices of this polyline
208
209 #---text flags
210 #horizontal
211 LEFT = 0
212 CENTER = 1
213 RIGHT = 2
214 ALIGNED = 3 #if vertical alignment = 0
215 MIDDLE = 4 #if vertical alignment = 0
216 FIT = 5 #if vertical alignment = 0
217 #vertical
218 BASELINE = 0
219 BOTTOM  = 1
220 MIDDLE = 2
221 TOP = 3
222
223 ####3) Classes
224 #---entitities -----------------------------------------------
225 #--------------------------
226 class Arc(_Entity):
227         """Arc, angles in degrees."""
228         def __init__(self,center=(0,0,0),radius=1,
229                                  startAngle=0.0,endAngle=90,**common):
230                 """Angles in degrees."""
231                 _Entity.__init__(self,**common)
232                 self.center=center
233                 self.radius=radius
234                 self.startAngle=startAngle
235                 self.endAngle=endAngle
236         def __str__(self):
237                 return '  0\nARC\n%s%s\n 40\n%s\n 50\n%s\n 51\n%s\n'%\
238                            (self._common(),_point(self.center),
239                                 self.radius,self.startAngle,self.endAngle)
240
241 #-----------------------------------------------
242 class Circle(_Entity):
243         """Circle"""
244         def __init__(self,center=(0,0,0),radius=1,**common):
245                 _Entity.__init__(self,**common)
246                 self.center=center
247                 self.radius=radius
248         def __str__(self):
249                 return '  0\nCIRCLE\n%s%s\n 40\n%s\n'%\
250                            (self._common(),_point(self.center),self.radius)
251
252 #-----------------------------------------------
253 class Face(_Entity):
254         """3dface"""
255         def __init__(self,points,**common):
256                 _Entity.__init__(self,**common)
257                 while len(points)<4: #fix for r12 format
258                         points.append(points[-1])
259                 self.points=points
260                 
261         def __str__(self):
262                 out = '  0\n3DFACE\n%s%s\n' %(self._common(),_points(self.points))
263                 #print 'deb:out=', out #-------------------
264                 return out
265
266 #-----------------------------------------------
267 class Insert(_Entity):
268         """Block instance."""
269         def __init__(self,name,point=(0,0,0),
270                                  xscale=None,yscale=None,zscale=None,
271                                  cols=None,colspacing=None,rows=None,rowspacing=None,
272                                  rotation=None,
273                                  **common):
274                 _Entity.__init__(self,**common)
275                 self.name=name
276                 self.point=point
277                 self.xscale=xscale
278                 self.yscale=yscale
279                 self.zscale=zscale
280                 self.cols=cols
281                 self.colspacing=colspacing
282                 self.rows=rows
283                 self.rowspacing=rowspacing
284                 self.rotation=rotation
285
286         def __str__(self):
287                 result='  0\nINSERT\n  2\n%s\n%s\n%s\n'%\
288                                 (self.name,self._common(),_point(self.point))
289                 if self.xscale!=None:result+=' 41\n%s\n'%self.xscale
290                 if self.yscale!=None:result+=' 42\n%s\n'%self.yscale
291                 if self.zscale!=None:result+=' 43\n%s\n'%self.zscale
292                 if self.rotation:result+=' 50\n%s\n'%self.rotation
293                 if self.cols!=None:result+=' 70\n%s\n'%self.cols
294                 if self.colspacing!=None:result+=' 44\n%s\n'%self.colspacing
295                 if self.rows!=None:result+=' 71\n%s\n'%self.rows
296                 if self.rowspacing!=None:result+=' 45\n%s\n'%self.rowspacing
297                 return result
298
299 #-----------------------------------------------
300 class Line(_Entity):
301         """Line"""
302         def __init__(self,points,**common):
303                 _Entity.__init__(self,**common)
304                 self.points=points
305         def __str__(self):
306                 return '  0\nLINE\n%s%s\n' %(
307                                 self._common(), _points(self.points))
308
309
310 #-----------------------------------------------
311 class PolyLine(_Entity):
312         def __init__(self,points,org_point=[0,0,0],flag=0,width=None,**common):
313                 #width = number, or width = list [width_start=None, width_end=None]
314                 #for 2d-polyline: points = [ [x, y, z, width_start=None, width_end=None, bulge=0 or None], ...]
315                 #for 3d-polyline: points = [ [x, y, z], ...]
316                 #for polyface: points = [points_list, faces_list]
317                 _Entity.__init__(self,**common)
318                 self.points=points
319                 self.org_point=org_point
320                 self.flag=flag
321                 self.polyface = False
322                 self.polyline2d = False
323                 self.faces = [] # dummy value
324                 self.width= None # dummy value
325                 if self.flag & POLYFACE_MESH:
326                         self.polyface=True
327                         self.points=points[0]
328                         self.faces=points[1]
329                         self.p_count=len(self.points)
330                         self.f_count=len(self.faces)
331                 elif not self.flag & POLYLINE_3D:
332                         self.polyline2d = True
333                         if width:
334                                 if type(width)!='list':
335                                         width=[width,width]
336                                 self.width=width
337
338         def __str__(self):
339                 result= '  0\nPOLYLINE\n%s 70\n%s\n' %(self._common(),self.flag)
340                 result+=' 66\n1\n'
341                 result+='%s\n' %_point(self.org_point)
342                 if self.polyface:
343                         result+=' 71\n%s\n' %self.p_count
344                         result+=' 72\n%s\n' %self.f_count
345                 elif self.polyline2d:
346                         if self.width!=None: result+=' 40\n%s\n 41\n%s\n' %(self.width[0],self.width[1])
347                 for point in self.points:
348                         result+='  0\nVERTEX\n'
349                         result+='  8\n%s\n' %self.layer
350                         if self.polyface:
351                                 result+='%s\n' %_point(point[0:3])
352                                 result+=' 70\n192\n'
353                         elif self.polyline2d:
354                                 result+='%s\n' %_point(point[0:2])
355                                 if len(point)>4:
356                                         width1, width2 = point[3], point[4]
357                                         if width1!=None: result+=' 40\n%s\n' %width1
358                                         if width2!=None: result+=' 41\n%s\n' %width2
359                                 if len(point)==6:
360                                         bulge = point[5]
361                                         if bulge: result+=' 42\n%s\n' %bulge
362                         else:
363                                 result+='%s\n' %_point(point[0:3])
364                 for face in self.faces:
365                         result+='  0\nVERTEX\n'
366                         result+='  8\n%s\n' %self.layer
367                         result+='%s\n' %_point(self.org_point)
368                         result+=' 70\n128\n'
369                         result+=' 71\n%s\n' %face[0]
370                         result+=' 72\n%s\n' %face[1]
371                         result+=' 73\n%s\n' %face[2]
372                         if len(face)==4: result+=' 74\n%s\n' %face[3]
373                 result+='  0\nSEQEND\n'
374                 result+='  8\n%s\n' %self.layer
375                 return result
376
377 #-----------------------------------------------
378 class Point(_Entity):
379         """Point."""
380         def __init__(self,points=None,**common):
381                 _Entity.__init__(self,**common)
382                 self.points=points
383         def __str__(self): #TODO:
384                 return '  0\nPOINT\n%s%s\n' %(self._common(),
385                          _points(self.points)
386                         )
387
388 #-----------------------------------------------
389 class Solid(_Entity):
390         """Colored solid fill."""
391         def __init__(self,points=None,**common):
392                 _Entity.__init__(self,**common)
393                 self.points=points
394         def __str__(self):
395                 return '  0\nSOLID\n%s%s\n' %(self._common(),
396                          _points(self.points[:2]+[self.points[3],self.points[2]])
397                         )
398
399
400 #-----------------------------------------------
401 class Text(_Entity):
402         """Single text line."""
403         def __init__(self,text='',point=(0,0,0),alignment=None,
404                                  flag=None,height=1,justifyhor=None,justifyver=None,
405                                  rotation=None,obliqueAngle=None,style=None,xscale=None,**common):
406                 _Entity.__init__(self,**common)
407                 self.text=text
408                 self.point=point
409                 self.alignment=alignment
410                 self.flag=flag
411                 self.height=height
412                 self.justifyhor=justifyhor
413                 self.justifyver=justifyver
414                 self.rotation=rotation
415                 self.obliqueAngle=obliqueAngle
416                 self.style=style
417                 self.xscale=xscale
418         def __str__(self):
419                 result= '  0\nTEXT\n%s%s\n 40\n%s\n  1\n%s\n'%\
420                                 (self._common(),_point(self.point),self.height,self.text)
421                 if self.rotation: result+=' 50\n%s\n'%self.rotation
422                 if self.xscale: result+=' 41\n%s\n'%self.xscale
423                 if self.obliqueAngle: result+=' 51\n%s\n'%self.obliqueAngle
424                 if self.style: result+='  7\n%s\n'%self.style
425                 if self.flag: result+=' 71\n%s\n'%self.flag
426                 if self.justifyhor: result+=' 72\n%s\n'%self.justifyhor
427                 #TODO: if self.alignment: result+='%s\n'%_point(self.alignment,1)
428                 if self.justifyver: result+=' 73\n%s\n'%self.justifyver
429                 return result
430
431 #-----------------------------------------------
432 class Mtext(Text):
433         """Surrogate for mtext, generates some Text instances."""
434         def __init__(self,text='',point=(0,0,0),width=250,spacingFactor=1.5,down=0,spacingWidth=None,**options):
435                 Text.__init__(self,text=text,point=point,**options)
436                 if down:spacingFactor*=-1
437                 self.spacingFactor=spacingFactor
438                 self.spacingWidth=spacingWidth
439                 self.width=width
440                 self.down=down
441         def __str__(self):
442                 texts=self.text.replace('\r\n','\n').split('\n')
443                 if not self.down:texts.reverse()
444                 result=''
445                 x=y=0
446                 if self.spacingWidth:spacingWidth=self.spacingWidth
447                 else:spacingWidth=self.height*self.spacingFactor
448                 for text in texts:
449                         while text:
450                                 result+='%s\n'%Text(text[:self.width],
451                                         point=(self.point[0]+x*spacingWidth,
452                                                    self.point[1]+y*spacingWidth,
453                                                    self.point[2]),
454                                         alignment=self.alignment,flag=self.flag,height=self.height,
455                                         justifyhor=self.justifyhor,justifyver=self.justifyver,
456                                         rotation=self.rotation,obliqueAngle=self.obliqueAngle,
457                                         style=self.style,xscale=self.xscale,parent=self
458                                 )
459                                 text=text[self.width:]
460                                 if self.rotation:x+=1
461                                 else:y+=1
462                 return result[1:]
463
464 #-----------------------------------------------
465 ##class _Mtext(_Entity):
466 ##      """Mtext not functioning for minimal dxf."""
467 ##      def __init__(self,text='',point=(0,0,0),attachment=1,
468 ##                               charWidth=None,charHeight=1,direction=1,height=100,rotation=0,
469 ##                               spacingStyle=None,spacingFactor=None,style=None,width=100,
470 ##                               xdirection=None,**common):
471 ##              _Entity.__init__(self,**common)
472 ##              self.text=text
473 ##              self.point=point
474 ##              self.attachment=attachment
475 ##              self.charWidth=charWidth
476 ##              self.charHeight=charHeight
477 ##              self.direction=direction
478 ##              self.height=height
479 ##              self.rotation=rotation
480 ##              self.spacingStyle=spacingStyle
481 ##              self.spacingFactor=spacingFactor
482 ##              self.style=style
483 ##              self.width=width
484 ##              self.xdirection=xdirection
485 ##      def __str__(self):
486 ##              input=self.text
487 ##              text=''
488 ##              while len(input)>250:
489 ##                      text+='3\n%s\n'%input[:250]
490 ##                      input=input[250:]
491 ##              text+='1\n%s\n'%input
492 ##              result= '0\nMTEXT\n%s\n%s\n40\n%s\n41\n%s\n71\n%s\n72\n%s%s\n43\n%s\n50\n%s\n'%\
493 ##                              (self._common(),_point(self.point),self.charHeight,self.width,
494 ##                               self.attachment,self.direction,text,
495 ##                               self.height,
496 ##                               self.rotation)
497 ##              if self.style:result+='7\n%s\n'%self.style
498 ##              if self.xdirection:result+='%s\n'%_point(self.xdirection,1)
499 ##              if self.charWidth:result+='42\n%s\n'%self.charWidth
500 ##              if self.spacingStyle:result+='73\n%s\n'%self.spacingStyle
501 ##              if self.spacingFactor:result+='44\n%s\n'%self.spacingFactor
502 ##              return result
503
504 #---tables ---------------------------------------------------
505 #-----------------------------------------------
506 class Block(_Collection):
507         """Use list methods to add entities, eg append."""
508         def __init__(self,name,layer='0',flag=0,base=(0,0,0),entities=[]):
509                 self.entities=copy.copy(entities)
510                 _Collection.__init__(self,entities)
511                 self.layer=layer
512                 self.name=name
513                 self.flag=0
514                 self.base=base
515         def __str__(self): #TODO:
516                 e=''.join([str(x)for x in self.entities])
517                 return '  0\nBLOCK\n  8\n%s\n  2\n%s\n 70\n%s\n%s\n  3\n%s\n%s  0\nENDBLK\n'%\
518                            (self.layer,self.name.upper(),self.flag,_point(self.base),self.name.upper(),e)
519
520 #-----------------------------------------------
521 class Layer(_Call):
522         """Layer"""
523         def __init__(self,name='pydxf',color=7,lineType='continuous',flag=64):
524                 self.name=name
525                 self.color=color
526                 self.lineType=lineType
527                 self.flag=flag
528         def __str__(self):
529                 return '  0\nLAYER\n  2\n%s\n 70\n%s\n 62\n%s\n  6\n%s\n'%\
530                            (self.name.upper(),self.flag,self.color,self.lineType)
531
532 #-----------------------------------------------
533 class LineType(_Call):
534         """Custom linetype"""
535         def __init__(self,name='continuous',description='Solid line',elements=[],flag=64):
536                 # TODO: Implement lineType elements
537                 self.name=name
538                 self.description=description
539                 self.elements=copy.copy(elements)
540                 self.flag=flag
541         def __str__(self):
542                 return '  0\nLTYPE\n  2\n%s\n 70\n%s\n  3\n%s\n 72\n65\n 73\n%s\n 40\n0.0\n'%\
543                         (self.name.upper(),self.flag,self.description,len(self.elements))
544
545 #-----------------------------------------------
546 class Style(_Call):
547         """Text style"""
548         def __init__(self,name='standard',flag=0,height=0,widthFactor=40,obliqueAngle=50,
549                                  mirror=0,lastHeight=1,font='arial.ttf',bigFont=''):
550                 self.name=name
551                 self.flag=flag
552                 self.height=height
553                 self.widthFactor=widthFactor
554                 self.obliqueAngle=obliqueAngle
555                 self.mirror=mirror
556                 self.lastHeight=lastHeight
557                 self.font=font
558                 self.bigFont=bigFont
559         def __str__(self):
560                 return '  0\nSTYLE\n  2\n%s\n 70\n%s\n 40\n%s\n 41\n%s\n 50\n%s\n 71\n%s\n 42\n%s\n 3\n%s\n 4\n%s\n'%\
561                            (self.name.upper(),self.flag,self.flag,self.widthFactor,
562                                 self.obliqueAngle,self.mirror,self.lastHeight,
563                                 self.font.upper(),self.bigFont.upper())
564
565 #-----------------------------------------------
566 class View(_Call):
567         def __init__(self,name,flag=0,width=1,height=1,center=(0.5,0.5),
568                                  direction=(0,0,1),target=(0,0,0),lens=50,
569                                  frontClipping=0,backClipping=0,twist=0,mode=0):
570                 self.name=name
571                 self.flag=flag
572                 self.width=width
573                 self.height=height
574                 self.center=center
575                 self.direction=direction
576                 self.target=target
577                 self.lens=lens
578                 self.frontClipping=frontClipping
579                 self.backClipping=backClipping
580                 self.twist=twist
581                 self.mode=mode
582         def __str__(self):
583                 return '  0\nVIEW\n  2\n%s\n 70\n%s\n 40\n%s\n%s\n 41\n%s\n%s\n%s\n 42\n%s\n 43\n%s\n 44\n%s\n 50\n%s\n 71\n%s\n'%\
584                            (self.name,self.flag,self.height,_point(self.center),self.width,
585                                 _point(self.direction,1),_point(self.target,2),self.lens,
586                                 self.frontClipping,self.backClipping,self.twist,self.mode)
587
588 #-----------------------------------------------
589 def ViewByWindow(name,leftBottom=(0,0),rightTop=(1,1),**options):
590         width=abs(rightTop[0]-leftBottom[0])
591         height=abs(rightTop[1]-leftBottom[1])
592         center=((rightTop[0]+leftBottom[0])*0.5,(rightTop[1]+leftBottom[1])*0.5)
593         return View(name=name,width=width,height=height,center=center,**options)
594
595 #---drawing
596 #-----------------------------------------------
597 class Drawing(_Collection):
598         """Dxf drawing. Use append or any other list methods to add objects."""
599         def __init__(self,insbase=(0.0,0.0,0.0),extmin=(0.0,0.0,0.0),extmax=(0.0,0.0,0.0),
600                                  layers=[Layer()],linetypes=[LineType()],styles=[Style()],blocks=[],
601                                  views=[],entities=None,fileName='test.dxf'):
602                 # TODO: replace list with None,arial
603                 if not entities:
604                         entities=[]
605                 _Collection.__init__(self,entities)
606                 self.insbase=insbase
607                 self.extmin=extmin
608                 self.extmax=extmax
609                 self.layers=copy.copy(layers)
610                 self.linetypes=copy.copy(linetypes)
611                 self.styles=copy.copy(styles)
612                 self.views=copy.copy(views)
613                 self.blocks=copy.copy(blocks)
614                 self.fileName=fileName
615                 #private
616                 #self.acadver='9\n$ACADVER\n1\nAC1006\n'
617                 self.acadver='  9\n$ACADVER\n  1\nAC1009\n'
618                 """DXF AutoCAD-Release format codes
619                 AC1021  2008, 2007 
620                 AC1018  2006, 2005, 2004 
621                 AC1015  2002, 2000i, 2000 
622                 AC1014  R14,14.01 
623                 AC1012  R13    
624                 AC1009  R12,11 
625                 AC1006  R10    
626                 AC1004  R9    
627                 AC1002  R2.6  
628                 AC1.50  R2.05 
629                 """
630
631         def _name(self,x):
632                 """Helper function for self._point"""
633                 return '  9\n$%s\n' %x.upper()
634
635         def _point(self,name,x):
636                 """Point setting from drawing like extmin,extmax,..."""
637                 return '%s%s' %(self._name(name),_point(x))
638
639         def _section(self,name,x):
640                 """Sections like tables,blocks,entities,..."""
641                 if x: xstr=''.join(x)
642                 else: xstr=''
643                 return '  0\nSECTION\n  2\n%s\n%s  0\nENDSEC\n'%(name.upper(),xstr)
644
645         def _table(self,name,x):
646                 """Tables like ltype,layer,style,..."""
647                 if x: xstr=''.join(x)
648                 else: xstr=''
649                 return '  0\nTABLE\n  2\n%s\n 70\n%s\n%s  0\nENDTAB\n'%(name.upper(),len(x),xstr)
650
651         def __str__(self):
652                 """Returns drawing as dxf string."""
653                 header=[self.acadver]+[self._point(attr,getattr(self,attr))+'\n' for attr in _HEADER_POINTS]
654                 header=self._section('header',header)
655
656                 tables=[self._table('ltype',[str(x) for x in self.linetypes]),
657                                 self._table('layer',[str(x) for x in self.layers]),
658                                 self._table('style',[str(x) for x in self.styles]),
659                                 self._table('view',[str(x) for x in self.views]),
660                 ]
661                 tables=self._section('tables',tables)
662
663                 blocks=self._section('blocks',[str(x) for x in self.blocks])
664
665                 entities=self._section('entities',[str(x) for x in self.entities])
666
667                 all=''.join([header,tables,blocks,entities,'  0\nEOF\n'])
668                 return all
669
670         def saveas(self,fileName):
671                 self.fileName=fileName
672                 self.save()
673
674         def save(self):
675                 test=open(self.fileName,'w')
676                 test.write(str(self))
677                 test.close()
678
679
680 #---extras
681 #-----------------------------------------------
682 class Rectangle(_Entity):
683         """Rectangle, creates lines."""
684         def __init__(self,point=(0,0,0),width=1,height=1,solid=None,line=1,**common):
685                 _Entity.__init__(self,**common)
686                 self.point=point
687                 self.width=width
688                 self.height=height
689                 self.solid=solid
690                 self.line=line
691         def __str__(self):
692                 result=''
693                 points=[self.point,(self.point[0]+self.width,self.point[1],self.point[2]),
694                         (self.point[0]+self.width,self.point[1]+self.height,self.point[2]),
695                         (self.point[0],self.point[1]+self.height,self.point[2]),self.point]
696                 if self.solid:
697                         result+= Solid(points=points[:-1],parent=self.solid)
698                 if self.line:
699                         for i in range(4):
700                                 result+= Line(points=[points[i],points[i+1]],parent=self)
701                 return result[1:]
702
703 #-----------------------------------------------
704 class LineList(_Entity):
705         """Like polyline, but built of individual lines."""
706         def __init__(self,points=[],org_point=[0,0,0],closed=0,**common):
707                 _Entity.__init__(self,**common)
708                 self.closed=closed
709                 self.points=copy.copy(points)
710         def __str__(self):
711                 if self.closed:points=self.points+[self.points[0]]
712                 else: points=self.points
713                 result=''
714                 for i in range(len(points)-1):
715                         result+= Line(points=[points[i],points[i+1]],parent=self)
716                 return result[1:]
717
718 #-----------------------------------------------------
719 def test():
720         #Blocks
721         b=Block('test')
722         b.append(Solid(points=[(0,0,0),(1,0,0),(1,1,0),(0,1,0)],color=1))
723         b.append(Arc(center=(1,0,0),color=2))
724
725         #Drawing
726         d=Drawing()
727         #tables
728         d.blocks.append(b)  #table blocks
729         d.styles.append(Style())  #table styles
730         d.views.append(View('Normal'))  #table view
731         d.views.append(ViewByWindow('Window',leftBottom=(1,0),rightTop=(2,1)))  #idem
732
733         #entities
734         d.append(Circle(center=(1,1,0),color=3))
735         d.append(Face(points=[(0,0,0),(1,0,0),(1,1,0),(0,1,0)],color=4))
736         d.append(Insert('test',point=(3,3,3),cols=5,colspacing=2))
737         d.append(Line(points=[(0,0,0),(1,1,1)]))
738         d.append(Mtext('Click on Ads\nmultiple lines with mtext',point=(1,1,1),color=5,rotation=90))
739         d.append(Text('Please donate!',point=(3,0,1)))
740         d.append(Rectangle(point=(2,2,2),width=4,height=3,color=6,solid=Solid(color=2)))
741         d.append(Solid(points=[(4,4,0),(5,4,0),(7,8,0),(9,9,0)],color=3))
742         d.append(PolyLine(points=[(1,1,1),(2,1,1),(2,2,1),(1,2,1)],closed=1,color=1))
743
744         #d.saveas('c:\\test.dxf')
745         d.saveas('test.dxf')
746
747
748 #-----------------------------------------------------
749 if __name__=='__main__':
750         if not copy:
751                 Draw.PupMenu('Error%t|This script requires a full python install')
752         else: main()
753