1. #!/usr/bin/env python
  2. '''
  3. Copyright (C) 2005,2007,2008 Aaron Spike, aaron@ekips.org
  4. Copyright (C) 2008,2010 Alvin Penner, penner@vaxxine.com
  5. This file output script for Inkscape creates a AutoCAD R14 DXF file.
  6. The spec can be found here: http://www.autodesk.com/techpubs/autocad/acadr14/dxf/index.htm.
  7. File history:
  8. - template dxf_outlines.dxf added Feb 2008 by Alvin Penner
  9. - ROBO-Master output option added Aug 2008
  10. - ROBO-Master multispline output added Sept 2008
  11. - LWPOLYLINE output modification added Dec 2008
  12. - toggle between LINE/LWPOLYLINE added Jan 2010
  13. - support for transform elements added July 2010
  14. - support for layers added July 2010
  15. - support for rectangle added Dec 2010
  16. This program is free software; you can redistribute it and/or modify
  17. it under the terms of the GNU General Public License as published by
  18. the Free Software Foundation; either version 2 of the License, or
  19. (at your option) any later version.
  20. This program is distributed in the hope that it will be useful,
  21. but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. GNU General Public License for more details.
  24. You should have received a copy of the GNU General Public License
  25. along with this program; if not, write to the Free Software
  26. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  27. '''
  28. # standard library
  29. import math
  30. # local library
  31. import inkex
  32. import simplestyle
  33. import simpletransform
  34. import cubicsuperpath
  35. import coloreffect
  36. import dxf_templates
  37. try:
  38. from numpy import *
  39. from numpy.linalg import solve
  40. except:
  41. # Initialize gettext for messages outside an inkex derived class
  42. inkex.localize()
  43. inkex.errormsg(_("Failed to import the numpy or numpy.linalg modules. These modules are required by this extension. Please install them and try again."))
  44. inkex.sys.exit()
  45. def pointdistance((x1,y1),(x2,y2)):
  46. return math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2))
  47. def get_fit(u, csp, col):
  48. return (1-u)**3*csp[0][col] + 3*(1-u)**2*u*csp[1][col] + 3*(1-u)*u**2*csp[2][col] + u**3*csp[3][col]
  49. def get_matrix(u, i, j):
  50. if j == i + 2:
  51. return (u[i]-u[i-1])*(u[i]-u[i-1])/(u[i+2]-u[i-1])/(u[i+1]-u[i-1])
  52. elif j == i + 1:
  53. return ((u[i]-u[i-1])*(u[i+2]-u[i])/(u[i+2]-u[i-1]) + (u[i+1]-u[i])*(u[i]-u[i-2])/(u[i+1]-u[i-2]))/(u[i+1]-u[i-1])
  54. elif j == i:
  55. return (u[i+1]-u[i])*(u[i+1]-u[i])/(u[i+1]-u[i-2])/(u[i+1]-u[i-1])
  56. else:
  57. return 0
  58. class MyEffect(inkex.Effect):
  59. def __init__(self):
  60. inkex.Effect.__init__(self)
  61. self.OptionParser.add_option("-R", "--ROBO", action="store",
  62. type="string", dest="ROBO",
  63. default=False)
  64. self.OptionParser.add_option("-P", "--POLY", action="store",
  65. type="string", dest="POLY",
  66. default=True)
  67. self.OptionParser.add_option("--units", action="store",
  68. type="string", dest="units",
  69. default="72./96") # Points
  70. self.OptionParser.add_option("--encoding", action="store",
  71. type="string", dest="char_encode",
  72. default="latin_1")
  73. self.OptionParser.add_option("--tab", action="store",
  74. type="string", dest="tab")
  75. self.OptionParser.add_option("--inputhelp", action="store",
  76. type="string", dest="inputhelp")
  77. self.OptionParser.add_option("--layer_option", action="store",
  78. type="string", dest="layer_option",
  79. default="all")
  80. self.OptionParser.add_option("--layer_name", action="store",
  81. type="string", dest="layer_name")
  82. self.dxf = []
  83. self.handle = 255 # handle for DXF ENTITY
  84. self.layers = ['0']
  85. self.layer = '0' # mandatory layer
  86. self.layernames = []
  87. self.csp_old = [[0.0,0.0]]*4 # previous spline
  88. self.d = array([0], float) # knot vector
  89. self.poly = [[0.0,0.0]] # LWPOLYLINE data
  90. def output(self):
  91. print ''.join(self.dxf)
  92. def dxf_add(self, str):
  93. self.dxf.append(str.encode(self.options.char_encode))
  94. def dxf_line(self,csp):
  95. self.handle += 1
  96. self.dxf_add(" 0\nLINE\n 5\n%x\n100\nAcDbEntity\n 8\n%s\n 62\n%d\n100\nAcDbLine\n" % (self.handle, self.layer, self.color))
  97. self.dxf_add(" 10\n%f\n 20\n%f\n 30\n0.0\n 11\n%f\n 21\n%f\n 31\n0.0\n" % (csp[0][0],csp[0][1],csp[1][0],csp[1][1]))
  98. def LWPOLY_line(self,csp):
  99. if (abs(csp[0][0] - self.poly[-1][0]) > .0001
  100. or abs(csp[0][1] - self.poly[-1][1]) > .0001):
  101. self.LWPOLY_output() # terminate current polyline
  102. self.poly = [csp[0]] # initiallize new polyline
  103. self.color_LWPOLY = self.color
  104. self.layer_LWPOLY = self.layer
  105. self.poly.append(csp[1])
  106. def LWPOLY_output(self):
  107. if len(self.poly) == 1:
  108. return
  109. self.handle += 1
  110. closed = 1
  111. if (abs(self.poly[0][0] - self.poly[-1][0]) > .0001
  112. or abs(self.poly[0][1] - self.poly[-1][1]) > .0001):
  113. closed = 0
  114. self.dxf_add(" 0\nLWPOLYLINE\n 5\n%x\n100\nAcDbEntity\n 8\n%s\n 62\n%d\n100\nAcDbPolyline\n 90\n%d\n 70\n%d\n" % (self.handle, self.layer_LWPOLY, self.color_LWPOLY, len(self.poly) - closed, closed))
  115. for i in range(len(self.poly) - closed):
  116. self.dxf_add(" 10\n%f\n 20\n%f\n 30\n0.0\n" % (self.poly[i][0],self.poly[i][1]))
  117. def dxf_spline(self,csp):
  118. knots = 8
  119. ctrls = 4
  120. self.handle += 1
  121. self.dxf_add(" 0\nSPLINE\n 5\n%x\n100\nAcDbEntity\n 8\n%s\n 62\n%d\n100\nAcDbSpline\n" % (self.handle, self.layer, self.color))
  122. self.dxf_add(" 70\n8\n 71\n3\n 72\n%d\n 73\n%d\n 74\n0\n" % (knots, ctrls))
  123. for i in range(2):
  124. for j in range(4):
  125. self.dxf_add(" 40\n%d\n" % i)
  126. for i in csp:
  127. self.dxf_add(" 10\n%f\n 20\n%f\n 30\n0.0\n" % (i[0],i[1]))
  128. def ROBO_spline(self,csp):
  129. # this spline has zero curvature at the endpoints, as in ROBO-Master
  130. if (abs(csp[0][0] - self.csp_old[3][0]) > .0001
  131. or abs(csp[0][1] - self.csp_old[3][1]) > .0001
  132. or abs((csp[1][1]-csp[0][1])*(self.csp_old[3][0]-self.csp_old[2][0]) - (csp[1][0]-csp[0][0])*(self.csp_old[3][1]-self.csp_old[2][1])) > .001):
  133. self.ROBO_output() # terminate current spline
  134. self.xfit = array([csp[0][0]], float) # initiallize new spline
  135. self.yfit = array([csp[0][1]], float)
  136. self.d = array([0], float)
  137. self.color_ROBO = self.color
  138. self.layer_ROBO = self.layer
  139. self.xfit = concatenate((self.xfit, zeros((3)))) # append to current spline
  140. self.yfit = concatenate((self.yfit, zeros((3))))
  141. self.d = concatenate((self.d, zeros((3))))
  142. for i in range(1, 4):
  143. j = len(self.d) + i - 4
  144. self.xfit[j] = get_fit(i/3.0, csp, 0)
  145. self.yfit[j] = get_fit(i/3.0, csp, 1)
  146. self.d[j] = self.d[j-1] + pointdistance((self.xfit[j-1],self.yfit[j-1]),(self.xfit[j],self.yfit[j]))
  147. self.csp_old = csp
  148. def ROBO_output(self):
  149. if len(self.d) == 1:
  150. return
  151. fits = len(self.d)
  152. ctrls = fits + 2
  153. knots = ctrls + 4
  154. self.xfit = concatenate((self.xfit, zeros((2)))) # pad with 2 endpoint constraints
  155. self.yfit = concatenate((self.yfit, zeros((2)))) # pad with 2 endpoint constraints
  156. self.d = concatenate((self.d, zeros((6)))) # pad with 3 duplicates at each end
  157. self.d[fits+2] = self.d[fits+1] = self.d[fits] = self.d[fits-1]
  158. solmatrix = zeros((ctrls,ctrls), dtype=float)
  159. for i in range(fits):
  160. solmatrix[i,i] = get_matrix(self.d, i, i)
  161. solmatrix[i,i+1] = get_matrix(self.d, i, i+1)
  162. solmatrix[i,i+2] = get_matrix(self.d, i, i+2)
  163. solmatrix[fits, 0] = self.d[2]/self.d[fits-1] # curvature at start = 0
  164. solmatrix[fits, 1] = -(self.d[1] + self.d[2])/self.d[fits-1]
  165. solmatrix[fits, 2] = self.d[1]/self.d[fits-1]
  166. solmatrix[fits+1, fits-1] = (self.d[fits-1] - self.d[fits-2])/self.d[fits-1] # curvature at end = 0
  167. solmatrix[fits+1, fits] = (self.d[fits-3] + self.d[fits-2] - 2*self.d[fits-1])/self.d[fits-1]
  168. solmatrix[fits+1, fits+1] = (self.d[fits-1] - self.d[fits-3])/self.d[fits-1]
  169. xctrl = solve(solmatrix, self.xfit)
  170. yctrl = solve(solmatrix, self.yfit)
  171. self.handle += 1
  172. self.dxf_add(" 0\nSPLINE\n 5\n%x\n100\nAcDbEntity\n 8\n%s\n 62\n%d\n100\nAcDbSpline\n" % (self.handle, self.layer_ROBO, self.color_ROBO))
  173. self.dxf_add(" 70\n0\n 71\n3\n 72\n%d\n 73\n%d\n 74\n%d\n" % (knots, ctrls, fits))
  174. for i in range(knots):
  175. self.dxf_add(" 40\n%f\n" % self.d[i-3])
  176. for i in range(ctrls):
  177. self.dxf_add(" 10\n%f\n 20\n%f\n 30\n0.0\n" % (xctrl[i],yctrl[i]))
  178. for i in range(fits):
  179. self.dxf_add(" 11\n%f\n 21\n%f\n 31\n0.0\n" % (self.xfit[i],self.yfit[i]))
  180. def process_shape(self, node, mat):
  181. rgb = (0,0,0)
  182. style = node.get('style')
  183. if style:
  184. style = simplestyle.parseStyle(style)
  185. if style.has_key('stroke'):
  186. if style['stroke'] and style['stroke'] != 'none' and style['stroke'][0:3] != 'url':
  187. rgb = simplestyle.parseColor(style['stroke'])
  188. hsl = coloreffect.ColorEffect.rgb_to_hsl(coloreffect.ColorEffect(),rgb[0]/255.0,rgb[1]/255.0,rgb[2]/255.0)
  189. self.color = 7 # default is black
  190. if hsl[2]:
  191. self.color = 1 + (int(6*hsl[0] + 0.5) % 6) # use 6 hues
  192. if node.tag == inkex.addNS('path','svg'):
  193. d = node.get('d')
  194. if not d:
  195. return
  196. p = cubicsuperpath.parsePath(d)
  197. elif node.tag == inkex.addNS('rect','svg'):
  198. x = float(node.get('x', 0))
  199. y = float(node.get('y', 0))
  200. width = float(node.get('width'))
  201. height = float(node.get('height'))
  202. d = "m %s,%s %s,%s %s,%s %s,%s z" % (x, y, width, 0, 0, height, -width, 0)
  203. p = cubicsuperpath.parsePath(d)
  204. elif node.tag == inkex.addNS('line','svg'):
  205. x1 = float(node.get('x1', 0))
  206. x2 = float(node.get('x2', 0))
  207. y1 = float(node.get('y1', 0))
  208. y2 = float(node.get('y2', 0))
  209. d = "M %s,%s L %s,%s" % (x1, y1, x2, y2)
  210. p = cubicsuperpath.parsePath(d)
  211. elif node.tag == inkex.addNS('circle','svg'):
  212. cx = float(node.get('cx', 0))
  213. cy = float(node.get('cy', 0))
  214. r = float(node.get('r'))
  215. d = "m %s,%s a %s,%s 0 0 1 %s,%s %s,%s 0 0 1 %s,%s z" % (cx + r, cy, r, r, -2*r, 0, r, r, 2*r, 0)
  216. p = cubicsuperpath.parsePath(d)
  217. elif node.tag == inkex.addNS('ellipse','svg'):
  218. cx = float(node.get('cx', 0))
  219. cy = float(node.get('cy', 0))
  220. rx = float(node.get('rx'))
  221. ry = float(node.get('ry'))
  222. d = "m %s,%s a %s,%s 0 0 1 %s,%s %s,%s 0 0 1 %s,%s z" % (cx + rx, cy, rx, ry, -2*rx, 0, rx, ry, 2*rx, 0)
  223. p = cubicsuperpath.parsePath(d)
  224. else:
  225. return
  226. trans = node.get('transform')
  227. if trans:
  228. mat = simpletransform.composeTransform(mat, simpletransform.parseTransform(trans))
  229. simpletransform.applyTransformToPath(mat, p)
  230. for sub in p:
  231. for i in range(len(sub)-1):
  232. s = sub[i]
  233. e = sub[i+1]
  234. if s[1] == s[2] and e[0] == e[1]:
  235. if (self.options.POLY == 'true'):
  236. self.LWPOLY_line([s[1],e[1]])
  237. else:
  238. self.dxf_line([s[1],e[1]])
  239. elif (self.options.ROBO == 'true'):
  240. self.ROBO_spline([s[1],s[2],e[0],e[1]])
  241. else:
  242. self.dxf_spline([s[1],s[2],e[0],e[1]])
  243. def process_clone(self, node):
  244. trans = node.get('transform')
  245. x = node.get('x')
  246. y = node.get('y')
  247. mat = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
  248. if trans:
  249. mat = simpletransform.composeTransform(mat, simpletransform.parseTransform(trans))
  250. if x:
  251. mat = simpletransform.composeTransform(mat, [[1.0, 0.0, float(x)], [0.0, 1.0, 0.0]])
  252. if y:
  253. mat = simpletransform.composeTransform(mat, [[1.0, 0.0, 0.0], [0.0, 1.0, float(y)]])
  254. # push transform
  255. if trans or x or y:
  256. self.groupmat.append(simpletransform.composeTransform(self.groupmat[-1], mat))
  257. # get referenced node
  258. refid = node.get(inkex.addNS('href','xlink'))
  259. refnode = self.getElementById(refid[1:])
  260. if refnode is not None:
  261. if refnode.tag == inkex.addNS('g','svg'):
  262. self.process_group(refnode)
  263. elif refnode.tag == inkex.addNS('use', 'svg'):
  264. self.process_clone(refnode)
  265. else:
  266. self.process_shape(refnode, self.groupmat[-1])
  267. # pop transform
  268. if trans or x or y:
  269. self.groupmat.pop()
  270. def process_group(self, group):
  271. if group.get(inkex.addNS('groupmode', 'inkscape')) == 'layer':
  272. style = group.get('style')
  273. if style:
  274. style = simplestyle.parseStyle(style)
  275. if style.has_key('display'):
  276. if style['display'] == 'none' and self.options.layer_option and self.options.layer_option=='visible':
  277. return
  278. layer = group.get(inkex.addNS('label', 'inkscape'))
  279. if self.options.layer_name and self.options.layer_option and self.options.layer_option=='name' and not layer.lower() in self.options.layer_name:
  280. return
  281. layer = layer.replace(' ', '_')
  282. if layer in self.layers:
  283. self.layer = layer
  284. trans = group.get('transform')
  285. if trans:
  286. self.groupmat.append(simpletransform.composeTransform(self.groupmat[-1], simpletransform.parseTransform(trans)))
  287. for node in group:
  288. if node.tag == inkex.addNS('g','svg'):
  289. self.process_group(node)
  290. elif node.tag == inkex.addNS('use', 'svg'):
  291. self.process_clone(node)
  292. else:
  293. self.process_shape(node, self.groupmat[-1])
  294. if trans:
  295. self.groupmat.pop()
  296. def effect(self):
  297. #Warn user if name match field is empty
  298. if self.options.layer_option and self.options.layer_option=='name' and not self.options.layer_name:
  299. inkex.errormsg(_("Error: Field 'Layer match name' must be filled when using 'By name match' option"))
  300. inkex.sys.exit()
  301. #Split user layer data into a list: "layerA,layerb,LAYERC" becomes ["layera", "layerb", "layerc"]
  302. if self.options.layer_name:
  303. self.options.layer_name = self.options.layer_name.lower().split(',')
  304. #References: Minimum Requirements for Creating a DXF File of a 3D Model By Paul Bourke
  305. # NURB Curves: A Guide for the Uninitiated By Philip J. Schneider
  306. # The NURBS Book By Les Piegl and Wayne Tiller (Springer, 1995)
  307. # self.dxf_add("999\nDXF created by Inkscape\n") # Some programs do not take comments in DXF files (KLayout 0.21.12 for example)
  308. self.dxf_add(dxf_templates.r14_header)
  309. for node in self.document.getroot().xpath('//svg:g', namespaces=inkex.NSS):
  310. if node.get(inkex.addNS('groupmode', 'inkscape')) == 'layer':
  311. layer = node.get(inkex.addNS('label', 'inkscape'))
  312. self.layernames.append(layer.lower())
  313. if self.options.layer_name and self.options.layer_option and self.options.layer_option=='name' and not layer.lower() in self.options.layer_name:
  314. continue
  315. layer = layer.replace(' ', '_')
  316. if layer and not layer in self.layers:
  317. self.layers.append(layer)
  318. self.dxf_add(" 2\nLAYER\n 5\n2\n100\nAcDbSymbolTable\n 70\n%s\n" % len(self.layers))
  319. for i in range(len(self.layers)):
  320. self.dxf_add(" 0\nLAYER\n 5\n%x\n100\nAcDbSymbolTableRecord\n100\nAcDbLayerTableRecord\n 2\n%s\n 70\n0\n 6\nCONTINUOUS\n" % (i + 80, self.layers[i]))
  321. self.dxf_add(dxf_templates.r14_style)
  322. scale = eval(self.options.units)
  323. if not scale:
  324. scale = 25.4/96 # if no scale is specified, assume inch as baseunit
  325. h = self.unittouu(self.getDocumentHeight())
  326. self.groupmat = [[[scale, 0.0, 0.0], [0.0, -scale, h*scale]]]
  327. doc = self.document.getroot()
  328. self.process_group(doc)
  329. if self.options.ROBO == 'true':
  330. self.ROBO_output()
  331. if self.options.POLY == 'true':
  332. self.LWPOLY_output()
  333. self.dxf_add(dxf_templates.r14_footer)
  334. #Warn user if layer data seems wrong
  335. if self.options.layer_name and self.options.layer_option and self.options.layer_option=='name':
  336. for layer in self.options.layer_name:
  337. if not layer in self.layernames:
  338. inkex.errormsg(_("Warning: Layer '%s' not found!") % (layer))
  339. if __name__ == '__main__':
  340. e = MyEffect()
  341. e.affect()
  342. # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
  1. More ...
 
 

685

Dxf Outlines test

by Inkscape contributors

For Tina for testing. Hope it works, else we'll find a different solution.

Private

Lines
363
Words
1607
Size
17.6 KB
Created
Type
text/x-python
General Public License v2 (GPLv2)
Please log in to leave a comment!