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