1
#dxfLibrary.py : provides functions for generating DXF files
2
# --------------------------------------------------------------------------
3
__version__ = "v1.27beta - 2008.10.05"
4
__author__ = "Stani Michiels(Stani), Remigiusz Fiedler(migius)"
6
__url__ = "http://wiki.blender.org/index.php/Scripts/Manual/Export/autodesk_dxf"
7
__bpydoc__ ="""The script exports geometry data to DXF format r12 version.
14
See the homepage for documentation.
24
v1.27 - 2008.10.07 by migius
25
- beautifying output code: keys whitespace prefix
26
- refactoring DXF-strings format: NewLine moved to the end of
27
v1.26 - 2008.10.05 by migius
28
- modif POLYLINE to support POLYFACE
29
v1.25 - 2008.09.28 by migius
30
- modif FACE class for r12
31
v1.24 - 2008.09.27 by migius
32
- modif POLYLINE class for r12
33
- changing output format from r9 to r12(AC1009)
34
v1.1 (20/6/2005) by www.stani.be/python/sdxf
35
- Python library to generate dxf drawings
36
______________________________________________________________
37
""" % (__author__,__version__,__license__,__url__)
39
# --------------------------------------------------------------------------
40
# DXF Library: copyright (C) 2005 by Stani Michiels (AKA Stani)
41
# 2008 modif by Remigiusz Fiedler (AKA migius)
42
# --------------------------------------------------------------------------
43
# ***** BEGIN GPL LICENSE BLOCK *****
45
# This program is free software; you can redistribute it and/or
46
# modify it under the terms of the GNU General Public License
47
# as published by the Free Software Foundation; either version 2
48
# of the License, or (at your option) any later version.
50
# This program is distributed in the hope that it will be useful,
51
# but WITHOUT ANY WARRANTY; without even the implied warranty of
52
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
53
# GNU General Public License for more details.
55
# You should have received a copy of the GNU General Public License
56
# along with this program; if not, write to the Free Software Foundation,
57
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
59
# ***** END GPL LICENCE BLOCK *****
63
#from Blender import Mathutils, Window, Scene, sys, Draw
68
#from struct import pack
72
####1) Private (only for developpers)
73
_HEADER_POINTS=['insbase','extmin','extmax']
75
#---helper functions-----------------------------------
76
def _point(x,index=0):
77
"""Convert tuple to a dxf point"""
78
#print 'deb: _point=', x #-------------
79
return '\n'.join([' %s\n%s'%((i+1)*10+index,x[i]) for i in range(len(x))])
82
"""Convert a list of tuples to dxf points"""
83
out = '\n'.join([_point(plist[i],i)for i in range(len(plist))])
84
#print 'deb: points=\n', out #-------------------
87
#---base classes----------------------------------------
89
"""Makes a callable class."""
92
return copy.deepcopy(self)
94
def __call__(self,**attrs):
95
"""Returns a copy with modified attributes."""
97
for attr in attrs:setattr(copied,attr,attrs[attr])
100
#-------------------------------------------------------
101
class _Entity(_Call):
102
"""Base class for _common group codes for entities."""
103
def __init__(self,color=None,extrusion=None,layer='0',
104
lineType=None,lineTypeScale=None,lineWeight=None,
105
thickness=None,parent=None):
106
"""None values will be omitted."""
108
self.extrusion = extrusion
110
self.lineType = lineType
111
self.lineTypeScale = lineTypeScale
112
self.lineWeight = lineWeight
113
self.thickness = thickness
117
"""Return common group codes as a string."""
118
if self.parent:parent=self.parent
121
if parent.layer!=None: result+=' 8\n%s\n'%parent.layer
122
if parent.color!=None: result+=' 62\n%s\n'%parent.color
123
if parent.extrusion!=None: result+='%s\n'%_point(parent.extrusion,200)
124
if parent.lineType!=None: result+=' 6\n%s\n'%parent.lineType
125
#TODO: if parent.lineWeight!=None: result+='370\n%s\n'%parent.lineWeight
126
if parent.lineTypeScale!=None: result+=' 48\n%s\n'%parent.lineTypeScale
127
if parent.thickness!=None: result+=' 39\n%s\n'%parent.thickness
130
#--------------------------
132
"""Base class to deal with composed objects."""
137
return ''.join([str(x) for x in self.__dxf__()])
139
#--------------------------
140
class _Collection(_Call):
141
"""Base class to expose entities methods to main object."""
142
def __init__(self,entities=[]):
143
self.entities=copy.copy(entities)
144
#link entities methods to drawing
145
for attr in dir(self.entities):
147
attrObject=getattr(self.entities,attr)
148
if callable(attrObject):
149
setattr(self,attr,attrObject)
156
#---block-type flags (bit coded values, may be combined):
157
ANONYMOUS =1 # This is an anonymous block generated by hatching, associative dimensioning, other internal operations, or an application
158
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)
159
XREF =4 # This block is an external reference (xref)
160
XREF_OVERLAY =8 # This block is an xref overlay
161
EXTERNAL =16 # This block is externally dependent
162
RESOLVED =32 # This is a resolved external reference, or dependent of an external reference (ignored on input)
163
REFERENCED =64 # This definition is a referenced external reference (ignored on input)
179
BY_STYLE = 5 #the flow direction is inherited from the associated text style
180
#line spacing style (optional):
181
AT_LEAST = 1 #taller characters will override
182
EXACT = 2 #taller characters will not override
185
CLOSED =1 # This is a closed polyline (or a polygon mesh closed in the M direction)
186
CURVE_FIT =2 # Curve-fit vertices have been added
187
SPLINE_FIT =4 # Spline-fit vertices have been added
188
POLYLINE_3D =8 # This is a 3D polyline
189
POLYGON_MESH =16 # This is a 3D polygon mesh
190
CLOSED_N =32 # The polygon mesh is closed in the N direction
191
POLYFACE_MESH =64 # The polyline is a polyface mesh
192
CONTINOUS_LINETYPE_PATTERN =128 # The linetype pattern is generated continuously around the vertices of this polyline
199
ALIGNED = 3 #if vertical alignment = 0
200
MIDDLE = 4 #if vertical alignment = 0
201
FIT = 5 #if vertical alignment = 0
209
#---entitities -----------------------------------------------
210
#--------------------------
212
"""Arc, angles in degrees."""
213
def __init__(self,center=(0,0,0),radius=1,
214
startAngle=0.0,endAngle=90,**common):
215
"""Angles in degrees."""
216
_Entity.__init__(self,**common)
219
self.startAngle=startAngle
220
self.endAngle=endAngle
222
return ' 0\nARC\n%s%s\n 40\n%s\n 50\n%s\n 51\n%s\n'%\
223
(self._common(),_point(self.center),
224
self.radius,self.startAngle,self.endAngle)
226
#-----------------------------------------------
227
class Circle(_Entity):
229
def __init__(self,center=(0,0,0),radius=1,**common):
230
_Entity.__init__(self,**common)
234
return ' 0\nCIRCLE\n%s%s\n 40\n%s\n'%\
235
(self._common(),_point(self.center),self.radius)
237
#-----------------------------------------------
240
def __init__(self,points,**common):
241
_Entity.__init__(self,**common)
242
if len(points)<4: #fix for r12 format
243
points.append(points[-1])
247
out = ' 0\n3DFACE\n%s%s\n' %(self._common(),_points(self.points))
248
#print 'deb:out=', out #-------------------
251
#-----------------------------------------------
252
class Insert(_Entity):
253
"""Block instance."""
254
def __init__(self,name,point=(0,0,0),
255
xscale=None,yscale=None,zscale=None,
256
cols=None,colspacing=None,rows=None,rowspacing=None,
259
_Entity.__init__(self,**common)
266
self.colspacing=colspacing
268
self.rowspacing=rowspacing
269
self.rotation=rotation
272
result=' 0\nINSERT\n 2\n%s\n%s\n%s\n'%\
273
(self.name,self._common(),_point(self.point))
274
if self.xscale!=None:result+=' 41\n%s\n'%self.xscale
275
if self.yscale!=None:result+=' 42\n%s\n'%self.yscale
276
if self.zscale!=None:result+=' 43\n%s\n'%self.zscale
277
if self.rotation:result+=' 50\n%s\n'%self.rotation
278
if self.cols!=None:result+=' 70\n%s\n'%self.cols
279
if self.colspacing!=None:result+=' 44\n%s\n'%self.colspacing
280
if self.rows!=None:result+=' 71\n%s\n'%self.rows
281
if self.rowspacing!=None:result+=' 45\n%s\n'%self.rowspacing
284
#-----------------------------------------------
287
def __init__(self,points,**common):
288
_Entity.__init__(self,**common)
291
return ' 0\nLINE\n%s%s\n' %(
292
self._common(), _points(self.points))
295
#-----------------------------------------------
296
class PolyLine(_Entity):
297
def __init__(self,points,org_point=[0,0,0],flag=0,width=None,**common):
298
_Entity.__init__(self,**common)
300
self.org_point=org_point
303
self.points=points[0]
305
self.p_count=len(self.points)
306
self.f_count=len(self.faces)
310
result= ' 0\nPOLYLINE\n%s 70\n%s\n' %(self._common(),self.flag)
311
#print 'deb: self._common()', self._common() #----------
313
result+='%s\n' %_point(self.org_point)
315
result+=' 71\n%s\n' %self.p_count
316
result+=' 72\n%s\n' %self.f_count
317
for point in self.points:
318
result+=' 0\nVERTEX\n'
319
result+=' 8\n%s\n' %self.layer
320
result+='%s\n' %_point(point)
321
if self.flag==64: result+=' 70\n192\n'
322
if self.width: result+=' 40\n%s\n 41\n%s\n' %(self.width,self.width)
324
for face in self.faces:
325
result+=' 0\nVERTEX\n'
326
result+=' 8\n%s\n' %self.layer
327
result+='%s\n' %_point(self.org_point)
329
result+=' 71\n%s\n' %face[0]
330
result+=' 72\n%s\n' %face[1]
331
result+=' 73\n%s\n' %face[2]
332
if len(face)==4: result+=' 74\n%s\n' %face[3]
333
result+=' 0\nSEQEND\n'
334
result+=' 8\n%s\n' %self.layer
337
#-----------------------------------------------
338
class Point(_Entity):
339
"""Colored solid fill."""
340
def __init__(self,points=None,**common):
341
_Entity.__init__(self,**common)
344
#-----------------------------------------------
345
class Solid(_Entity):
346
"""Colored solid fill."""
347
def __init__(self,points=None,**common):
348
_Entity.__init__(self,**common)
351
return ' 0\nSOLID\n%s%s\n' %(self._common(),
352
_points(self.points[:2]+[self.points[3],self.points[2]])
356
#-----------------------------------------------
358
"""Single text line."""
359
def __init__(self,text='',point=(0,0,0),alignment=None,
360
flag=None,height=1,justifyhor=None,justifyver=None,
361
rotation=None,obliqueAngle=None,style=None,xscale=None,**common):
362
_Entity.__init__(self,**common)
365
self.alignment=alignment
368
self.justifyhor=justifyhor
369
self.justifyver=justifyver
370
self.rotation=rotation
371
self.obliqueAngle=obliqueAngle
375
result= ' 0\nTEXT\n%s%s\n 40\n%s\n 1\n%s\n'%\
376
(self._common(),_point(self.point),self.height,self.text)
377
if self.rotation: result+=' 50\n%s\n'%self.rotation
378
if self.xscale: result+=' 41\n%s\n'%self.xscale
379
if self.obliqueAngle: result+=' 51\n%s\n'%self.obliqueAngle
380
if self.style: result+=' 7\n%s\n'%self.style
381
if self.flag: result+=' 71\n%s\n'%self.flag
382
if self.justifyhor: result+=' 72\n%s\n'%self.justifyhor
383
#TODO: if self.alignment: result+='%s\n'%_point(self.alignment,1)
384
if self.justifyver: result+=' 73\n%s\n'%self.justifyver
387
#-----------------------------------------------
389
"""Surrogate for mtext, generates some Text instances."""
390
def __init__(self,text='',point=(0,0,0),width=250,spacingFactor=1.5,down=0,spacingWidth=None,**options):
391
Text.__init__(self,text=text,point=point,**options)
392
if down:spacingFactor*=-1
393
self.spacingFactor=spacingFactor
394
self.spacingWidth=spacingWidth
398
texts=self.text.replace('\r\n','\n').split('\n')
399
if not self.down:texts.reverse()
402
if self.spacingWidth:spacingWidth=self.spacingWidth
403
else:spacingWidth=self.height*self.spacingFactor
406
result+='%s\n'%Text(text[:self.width],
407
point=(self.point[0]+x*spacingWidth,
408
self.point[1]+y*spacingWidth,
410
alignment=self.alignment,flag=self.flag,height=self.height,
411
justifyhor=self.justifyhor,justifyver=self.justifyver,
412
rotation=self.rotation,obliqueAngle=self.obliqueAngle,
413
style=self.style,xscale=self.xscale,parent=self
415
text=text[self.width:]
416
if self.rotation:x+=1
420
#-----------------------------------------------
421
##class _Mtext(_Entity):
422
## """Mtext not functioning for minimal dxf."""
423
## def __init__(self,text='',point=(0,0,0),attachment=1,
424
## charWidth=None,charHeight=1,direction=1,height=100,rotation=0,
425
## spacingStyle=None,spacingFactor=None,style=None,width=100,
426
## xdirection=None,**common):
427
## _Entity.__init__(self,**common)
430
## self.attachment=attachment
431
## self.charWidth=charWidth
432
## self.charHeight=charHeight
433
## self.direction=direction
434
## self.height=height
435
## self.rotation=rotation
436
## self.spacingStyle=spacingStyle
437
## self.spacingFactor=spacingFactor
440
## self.xdirection=xdirection
441
## def __str__(self):
444
## while len(input)>250:
445
## text+='3\n%s\n'%input[:250]
447
## text+='1\n%s\n'%input
448
## 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'%\
449
## (self._common(),_point(self.point),self.charHeight,self.width,
450
## self.attachment,self.direction,text,
453
## if self.style:result+='7\n%s\n'%self.style
454
## if self.xdirection:result+='%s\n'%_point(self.xdirection,1)
455
## if self.charWidth:result+='42\n%s\n'%self.charWidth
456
## if self.spacingStyle:result+='73\n%s\n'%self.spacingStyle
457
## if self.spacingFactor:result+='44\n%s\n'%self.spacingFactor
460
#---tables ---------------------------------------------------
461
#-----------------------------------------------
462
class Block(_Collection):
463
"""Use list methods to add entities, eg append."""
464
def __init__(self,name,layer='0',flag=0,base=(0,0,0),entities=[]):
465
self.entities=copy.copy(entities)
466
_Collection.__init__(self,entities)
472
e=''.join([str(x)for x in self.entities])
473
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'%\
474
(self.layer,self.name.upper(),self.flag,_point(self.base),self.name.upper(),e)
476
#-----------------------------------------------
479
def __init__(self,name='pydxf',color=7,lineType='continuous',flag=64):
482
self.lineType=lineType
485
return ' 0\nLAYER\n 2\n%s\n 70\n%s\n 62\n%s\n 6\n%s\n'%\
486
(self.name.upper(),self.flag,self.color,self.lineType)
488
#-----------------------------------------------
489
class LineType(_Call):
490
"""Custom linetype"""
491
def __init__(self,name='continuous',description='Solid line',elements=[],flag=64):
492
# TODO: Implement lineType elements
494
self.description=description
495
self.elements=copy.copy(elements)
498
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'%\
499
(self.name.upper(),self.flag,self.description,len(self.elements))
501
#-----------------------------------------------
504
def __init__(self,name='standard',flag=0,height=0,widthFactor=40,obliqueAngle=50,
505
mirror=0,lastHeight=1,font='arial.ttf',bigFont=''):
509
self.widthFactor=widthFactor
510
self.obliqueAngle=obliqueAngle
512
self.lastHeight=lastHeight
516
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'%\
517
(self.name.upper(),self.flag,self.flag,self.widthFactor,
518
self.obliqueAngle,self.mirror,self.lastHeight,
519
self.font.upper(),self.bigFont.upper())
521
#-----------------------------------------------
523
def __init__(self,name,flag=0,width=1,height=1,center=(0.5,0.5),
524
direction=(0,0,1),target=(0,0,0),lens=50,
525
frontClipping=0,backClipping=0,twist=0,mode=0):
531
self.direction=direction
534
self.frontClipping=frontClipping
535
self.backClipping=backClipping
539
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'%\
540
(self.name,self.flag,self.height,_point(self.center),self.width,
541
_point(self.direction,1),_point(self.target,2),self.lens,
542
self.frontClipping,self.backClipping,self.twist,self.mode)
544
#-----------------------------------------------
545
def ViewByWindow(name,leftBottom=(0,0),rightTop=(1,1),**options):
546
width=abs(rightTop[0]-leftBottom[0])
547
height=abs(rightTop[1]-leftBottom[1])
548
center=((rightTop[0]+leftBottom[0])*0.5,(rightTop[1]+leftBottom[1])*0.5)
549
return View(name=name,width=width,height=height,center=center,**options)
552
#-----------------------------------------------
553
class Drawing(_Collection):
554
"""Dxf drawing. Use append or any other list methods to add objects."""
555
def __init__(self,insbase=(0.0,0.0,0.0),extmin=(0.0,0.0),extmax=(0.0,0.0),
556
layers=[Layer()],linetypes=[LineType()],styles=[Style()],blocks=[],
557
views=[],entities=None,fileName='test.dxf'):
558
# TODO: replace list with None,arial
559
if not entities:entities=[]
560
_Collection.__init__(self,entities)
564
self.layers=copy.copy(layers)
565
self.linetypes=copy.copy(linetypes)
566
self.styles=copy.copy(styles)
567
self.views=copy.copy(views)
568
self.blocks=copy.copy(blocks)
569
self.fileName=fileName
571
#self.acadver='9\n$ACADVER\n1\nAC1006\n'
572
self.acadver=' 9\n$ACADVER\n 1\nAC1009\n'
573
"""DXF AutoCAD-Release format codes
575
AC1018 2006, 2005, 2004
576
AC1015 2002, 2000i, 2000
587
"""Helper function for self._point"""
588
return ' 9\n$%s\n' %x.upper()
590
def _point(self,name,x):
591
"""Point setting from drawing like extmin,extmax,..."""
592
return '%s%s' %(self._name(name),_point(x))
594
def _section(self,name,x):
595
"""Sections like tables,blocks,entities,..."""
596
if x: xstr=''.join(x)
598
return ' 0\nSECTION\n 2\n%s\n%s 0\nENDSEC\n'%(name.upper(),xstr)
600
def _table(self,name,x):
601
"""Tables like ltype,layer,style,..."""
602
if x: xstr=''.join(x)
604
return ' 0\nTABLE\n 2\n%s\n 70\n%s\n%s 0\nENDTAB\n'%(name.upper(),len(x),xstr)
607
"""Returns drawing as dxf string."""
608
header=[self.acadver]+[self._point(attr,getattr(self,attr))+'\n' for attr in _HEADER_POINTS]
609
header=self._section('header',header)
611
tables=[self._table('ltype',[str(x) for x in self.linetypes]),
612
self._table('layer',[str(x) for x in self.layers]),
613
self._table('style',[str(x) for x in self.styles]),
614
self._table('view',[str(x) for x in self.views]),
616
tables=self._section('tables',tables)
618
blocks=self._section('blocks',[str(x) for x in self.blocks])
620
entities=self._section('entities',[str(x) for x in self.entities])
622
all=''.join([header,tables,blocks,entities,' 0\nEOF\n'])
625
def saveas(self,fileName):
626
self.fileName=fileName
630
test=open(self.fileName,'w')
631
test.write(str(self))
636
#-----------------------------------------------
637
class Rectangle(_Entity):
638
"""Rectangle, creates lines."""
639
def __init__(self,point=(0,0,0),width=1,height=1,solid=None,line=1,**common):
640
_Entity.__init__(self,**common)
648
points=[self.point,(self.point[0]+self.width,self.point[1],self.point[2]),
649
(self.point[0]+self.width,self.point[1]+self.height,self.point[2]),
650
(self.point[0],self.point[1]+self.height,self.point[2]),self.point]
652
result+= Solid(points=points[:-1],parent=self.solid)
655
result+= Line(points=[points[i],points[i+1]],parent=self)
658
#-----------------------------------------------
659
class LineList(_Entity):
660
"""Like polyline, but built of individual lines."""
661
def __init__(self,points=[],org_point=[0,0,0],closed=0,**common):
662
_Entity.__init__(self,**common)
664
self.points=copy.copy(points)
666
if self.closed:points=self.points+[self.points[0]]
667
else: points=self.points
669
for i in range(len(points)-1):
670
result+= Line(points=[points[i],points[i+1]],parent=self)
673
#-----------------------------------------------------
677
b.append(Solid(points=[(0,0,0),(1,0,0),(1,1,0),(0,1,0)],color=1))
678
b.append(Arc(center=(1,0,0),color=2))
683
d.blocks.append(b) #table blocks
684
d.styles.append(Style()) #table styles
685
d.views.append(View('Normal')) #table view
686
d.views.append(ViewByWindow('Window',leftBottom=(1,0),rightTop=(2,1))) #idem
689
d.append(Circle(center=(1,1,0),color=3))
690
d.append(Face(points=[(0,0,0),(1,0,0),(1,1,0),(0,1,0)],color=4))
691
d.append(Insert('test',point=(3,3,3),cols=5,colspacing=2))
692
d.append(Line(points=[(0,0,0),(1,1,1)]))
693
d.append(Mtext('Click on Ads\nmultiple lines with mtext',point=(1,1,1),color=5,rotation=90))
694
d.append(Text('Please donate!',point=(3,0,1)))
695
d.append(Rectangle(point=(2,2,2),width=4,height=3,color=6,solid=Solid(color=2)))
696
d.append(Solid(points=[(4,4,0),(5,4,0),(7,8,0),(9,9,0)],color=3))
697
d.append(PolyLine(points=[(1,1,1),(2,1,1),(2,2,1),(1,2,1)],closed=1,color=1))
699
#d.saveas('c:\\test.dxf')
703
#-----------------------------------------------------
704
if __name__=='__main__':
706
Draw.PupMenu('Error%t|This script requires a full python install')
b'\\ No newline at end of file'