860 lines
28 KiB
Python
860 lines
28 KiB
Python
|
|
#!BPY
|
||
|
|
|
||
|
|
"""
|
||
|
|
Name: 'Autodesk DXF (.dxf)'
|
||
|
|
Blender: 247
|
||
|
|
Group: 'Export'
|
||
|
|
Tooltip: 'Export geometry to DXF-r12 (Drawing eXchange Format).'
|
||
|
|
"""
|
||
|
|
|
||
|
|
__version__ = "v1.25beta - 2008.09.28"
|
||
|
|
__author__ = "Stani & migius(Remigiusz Fiedler)"
|
||
|
|
__license__ = "GPL"
|
||
|
|
__url__ = "http://wiki.blender.org/index.php/Scripts/Manual/Export/autodesk_dxf"
|
||
|
|
__bpydoc__ ="""The script exports Blender geometry to DXF format r12 version.
|
||
|
|
|
||
|
|
Copyright %s
|
||
|
|
Version %s
|
||
|
|
License %s
|
||
|
|
Homepage %s
|
||
|
|
|
||
|
|
See the homepage for documentation.
|
||
|
|
url:
|
||
|
|
""" % (__author__,__version__,__license__,__url__)
|
||
|
|
|
||
|
|
"""
|
||
|
|
IDEAs:
|
||
|
|
- correct normals for POLYLINE-POLYFACE objects via correct point-order
|
||
|
|
- HPGL output for 2d and flattened3d content
|
||
|
|
|
||
|
|
TODO:
|
||
|
|
- support hierarchies: groups, instances, parented structures
|
||
|
|
- support 210-code (3d orientation vector)
|
||
|
|
- presets for architectural scales
|
||
|
|
|
||
|
|
History
|
||
|
|
v1.25 - 2008.09.28 by migius
|
||
|
|
- modif FACE class for r12
|
||
|
|
- add mesh-polygon -> Bezier-curve converter (Yorik's code)
|
||
|
|
- add support for curves ->POLYLINEs
|
||
|
|
- add "3d-View to Flat" - geometry projection to XY-plane
|
||
|
|
v1.24 - 2008.09.27 by migius
|
||
|
|
- add start UI with preferences
|
||
|
|
- modif POLYLINE class for r12
|
||
|
|
- changing output format from r9 to r12(AC1009)
|
||
|
|
v1.23 - 2008.09.26 by migius
|
||
|
|
- add finish message-box
|
||
|
|
v1.22 - 2008.09.26 by migius
|
||
|
|
- add support for curves ->LINEs
|
||
|
|
- add support for mesh-edges ->LINEs
|
||
|
|
v1.21 - 2008.06.04 by migius
|
||
|
|
- initial adaptation for Blender
|
||
|
|
v1.1 (20/6/2005) by www.stani.be/python/sdxf
|
||
|
|
- Python library to generate dxf drawings
|
||
|
|
______________________________________________________________
|
||
|
|
"""
|
||
|
|
# ***** BEGIN GPL LICENSE BLOCK *****
|
||
|
|
#
|
||
|
|
# This program is free software; you can redistribute it and/or
|
||
|
|
# modify it under the terms of the GNU General Public License
|
||
|
|
# as published by the Free Software Foundation; either version 2
|
||
|
|
# of the License, or (at your option) any later version.
|
||
|
|
#
|
||
|
|
# This program is distributed in the hope that it will be useful,
|
||
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
|
# GNU General Public License for more details.
|
||
|
|
#
|
||
|
|
# You should have received a copy of the GNU General Public License
|
||
|
|
# along with this program; if not, write to the Free Software Foundation,
|
||
|
|
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||
|
|
#
|
||
|
|
# ***** END GPL LICENCE BLOCK *****
|
||
|
|
|
||
|
|
|
||
|
|
import Blender
|
||
|
|
from Blender import Mathutils, Window, Scene, sys, Draw
|
||
|
|
import BPyMessages
|
||
|
|
|
||
|
|
try:
|
||
|
|
import copy
|
||
|
|
#from struct import pack
|
||
|
|
except:
|
||
|
|
copy = None
|
||
|
|
|
||
|
|
####1) Private (only for developpers)
|
||
|
|
_HEADER_POINTS=['insbase','extmin','extmax']
|
||
|
|
|
||
|
|
#---helper functions-----------------------------------
|
||
|
|
def _point(x,index=0):
|
||
|
|
"""Convert tuple to a dxf point"""
|
||
|
|
#print '_point=', x #-------------
|
||
|
|
return '\n'.join(['%s\n%s'%((i+1)*10+index,x[i]) for i in range(len(x))])
|
||
|
|
|
||
|
|
def _points(plist):
|
||
|
|
"""Convert a list of tuples to dxf points"""
|
||
|
|
return [_point(plist[i],i)for i in range(len(plist))]
|
||
|
|
|
||
|
|
#---base classes----------------------------------------
|
||
|
|
class _Call:
|
||
|
|
"""Makes a callable class."""
|
||
|
|
def copy(self):
|
||
|
|
"""Returns a copy."""
|
||
|
|
return copy.deepcopy(self)
|
||
|
|
|
||
|
|
def __call__(self,**attrs):
|
||
|
|
"""Returns a copy with modified attributes."""
|
||
|
|
copied=self.copy()
|
||
|
|
for attr in attrs:setattr(copied,attr,attrs[attr])
|
||
|
|
return copied
|
||
|
|
|
||
|
|
#-------------------------------------------------------
|
||
|
|
class _Entity(_Call):
|
||
|
|
"""Base class for _common group codes for entities."""
|
||
|
|
def __init__(self,color=None,extrusion=None,layer='0',
|
||
|
|
lineType=None,lineTypeScale=None,lineWeight=None,
|
||
|
|
thickness=None,parent=None):
|
||
|
|
"""None values will be omitted."""
|
||
|
|
self.color = color
|
||
|
|
self.extrusion = extrusion
|
||
|
|
self.layer = layer
|
||
|
|
self.lineType = lineType
|
||
|
|
self.lineTypeScale = lineTypeScale
|
||
|
|
self.lineWeight = lineWeight
|
||
|
|
self.thickness = thickness
|
||
|
|
self.parent = parent
|
||
|
|
|
||
|
|
def _common(self):
|
||
|
|
"""Return common group codes as a string."""
|
||
|
|
if self.parent:parent=self.parent
|
||
|
|
else:parent=self
|
||
|
|
result='8\n%s'%parent.layer
|
||
|
|
if parent.color!=None: result+='\n62\n%s'%parent.color
|
||
|
|
if parent.extrusion!=None: result+='\n%s'%_point(parent.extrusion,200)
|
||
|
|
if parent.lineType!=None: result+='\n6\n%s'%parent.lineType
|
||
|
|
if parent.lineWeight!=None: result+='\n370\n%s'%parent.lineWeight
|
||
|
|
if parent.lineTypeScale!=None: result+='\n48\n%s'%parent.lineTypeScale
|
||
|
|
if parent.thickness!=None: result+='\n39\n%s'%parent.thickness
|
||
|
|
return result
|
||
|
|
|
||
|
|
#--------------------------
|
||
|
|
class _Entities:
|
||
|
|
"""Base class to deal with composed objects."""
|
||
|
|
def __dxf__(self):
|
||
|
|
return []
|
||
|
|
|
||
|
|
def __str__(self):
|
||
|
|
return '\n'.join([str(x) for x in self.__dxf__()])
|
||
|
|
|
||
|
|
#--------------------------
|
||
|
|
class _Collection(_Call):
|
||
|
|
"""Base class to expose entities methods to main object."""
|
||
|
|
def __init__(self,entities=[]):
|
||
|
|
self.entities=copy.copy(entities)
|
||
|
|
#link entities methods to drawing
|
||
|
|
for attr in dir(self.entities):
|
||
|
|
if attr[0]!='_':
|
||
|
|
attrObject=getattr(self.entities,attr)
|
||
|
|
if callable(attrObject):
|
||
|
|
setattr(self,attr,attrObject)
|
||
|
|
|
||
|
|
####2) Constants
|
||
|
|
#---color values
|
||
|
|
BYBLOCK=0
|
||
|
|
BYLAYER=256
|
||
|
|
|
||
|
|
#---block-type flags (bit coded values, may be combined):
|
||
|
|
ANONYMOUS =1 # This is an anonymous block generated by hatching, associative dimensioning, other internal operations, or an application
|
||
|
|
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)
|
||
|
|
XREF =4 # This block is an external reference (xref)
|
||
|
|
XREF_OVERLAY =8 # This block is an xref overlay
|
||
|
|
EXTERNAL =16 # This block is externally dependent
|
||
|
|
RESOLVED =32 # This is a resolved external reference, or dependent of an external reference (ignored on input)
|
||
|
|
REFERENCED =64 # This definition is a referenced external reference (ignored on input)
|
||
|
|
|
||
|
|
#---mtext flags
|
||
|
|
#attachment point
|
||
|
|
TOP_LEFT = 1
|
||
|
|
TOP_CENTER = 2
|
||
|
|
TOP_RIGHT = 3
|
||
|
|
MIDDLE_LEFT = 4
|
||
|
|
MIDDLE_CENTER = 5
|
||
|
|
MIDDLE_RIGHT = 6
|
||
|
|
BOTTOM_LEFT = 7
|
||
|
|
BOTTOM_CENTER = 8
|
||
|
|
BOTTOM_RIGHT = 9
|
||
|
|
#drawing direction
|
||
|
|
LEFT_RIGHT = 1
|
||
|
|
TOP_BOTTOM = 3
|
||
|
|
BY_STYLE = 5 #the flow direction is inherited from the associated text style
|
||
|
|
#line spacing style (optional):
|
||
|
|
AT_LEAST = 1 #taller characters will override
|
||
|
|
EXACT = 2 #taller characters will not override
|
||
|
|
|
||
|
|
#---polyline flags
|
||
|
|
CLOSED =1 # This is a closed polyline (or a polygon mesh closed in the M direction)
|
||
|
|
CURVE_FIT =2 # Curve-fit vertices have been added
|
||
|
|
SPLINE_FIT =4 # Spline-fit vertices have been added
|
||
|
|
POLYLINE_3D =8 # This is a 3D polyline
|
||
|
|
POLYGON_MESH =16 # This is a 3D polygon mesh
|
||
|
|
CLOSED_N =32 # The polygon mesh is closed in the N direction
|
||
|
|
POLYFACE_MESH =64 # The polyline is a polyface mesh
|
||
|
|
CONTINOUS_LINETYPE_PATTERN =128 # The linetype pattern is generated continuously around the vertices of this polyline
|
||
|
|
|
||
|
|
#---text flags
|
||
|
|
#horizontal
|
||
|
|
LEFT = 0
|
||
|
|
CENTER = 1
|
||
|
|
RIGHT = 2
|
||
|
|
ALIGNED = 3 #if vertical alignment = 0
|
||
|
|
MIDDLE = 4 #if vertical alignment = 0
|
||
|
|
FIT = 5 #if vertical alignment = 0
|
||
|
|
#vertical
|
||
|
|
BASELINE = 0
|
||
|
|
BOTTOM = 1
|
||
|
|
MIDDLE = 2
|
||
|
|
TOP = 3
|
||
|
|
|
||
|
|
####3) Classes
|
||
|
|
#---entitities -----------------------------------------------
|
||
|
|
#--------------------------
|
||
|
|
class Arc(_Entity):
|
||
|
|
"""Arc, angles in degrees."""
|
||
|
|
def __init__(self,center=(0,0,0),radius=1,
|
||
|
|
startAngle=0.0,endAngle=90,**common):
|
||
|
|
"""Angles in degrees."""
|
||
|
|
_Entity.__init__(self,**common)
|
||
|
|
self.center=center
|
||
|
|
self.radius=radius
|
||
|
|
self.startAngle=startAngle
|
||
|
|
self.endAngle=endAngle
|
||
|
|
def __str__(self):
|
||
|
|
return '0\nARC\n%s\n%s\n40\n%s\n50\n%s\n51\n%s'%\
|
||
|
|
(self._common(),_point(self.center),
|
||
|
|
self.radius,self.startAngle,self.endAngle)
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
class Circle(_Entity):
|
||
|
|
"""Circle"""
|
||
|
|
def __init__(self,center=(0,0,0),radius=1,**common):
|
||
|
|
_Entity.__init__(self,**common)
|
||
|
|
self.center=center
|
||
|
|
self.radius=radius
|
||
|
|
def __str__(self):
|
||
|
|
return '0\nCIRCLE\n%s\n%s\n40\n%s'%\
|
||
|
|
(self._common(),_point(self.center),self.radius)
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
class Face(_Entity):
|
||
|
|
"""3dface"""
|
||
|
|
def __init__(self,points,**common):
|
||
|
|
_Entity.__init__(self,**common)
|
||
|
|
if len(points)<4: #fix for r12 format
|
||
|
|
points.append(points[-1])
|
||
|
|
self.points=points
|
||
|
|
|
||
|
|
def __str__(self):
|
||
|
|
return '\n'.join(['0\n3DFACE',self._common()]+
|
||
|
|
_points(self.points)
|
||
|
|
)
|
||
|
|
#-----------------------------------------------
|
||
|
|
class Insert(_Entity):
|
||
|
|
"""Block instance."""
|
||
|
|
def __init__(self,name,point=(0,0,0),
|
||
|
|
xscale=None,yscale=None,zscale=None,
|
||
|
|
cols=None,colspacing=None,rows=None,rowspacing=None,
|
||
|
|
rotation=None,
|
||
|
|
**common):
|
||
|
|
_Entity.__init__(self,**common)
|
||
|
|
self.name=name
|
||
|
|
self.point=point
|
||
|
|
self.xscale=xscale
|
||
|
|
self.yscale=yscale
|
||
|
|
self.zscale=zscale
|
||
|
|
self.cols=cols
|
||
|
|
self.colspacing=colspacing
|
||
|
|
self.rows=rows
|
||
|
|
self.rowspacing=rowspacing
|
||
|
|
self.rotation=rotation
|
||
|
|
|
||
|
|
def __str__(self):
|
||
|
|
result='0\nINSERT\n2\n%s\n%s\n%s'%\
|
||
|
|
(self.name,self._common(),_point(self.point))
|
||
|
|
if self.xscale!=None:result+='\n41\n%s'%self.xscale
|
||
|
|
if self.yscale!=None:result+='\n42\n%s'%self.yscale
|
||
|
|
if self.zscale!=None:result+='\n43\n%s'%self.zscale
|
||
|
|
if self.rotation:result+='\n50\n%s'%self.rotation
|
||
|
|
if self.cols!=None:result+='\n70\n%s'%self.cols
|
||
|
|
if self.colspacing!=None:result+='\n44\n%s'%self.colspacing
|
||
|
|
if self.rows!=None:result+='\n71\n%s'%self.rows
|
||
|
|
if self.rowspacing!=None:result+='\n45\n%s'%self.rowspacing
|
||
|
|
return result
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
class Line(_Entity):
|
||
|
|
"""Line"""
|
||
|
|
def __init__(self,points,**common):
|
||
|
|
_Entity.__init__(self,**common)
|
||
|
|
self.points=points
|
||
|
|
def __str__(self):
|
||
|
|
return '\n'.join(['0\nLINE',self._common()]+
|
||
|
|
_points(self.points))
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
class PolyLine(_Entity):
|
||
|
|
#TODO: Finish polyline (now implemented as a series of lines)
|
||
|
|
def __init__(self,points,org_point=[0,0,0],flag=0,width=None,**common):
|
||
|
|
_Entity.__init__(self,**common)
|
||
|
|
self.points=points
|
||
|
|
self.org_point=org_point
|
||
|
|
self.flag=flag
|
||
|
|
self.width=width
|
||
|
|
def __str__(self):
|
||
|
|
result= '0\nPOLYLINE\n%s\n70\n%s' %(self._common(),self.flag)
|
||
|
|
#print 'self._common()', self._common() #----------
|
||
|
|
result+='\n66\n1'
|
||
|
|
result+='\n%s' %_point(self.org_point)
|
||
|
|
for point in self.points:
|
||
|
|
result+='\n0\nVERTEX'
|
||
|
|
result+='\n8\n%s' %self.layer
|
||
|
|
result+='\n%s' %_point(point)
|
||
|
|
if self.width:result+='\n40\n%s\n41\n%s' %(self.width,self.width)
|
||
|
|
result+='\n0\nSEQEND'
|
||
|
|
result+='\n8\n%s' %self.layer
|
||
|
|
return result
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
class Point(_Entity):
|
||
|
|
"""Colored solid fill."""
|
||
|
|
def __init__(self,points=None,**common):
|
||
|
|
_Entity.__init__(self,**common)
|
||
|
|
self.points=points
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
class Solid(_Entity):
|
||
|
|
"""Colored solid fill."""
|
||
|
|
def __init__(self,points=None,**common):
|
||
|
|
_Entity.__init__(self,**common)
|
||
|
|
self.points=points
|
||
|
|
def __str__(self):
|
||
|
|
return '\n'.join(['0\nSOLID',self._common()]+
|
||
|
|
_points(self.points[:2]+[self.points[3],self.points[2]])
|
||
|
|
)
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
class Text(_Entity):
|
||
|
|
"""Single text line."""
|
||
|
|
def __init__(self,text='',point=(0,0,0),alignment=None,
|
||
|
|
flag=None,height=1,justifyhor=None,justifyver=None,
|
||
|
|
rotation=None,obliqueAngle=None,style=None,xscale=None,**common):
|
||
|
|
_Entity.__init__(self,**common)
|
||
|
|
self.text=text
|
||
|
|
self.point=point
|
||
|
|
self.alignment=alignment
|
||
|
|
self.flag=flag
|
||
|
|
self.height=height
|
||
|
|
self.justifyhor=justifyhor
|
||
|
|
self.justifyver=justifyver
|
||
|
|
self.rotation=rotation
|
||
|
|
self.obliqueAngle=obliqueAngle
|
||
|
|
self.style=style
|
||
|
|
self.xscale=xscale
|
||
|
|
def __str__(self):
|
||
|
|
result= '0\nTEXT\n%s\n%s\n40\n%s\n1\n%s'%\
|
||
|
|
(self._common(),_point(self.point),self.height,self.text)
|
||
|
|
if self.rotation:result+='\n50\n%s'%self.rotation
|
||
|
|
if self.xscale:result+='\n41\n%s'%self.xscale
|
||
|
|
if self.obliqueAngle:result+='\n51\n%s'%self.obliqueAngle
|
||
|
|
if self.style:result+='\n7\n%s'%self.style
|
||
|
|
if self.flag:result+='\n71\n%s'%self.flag
|
||
|
|
if self.justifyhor:result+='\n72\n%s'%self.justifyhor
|
||
|
|
if self.alignment:result+='\n%s'%_point(self.alignment,1)
|
||
|
|
if self.justifyver:result+='\n73\n%s'%self.justifyver
|
||
|
|
return result
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
class Mtext(Text):
|
||
|
|
"""Surrogate for mtext, generates some Text instances."""
|
||
|
|
def __init__(self,text='',point=(0,0,0),width=250,spacingFactor=1.5,down=0,spacingWidth=None,**options):
|
||
|
|
Text.__init__(self,text=text,point=point,**options)
|
||
|
|
if down:spacingFactor*=-1
|
||
|
|
self.spacingFactor=spacingFactor
|
||
|
|
self.spacingWidth=spacingWidth
|
||
|
|
self.width=width
|
||
|
|
self.down=down
|
||
|
|
def __str__(self):
|
||
|
|
texts=self.text.replace('\r\n','\n').split('\n')
|
||
|
|
if not self.down:texts.reverse()
|
||
|
|
result=''
|
||
|
|
x=y=0
|
||
|
|
if self.spacingWidth:spacingWidth=self.spacingWidth
|
||
|
|
else:spacingWidth=self.height*self.spacingFactor
|
||
|
|
for text in texts:
|
||
|
|
while text:
|
||
|
|
result+='\n%s'%Text(text[:self.width],
|
||
|
|
point=(self.point[0]+x*spacingWidth,
|
||
|
|
self.point[1]+y*spacingWidth,
|
||
|
|
self.point[2]),
|
||
|
|
alignment=self.alignment,flag=self.flag,height=self.height,
|
||
|
|
justifyhor=self.justifyhor,justifyver=self.justifyver,
|
||
|
|
rotation=self.rotation,obliqueAngle=self.obliqueAngle,
|
||
|
|
style=self.style,xscale=self.xscale,parent=self
|
||
|
|
)
|
||
|
|
text=text[self.width:]
|
||
|
|
if self.rotation:x+=1
|
||
|
|
else:y+=1
|
||
|
|
return result[1:]
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
##class _Mtext(_Entity):
|
||
|
|
## """Mtext not functioning for minimal dxf."""
|
||
|
|
## def __init__(self,text='',point=(0,0,0),attachment=1,
|
||
|
|
## charWidth=None,charHeight=1,direction=1,height=100,rotation=0,
|
||
|
|
## spacingStyle=None,spacingFactor=None,style=None,width=100,
|
||
|
|
## xdirection=None,**common):
|
||
|
|
## _Entity.__init__(self,**common)
|
||
|
|
## self.text=text
|
||
|
|
## self.point=point
|
||
|
|
## self.attachment=attachment
|
||
|
|
## self.charWidth=charWidth
|
||
|
|
## self.charHeight=charHeight
|
||
|
|
## self.direction=direction
|
||
|
|
## self.height=height
|
||
|
|
## self.rotation=rotation
|
||
|
|
## self.spacingStyle=spacingStyle
|
||
|
|
## self.spacingFactor=spacingFactor
|
||
|
|
## self.style=style
|
||
|
|
## self.width=width
|
||
|
|
## self.xdirection=xdirection
|
||
|
|
## def __str__(self):
|
||
|
|
## input=self.text
|
||
|
|
## text=''
|
||
|
|
## while len(input)>250:
|
||
|
|
## text+='\n3\n%s'%input[:250]
|
||
|
|
## input=input[250:]
|
||
|
|
## text+='\n1\n%s'%input
|
||
|
|
## 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'%\
|
||
|
|
## (self._common(),_point(self.point),self.charHeight,self.width,
|
||
|
|
## self.attachment,self.direction,text,
|
||
|
|
## self.height,
|
||
|
|
## self.rotation)
|
||
|
|
## if self.style:result+='\n7\n%s'%self.style
|
||
|
|
## if self.xdirection:result+='\n%s'%_point(self.xdirection,1)
|
||
|
|
## if self.charWidth:result+='\n42\n%s'%self.charWidth
|
||
|
|
## if self.spacingStyle:result+='\n73\n%s'%self.spacingStyle
|
||
|
|
## if self.spacingFactor:result+='\n44\n%s'%self.spacingFactor
|
||
|
|
## return result
|
||
|
|
|
||
|
|
#---tables ---------------------------------------------------
|
||
|
|
#-----------------------------------------------
|
||
|
|
class Block(_Collection):
|
||
|
|
"""Use list methods to add entities, eg append."""
|
||
|
|
def __init__(self,name,layer='0',flag=0,base=(0,0,0),entities=[]):
|
||
|
|
self.entities=copy.copy(entities)
|
||
|
|
_Collection.__init__(self,entities)
|
||
|
|
self.layer=layer
|
||
|
|
self.name=name
|
||
|
|
self.flag=0
|
||
|
|
self.base=base
|
||
|
|
def __str__(self):
|
||
|
|
e='\n'.join([str(x)for x in self.entities])
|
||
|
|
return '0\nBLOCK\n8\n%s\n2\n%s\n70\n%s\n%s\n3\n%s\n%s\n0\nENDBLK'%\
|
||
|
|
(self.layer,self.name.upper(),self.flag,_point(self.base),self.name.upper(),e)
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
class Layer(_Call):
|
||
|
|
"""Layer"""
|
||
|
|
def __init__(self,name='pydxf',color=7,lineType='continuous',flag=64):
|
||
|
|
self.name=name
|
||
|
|
self.color=color
|
||
|
|
self.lineType=lineType
|
||
|
|
self.flag=flag
|
||
|
|
def __str__(self):
|
||
|
|
return '0\nLAYER\n2\n%s\n70\n%s\n62\n%s\n6\n%s'%\
|
||
|
|
(self.name.upper(),self.flag,self.color,self.lineType)
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
class LineType(_Call):
|
||
|
|
"""Custom linetype"""
|
||
|
|
def __init__(self,name='continuous',description='Solid line',elements=[],flag=64):
|
||
|
|
# TODO: Implement lineType elements
|
||
|
|
self.name=name
|
||
|
|
self.description=description
|
||
|
|
self.elements=copy.copy(elements)
|
||
|
|
self.flag=flag
|
||
|
|
def __str__(self):
|
||
|
|
return '0\nLTYPE\n2\n%s\n70\n%s\n3\n%s\n72\n65\n73\n%s\n40\n0.0'%\
|
||
|
|
(self.name.upper(),self.flag,self.description,len(self.elements))
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
class Style(_Call):
|
||
|
|
"""Text style"""
|
||
|
|
def __init__(self,name='standard',flag=0,height=0,widthFactor=40,obliqueAngle=50,
|
||
|
|
mirror=0,lastHeight=1,font='arial.ttf',bigFont=''):
|
||
|
|
self.name=name
|
||
|
|
self.flag=flag
|
||
|
|
self.height=height
|
||
|
|
self.widthFactor=widthFactor
|
||
|
|
self.obliqueAngle=obliqueAngle
|
||
|
|
self.mirror=mirror
|
||
|
|
self.lastHeight=lastHeight
|
||
|
|
self.font=font
|
||
|
|
self.bigFont=bigFont
|
||
|
|
def __str__(self):
|
||
|
|
return '0\nSTYLE\n2\n%s\n70\n%s\n40\n%s\n41\n%s\n50\n%s\n71\n%s\n42\n%s\n3\n%s\n4\n%s'%\
|
||
|
|
(self.name.upper(),self.flag,self.flag,self.widthFactor,
|
||
|
|
self.obliqueAngle,self.mirror,self.lastHeight,
|
||
|
|
self.font.upper(),self.bigFont.upper())
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
class View(_Call):
|
||
|
|
def __init__(self,name,flag=0,width=1,height=1,center=(0.5,0.5),
|
||
|
|
direction=(0,0,1),target=(0,0,0),lens=50,
|
||
|
|
frontClipping=0,backClipping=0,twist=0,mode=0):
|
||
|
|
self.name=name
|
||
|
|
self.flag=flag
|
||
|
|
self.width=width
|
||
|
|
self.height=height
|
||
|
|
self.center=center
|
||
|
|
self.direction=direction
|
||
|
|
self.target=target
|
||
|
|
self.lens=lens
|
||
|
|
self.frontClipping=frontClipping
|
||
|
|
self.backClipping=backClipping
|
||
|
|
self.twist=twist
|
||
|
|
self.mode=mode
|
||
|
|
def __str__(self):
|
||
|
|
return '0\nVIEW\n2\n%s\n70\n%s\n40\n%s\n%s\n41\n%s\n%s\n%s\n42\n%s\n43\n%s\n44\n%s\n50\n%s\n71\n%s'%\
|
||
|
|
(self.name,self.flag,self.height,_point(self.center),self.width,
|
||
|
|
_point(self.direction,1),_point(self.target,2),self.lens,
|
||
|
|
self.frontClipping,self.backClipping,self.twist,self.mode)
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
def ViewByWindow(name,leftBottom=(0,0),rightTop=(1,1),**options):
|
||
|
|
width=abs(rightTop[0]-leftBottom[0])
|
||
|
|
height=abs(rightTop[1]-leftBottom[1])
|
||
|
|
center=((rightTop[0]+leftBottom[0])*0.5,(rightTop[1]+leftBottom[1])*0.5)
|
||
|
|
return View(name=name,width=width,height=height,center=center,**options)
|
||
|
|
|
||
|
|
#---drawing
|
||
|
|
#-----------------------------------------------
|
||
|
|
class Drawing(_Collection):
|
||
|
|
"""Dxf drawing. Use append or any other list methods to add objects."""
|
||
|
|
def __init__(self,insbase=(0.0,0.0,0.0),extmin=(0.0,0.0),extmax=(0.0,0.0),
|
||
|
|
layers=[Layer()],linetypes=[LineType()],styles=[Style()],blocks=[],
|
||
|
|
views=[],entities=None,fileName='test.dxf'):
|
||
|
|
# TODO: replace list with None,arial
|
||
|
|
if not entities:entities=[]
|
||
|
|
_Collection.__init__(self,entities)
|
||
|
|
self.insbase=insbase
|
||
|
|
self.extmin=extmin
|
||
|
|
self.extmax=extmax
|
||
|
|
self.layers=copy.copy(layers)
|
||
|
|
self.linetypes=copy.copy(linetypes)
|
||
|
|
self.styles=copy.copy(styles)
|
||
|
|
self.views=copy.copy(views)
|
||
|
|
self.blocks=copy.copy(blocks)
|
||
|
|
self.fileName=fileName
|
||
|
|
#private
|
||
|
|
#self.acadver='9\n$ACADVER\n1\nAC1006'
|
||
|
|
self.acadver='9\n$ACADVER\n1\nAC1009'
|
||
|
|
"""DXF AutoCAD-Release format code
|
||
|
|
AC1021 2008, 2007
|
||
|
|
AC1018 2006, 2005, 2004
|
||
|
|
AC1015 2002, 2000i, 2000
|
||
|
|
AC1014 R14,14.01
|
||
|
|
AC1012 R13
|
||
|
|
AC1009 R12,11
|
||
|
|
AC1006 R10
|
||
|
|
AC1004 R9
|
||
|
|
AC1002 R2.6
|
||
|
|
AC1.50 R2.05
|
||
|
|
"""
|
||
|
|
|
||
|
|
def _name(self,x):
|
||
|
|
"""Helper function for self._point"""
|
||
|
|
return '9\n$%s'%x.upper()
|
||
|
|
|
||
|
|
def _point(self,name,x):
|
||
|
|
"""Point setting from drawing like extmin,extmax,..."""
|
||
|
|
return '%s\n%s'%(self._name(name),_point(x))
|
||
|
|
|
||
|
|
def _section(self,name,x):
|
||
|
|
"""Sections like tables,blocks,entities,..."""
|
||
|
|
if x:xstr='\n'+'\n'.join(x)
|
||
|
|
else:xstr=''
|
||
|
|
return '0\nSECTION\n2\n%s%s\n0\nENDSEC'%(name.upper(),xstr)
|
||
|
|
|
||
|
|
def _table(self,name,x):
|
||
|
|
"""Tables like ltype,layer,style,..."""
|
||
|
|
if x:xstr='\n'+'\n'.join(x)
|
||
|
|
else:xstr=''
|
||
|
|
return '0\nTABLE\n2\n%s\n70\n%s%s\n0\nENDTAB'%(name.upper(),len(x),xstr)
|
||
|
|
|
||
|
|
def __str__(self):
|
||
|
|
"""Returns drawing as dxf string."""
|
||
|
|
header=[self.acadver]+[self._point(attr,getattr(self,attr)) for attr in _HEADER_POINTS]
|
||
|
|
header=self._section('header',header)
|
||
|
|
|
||
|
|
tables=[self._table('ltype',[str(x) for x in self.linetypes]),
|
||
|
|
self._table('layer',[str(x) for x in self.layers]),
|
||
|
|
self._table('style',[str(x) for x in self.styles]),
|
||
|
|
self._table('view',[str(x) for x in self.views]),
|
||
|
|
]
|
||
|
|
tables=self._section('tables',tables)
|
||
|
|
|
||
|
|
blocks=self._section('blocks',[str(x) for x in self.blocks])
|
||
|
|
|
||
|
|
entities=self._section('entities',[str(x) for x in self.entities])
|
||
|
|
|
||
|
|
all='\n'.join([header,tables,blocks,entities,'0\nEOF\n'])
|
||
|
|
return all
|
||
|
|
|
||
|
|
def saveas(self,fileName):
|
||
|
|
self.fileName=fileName
|
||
|
|
self.save()
|
||
|
|
|
||
|
|
def save(self):
|
||
|
|
test=open(self.fileName,'w')
|
||
|
|
test.write(str(self))
|
||
|
|
test.close()
|
||
|
|
|
||
|
|
|
||
|
|
#---extras
|
||
|
|
#-----------------------------------------------
|
||
|
|
class Rectangle(_Entity):
|
||
|
|
"""Rectangle, creates lines."""
|
||
|
|
def __init__(self,point=(0,0,0),width=1,height=1,solid=None,line=1,**common):
|
||
|
|
_Entity.__init__(self,**common)
|
||
|
|
self.point=point
|
||
|
|
self.width=width
|
||
|
|
self.height=height
|
||
|
|
self.solid=solid
|
||
|
|
self.line=line
|
||
|
|
def __str__(self):
|
||
|
|
result=''
|
||
|
|
points=[self.point,(self.point[0]+self.width,self.point[1],self.point[2]),
|
||
|
|
(self.point[0]+self.width,self.point[1]+self.height,self.point[2]),
|
||
|
|
(self.point[0],self.point[1]+self.height,self.point[2]),self.point]
|
||
|
|
if self.solid:
|
||
|
|
result+='\n%s'%Solid(points=points[:-1],parent=self.solid)
|
||
|
|
if self.line:
|
||
|
|
for i in range(4):result+='\n%s'%\
|
||
|
|
Line(points=[points[i],points[i+1]],parent=self)
|
||
|
|
return result[1:]
|
||
|
|
|
||
|
|
#-----------------------------------------------
|
||
|
|
class LineList(_Entity):
|
||
|
|
"""Like polyline, but built of individual lines."""
|
||
|
|
def __init__(self,points=[],org_point=[0,0,0],closed=0,**common):
|
||
|
|
_Entity.__init__(self,**common)
|
||
|
|
self.closed=closed
|
||
|
|
self.points=copy.copy(points)
|
||
|
|
def __str__(self):
|
||
|
|
if self.closed:points=self.points+[self.points[0]]
|
||
|
|
else: points=self.points
|
||
|
|
result=''
|
||
|
|
for i in range(len(points)-1):
|
||
|
|
result+='\n%s' %Line(points=[points[i],points[i+1]],parent=self)
|
||
|
|
return result[1:]
|
||
|
|
|
||
|
|
#-----------------------------------------------------
|
||
|
|
def projected_co(vec, mw):
|
||
|
|
# convert the world coordinates of v to screen coordinates
|
||
|
|
#co = vec.co.copy().resize4D()
|
||
|
|
co = vec.copy().resize4D()
|
||
|
|
co[3] = 1.0
|
||
|
|
sc = co * mw
|
||
|
|
#print 'viewprojection=', sc #---------
|
||
|
|
return [sc[0],sc[1],0.0]
|
||
|
|
|
||
|
|
|
||
|
|
#-----------------------------------------------------
|
||
|
|
def dxf_export_ui(filepath):
|
||
|
|
print '\n\nDXF-Export %s' %__version__
|
||
|
|
#filepath = 'blend_test.dxf'
|
||
|
|
# Dont overwrite
|
||
|
|
if not BPyMessages.Warning_SaveOver(filepath):
|
||
|
|
print 'Aborted by user: nothing exported'
|
||
|
|
return
|
||
|
|
#test():return
|
||
|
|
|
||
|
|
ONLYSELECTED = True
|
||
|
|
POLYLINES = True
|
||
|
|
ONLYFACES = False
|
||
|
|
FLATTEN = 0 #dimmensions:1,2,3. Force Z dimmension value to 0.0, equal ground projection
|
||
|
|
SCALE_FACTOR = 1.0 #optional, can be done in CAD too
|
||
|
|
PREF_ONLYSELECTED= Draw.Create(ONLYSELECTED)
|
||
|
|
PREF_POLYLINES= Draw.Create(POLYLINES)
|
||
|
|
PREF_ONLYFACES= Draw.Create(ONLYFACES)
|
||
|
|
PREF_FLATTEN= Draw.Create(FLATTEN)
|
||
|
|
PREF_SCALE_FACTOR= Draw.Create(SCALE_FACTOR)
|
||
|
|
PREF_HELP= Draw.Create(0)
|
||
|
|
block = [\
|
||
|
|
("only selected", PREF_ONLYSELECTED, "export only selected geometry"),\
|
||
|
|
("global Scale:", PREF_SCALE_FACTOR, 0.001, 1000, "set global Scale factor for exporting geometry"),\
|
||
|
|
("only faces", PREF_ONLYFACES, "from mesh-objects export only faces, not edges"),\
|
||
|
|
("write POLYLINEs", PREF_POLYLINES, "export curves to POLYLINEs, otherwise to LINEs"),\
|
||
|
|
("3D-View to Flat", PREF_FLATTEN, "flatten geometry according current 3d-View"),\
|
||
|
|
(''),\
|
||
|
|
("online Help", PREF_HELP, "calls DXF-Exporter Manual Page on Wiki.Blender.org"),\
|
||
|
|
]
|
||
|
|
|
||
|
|
if not Draw.PupBlock("DXF-Exporter %s" %__version__[:10], block):
|
||
|
|
return
|
||
|
|
|
||
|
|
if PREF_HELP.val!=0:
|
||
|
|
try:
|
||
|
|
import webbrowser
|
||
|
|
webbrowser.open('http://wiki.blender.org/index.php?title=Scripts/Manual/Export/autodesk_dxf')
|
||
|
|
except:
|
||
|
|
Draw.PupMenu('DXF Exporter: %t|no connection to manual-page on Blender-Wiki! try:|\
|
||
|
|
http://wiki.blender.org/index.php?title=Scripts/Manual/Export/autodesk_dxf')
|
||
|
|
return
|
||
|
|
|
||
|
|
ONLYSELECTED = PREF_ONLYSELECTED.val
|
||
|
|
POLYLINES = PREF_POLYLINES.val
|
||
|
|
ONLYFACES = PREF_ONLYFACES.val
|
||
|
|
FLATTEN = PREF_FLATTEN.val
|
||
|
|
SCALE_FACTOR = PREF_SCALE_FACTOR.val
|
||
|
|
|
||
|
|
sce = Scene.GetCurrent()
|
||
|
|
if ONLYSELECTED: sel_group = sce.objects.selected
|
||
|
|
else: sel_group = sce.objects
|
||
|
|
|
||
|
|
if sel_group:
|
||
|
|
Window.WaitCursor(1)
|
||
|
|
t = sys.time()
|
||
|
|
|
||
|
|
#init Drawing ---------------------
|
||
|
|
d=Drawing()
|
||
|
|
#add Tables -----------------
|
||
|
|
#d.blocks.append(b) #table blocks
|
||
|
|
d.styles.append(Style()) #table styles
|
||
|
|
d.views.append(View('Normal')) #table view
|
||
|
|
d.views.append(ViewByWindow('Window',leftBottom=(1,0),rightTop=(2,1))) #idem
|
||
|
|
|
||
|
|
#add Entities --------------------
|
||
|
|
something_ready = False
|
||
|
|
mw = Window.GetViewMatrix()
|
||
|
|
#mw = Window.GetPerspMatrix() #TODO: how get it working?
|
||
|
|
for ob in sel_group:
|
||
|
|
entities = []
|
||
|
|
mx = ob.matrix
|
||
|
|
if (ob.type == 'Mesh'):
|
||
|
|
me = ob.getData(mesh=1)
|
||
|
|
faces=[]
|
||
|
|
edges=[]
|
||
|
|
if (not FLATTEN) and len(me.faces)>0 and ONLYFACES:
|
||
|
|
#export 3D as 3DFACEs
|
||
|
|
for f in me.faces:
|
||
|
|
#print 'face=', f #---------
|
||
|
|
verts = f.verts
|
||
|
|
points = [verts[i].co*mx for i in range(len(verts))]
|
||
|
|
if SCALE_FACTOR!=1.0:
|
||
|
|
points = [p*SCALE_FACTOR for p in points]
|
||
|
|
#print 'points=', points #---------
|
||
|
|
dxfFACE = Face(points)
|
||
|
|
entities.append(dxfFACE)
|
||
|
|
else: #export 3D as LINEs
|
||
|
|
for e in me.edges:
|
||
|
|
#print 'edge=', e #---------
|
||
|
|
points=[]
|
||
|
|
points = [e.v1.co*mx, e.v2.co*mx]
|
||
|
|
if SCALE_FACTOR!=1.0:
|
||
|
|
points = [p*SCALE_FACTOR for p in points]
|
||
|
|
if FLATTEN:
|
||
|
|
# for p in points: p[FLATTEN-1]=0.0
|
||
|
|
for i,v in enumerate(points):
|
||
|
|
v = projected_co(v, mw)
|
||
|
|
points[i]=v
|
||
|
|
#print 'flatten points=', points #---------
|
||
|
|
dxfLINE = Line(points)
|
||
|
|
entities.append(dxfLINE)
|
||
|
|
elif (ob.type == 'Curve'):
|
||
|
|
curve = ob.getData()
|
||
|
|
for cur in curve:
|
||
|
|
#print 'deb: START cur=', cur #--------------
|
||
|
|
if 1: #not cur.isNurb():
|
||
|
|
#print 'deb: START points' #--------------
|
||
|
|
points = []
|
||
|
|
org_point = [0.0,0.0,0.0]
|
||
|
|
for point in cur:
|
||
|
|
#print 'point=', point #---------
|
||
|
|
if cur.isNurb():
|
||
|
|
vec = point[0:3]
|
||
|
|
else:
|
||
|
|
point = point.getTriple()
|
||
|
|
#print 'point=', point #---------
|
||
|
|
vec = point[1]
|
||
|
|
#print 'vec=', vec #---------
|
||
|
|
pkt = Mathutils.Vector(vec) * mx
|
||
|
|
#print 'pkt=', pkt #---------
|
||
|
|
pkt *= SCALE_FACTOR
|
||
|
|
if FLATTEN:
|
||
|
|
pkt = projected_co(pkt, mw)
|
||
|
|
points.append(pkt)
|
||
|
|
if cur.isCyclic(): closed = 1
|
||
|
|
else: closed = 0
|
||
|
|
if len(points)>1:
|
||
|
|
#print 'deb: points', points #--------------
|
||
|
|
if POLYLINES: dxfPLINE = PolyLine(points,org_point,closed)
|
||
|
|
else: dxfPLINE = LineList(points,org_point,closed)
|
||
|
|
entities.append(dxfPLINE)
|
||
|
|
for e in entities:
|
||
|
|
d.append(e)
|
||
|
|
something_ready = True
|
||
|
|
if something_ready:
|
||
|
|
d.saveas(filepath)
|
||
|
|
Window.WaitCursor(0)
|
||
|
|
#Draw.PupMenu('DXF Exporter: job finished|search for blend_test.dxf in current project directory')
|
||
|
|
print 'exported to %s' % filepath
|
||
|
|
print 'finished in %.2f seconds' % (sys.time()-t)
|
||
|
|
else:
|
||
|
|
print "Abort: no supported object types selected, nothing exported!"
|
||
|
|
Draw.PupMenu('DXF Exporter: Abort!|Not-supported object types selected.')
|
||
|
|
else:
|
||
|
|
print "Abort: selection was empty, no object to export!"
|
||
|
|
Draw.PupMenu('DXF Exporter: Abort!|empty selection, no object to export!')
|
||
|
|
# Timing the script is a good way to be aware on any speed hits when scripting
|
||
|
|
|
||
|
|
|
||
|
|
#-----------------------------------------------------
|
||
|
|
def test():
|
||
|
|
#Blocks
|
||
|
|
b=Block('test')
|
||
|
|
b.append(Solid(points=[(0,0,0),(1,0,0),(1,1,0),(0,1,0)],color=1))
|
||
|
|
b.append(Arc(center=(1,0,0),color=2))
|
||
|
|
|
||
|
|
#Drawing
|
||
|
|
d=Drawing()
|
||
|
|
#tables
|
||
|
|
d.blocks.append(b) #table blocks
|
||
|
|
d.styles.append(Style()) #table styles
|
||
|
|
d.views.append(View('Normal')) #table view
|
||
|
|
d.views.append(ViewByWindow('Window',leftBottom=(1,0),rightTop=(2,1))) #idem
|
||
|
|
|
||
|
|
#entities
|
||
|
|
d.append(Circle(center=(1,1,0),color=3))
|
||
|
|
d.append(Face(points=[(0,0,0),(1,0,0),(1,1,0),(0,1,0)],color=4))
|
||
|
|
d.append(Insert('test',point=(3,3,3),cols=5,colspacing=2))
|
||
|
|
d.append(Line(points=[(0,0,0),(1,1,1)]))
|
||
|
|
d.append(Mtext('Click on Ads\nmultiple lines with mtext',point=(1,1,1),color=5,rotation=90))
|
||
|
|
d.append(Text('Please donate!',point=(3,0,1)))
|
||
|
|
d.append(Rectangle(point=(2,2,2),width=4,height=3,color=6,solid=Solid(color=2)))
|
||
|
|
d.append(Solid(points=[(4,4,0),(5,4,0),(7,8,0),(9,9,0)],color=3))
|
||
|
|
d.append(PolyLine(points=[(1,1,1),(2,1,1),(2,2,1),(1,2,1)],closed=1,color=1))
|
||
|
|
|
||
|
|
#d.saveas('c:\\test.dxf')
|
||
|
|
d.saveas('test.dxf')
|
||
|
|
|
||
|
|
|
||
|
|
#-----------------------------------------------------
|
||
|
|
if __name__=='__main__':
|
||
|
|
#main()
|
||
|
|
if not copy:
|
||
|
|
Draw.PupMenu('Error%t|This script requires a full python install')
|
||
|
|
Window.FileSelector(dxf_export_ui, 'EXPORT DXF', sys.makename(ext='.dxf'))
|
||
|
|
|
||
|
|
|
||
|
|
|