├── .gitignore ├── .project ├── .pydevproject ├── README.md └── dxf2svg.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | __pycache__ 3 | *~ 4 | 5 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | dxf2laser 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /${PROJECT_DIR_NAME} 5 | 6 | python 3.0 7 | Default 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dxf2svg 2 | 3 | Basic DXF to SVG converter. 4 | 5 | ## Usage 6 | 7 | The only non-standard dependency is `dxfgrabber`. 8 | If you dan't have it already, please install with 9 | your favourite package manager, e.g. 10 | ``` 11 | pip install dxfgrabber 12 | ``` 13 | When it's done, you should be able to do things like: 14 | ``` 15 | python dxf2svg.py myDxfFile.dxf 16 | ``` 17 | Please note that currently suported types are only `LINE`, `LWPOLYLINE`, `CIRCLE` and `ARC`. 18 | There is no support for block instances yet, though it should be quite easy to implement. 19 | 20 | ## TODO 21 | 22 | * Add support for other entity types. 23 | * Add support for block instances. 24 | * Add support for line thickness and color. 25 | * Add some example files. 26 | 27 | -------------------------------------------------------------------------------- /dxf2svg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import dxfgrabber 4 | import math 5 | import sys 6 | import os 7 | 8 | # SVG TEMPLATES 9 | 10 | # TODO: support arbitrary, user-specifiable units 11 | SVG_PREAMBLE = \ 12 | '\n' 14 | 15 | # SVG_MOVE_TO = 'M {0} {1:.2f} ' 16 | # SVG_LINE_TO = 'L {0} {1:.2f} ' 17 | # SVG_ARC_TO = 'A {0} {1:.2f} {2} {3} {4} {5:.2f} {6:.2f} ' 18 | 19 | SVG_MOVE_TO = 'M {0} {1} ' 20 | SVG_LINE_TO = 'L {0} {1} ' 21 | SVG_ARC_TO = 'A {0} {1} {2} {3} {4} {5} {6} ' 22 | SVG_CURVE_TO = 'C {0} {1} {2} {3} {4} {5} ' 23 | SVG_CURVE_S_TO = 'S {0} {1} {2} {3} ' 24 | 25 | SVG_PATH = \ 26 | '\n' 27 | 28 | SVG_LINE = \ 29 | '\n' 30 | 31 | SVG_CIRCLE = \ 32 | '\n' 33 | 34 | SVG_TEXT = \ 35 | '{2}\n' 36 | 37 | # from http://www.w3.org/TR/SVG/coords.html#Units 38 | CUT_STROKE_COLOR = '#ff0000' 39 | CUT_STROKE_WIDTH = 0.001/(90) # TODO: user-specified, arbitrary units 40 | 41 | ENGRAVE_STOKE_COLOR = '#000000' 42 | 43 | # SVG DRAWING HELPERS 44 | 45 | def angularDifference(startangle, endangle): 46 | result = endangle - startangle 47 | while result >= 360: 48 | result -= 360 49 | while result < 0: 50 | result += 360 51 | return result 52 | 53 | def pathStringFromPoints(points): 54 | pathString = SVG_MOVE_TO.format(*points[0]) 55 | for i in range(1,len(points)): 56 | pathString += SVG_LINE_TO.format(*points[i]) 57 | return pathString 58 | 59 | def curveStringFromControlPoints(points): 60 | pathString = SVG_MOVE_TO.format(*points[0]) 61 | pathString += SVG_CURVE_TO.format(points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][1]) 62 | for i in range(4, len(points) - 1): 63 | pathString += SVG_CURVE_S_TO.format(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1]) 64 | return pathString 65 | 66 | # CONVERTING TO SVG 67 | 68 | def handleEntity(svgFile, e): 69 | # TODO: handle colors and thinckness 70 | # TODO: handle elipse and spline and some other types 71 | 72 | if isinstance(e, dxfgrabber.dxfentities.Line): 73 | svgFile.write(SVG_LINE.format( 74 | e.start[0], e.start[1], e.end[0], e.end[1], 75 | 'black', 1 76 | )) 77 | 78 | elif isinstance(e, dxfgrabber.dxfentities.LWPolyline): 79 | pathString = pathStringFromPoints(e) 80 | if e.is_closed: 81 | pathString += 'Z' 82 | svgFile.write(SVG_PATH.format(pathString, CUT_STROKE_COLOR, CUT_STROKE_WIDTH)) 83 | 84 | elif isinstance(e, dxfgrabber.dxfentities.Polyline): 85 | pathString = pathStringFromPoints(e) 86 | if e.is_closed: 87 | pathString += 'Z' 88 | svgFile.write(SVG_PATH.format(pathString, CUT_STROKE_COLOR, CUT_STROKE_WIDTH)) 89 | 90 | elif isinstance(e, dxfgrabber.dxfentities.Circle): 91 | svgFile.write(SVG_CIRCLE.format(e.center[0], e.center[1], 92 | e.radius, 'black', 1)) 93 | 94 | elif isinstance(e, dxfgrabber.dxfentities.Arc): 95 | # compute end points of the arc 96 | x1 = e.center[0] + e.radius * math.cos(math.pi * e.startangle / 180) 97 | y1 = e.center[1] + e.radius * math.sin(math.pi * e.startangle / 180) 98 | x2 = e.center[0] + e.radius * math.cos(math.pi * e.endangle / 180) 99 | y2 = e.center[1] + e.radius * math.sin(math.pi * e.endangle / 180) 100 | 101 | pathString = SVG_MOVE_TO.format(x1, y1) 102 | pathString += SVG_ARC_TO.format(e.radius, e.radius, 0, 103 | int(angularDifference(e.startangle, e.endangle) > 180), 1, x2, y2) 104 | 105 | svgFile.write(SVG_PATH.format(pathString, CUT_STROKE_COLOR, CUT_STROKE_WIDTH)) 106 | 107 | elif isinstance(e, dxfgrabber.dxfentities.Solid): 108 | reordered_points = [e.points[0], e.points[1], e.points[3], e.points[2]] 109 | pathString = pathStringFromPoints(reordered_points) 110 | pathString += 'Z' 111 | svgFile.write(SVG_PATH.format(pathString, CUT_STROKE_COLOR, CUT_STROKE_WIDTH)) 112 | 113 | elif isinstance(e, dxfgrabber.dxfentities.Spline): 114 | pathString = curveStringFromControlPoints(e.control_points) 115 | if e.is_closed: 116 | pathString += 'Z' 117 | svgFile.write(SVG_PATH.format(pathString, CUT_STROKE_COLOR, CUT_STROKE_WIDTH)) 118 | 119 | elif isinstance(e, dxfgrabber.dxfentities.Insert): 120 | print "Can't handle INSERT yet" 121 | 122 | elif isinstance(e, dxfgrabber.dxfentities.MText): 123 | svgFile.write(SVG_TEXT.format(e.insert[0], e.insert[1], e.plain_text(), e.height)) 124 | 125 | else: 126 | raise Exception("Unknown type %s" % e) 127 | #end: handleEntity 128 | 129 | def saveToSVG(svgFile, dxfData): 130 | 131 | if '$LIMMIN' in dxfData.header and '$LIMMAX' in dxfData.header: 132 | minX = min(dxfData.header['$EXTMIN'][0], dxfData.header['$LIMMIN'][0]) 133 | minY = min(dxfData.header['$EXTMIN'][1], dxfData.header['$LIMMIN'][1]) 134 | maxX = max(dxfData.header['$EXTMAX'][0], dxfData.header['$LIMMAX'][0]) 135 | maxY = max(dxfData.header['$EXTMAX'][1], dxfData.header['$LIMMAX'][1]) 136 | else: 137 | minX = dxfData.header['$EXTMIN'][0] 138 | minY = dxfData.header['$EXTMIN'][1] 139 | maxX = dxfData.header['$EXTMAX'][0] 140 | maxY = dxfData.header['$EXTMAX'][1] 141 | 142 | 143 | # TODO: also handle groups 144 | svgFile.write(SVG_PREAMBLE.format( 145 | minX, minY, maxX - minX, maxY - minY, 146 | abs(maxX - minX), abs(maxY - minY))) 147 | 148 | for entity in dxfData.entities: 149 | layer = dxfData.layers[entity.layer] 150 | if layer.on and not layer.frozen: 151 | handleEntity(svgFile, entity) 152 | else: 153 | print "Not handeling entity " + str(entity) 154 | 155 | svgFile.write('\n') 156 | #end: saveToSVG 157 | 158 | if __name__ == '__main__': 159 | # TODO: error handling 160 | if len(sys.argv) < 2: 161 | sys.exit('Usage: {0} file-name'.format(sys.argv[0])) 162 | 163 | for filename in sys.argv[1:]: 164 | # grab data from file 165 | dxfData = dxfgrabber.readfile(filename) 166 | 167 | # convert and save to svg 168 | svgName = '.'.join([os.path.splitext(filename)[0]] + ['svg']) 169 | if os.path.exists(svgName): 170 | if not raw_input("Overwrite existing file? (y/N) ").lower() == 'y': 171 | quit("Quitting.") 172 | 173 | svgFile = open(svgName, 'w') 174 | 175 | label_basename = os.path.basename(filename).split('_')[0] 176 | print("Opening '%s' (%s)" % (filename, label_basename)) 177 | 178 | print "Saving to SVG file: {0}".format(svgName) 179 | saveToSVG(svgFile, dxfData) 180 | 181 | svgFile.close() 182 | #end: __main__ 183 | 184 | --------------------------------------------------------------------------------