Cleanup: tabs -> spaces
[blender-addons.git] / io_export_dxf / model / dxfLibrary.py
blob50aa4d567eca7978e2e9537c5300848a810fe1fc
1 #dxfLibrary.py : provides functions for generating DXF files
2 # --------------------------------------------------------------------------
3 __version__ = "v1.35 - 2010.06.23"
4 __author__ = "Stani Michiels(Stani), Remigiusz Fiedler(migius)"
5 __license__ = "GPL"
6 __url__ = "http://wiki.blender.org/index.php/Extensions:2.4/Py/Scripts/Export/DXF"
7 __bpydoc__ ="""The library to export geometry data to DXF format r12 version.
9 Copyright %s
10 Version %s
11 License %s
12 Homepage %s
14 See the homepage for documentation.
15 Dedicated thread on BlenderArtists: http://blenderartists.org/forum/showthread.php?t=136439
17 IDEAs:
20 TODO:
21 - add support for DXFr14 version (needs extended file header)
22 - add support for DXF-SPLINEs (possible first in DXFr14 version)
23 - add support for DXF-MTEXT
24 - add user preset for floating point precision (3-16?)
26 History
27 v1.35 - 2010.06.23 by migius
28 - added (as default) writing to DXF file without RAM-buffering: faster and low-RAM-machines friendly
29 v1.34 - 2010.06.20 by migius
30 - bugfix POLYFACE
31 - added DXF-flags for POLYLINE and VERTEX class (NURBS-export)
32 v1.33 - 2009.07.03 by migius
33 - fix MTEXT newline bug (not supported by DXF-Exporter yet)
34 - modif _point(): converts all coords to floats
35 - modif LineType class: implement elements
36 - added VPORT class, incl. defaults
37 - fix Insert class
38 v1.32 - 2009.06.06 by migius
39 - modif Style class: changed defaults to widthFactor=1.0, obliqueAngle=0.0
40 - modif Text class: alignment parameter reactivated
41 v1.31 - 2009.06.02 by migius
42 - modif _Entity class: added paperspace,elevation
43 v1.30 - 2009.05.28 by migius
44 - bugfix 3dPOLYLINE/POLYFACE: VERTEX needs x,y,z coordinates, index starts with 1 not 0
45 v1.29 - 2008.12.28 by Yorik
46 - modif POLYLINE to support bulge segments
47 v1.28 - 2008.12.13 by Steeve/BlenderArtists
48 - bugfix for EXTMIN/EXTMAX to suit Cycas-CAD
49 v1.27 - 2008.10.07 by migius
50 - beautifying output code: keys whitespace prefix
51 - refactoring DXF-strings format: NewLine moved to the end of
52 v1.26 - 2008.10.05 by migius
53 - modif POLYLINE to support POLYFACE
54 v1.25 - 2008.09.28 by migius
55 - modif FACE class for r12
56 v1.24 - 2008.09.27 by migius
57 - modif POLYLINE class for r12
58 - changing output format from r9 to r12(AC1009)
59 v1.1 (20/6/2005) by www.stani.be/python/sdxf
60 - Python library to generate dxf drawings
61 ______________________________________________________________
62 """ % (__author__,__version__,__license__,__url__)
64 # --------------------------------------------------------------------------
65 # DXF Library: copyright (C) 2005 by Stani Michiels (AKA Stani)
66 # 2008/2009 modif by Remigiusz Fiedler (AKA migius)
67 # --------------------------------------------------------------------------
68 # ***** BEGIN GPL LICENSE BLOCK *****
70 # This program is free software; you can redistribute it and/or
71 # modify it under the terms of the GNU General Public License
72 # as published by the Free Software Foundation; either version 2
73 # of the License, or (at your option) any later version.
75 # This program is distributed in the hope that it will be useful,
76 # but WITHOUT ANY WARRANTY; without even the implied warranty of
77 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
78 # GNU General Public License for more details.
80 # You should have received a copy of the GNU General Public License
81 # along with this program; if not, write to the Free Software Foundation,
82 # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
84 # ***** END GPL LICENCE BLOCK *****
87 #import Blender
88 #from Blender import Mathutils, Window, Scene, sys, Draw
89 #import BPyMessages
91 try:
92 import copy
93 #from struct import pack
94 except:
95 copy = None
97 ####1) Private (only for developers)
98 _HEADER_POINTS=['insbase','extmin','extmax']
100 #---helper functions-----------------------------------
101 def _point(x,index=0):
102 """Convert tuple to a dxf point"""
103 return '\n'.join([' %s\n%s'%((i+1)*10+index,float(x[i])) for i in range(len(x))])
105 def _points(plist):
106 """Convert a list of tuples to dxf points"""
107 out = '\n'.join([_point(plist[i],i)for i in range(len(plist))])
108 return out
110 #---base classes----------------------------------------
111 class _Call:
112 """Makes a callable class."""
113 def copy(self):
114 """Returns a copy."""
115 return copy.deepcopy(self)
117 def __call__(self,**attrs):
118 """Returns a copy with modified attributes."""
119 copied=self.copy()
120 for attr in attrs:setattr(copied,attr,attrs[attr])
121 return copied
123 #-------------------------------------------------------
124 class _Entity(_Call):
125 """Base class for _common group codes for entities."""
126 def __init__(self,paperspace=None,color=None,layer='0',
127 lineType=None,lineTypeScale=None,lineWeight=None,
128 extrusion=None,elevation=None,thickness=None,
129 parent=None):
130 """None values will be omitted."""
131 self.paperspace = paperspace
132 self.color = color
133 self.layer = layer
134 self.lineType = lineType
135 self.lineTypeScale = lineTypeScale
136 self.lineWeight = lineWeight
137 self.extrusion = extrusion
138 self.elevation = elevation
139 self.thickness = thickness
140 #self.visible = visible
141 self.parent = parent
143 def _common(self):
144 """Return common group codes as a string."""
145 if self.parent:parent=self.parent
146 else:parent=self
147 result =''
148 if parent.paperspace==1: result+=' 67\n1\n'
149 if parent.layer!=None: result+=' 8\n%s\n'%parent.layer
150 if parent.color!=None: result+=' 62\n%s\n'%parent.color
151 if parent.lineType!=None: result+=' 6\n%s\n'%parent.lineType
152 # TODO: if parent.lineWeight!=None: result+='370\n%s\n'%parent.lineWeight
153 # TODO: if parent.visible!=None: result+='60\n%s\n'%parent.visible
154 if parent.lineTypeScale!=None: result+=' 48\n%s\n'%parent.lineTypeScale
155 if parent.elevation!=None: result+=' 38\n%s\n'%parent.elevation
156 if parent.thickness!=None: result+=' 39\n%s\n'%parent.thickness
157 if parent.extrusion!=None: result+='%s\n'%_point(parent.extrusion,200)
158 return result
160 #--------------------------
161 class _Entities:
162 """Base class to deal with composed objects."""
163 def __dxf__(self):
164 return []
166 def __str__(self):
167 return ''.join([str(x) for x in self.__dxf__()])
169 #--------------------------
170 class _Collection(_Call):
171 """Base class to expose entities methods to main object."""
172 def __init__(self,entities=[]):
173 self.entities=copy.copy(entities)
174 #link entities methods to drawing
175 for attr in dir(self.entities):
176 if attr[0]!='_':
177 attrObject=getattr(self.entities,attr)
178 if callable(attrObject):
179 setattr(self,attr,attrObject)
181 ####2) Constants
182 #---color values
183 BYBLOCK=0
184 BYLAYER=256
186 #---block-type flags (bit coded values, may be combined):
187 ANONYMOUS =1 # This is an anonymous block generated by hatching, associative dimensioning, other internal operations, or an application
188 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)
189 XREF =4 # This block is an external reference (xref)
190 XREF_OVERLAY =8 # This block is an xref overlay
191 EXTERNAL =16 # This block is externally dependent
192 RESOLVED =32 # This is a resolved external reference, or dependent of an external reference (ignored on input)
193 REFERENCED =64 # This definition is a referenced external reference (ignored on input)
195 #---mtext flags
196 #attachment point
197 TOP_LEFT = 1
198 TOP_CENTER = 2
199 TOP_RIGHT = 3
200 MIDDLE_LEFT = 4
201 MIDDLE_CENTER = 5
202 MIDDLE_RIGHT = 6
203 BOTTOM_LEFT = 7
204 BOTTOM_CENTER = 8
205 BOTTOM_RIGHT = 9
206 #drawing direction
207 LEFT_RIGHT = 1
208 TOP_BOTTOM = 3
209 BY_STYLE = 5 #the flow direction is inherited from the associated text style
210 #line spacing style (optional):
211 AT_LEAST = 1 #taller characters will override
212 EXACT = 2 #taller characters will not override
214 #---polyline flag 70
215 CLOSED =1 # This is a closed polyline (or a polygon mesh closed in the M direction)
216 CURVE_FIT =2 # Curve-fit vertices have been added
217 SPLINE_FIT =4 # Spline-fit vertices have been added
218 POLYLINE_3D =8 # This is a 3D polyline
219 POLYGON_MESH =16 # This is a 3D polygon mesh
220 CLOSED_N =32 # The polygon mesh is closed in the N direction
221 POLYFACE_MESH =64 # The polyline is a polyface mesh
222 CONTINOUS_LINETYPE_PATTERN =128 # The linetype pattern is generated continuously around the vertices of this polyline
224 #---polyline flag 75, = curve type
225 QUADRIC_NURBS = 5
226 CUBIC_NURBS = 6
227 BEZIER_CURVE = 8
230 #---text flags
231 #horizontal
232 LEFT = 0
233 CENTER = 1
234 RIGHT = 2
235 ALIGNED = 3 #if vertical alignment = 0
236 MIDDLE = 4 #if vertical alignment = 0
237 FIT = 5 #if vertical alignment = 0
238 #vertical
239 BASELINE = 0
240 BOTTOM = 1
241 MIDDLE = 2
242 TOP = 3
244 ####3) Classes
245 #---entitities -----------------------------------------------
246 #--------------------------
247 class Arc(_Entity):
248 """Arc, angles in degrees."""
249 def __init__(self,center=(0,0,0),radius=1,
250 startAngle=0.0,endAngle=90,**common):
251 """Angles in degrees."""
252 _Entity.__init__(self,**common)
253 self.center=center
254 self.radius=radius
255 self.startAngle=startAngle
256 self.endAngle=endAngle
257 def __str__(self):
258 return ' 0\nARC\n%s%s\n 40\n%s\n 50\n%s\n 51\n%s\n'%\
259 (self._common(),_point(self.center),
260 self.radius,self.startAngle,self.endAngle)
262 #-----------------------------------------------
263 class Circle(_Entity):
264 """Circle"""
265 def __init__(self,center=(0,0,0),radius=1,**common):
266 _Entity.__init__(self,**common)
267 self.center=center
268 self.radius=radius
269 def __str__(self):
270 return ' 0\nCIRCLE\n%s%s\n 40\n%s\n'%\
271 (self._common(),_point(self.center),self.radius)
273 #-----------------------------------------------
274 class Face(_Entity):
275 """3dface"""
276 def __init__(self,points,**common):
277 _Entity.__init__(self,**common)
278 while len(points)<4: #fix for r12 format
279 points.append(points[-1])
280 self.points=points
282 def __str__(self):
283 out = ' 0\n3DFACE\n%s%s\n' %(self._common(),_points(self.points))
284 return out
286 #-----------------------------------------------
287 class Insert(_Entity):
288 """Block instance."""
289 def __init__(self,name,point=(0,0,0),
290 xscale=None,yscale=None,zscale=None,
291 cols=None,colspacing=None,rows=None,rowspacing=None,
292 rotation=None,
293 **common):
294 _Entity.__init__(self,**common)
295 self.name=name
296 self.point=point
297 self.xscale=xscale
298 self.yscale=yscale
299 self.zscale=zscale
300 self.cols=cols
301 self.colspacing=colspacing
302 self.rows=rows
303 self.rowspacing=rowspacing
304 self.rotation=rotation
306 def __str__(self):
307 result=' 0\nINSERT\n 2\n%s\n%s%s\n'%\
308 (self.name,self._common(),_point(self.point))
309 if self.xscale!=None:result+=' 41\n%s\n'%self.xscale
310 if self.yscale!=None:result+=' 42\n%s\n'%self.yscale
311 if self.zscale!=None:result+=' 43\n%s\n'%self.zscale
312 if self.rotation:result+=' 50\n%s\n'%self.rotation
313 if self.cols!=None:result+=' 70\n%s\n'%self.cols
314 if self.colspacing!=None:result+=' 44\n%s\n'%self.colspacing
315 if self.rows!=None:result+=' 71\n%s\n'%self.rows
316 if self.rowspacing!=None:result+=' 45\n%s\n'%self.rowspacing
317 return result
319 #-----------------------------------------------
320 class Line(_Entity):
321 """Line"""
322 def __init__(self,points,**common):
323 _Entity.__init__(self,**common)
324 self.points=points
325 def __str__(self):
326 return ' 0\nLINE\n%s%s\n' %(
327 self._common(), _points(self.points))
330 #-----------------------------------------------
331 class PolyLine(_Entity):
332 def __init__(self,points,org_point=[0,0,0],flag70=0,flag75=0,width=None,**common):
333 #width = number, or width = list [width_start=None, width_end=None]
334 #for 2d-polyline: points = [ [[x, y, z], vflag=None, [width_start=None, width_end=None], bulge=0 or None] ...]
335 #for 3d-polyline: points = [ [[x, y, z], vflag=None], ...]
336 #for polyface: points = [points_list, faces_list]
337 _Entity.__init__(self,**common)
338 self.points=points
339 self.org_point=org_point
340 self.pflag70 = flag70
341 self.pflag75 = flag75
342 self.polyface = False
343 self.polyline2d = False
344 self.faces = [] # dummy value
345 self.width= None # dummy value
346 if self.pflag70 & POLYFACE_MESH:
347 self.polyface=True
348 self.points=points[0]
349 self.faces=points[1]
350 self.p_count=len(self.points)
351 self.f_count=len(self.faces)
352 elif not (self.pflag70 & POLYLINE_3D):
353 self.polyline2d = True
354 if width:
355 if type(width)!='list': width=[width,width]
356 self.width=width
358 def __str__(self):
359 result= ' 0\nPOLYLINE\n%s 70\n%s\n' %(self._common(),self.pflag70)
360 result+=' 66\n1\n'
361 result+='%s\n' %_point(self.org_point)
362 if self.polyface:
363 result+=' 71\n%s\n' %self.p_count
364 result+=' 72\n%s\n' %self.f_count
365 elif self.polyline2d:
366 if self.width!=None: result+=' 40\n%s\n 41\n%s\n' %(self.width[0],self.width[1])
367 if self.pflag75:
368 result+=' 75\n%s\n' %self.pflag75
369 for point in self.points:
370 result+=' 0\nVERTEX\n'
371 result+=' 8\n%s\n' %self.layer
372 if self.polyface:
373 result+='%s\n' %_point(point)
374 result+=' 70\n192\n'
375 elif self.polyline2d:
376 result+='%s\n' %_point(point[0])
377 flag = point[1]
378 if len(point)>2:
379 [width1, width2] = point[2]
380 if width1!=None: result+=' 40\n%s\n' %width1
381 if width2!=None: result+=' 41\n%s\n' %width2
382 if len(point)==4:
383 bulge = point[3]
384 if bulge: result+=' 42\n%s\n' %bulge
385 if flag:
386 result+=' 70\n%s\n' %flag
387 else:
388 result+='%s\n' %_point(point[0])
389 flag = point[1]
390 if flag:
391 result+=' 70\n%s\n' %flag
393 for face in self.faces:
394 result+=' 0\nVERTEX\n'
395 result+=' 8\n%s\n' %self.layer
396 result+='%s\n' %_point(self.org_point)
397 result+=' 70\n128\n'
398 result+=' 71\n%s\n' %face[0]
399 result+=' 72\n%s\n' %face[1]
400 result+=' 73\n%s\n' %face[2]
401 if len(face)==4: result+=' 74\n%s\n' %face[3]
402 result+=' 0\nSEQEND\n'
403 result+=' 8\n%s\n' %self.layer
404 return result
406 #-----------------------------------------------
407 class Point(_Entity):
408 """Point."""
409 def __init__(self,points=None,**common):
410 _Entity.__init__(self,**common)
411 self.points=points
412 def __str__(self): # TODO:
413 return ' 0\nPOINT\n%s%s\n' %(self._common(),
414 _points(self.points)
417 #-----------------------------------------------
418 class Solid(_Entity):
419 """Colored solid fill."""
420 def __init__(self,points=None,**common):
421 _Entity.__init__(self,**common)
422 self.points=points
423 def __str__(self):
424 return ' 0\nSOLID\n%s%s\n' %(self._common(),
425 _points(self.points[:2]+[self.points[3],self.points[2]])
429 #-----------------------------------------------
430 class Text(_Entity):
431 """Single text line."""
432 def __init__(self,text='',point=(0,0,0),alignment=None,
433 flag=None,height=1,justifyhor=None,justifyver=None,
434 rotation=None,obliqueAngle=None,style=None,xscale=None,**common):
435 _Entity.__init__(self,**common)
436 self.text=text
437 self.point=point
438 self.alignment=alignment
439 self.flag=flag
440 self.height=height
441 self.justifyhor=justifyhor
442 self.justifyver=justifyver
443 self.rotation=rotation
444 self.obliqueAngle=obliqueAngle
445 self.style=style
446 self.xscale=xscale
447 def __str__(self):
448 result= ' 0\nTEXT\n%s%s\n 40\n%s\n 1\n%s\n'%\
449 (self._common(),_point(self.point),self.height,self.text)
450 if self.rotation: result+=' 50\n%s\n'%self.rotation
451 if self.xscale: result+=' 41\n%s\n'%self.xscale
452 if self.obliqueAngle: result+=' 51\n%s\n'%self.obliqueAngle
453 if self.style: result+=' 7\n%s\n'%self.style
454 if self.flag: result+=' 71\n%s\n'%self.flag
455 if self.justifyhor: result+=' 72\n%s\n'%self.justifyhor
456 if self.alignment: result+='%s\n'%_point(self.alignment,1)
457 if self.justifyver: result+=' 73\n%s\n'%self.justifyver
458 return result
460 #-----------------------------------------------
461 class Mtext(Text):
462 """Surrogate for mtext, generates some Text instances."""
463 def __init__(self,text='',point=(0,0,0),width=250,spacingFactor=1.5,down=0,spacingWidth=None,**options):
464 Text.__init__(self,text=text,point=point,**options)
465 if down:spacingFactor*=-1
466 self.spacingFactor=spacingFactor
467 self.spacingWidth=spacingWidth
468 self.width=width
469 self.down=down
470 def __str__(self):
471 texts=self.text.replace('\r\n','\n').split('\n')
472 if not self.down:texts.reverse()
473 result=''
474 x=y=0
475 if self.spacingWidth:spacingWidth=self.spacingWidth
476 else:spacingWidth=self.height*self.spacingFactor
477 for text in texts:
478 while text:
479 result+='%s' %Text(text[:self.width],
480 point=(self.point[0]+x*spacingWidth,
481 self.point[1]+y*spacingWidth,
482 self.point[2]),
483 alignment=self.alignment,flag=self.flag,height=self.height,
484 justifyhor=self.justifyhor,justifyver=self.justifyver,
485 rotation=self.rotation,obliqueAngle=self.obliqueAngle,
486 style=self.style,xscale=self.xscale,parent=self
488 text=text[self.width:]
489 if self.rotation:x+=1
490 else:y+=1
491 return result[1:]
493 #-----------------------------------------------
494 ##class _Mtext(_Entity):
495 """Mtext not functioning for minimal dxf."""
497 def __init__(self,text='',point=(0,0,0),attachment=1,
498 charWidth=None,charHeight=1,direction=1,height=100,rotation=0,
499 spacingStyle=None,spacingFactor=None,style=None,width=100,
500 xdirection=None,**common):
501 _Entity.__init__(self,**common)
502 self.text=text
503 self.point=point
504 self.attachment=attachment
505 self.charWidth=charWidth
506 self.charHeight=charHeight
507 self.direction=direction
508 self.height=height
509 self.rotation=rotation
510 self.spacingStyle=spacingStyle
511 self.spacingFactor=spacingFactor
512 self.style=style
513 self.width=width
514 self.xdirection=xdirection
515 def __str__(self):
516 input=self.text
517 text=''
518 while len(input)>250:
519 text+='3\n%s\n'%input[:250]
520 input=input[250:]
521 text+='1\n%s\n'%input
522 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'%\
523 (self._common(),_point(self.point),self.charHeight,self.width,
524 self.attachment,self.direction,text,
525 self.height,
526 self.rotation)
527 if self.style:result+='7\n%s\n'%self.style
528 if self.xdirection:result+='%s\n'%_point(self.xdirection,1)
529 if self.charWidth:result+='42\n%s\n'%self.charWidth
530 if self.spacingStyle:result+='73\n%s\n'%self.spacingStyle
531 if self.spacingFactor:result+='44\n%s\n'%self.spacingFactor
532 return result
536 #---tables ---------------------------------------------------
537 #-----------------------------------------------
538 class Block(_Collection):
539 """Use list methods to add entities, eg append."""
540 def __init__(self,name,layer='0',flag=0,base=(0,0,0),entities=[]):
541 self.entities=copy.copy(entities)
542 _Collection.__init__(self,entities)
543 self.layer=layer
544 self.name=name
545 self.flag=0
546 self.base=base
547 def __str__(self): # TODO:
548 e=''.join([str(x)for x in self.entities])
549 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'%\
550 (self.layer,self.name.upper(),self.flag,_point(self.base),self.name.upper(),e)
552 #-----------------------------------------------
553 class Layer(_Call):
554 """Layer"""
555 def __init__(self,name='pydxf',color=7,lineType='continuous',flag=64):
556 self.name=name
557 self.color=color
558 self.lineType=lineType
559 self.flag=flag
560 def __str__(self):
561 return ' 0\nLAYER\n 2\n%s\n 70\n%s\n 62\n%s\n 6\n%s\n'%\
562 (self.name.upper(),self.flag,self.color,self.lineType)
564 #-----------------------------------------------
565 class LineType(_Call):
566 """Custom linetype"""
567 def __init__(self,name='CONTINUOUS',description='Solid line',elements=[0.0],flag=0):
568 self.name=name
569 self.description=description
570 self.elements=copy.copy(elements)
571 self.flag=flag
572 def __str__(self):
573 result = ' 0\nLTYPE\n 2\n%s\n 70\n%s\n 3\n%s\n 72\n65\n'%\
574 (self.name.upper(),self.flag,self.description)
575 if self.elements:
576 elements = ' 73\n%s\n' %(len(self.elements)-1)
577 elements += ' 40\n%s\n' %(self.elements[0])
578 for e in self.elements[1:]:
579 elements += ' 49\n%s\n' %e
580 result += elements
581 return result
584 #-----------------------------------------------
585 class Style(_Call):
586 """Text style"""
587 def __init__(self,name='standard',flag=0,height=0,widthFactor=1.0,obliqueAngle=0.0,
588 mirror=0,lastHeight=1,font='arial.ttf',bigFont=''):
589 self.name=name
590 self.flag=flag
591 self.height=height
592 self.widthFactor=widthFactor
593 self.obliqueAngle=obliqueAngle
594 self.mirror=mirror
595 self.lastHeight=lastHeight
596 self.font=font
597 self.bigFont=bigFont
598 def __str__(self):
599 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'%\
600 (self.name.upper(),self.flag,self.flag,self.widthFactor,
601 self.obliqueAngle,self.mirror,self.lastHeight,
602 self.font.upper(),self.bigFont.upper())
604 #-----------------------------------------------
605 class VPort(_Call):
606 def __init__(self,name,flag=0,
607 leftBottom=(0.0,0.0),
608 rightTop=(1.0,1.0),
609 center=(0.5,0.5),
610 snap_base=(0.0,0.0),
611 snap_spacing=(0.1,0.1),
612 grid_spacing=(0.1,0.1),
613 direction=(0.0,0.0,1.0),
614 target=(0.0,0.0,0.0),
615 height=1.0,
616 ratio=1.0,
617 lens=50.0,
618 frontClipping=0.0,
619 backClipping=0.0,
620 snap_rotation=0.0,
621 twist=0.0,
622 mode=0,
623 circle_zoom=100,
624 fast_zoom=1,
625 ucsicon=1,
626 snap_on=0,
627 grid_on=0,
628 snap_style=0,
629 snap_isopair=0
631 self.name=name
632 self.flag=flag
633 self.leftBottom=leftBottom
634 self.rightTop=rightTop
635 self.center=center
636 self.snap_base=snap_base
637 self.snap_spacing=snap_spacing
638 self.grid_spacing=grid_spacing
639 self.direction=direction
640 self.target=target
641 self.height=float(height)
642 self.ratio=float(ratio)
643 self.lens=float(lens)
644 self.frontClipping=float(frontClipping)
645 self.backClipping=float(backClipping)
646 self.snap_rotation=float(snap_rotation)
647 self.twist=float(twist)
648 self.mode=mode
649 self.circle_zoom=circle_zoom
650 self.fast_zoom=fast_zoom
651 self.ucsicon=ucsicon
652 self.snap_on=snap_on
653 self.grid_on=grid_on
654 self.snap_style=snap_style
655 self.snap_isopair=snap_isopair
656 def __str__(self):
657 output = [' 0', 'VPORT',
658 ' 2', self.name,
659 ' 70', self.flag,
660 _point(self.leftBottom),
661 _point(self.rightTop,1),
662 _point(self.center,2), # View center point (in DCS)
663 _point(self.snap_base,3),
664 _point(self.snap_spacing,4),
665 _point(self.grid_spacing,5),
666 _point(self.direction,6), #view direction from target (in WCS)
667 _point(self.target,7),
668 ' 40', self.height,
669 ' 41', self.ratio,
670 ' 42', self.lens,
671 ' 43', self.frontClipping,
672 ' 44', self.backClipping,
673 ' 50', self.snap_rotation,
674 ' 51', self.twist,
675 ' 71', self.mode,
676 ' 72', self.circle_zoom,
677 ' 73', self.fast_zoom,
678 ' 74', self.ucsicon,
679 ' 75', self.snap_on,
680 ' 76', self.grid_on,
681 ' 77', self.snap_style,
682 ' 78', self.snap_isopair
685 output_str = ''
686 for s in output:
687 output_str += '%s\n' %s
688 return output_str
692 #-----------------------------------------------
693 class View(_Call):
694 def __init__(self,name,flag=0,
695 width=1.0,
696 height=1.0,
697 center=(0.5,0.5),
698 direction=(0.0,0.0,1.0),
699 target=(0.0,0.0,0.0),
700 lens=50.0,
701 frontClipping=0.0,
702 backClipping=0.0,
703 twist=0.0,mode=0
705 self.name=name
706 self.flag=flag
707 self.width=float(width)
708 self.height=float(height)
709 self.center=center
710 self.direction=direction
711 self.target=target
712 self.lens=float(lens)
713 self.frontClipping=float(frontClipping)
714 self.backClipping=float(backClipping)
715 self.twist=float(twist)
716 self.mode=mode
717 def __str__(self):
718 output = [' 0', 'VIEW',
719 ' 2', self.name,
720 ' 70', self.flag,
721 ' 40', self.height,
722 _point(self.center),
723 ' 41', self.width,
724 _point(self.direction,1),
725 _point(self.target,2),
726 ' 42', self.lens,
727 ' 43', self.frontClipping,
728 ' 44', self.backClipping,
729 ' 50', self.twist,
730 ' 71', self.mode
732 output_str = ''
733 for s in output:
734 output_str += '%s\n' %s
735 return output_str
737 #-----------------------------------------------
738 def ViewByWindow(name,leftBottom=(0,0),rightTop=(1,1),**options):
739 width=abs(rightTop[0]-leftBottom[0])
740 height=abs(rightTop[1]-leftBottom[1])
741 center=((rightTop[0]+leftBottom[0])*0.5,(rightTop[1]+leftBottom[1])*0.5)
742 return View(name=name,width=width,height=height,center=center,**options)
744 #---drawing
745 #-----------------------------------------------
746 class Drawing(_Collection):
747 """Dxf drawing. Use append or any other list methods to add objects."""
748 def __init__(self,insbase=(0.0,0.0,0.0),extmin=(0.0,0.0,0.0),extmax=(0.0,0.0,0.0),
749 layers=[Layer()],linetypes=[LineType()],styles=[Style()],blocks=[],
750 views=[],vports=[],entities=None,fileName='test.dxf'):
751 # TODO: replace list with None,arial
752 if not entities:
753 entities=[]
754 _Collection.__init__(self,entities)
755 self.insbase=insbase
756 self.extmin=extmin
757 self.extmax=extmax
758 self.layers=copy.copy(layers)
759 self.linetypes=copy.copy(linetypes)
760 self.styles=copy.copy(styles)
761 self.views=copy.copy(views)
762 self.vports=copy.copy(vports)
763 self.blocks=copy.copy(blocks)
764 self.fileName=fileName
765 #print 'deb: blocks=',blocks #----------
766 #private
767 #self.acadver='9\n$ACADVER\n1\nAC1006\n'
768 self.acadver=' 9\n$ACADVER\n 1\nAC1009\n'
769 """DXF AutoCAD-Release format codes:
770 AC1021 2008, 2007
771 AC1018 2006, 2005, 2004
772 AC1015 2002, 2000i, 2000
773 AC1014 R14,14.01
774 AC1012 R13
775 AC1009 R12,11
776 AC1006 R10
777 AC1004 R9
778 AC1002 R2.6
779 AC1.50 R2.05
782 def _name(self,x):
783 """Helper function for self._point"""
784 return ' 9\n$%s\n' %x.upper()
786 def _point(self,name,x):
787 """Point setting from drawing like extmin,extmax,..."""
788 return '%s%s' %(self._name(name),_point(x))
790 def _section(self,name,x):
791 """Sections like tables,blocks,entities,..."""
792 if x: xstr=''.join(x)
793 else: xstr=''
794 return ' 0\nSECTION\n 2\n%s\n%s 0\nENDSEC\n'%(name.upper(),xstr)
796 def _table(self,name,x):
797 """Tables like ltype,layer,style,..."""
798 if x: xstr=''.join(x)
799 else: xstr=''
800 return ' 0\nTABLE\n 2\n%s\n 70\n%s\n%s 0\nENDTAB\n'%(name.upper(),len(x),xstr)
802 def __str__(self):
803 """Returns drawing as dxf string."""
804 header=[self.acadver]+[self._point(attr,getattr(self,attr))+'\n' for attr in _HEADER_POINTS]
805 header=self._section('header',header)
807 tables=[self._table('vport',[str(x) for x in self.vports]),
808 self._table('ltype',[str(x) for x in self.linetypes]),
809 self._table('layer',[str(x) for x in self.layers]),
810 self._table('style',[str(x) for x in self.styles]),
811 self._table('view',[str(x) for x in self.views]),
813 tables=self._section('tables',tables)
814 blocks=self._section('blocks',[str(x) for x in self.blocks])
815 entities=self._section('entities',[str(x) for x in self.entities])
816 all=''.join([header,tables,blocks,entities,' 0\nEOF\n'])
817 return all
819 def _write_section(self,file,name,data):
820 file.write(' 0\nSECTION\n 2\n%s\n'%name.upper())
821 for x in data:
822 file.write(str(x))
823 file.write(' 0\nENDSEC\n')
825 def saveas(self,fileName,buffer=0):
826 """Writes DXF file. Needs target file name. If optional parameter buffer>0, then switch to old behavior: store entire output string in RAM.
828 self.fileName=fileName
829 if buffer: self.save()
830 else: self.export()
832 def save(self):
833 outfile=open(self.fileName,'w')
834 outfile.write(str(self))
835 outfile.close()
837 def export(self):
838 outfile=open(self.fileName,'w')
839 header=[self.acadver]+[self._point(attr,getattr(self,attr))+'\n' for attr in _HEADER_POINTS]
840 self._write_section(outfile,'header',header)
841 tables=[self._table('vport',[str(x) for x in self.vports]),
842 self._table('ltype',[str(x) for x in self.linetypes]),
843 self._table('layer',[str(x) for x in self.layers]),
844 self._table('style',[str(x) for x in self.styles]),
845 self._table('view',[str(x) for x in self.views]),
847 self._write_section(outfile,'tables',tables)
848 self._write_section(outfile,'blocks',self.blocks)
849 self._write_section(outfile,'entities',self.entities)
850 outfile.write(' 0\nEOF\n')
851 outfile.close()
854 #---extras
855 #-----------------------------------------------
856 class Rectangle(_Entity):
857 """Rectangle, creates lines."""
858 def __init__(self,point=(0,0,0),width=1,height=1,solid=None,line=1,**common):
859 _Entity.__init__(self,**common)
860 self.point=point
861 self.width=width
862 self.height=height
863 self.solid=solid
864 self.line=line
865 def __str__(self):
866 result=''
867 points=[self.point,(self.point[0]+self.width,self.point[1],self.point[2]),
868 (self.point[0]+self.width,self.point[1]+self.height,self.point[2]),
869 (self.point[0],self.point[1]+self.height,self.point[2]),self.point]
870 if self.solid:
871 result+= Solid(points=points[:-1],parent=self.solid)
872 if self.line:
873 for i in range(4):
874 result+= Line(points=[points[i],points[i+1]],parent=self)
875 return result[1:]
877 #-----------------------------------------------
878 class LineList(_Entity):
879 """Like polyline, but built of individual lines."""
880 def __init__(self,points=[],org_point=[0,0,0],closed=0,**common):
881 _Entity.__init__(self,**common)
882 self.closed=closed
883 self.points=copy.copy(points)
884 def __str__(self):
885 if self.closed:
886 points=self.points+[self.points[0]]
887 else: points=self.points
888 result=''
889 for i in range(len(points)-1):
890 result+= Line(points=[points[i][0],points[i+1][0]],parent=self)
891 return result[1:]
893 #-----------------------------------------------------
894 def test():
895 #Blocks
896 b=Block('test')
897 b.append(Solid(points=[(0,0,0),(1,0,0),(1,1,0),(0,1,0)],color=1))
898 b.append(Arc(center=(1,0,0),color=2))
900 #Drawing
901 d=Drawing()
902 #tables
903 d.blocks.append(b) #table blocks
904 d.styles.append(Style()) #table styles
905 d.views.append(View('Normal')) #table view
906 d.views.append(ViewByWindow('Window',leftBottom=(1,0),rightTop=(2,1))) #idem
908 #entities
909 d.append(Circle(center=(1,1,0),color=3))
910 d.append(Face(points=[(0,0,0),(1,0,0),(1,1,0),(0,1,0)],color=4))
911 d.append(Insert('test',point=(3,3,3),cols=5,colspacing=2))
912 d.append(Line(points=[(0,0,0),(1,1,1)]))
913 d.append(Mtext('Click on Ads\nmultiple lines with mtext',point=(1,1,1),color=5,rotation=90))
914 d.append(Text('Please donate!',point=(3,0,1)))
915 #d.append(Rectangle(point=(2,2,2),width=4,height=3,color=6,solid=Solid(color=2)))
916 d.append(Solid(points=[(4,4,0),(5,4,0),(7,8,0),(9,9,0)],color=3))
917 #d.append(PolyLine(points=[(1,1,1),(2,1,1),(2,2,1),(1,2,1)],flag=1,color=1))
919 #d.saveas('c:\\test.dxf')
920 d.saveas('test.dxf')
922 #-----------------------------------------------------
923 if __name__=='__main__':
924 if not copy:
925 Draw.PupMenu('Error%t|This script requires a full python install')
926 else: test()