├── README.md └── sdxf.py /README.md: -------------------------------------------------------------------------------- 1 | SDXF is a python library for creating lines, circles, and polygons in DXF (Document Exchange Format). DXF is an ASCII format published by AutoCAD and readable by a variety of programs. 2 | 3 | The library was created by a guy named [Stani](http://www.stani.be) and appears to be abandoned. I couldn't find any documentation for it so I [wrote my own](http://www.kellbot.com/sdxf-python-library-for-dxf). It's not being actively maintained, but if you've got a bug fix feel free to fork it and submit a pull request. 4 | 5 | If anyone wants to put together some test scripts to make sure things don't get broken along the way, please do! 6 | 7 | [Stani's DXF library for python][1] is an excellent tool for writing lines, circles, and polygons in DXF format. DXF (Document Exchange Format) is an ASCII file format published by AutoCad which can be read by a variety of programs, including Blender, Maya, CorelDraw, and a [host of others][2]. 8 | 9 | I'll attempt to document the library here as I figure it out, but am no longer actively working with this library. As of July 2012, it [has a new home on GitHub][1]! If you have an update / bug fix, you can submit a pull request. 10 | 11 | [Download SDXF 1.1.1][3] 12 | 13 | ### Changes in 1.1.1 14 | 15 | * Added support for LWPOLYLINE, so you can now make a continuous polyline instead of several separate lines 16 | 17 | ## Example Code 18 | 19 | This will draw a line from (0,0) to (1,1) and "Hello World" at (3,0) 20 | 21 | import sdxf 22 | 23 | d = sdxf.Drawing() 24 | 25 | #set the color of the text layer to green 26 | d.layers.append(sdxf.Layer(name = "textlayer", color=3)) 27 | 28 | #add drawing elements 29 | d.append(sdxf.Text('Hello World!',point=(3,0), layer="textlayer")) 30 | d.append(sdxf.Line(points=[(0, 0), (1, 1)], layer="drawinglayer")) 31 | 32 | d.saveas('hello_world.dxf') 33 | 34 | ## Overview 35 | 36 | ### Entities 37 | 38 | An Entity is an individual drawing object, like a line or circle. These are appended to the Drawing and rendered in the order they're added. 39 | 40 | ### Layers 41 | 42 | Layers are used to organize entities. Layers can be assigned colors to make drawings easier to read. An entity can be assigned to a new layer on the fly, without explicitly defining the layer first. 43 | 44 | Layer(name="mynewlayer",color=8) 45 | 46 | ### Blocks 47 | 48 | Blocks are reusable symbols. A block is defined once and can then be appended to the drawing using the [Insert][4] entity. A block can be inserted multiple times into a drawing, at different points. 49 | 50 | # define the block, a Solid and an Arc 51 | b = Block('test') 52 | b.append(Solid(points=[(0,0,0),(1,0,0),(1,1,0),(0,1,0)],color=1)) 53 | b.append(Arc(center=(1,0,0),color=2)) 54 | 55 | #create a new drawing 56 | d = Drawing() 57 | 58 | #add the block to the Blocks table, so it can be referenced later 59 | d.blocks.append(b) 60 | 61 | #add entities to the drawing, including the block using the Insert entity 62 | d.append(Circle(center=(1,1,0),color=3)) 63 | d.append(Face(points=[(0,0,0),(1,0,0),(1,1,0),(0,1,0)],color=4)) 64 | d.append(Insert('test',point=(3,3,3),cols=5,colspacing=2)) 65 | d.append(Line(points=[(0,0,0),(1,1,1)])) 66 | 67 | ## Supported Entities 68 | 69 | These entities are currently in the library. In addition to passing the arguments for the individual entity type, you can pass common arguments (group codes) which are available for all entities. 70 | 71 | ### Common Group Codes 72 | 73 | * color - the color of the entity. Represented by a number. See the [Color List][5] below. 74 | * extrusion - ? 75 | * layer - which layer to place the element on. You do not need to explicitly declare a layer before assigning entities to it 76 | * lineType - ? 77 | * lineTypeScale - ? 78 | * thickness - thickness of the entity lines 79 | * parent - ? 80 | 81 | ### Arc 82 | 83 | Draws an arc (part of a circle). 84 | 85 | * center (x, y, z) - The center of the circle from which the arc is to be taken. Z is optional. 86 | * radius - The radius from the center to the arc 87 | * startAngle - The angle, in degrees, for the start of the arc. 88 | * endAngle - The angle, in degrees, for the end of the arc 89 | 90 | Arc(center=(3,0),radius=2,startAngle=0,endAngle=90) 91 | 92 | ### Circle 93 | 94 | Draws a circle. 95 | 96 | * center (x,y,z) - the center of the circle. Z is optional. 97 | * radius - the radius of the circle 98 | 99 | Arc(center=(3,0),radius=2) 100 | 101 | ### Face 102 | 103 | Creates a 3d face. A 3d face takes 4 points, which may or may not all be on the same plane. 104 | 105 | ### Insert 106 | 107 | Blocks are added to a file using the Insert entity. The block must be added to the Blocks table before it can be used. 108 | 109 | * name - Block name (defined when the block was added to the Blocks table) 110 | * point - Insertion point (x,y,z) to add the block 111 | * xscale - x scale factor; optional, defaults to 1 112 | * yscale - y scale factor; optional, defaults to 1 113 | * zscale - z scale factor; optional, defaults to 1 114 | * cols - column count; optional, defaults to 1 115 | * colspacing - column spacing; optional, defaults to 0 116 | * rows - row count; optional, defaults to 1 117 | * rowspacing - row spacing; optional, defaults to 0 118 | * rotation - rotation angle; optional, defaults to 0 119 | 120 | Insert('test',point=(3,3,3),cols=5,colspacing=2) 121 | 122 | ### Line 123 | 124 | Makes a line! Takes a list containing two points. Points can be (x,y) or (x,y,z) 125 | 126 | Line(points=[(0,0),(1,1)]) 127 | 128 | ### LwPolyLine 129 | 130 | Makes a line with vertexes. Takes a list of points. 131 | 132 | linePoints = [(0,0),(1,1),(1,0)] 133 | LwPolyLine(points=linePoints,flag=1) 134 | 135 | ### Polyline 136 | 137 | In the current implementation, actually just makes a bunch of Lines rather than a polyline. 138 | 139 | ### Point 140 | 141 | A point in space. Takes a point (duh). 142 | 143 | ### Solid 144 | 145 | From what I can tell, this creates a 3D solid by taking either 3 or 4 points and then extruding the face in various directions. 146 | 147 | ### Text 148 | 149 | Renders a string as text at the given point. 150 | 151 | Text('Hello World!',point=(3,0)) 152 | 153 | ### Mtext 154 | 155 | I think this is like Text but supports line breaks. In this version of SDXF it just creates multiple Text entities. 156 | 157 | ## Extras 158 | 159 | These are not actual DXF entities, but are classes that make building other shapes easier. 160 | 161 | ### Rectangle 162 | 163 | Creates a rectangle using 4 lines. Could probably be modified to use LwPolyLine instead. 164 | 165 | * point - lower left (?) corner point 166 | * width - rectangle width 167 | * height - rectangle height 168 | * solid - ? 169 | * line - ? 170 | 171 | ### Line List 172 | 173 | Creates a bunch of lines from a set of points. Currently used instead of PolyLine. 174 | 175 | * points - list of verticies 176 | * closed - whether to close the shape; defaults to 0 177 | 178 | ## Color List 179 | 180 | The colors may vary depending on the rendering software used. 181 | 1 - Red 182 | 2 - Yellow 183 | 3 - Green 184 | 4 - Cyan 185 | 5 - Blue 186 | 6 - Magenta 187 | 7 - White 188 | 8 - Black 189 | 190 | [1]: https://github.com/nycresistor/SDXF 191 | [2]: http://en.wikipedia.org/wiki/AutoCAD_DXF#Software_which_supports_DXF 192 | [3]: https://github.com/downloads/nycresistor/SDXF/sdxf.py 193 | [4]: http://www.kellbot.com/sdxf-python-library-for-dxf/#entity_insert 194 | [5]: http://www.kellbot.com/sdxf-python-library-for-dxf/#color_list 195 | -------------------------------------------------------------------------------- /sdxf.py: -------------------------------------------------------------------------------- 1 | #(c)www.stani.be 2 | 3 | __version__ = """v1.1.2 (07/06/12)""" 4 | __author__ = "www.stani.be" 5 | __license__ = "GPL" 6 | __url__ = "https://github.com/nycresistor/SDXF" 7 | """SDXF - Stani's DXF 8 | Python library to generate dxf drawings 9 | 10 | Copyright %s 11 | Version %s 12 | License %s 13 | Homepage %s 14 | 15 | Library by Stani, whose website is now defunct. 16 | Now on github, loosely maintained by Kellbot (http://www.kellbot.com) 17 | """ % (__author__, __version__, __license__, __url__) 18 | 19 | import copy 20 | 21 | ####1) Private (only for developers) 22 | _HEADER_POINTS = ['insbase', 'extmin', 'extmax'] 23 | 24 | #---helper functions 25 | 26 | 27 | def _point(x, index=0): 28 | """Convert tuple to a dxf point""" 29 | return '\n'.join(['%s\n%s' % ((i + 1) * 10 + index, x[i]) 30 | for i in range(len(x))]) 31 | 32 | 33 | def _points(p): 34 | """Convert a list of tuples to dxf points""" 35 | return [_point(p[i], i) for i in range(len(p))] 36 | 37 | #---base classes 38 | 39 | 40 | class _Call: 41 | """Makes a callable class.""" 42 | def copy(self): 43 | """Returns a copy.""" 44 | return copy.deepcopy(self) 45 | 46 | def __call__(self, **attrs): 47 | """Returns a copy with modified attributes.""" 48 | copied = self.copy() 49 | for attr in attrs: 50 | setattr(copied, attr, attrs[attr]) 51 | return copied 52 | 53 | 54 | class _Entity(_Call): 55 | """Base class for _common group codes for entities.""" 56 | def __init__(self, color=None, extrusion=None, layer='0', 57 | lineType=None, lineTypeScale=None, lineWeight=None, 58 | thickness=None, parent=None): 59 | """None values will be omitted.""" 60 | self.color = color 61 | self.extrusion = extrusion 62 | self.layer = layer 63 | self.lineType = lineType 64 | self.lineTypeScale = lineTypeScale 65 | self.lineWeight = lineWeight 66 | self.thickness = thickness 67 | self.parent = parent 68 | 69 | def _common(self): 70 | """Return common group codes as a string.""" 71 | parent = self.parent if self.parent else self 72 | result = '8\n%s' % parent.layer 73 | if parent.color is not None: 74 | result += '\n62\n%s' % parent.color 75 | if parent.extrusion is not None: 76 | result += '\n%s' % _point(parent.extrusion, 200) 77 | if parent.lineType is not None: 78 | result += '\n6\n%s' % parent.lineType 79 | if parent.lineWeight is not None: 80 | result += '\n370\n%s' % parent.lineWeight 81 | if parent.lineTypeScale is not None: 82 | result += '\n48\n%s' % parent.lineTypeScale 83 | if parent.thickness is not None: 84 | result += '\n39\n%s' % parent.thickness 85 | return result 86 | 87 | 88 | class _Entities: 89 | """Base class to deal with composed objects.""" 90 | def __dxf__(self): 91 | return [] 92 | 93 | def __str__(self): 94 | return '\n'.join([str(x) for x in self.__dxf__()]) 95 | 96 | 97 | class _Collection(_Call): 98 | """Base class to expose entities methods to main object.""" 99 | def __init__(self, entities=None): 100 | self.entities = copy.copy(entities or []) 101 | #link entities methods to drawing 102 | for attr in dir(self.entities): 103 | if attr[0] != '_': 104 | attrObject = getattr(self.entities, attr) 105 | if callable(attrObject): 106 | setattr(self, attr, attrObject) 107 | 108 | ####2) Constants 109 | #---color values 110 | BYBLOCK = 0 111 | BYLAYER = 256 112 | 113 | #---block-type flags (bit coded values, may be combined): 114 | 115 | # This is an anonymous block generated by hatching, associative 116 | # dimensioning, other internal operations, or an application 117 | ANONYMOUS = 1 118 | # This block has non-constant attribute definitions (this bit is not set if 119 | # the block has any attribute definitions that are constant, or has no 120 | # attribute definitions at all) 121 | NON_CONSTANT_ATTRIBUTES = 2 122 | XREF = 4 # This block is an external reference (xref) 123 | XREF_OVERLAY = 8 # This block is an xref overlay 124 | EXTERNAL = 16 # This block is externally dependent 125 | # This is a resolved external reference, or dependent of an external 126 | # reference (ignored on input) 127 | RESOLVED = 32 128 | # This definition is a referenced external reference (ignored on input) 129 | REFERENCED = 64 130 | 131 | #---mtext flags 132 | # attachment point 133 | TOP_LEFT, TOP_CENTER, TOP_RIGHT = 1, 2, 3 134 | MIDDLE_LEFT, MIDDLE_CENTER, MIDDLE_RIGHT = 4, 5, 6 135 | BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT = 7, 8, 9 136 | # drawing direction 137 | LEFT_RIGHT = 1 138 | TOP_BOTTOM = 3 139 | # the flow direction is inherited from the associated text style 140 | BY_STYLE = 5 141 | # line spacing style (optional): 142 | AT_LEAST = 1 # taller characters will override 143 | EXACT = 2 # taller characters will not override 144 | 145 | #---polyline flags 146 | # This is a closed polyline (or a polygon mesh closed in the M direction) 147 | CLOSED = 1 148 | CURVE_FIT = 2 # Curve-fit vertices have been added 149 | SPLINE_FIT = 4 # Spline-fit vertices have been added 150 | POLYLINE_3D = 8 # This is a 3D polyline 151 | POLYGON_MESH = 16 # This is a 3D polygon mesh 152 | CLOSED_N = 32 # The polygon mesh is closed in the N direction 153 | POLYFACE_MESH = 64 # The polyline is a polyface mesh 154 | # The linetype pattern is generated continuously around the vertices of this 155 | # polyline 156 | CONTINOUS_LINETYPE_PATTERN = 128 157 | 158 | #---text flags 159 | # horizontal 160 | LEFT, CENTER, RIGHT = 0, 1, 2 161 | ALIGNED, MIDDLE, FIT = 3, 4, 5 # if vertical alignment = 0 162 | # vertical 163 | BASELINE, BOTTOM, MIDDLE, TOP = 0, 1, 2, 3 164 | 165 | ####3) Classes 166 | #---entitities 167 | 168 | 169 | class Arc(_Entity): 170 | """Arc, angles in degrees.""" 171 | def __init__(self, center=(0, 0, 0), radius=1, 172 | startAngle=0.0, endAngle=90, **common): 173 | """Angles in degrees.""" 174 | _Entity.__init__(self, **common) 175 | self.center = center 176 | self.radius = radius 177 | self.startAngle = startAngle 178 | self.endAngle = endAngle 179 | 180 | def __str__(self): 181 | return '0\nARC\n%s\n%s\n40\n%s\n50\n%s\n51\n%s' % \ 182 | (self._common(), _point(self.center), 183 | self.radius, self.startAngle, self.endAngle) 184 | 185 | 186 | class Circle(_Entity): 187 | """Circle""" 188 | def __init__(self, center=(0, 0, 0), radius=1, **common): 189 | _Entity.__init__(self, **common) 190 | self.center = center 191 | self.radius = radius 192 | 193 | def __str__(self): 194 | return '0\nCIRCLE\n%s\n%s\n40\n%s' % \ 195 | (self._common(), _point(self.center), self.radius) 196 | 197 | 198 | class Face(_Entity): 199 | """3dface""" 200 | def __init__(self, points, **common): 201 | _Entity.__init__(self, **common) 202 | self.points = points 203 | 204 | def __str__(self): 205 | return '\n'.join(['0\n3DFACE', self._common()] + 206 | _points(self.points)) 207 | 208 | 209 | class Insert(_Entity): 210 | """Block instance.""" 211 | def __init__(self, name, point=(0, 0, 0), 212 | xscale=None, yscale=None, zscale=None, 213 | cols=None, colspacing=None, rows=None, rowspacing=None, 214 | rotation=None, **common): 215 | _Entity.__init__(self, **common) 216 | self.name = name 217 | self.point = point 218 | self.xscale = xscale 219 | self.yscale = yscale 220 | self.zscale = zscale 221 | self.cols = cols 222 | self.colspacing = colspacing 223 | self.rows = rows 224 | self.rowspacing = rowspacing 225 | self.rotation = rotation 226 | 227 | def __str__(self): 228 | result = '0\nINSERT\n2\n%s\n%s\n%s' % (self.name, self._common(), 229 | _point(self.point)) 230 | if self.xscale is not None: 231 | result += '\n41\n%s' % self.xscale 232 | if self.yscale is not None: 233 | result += '\n42\n%s' % self.yscale 234 | if self.zscale is not None: 235 | result += '\n43\n%s' % self.zscale 236 | if self.rotation: 237 | result += '\n50\n%s' % self.rotation 238 | if self.cols is not None: 239 | result += '\n70\n%s' % self.cols 240 | if self.colspacing is not None: 241 | result += '\n44\n%s' % self.colspacing 242 | if self.rows is not None: 243 | result += '\n71\n%s' % self.rows 244 | if self.rowspacing is not None: 245 | result += '\n45\n%s' % self.rowspacing 246 | return result 247 | 248 | 249 | class Line(_Entity): 250 | """Line""" 251 | def __init__(self, points, **common): 252 | _Entity.__init__(self, **common) 253 | self.points = points 254 | 255 | def __str__(self): 256 | return '\n'.join(['0\nLINE', self._common()] + _points(self.points)) 257 | 258 | 259 | class LwPolyLine(_Entity): 260 | """This is a LWPOLYLINE. I have no idea how it differs from a normal 261 | PolyLine""" 262 | 263 | def __init__(self, points, flag=0, width=None, **common): 264 | _Entity.__init__(self, **common) 265 | self.points = points 266 | self.flag = flag 267 | self.width = width 268 | 269 | def __str__(self): 270 | result = '0\nLWPOLYLINE\n%s\n70\n%s' % (self._common(), self.flag) 271 | result += '\n90\n%s' % len(self.points) 272 | for point in self.points: 273 | result += '\n%s' % _point(point) 274 | if self.width: 275 | result += '\n40\n%s\n41\n%s' % (self.width, self.width) 276 | return result 277 | 278 | 279 | class PolyLine(_Entity): 280 | # TODO: Finish polyline (now implemented as a series of lines) 281 | def __init__(self, points, flag=0, width=None, **common): 282 | _Entity.__init__(self, **common) 283 | self.points = points 284 | self.flag = flag 285 | self.width = width 286 | 287 | def __str__(self): 288 | result = '0\nPOLYLINE\n%s\n70\n%s' % (self._common(), self.flag) 289 | for point in self.points: 290 | result += '\n0\nVERTEX\n%s' % _point(point) 291 | if self.width: 292 | result += '\n40\n%s\n41\n%s' % (self.width, self.width) 293 | result += '\n0\nSEQEND' 294 | return result 295 | 296 | 297 | class Point(_Entity): 298 | """Colored solid fill.""" 299 | def __init__(self, points=None, **common): 300 | _Entity.__init__(self, **common) 301 | self.points = points 302 | 303 | def __str__(self): 304 | result = '0\nPOINT\n%s' % (self._common()) 305 | result += '\n%s' % _point(self.points) 306 | return result 307 | 308 | 309 | class Solid(_Entity): 310 | """Colored solid fill.""" 311 | def __init__(self, points=None, **common): 312 | _Entity.__init__(self, **common) 313 | self.points = points 314 | 315 | def __str__(self): 316 | return '\n'.join(['0\nSOLID', self._common()] + 317 | _points(self.points[:2] + [self.points[3], self.points[2]])) 318 | 319 | 320 | class Text(_Entity): 321 | """Single text line.""" 322 | def __init__(self, text='', point=(0, 0, 0), alignment=None, 323 | flag=None, height=1, justifyhor=None, justifyver=None, 324 | rotation=None, obliqueAngle=None, style=None, xscale=None, 325 | **common): 326 | _Entity.__init__(self, **common) 327 | self.text = text 328 | self.point = point 329 | self.alignment = alignment 330 | self.flag = flag 331 | self.height = height 332 | self.justifyhor = justifyhor 333 | self.justifyver = justifyver 334 | self.rotation = rotation 335 | self.obliqueAngle = obliqueAngle 336 | self.style = style 337 | self.xscale = xscale 338 | 339 | def __str__(self): 340 | result = '0\nTEXT\n%s\n%s\n40\n%s\n1\n%s' % (self._common(), 341 | _point(self.point), self.height, self.text) 342 | if self.rotation: 343 | result += '\n50\n%s' % self.rotation 344 | if self.xscale: 345 | result += '\n41\n%s' % self.xscale 346 | if self.obliqueAngle: 347 | result += '\n51\n%s' % self.obliqueAngle 348 | if self.style: 349 | result += '\n7\n%s' % self.style 350 | if self.flag: 351 | result += '\n71\n%s' % self.flag 352 | if self.justifyhor: 353 | result += '\n72\n%s' % self.justifyhor 354 | if self.alignment: 355 | result += '\n%s' % _point(self.alignment, 1) 356 | if self.justifyver: 357 | result += '\n73\n%s' % self.justifyver 358 | return result 359 | 360 | 361 | class Mtext(Text): 362 | """Surrogate for mtext, generates some Text instances.""" 363 | def __init__(self, text='', point=(0, 0, 0), width=250, 364 | spacingFactor=1.5, down=0, spacingWidth=None, **options): 365 | Text.__init__(self, text=text, point=point, **options) 366 | if down: 367 | spacingFactor *= -1 368 | self.spacingFactor = spacingFactor 369 | self.spacingWidth = spacingWidth 370 | self.width = width 371 | self.down = down 372 | 373 | def __str__(self): 374 | texts = self.text.replace('\r\n', '\n').split('\n') 375 | if not self.down: 376 | texts.reverse() 377 | result = '' 378 | x = y = 0 379 | if self.spacingWidth: 380 | spacingWidth = self.spacingWidth 381 | else: 382 | spacingWidth = self.height * self.spacingFactor 383 | for text in texts: 384 | while text: 385 | result += '\n%s' % Text(text[:self.width], 386 | point=(self.point[0] + x * spacingWidth, 387 | self.point[1] + y * spacingWidth, 388 | self.point[2]), 389 | alignment=self.alignment, flag=self.flag, 390 | height=self.height, 391 | justifyhor = self.justifyhor, justifyver=self.justifyver, 392 | rotation = self.rotation, obliqueAngle=self.obliqueAngle, 393 | style=self.style, xscale=self.xscale, parent=self) 394 | text = text[self.width:] 395 | if self.rotation: 396 | x += 1 397 | else: 398 | y += 1 399 | return result[1:] 400 | 401 | ##class _Mtext(_Entity): 402 | ## """Mtext not functioning for minimal dxf.""" 403 | ## def __init__(self,text='',point=(0,0,0),attachment=1, 404 | ## charWidth=None,charHeight=1,direction=1,height=100,rotation=0, 405 | ## spacingStyle=None,spacingFactor=None,style=None,width=100, 406 | ## xdirection=None,**common): 407 | ## _Entity.__init__(self,**common) 408 | ## self.text=text 409 | ## self.point=point 410 | ## self.attachment=attachment 411 | ## self.charWidth=charWidth 412 | ## self.charHeight=charHeight 413 | ## self.direction=direction 414 | ## self.height=height 415 | ## self.rotation=rotation 416 | ## self.spacingStyle=spacingStyle 417 | ## self.spacingFactor=spacingFactor 418 | ## self.style=style 419 | ## self.width=width 420 | ## self.xdirection=xdirection 421 | ## def __str__(self): 422 | ## input=self.text 423 | ## text='' 424 | ## while len(input)>250: 425 | ## text+='\n3\n%s'%input[:250] 426 | ## input=input[250:] 427 | ## text+='\n1\n%s'%input 428 | ## 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'%\ 429 | ## (self._common(),_point(self.point),self.charHeight,self.width, 430 | ## self.attachment,self.direction,text, 431 | ## self.height, 432 | ## self.rotation) 433 | ## if self.style:result+='\n7\n%s'%self.style 434 | ## if self.xdirection:result+='\n%s'%_point(self.xdirection,1) 435 | ## if self.charWidth:result+='\n42\n%s'%self.charWidth 436 | ## if self.spacingStyle:result+='\n73\n%s'%self.spacingStyle 437 | ## if self.spacingFactor:result+='\n44\n%s'%self.spacingFactor 438 | ## return result 439 | 440 | #---tables 441 | 442 | 443 | class Block(_Collection): 444 | """Use list methods to add entities, eg append.""" 445 | def __init__(self, name, layer='0', flag=0, base=(0, 0, 0), entities=[]): 446 | self.entities = copy.copy(entities) 447 | _Collection.__init__(self, entities) 448 | self.layer = layer 449 | self.name = name 450 | self.flag = 0 451 | self.base = base 452 | 453 | def __str__(self): 454 | e = '\n'.join([str(x)for x in self.entities]) 455 | return '0\nBLOCK\n8\n%s\n2\n%s\n70\n%s\n%s\n3\n%s\n%s\n0\nENDBLK' % ( 456 | self.layer, self.name.upper(), self.flag, _point(self.base), 457 | self.name.upper(), e) 458 | 459 | 460 | class Layer(_Call): 461 | """Layer""" 462 | def __init__(self, name='pydxf', color=7, lineType='continuous', 463 | flag=64): 464 | self.name = name 465 | self.color = color 466 | self.lineType = lineType 467 | self.flag = flag 468 | 469 | def __str__(self): 470 | return '0\nLAYER\n2\n%s\n70\n%s\n62\n%s\n6\n%s' % (self.name.upper(), 471 | self.flag, self.color, self.lineType) 472 | 473 | 474 | class LineType(_Call): 475 | """Custom linetype""" 476 | def __init__(self, name='continuous', description='Solid line', 477 | elements=[], flag=64): 478 | # TODO: Implement lineType elements 479 | self.name = name 480 | self.description = description 481 | self.elements = copy.copy(elements) 482 | self.flag = flag 483 | 484 | def __str__(self): 485 | return '0\nLTYPE\n2\n%s\n70\n%s\n3\n%s\n72\n65\n73\n%s\n40\n0.0' % ( 486 | self.name.upper(), self.flag, self.description, 487 | len(self.elements)) 488 | 489 | 490 | class Style(_Call): 491 | """Text style""" 492 | def __init__(self, name='standard', flag=0, height=0, widthFactor=40, 493 | obliqueAngle=50, mirror=0, lastHeight=1, font='arial.ttf', 494 | bigFont=''): 495 | self.name = name 496 | self.flag = flag 497 | self.height = height 498 | self.widthFactor = widthFactor 499 | self.obliqueAngle = obliqueAngle 500 | self.mirror = mirror 501 | self.lastHeight = lastHeight 502 | self.font = font 503 | self.bigFont = bigFont 504 | 505 | def __str__(self): 506 | return ('0\nSTYLE\n2\n%s\n70\n%s\n40\n%s\n41\n%s\n50\n%s\n71' 507 | '\n%s\n42\n%s\n3\n%s\n4\n%s' % (self.name.upper(), 508 | self.flag, self.flag, 509 | self.widthFactor, self.obliqueAngle, self.mirror, 510 | self.lastHeight, self.font.upper(), self.bigFont.upper())) 511 | 512 | 513 | class View(_Call): 514 | def __init__(self, name, flag=0, width=1, height=1, center=(0.5, 0.5), 515 | direction=(0, 0, 1), target=(0, 0, 0), lens=50, 516 | frontClipping=0, backClipping=0, twist=0, mode=0): 517 | self.name = name 518 | self.flag = flag 519 | self.width = width 520 | self.height = height 521 | self.center = center 522 | self.direction = direction 523 | self.target = target 524 | self.lens = lens 525 | self.frontClipping = frontClipping 526 | self.backClipping = backClipping 527 | self.twist = twist 528 | self.mode = mode 529 | 530 | def __str__(self): 531 | return ('0\nVIEW\n2\n%s\n70\n%s\n40\n%s\n%s\n41\n%s\n%s\n%s\n42\n%s' 532 | '\n43\n%s\n44\n%s\n50\n%s\n71\n%s' % (self.name, 533 | self.flag, self.height, _point(self.center), self.width, 534 | _point(self.direction, 1), _point(self.target, 2), 535 | self.lens, self.frontClipping, self.backClipping, 536 | self.twist, self.mode)) 537 | 538 | 539 | def ViewByWindow(name, leftBottom=(0, 0), rightTop=(1, 1), **options): 540 | width = abs(rightTop[0] - leftBottom[0]) 541 | height = abs(rightTop[1] - leftBottom[1]) 542 | center = ((rightTop[0] + leftBottom[0]) * 0.5, 543 | (rightTop[1] + leftBottom[1]) * 0.5) 544 | return View(name=name, width=width, height=height, center=center, 545 | **options) 546 | 547 | #---drawing 548 | 549 | 550 | class Drawing(_Collection): 551 | """Dxf drawing. Use append or any other list methods to add objects.""" 552 | def __init__(self, insbase=(0.0, 0.0, 0.0), extmin=(0.0, 0.0), 553 | extmax=(0.0, 0.0), layers=[Layer()], linetypes=[LineType()], 554 | styles=[Style()], blocks=[], views=[], entities=None, 555 | fileName='test.dxf'): 556 | # TODO: replace list with None,arial 557 | entities = entities or [] 558 | _Collection.__init__(self, entities) 559 | self.insbase = insbase 560 | self.extmin = extmin 561 | self.extmax = extmax 562 | self.layers = copy.copy(layers) 563 | self.linetypes = copy.copy(linetypes) 564 | self.styles = copy.copy(styles) 565 | self.views = copy.copy(views) 566 | self.blocks = copy.copy(blocks) 567 | self.fileName = fileName 568 | #private 569 | self.acadver = '9\n$ACADVER\n1\nAC1006' 570 | 571 | def _name(self, x): 572 | """Helper function for self._point""" 573 | return '9\n$%s' % x.upper() 574 | 575 | def _point(self, name, x): 576 | """Point setting from drawing like extmin,extmax,...""" 577 | return '%s\n%s' % (self._name(name), _point(x)) 578 | 579 | def _section(self, name, x): 580 | """Sections like tables,blocks,entities,...""" 581 | if x: 582 | xstr = '\n' + '\n'.join(x) 583 | else: 584 | xstr = '' 585 | return '0\nSECTION\n2\n%s%s\n0\nENDSEC' % (name.upper(), xstr) 586 | 587 | def _table(self, name, x): 588 | """Tables like ltype,layer,style,...""" 589 | if x: 590 | xstr = '\n' + '\n'.join(x) 591 | else: 592 | xstr = '' 593 | return '0\nTABLE\n2\n%s\n70\n%s%s\n0\nENDTAB' % (name.upper(), 594 | len(x), xstr) 595 | 596 | def __str__(self): 597 | """Returns drawing as dxf string.""" 598 | header = [self.acadver] + [self._point(attr, getattr(self, attr)) 599 | for attr in _HEADER_POINTS] 600 | header = self._section('header', header) 601 | tables = [self._table('ltype', [str(x) for x in self.linetypes]), 602 | self._table('layer', [str(x) for x in self.layers]), 603 | self._table('style', [str(x) for x in self.styles]), 604 | self._table('view', [str(x) for x in self.views])] 605 | tables = self._section('tables', tables) 606 | blocks = self._section('blocks', [str(x) for x in self.blocks]) 607 | entities = self._section('entities', [str(x) for x in self.entities]) 608 | return '\n'.join([header, tables, blocks, entities, '0\nEOF\n']) 609 | 610 | def saveas(self, fileName): 611 | self.fileName = fileName 612 | self.save() 613 | 614 | def save(self): 615 | test = open(self.fileName, 'w') 616 | test.write(str(self)) 617 | test.close() 618 | 619 | 620 | #---extras 621 | class Rectangle(_Entity): 622 | """Rectangle, creates lines.""" 623 | def __init__(self, point=(0, 0, 0), width=1, height=1, solid=None, 624 | line=1, **common): 625 | _Entity.__init__(self, **common) 626 | self.point = point 627 | self.width = width 628 | self.height = height 629 | self.solid = solid 630 | self.line = line 631 | 632 | def __str__(self): 633 | result = '' 634 | points = [self.point, 635 | (self.point[0] + self.width, self.point[1], self.point[2]), 636 | (self.point[0] + self.width, self.point[1] + self.height, 637 | self.point[2]), 638 | (self.point[0], self.point[1] + self.height, self.point[2]), 639 | self.point] 640 | if self.solid: 641 | result += '\n%s' % Solid(points=points[:-1], parent=self.solid) 642 | if self.line: 643 | for i in range(4): 644 | result += '\n%s' % Line(points=[points[i], points[i + 1]], 645 | parent=self) 646 | return result[1:] 647 | 648 | 649 | class LineList(_Entity): 650 | """Like polyline, but built of individual lines.""" 651 | def __init__(self, points=[], closed=0, **common): 652 | _Entity.__init__(self, **common) 653 | self.closed = closed 654 | self.points = copy.copy(points) 655 | 656 | def __str__(self): 657 | if self.closed: 658 | points = self.points + [self.points[0]] 659 | else: 660 | points = self.points 661 | result = '' 662 | for i in range(len(points) - 1): 663 | result += '\n%s' % Line(points=[points[i], points[i + 1]], 664 | parent=self) 665 | return result[1:] 666 | 667 | PolyLine = LineList 668 | 669 | #---test 670 | 671 | 672 | def main(): 673 | #Blocks 674 | b = Block('test') 675 | b.append(Solid(points=[(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)], 676 | color=1)) 677 | b.append(Arc(center=(1, 0, 0), color=2)) 678 | 679 | #Drawing 680 | d = Drawing() 681 | #tables 682 | d.blocks.append(b) # table blocks 683 | d.styles.append(Style()) # table styles 684 | d.views.append(View('Normal')) # table view 685 | d.views.append(ViewByWindow('Window', leftBottom=(1, 0), 686 | rightTop=(2, 1))) # idem 687 | 688 | #entities 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)], 691 | color=4)) 692 | d.append(Insert('test', point=(3, 3, 3), cols=5, colspacing=2)) 693 | d.append(Line(points=[(0, 0, 0), (1, 1, 1)])) 694 | d.append(Mtext('Click on Ads\nmultiple lines with mtext', 695 | point=(1, 1, 1), color=5, rotation=90)) 696 | d.append(Text('Please donate!', point=(3, 0, 1))) 697 | d.append(Rectangle(point=(2, 2, 2), width=4, height=3, color=6, 698 | solid=Solid(color=2))) 699 | d.append(Solid(points=[(4, 4, 0), (5, 4, 0), (7, 8, 0), (9, 9, 0)], 700 | color=3)) 701 | d.append(PolyLine(points=[(1, 1, 1), (2, 1, 1), (2, 2, 1), (1, 2, 1)], 702 | closed=1, color=1)) 703 | 704 | d.saveas('c:\\test.dxf') 705 | 706 | if __name__ == '__main__': 707 | main() 708 | --------------------------------------------------------------------------------