Merge branch 'master' into no-examples

Conflicts:
	.gitignore
This commit is contained in:
Michael Schwarz 2014-12-25 15:16:42 +01:00
commit d08e4be25f
18 changed files with 1077 additions and 1091 deletions

3
.gitignore vendored
View file

@ -1,5 +1,2 @@
/config.mk
*.pyc
*.dxf
*.stl
*.d

View file

@ -2,17 +2,33 @@
== Prerequisites
* OpenSCAD snapshot > 2014.11.05
** Used to compile OpenSCAD source files to STL.
** It is recommended to a recent development snapshot, e.g. version 2014.11.05 or later.
** The current release version (2014.03) generates invalid dependency information if the path to the project contains spaces or other characters that need to be treated specially in a makefile and also has trouble with 2D shapes containing holes. The current development version solves these problems.
* Inkscape
** Used to export DXF files to SVG.
** Recommended to be used to edit SVG files, especially if its necessary to create multiple layers and import them separately in OpenSCAD.
* OpenSCAD
** The currently newest version (2014.03) has trouble with 2D shapes containing holes. The current development version solves these problems.
** It is assumed that the 'openscad' binary is on $PATH.
* Python 2.7
** Used for to run the plugin that exports DXF to SVG and to run scripts that wrap the OpenSCAD command line tool and work around problems with generation of dependency information of OpenSCAD.
** Should already be installed as a dependency to Inkscape. The most recent version of Python 2.7 is recommended.
If any of the required binaries ('inkscape' for Inkscape and 'openscad' for OpenSCAD) is not available on $PATH, paths to these binaries can be configured by creating a file called 'config.mk' in the same directory as the makefile. There, the variables 'OPENSCAD' and 'INKSCAPE' can be set to the appropriate paths like this:
=== Explicitly specifying paths to binaries
If any of the required binaries is not available on `$PATH` or different versions should be used, the paths to these binaries can be configured by creating a file called 'config.mk' in the same directory as the makefile. There, variables can be set to the paths to these binaries (or to a different binary name which can be found on `$PATH`), like shown in the following example:
{{{
# Path to the OpenSCAD binary
OPENSCAD := /Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD
# Path to the Inkscape binary
INKSCAPE := /opt/local/bin/inkscape
# Path to the Python 2.7 binary
PYTHON := /opt/local/bin/python2.7
}}}
@ -26,7 +42,20 @@ Any file whose name ends in `.svg` may be used from an OpenSCAD file like this:
import("file.dxf");
}}}
The makefile will automatically convert the SVG file to a DXF file.
The makefile will automatically convert the SVG file to a DXF file when building the project. If Inkscape is used to edit the SVG file, multiple layers can be created which can then be imported individually:
{{{
import("file.dxf", "background");
}}}
The DXF export supports all shapes supported by Inkscape (e.g. rectangles, circles, paths, spiro lines, text, ...). Before the object are exported, all objects are converted to paths and combined using the union operation. Then, the resulting paths are converted to line segments which closely follow the curved parts of the path [0]. The resulting line segments are exported to DXF and combined to the original shapes when imported in OpenSCAD. For these transformations to work, the objects need to be placed in Inkscape layers.
OpenSCAD itself does not defined in which unit any numbers are interpreted [1]. Inkscape OTOH allows the used to defined a document wide unit as well as using different units when specifying the size and position of shapes. When exporting the SVG document using Inkscape, all numbers are converted to the unit specified under ''General'' in Inkscape's ''Document Properties'' dialog. These numbers are then used when writing the DXF document and these are the numeric sizes and positions that OpenSCAD will see.
DXF and OpenSCAD both use a right-handed coordinate system (the Y axis runs up when the X-axis runs to the right). While SVG uses a left-handed coordinate system (the Y axis runs down if the same orientation is used). Inkscape, surprisingly also uses a right-handed coordinate system. The DXF export script honors that and places assumes the origin of the document in the lower left corner when exporting the document.
[0]: Controlling how fine curved parts are subdivided currently cannot controlled without editing the DXF export scripts. This is a pending issue.
[1]: Although millimeters seems to be the predominant unit.
=== OpenSCAD files
@ -38,30 +67,25 @@ include <filename>
use <filename>
}}}
Please see the [http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Print_version|manual] for details.
OpenSCAD files may be compiled to STL and used from other OpenSCAD files at the same time. Please see the [http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Print_version|manual] for details and on how to use OpenSCAD in general.
== Generating Source files
This template allows files to be generated automatically. Currently supported for inclusion in the build proess are OpenSCAD and SVG files. This works by editing the `generate_sources.sh` script, which is run by the Makefile was changed.
This template includes support automatically generated source files. Currently supported for inclusion in the build process are OpenSCAD and SVG files. This works by editing the `generate_sources.sh` script.
The script should call the function `generate_file()` once for each file which should be generated. The first argument to the function should be the name of the file to be generated, the remaining arguments a command, which when run should output the file's content to standard output. How this function is called is up to the scrtip and may e.g. be done from a `for` loop or while iterating over a set of other source files.
The script defines a function `generate_file()`, which should be called in the remainder of the script once for each file which should be generated. The first argument to the function is be the name of the file to be generated, the remaining arguments a command, which when run should output the file's content to standard output. How the function `generate_file()` is called is up to the script and may e.g. be done from a `for` loop or while iterating over a set of other source files.
== Compiling
To compile the whole project, run `make` from the directory in which this readme is. This will process all necessary SVG files and produce an STL file for each OpenSCAD source file. Individual files may be created or updated by passing their names to the make command, as ussual.
To compile the whole project, run `make` from the directory in which this readme is. This will generate all sources files, if any, process all SVG files and produce an STL file for each OpenSCAD source file whose name does not start with `_`. Individual files may be created or updated by passing their names to the make command, as usual.
To remove all generated files, run `make clean`.
=== Dependency tracking
OpenSCAD actually does have the ability to produce dependency files which can be read by `make` but does this in a way that makes using them in a project with multiple directories near-impossible. Because of this, dependencies between files are generated from a few assumptions based on simple naming-conventions:
OpenSCAD has the ability to write dependency files which record all files used while producing an STL file. These dependency files can be read by `make`. This ability is used to only recompile necessary files when running make.
* Only files in the `src` directory are compiled.
* Each generated STL file solely depends on the OpenSCAD file with the same name.
* Each generated DXF files solely depends on the SVG files with the same name.
* Each OpenSCAD file depends whose name does not begin with `_` depends on two sets of files:
** All OpenSCAD files in the same directory whose names do begin with `_`.
** All DXF files in the same directory.
This same mechanism is currently not used for converting SVG files referring to other files or for the script used to generate source files. There, if other file are used in the process, the source file tracked by the makefile (the main SVG files or the files `generate_sources.sh` in case of generated sources) needs to be manually updated by running `touch` on the file before calling `make`.

6
src/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
*.dxf
*.stl
*.d
# Generated files
/generated/

View file

@ -1,14 +1,14 @@
import sys, os, xml.etree.ElementTree, shutil
from lib import util
from . import better_dxf_outlines
from . import effect
def _export_dxf(in_path, out_path):
dxf_export = better_dxf_outlines.MyEffect()
dxf_export = effect.DXFExportEffect()
dxf_export.affect(args = [in_path], output = False)
with open(out_path, 'w') as file:
file.write(dxf_export.dxf)
dxf_export.write(file)
def _get_inkscape_layer_count(svg_path):
@ -76,4 +76,10 @@ def main(in_path, out_path):
_export_dxf(temp_svg_path, out_path)
main(*sys.argv[1:])
try:
main(*sys.argv[1:])
except util.UserError as e:
print 'Error:', e
sys.exit(1)
except KeyboardInterrupt:
sys.exit(2)

View file

@ -1,123 +0,0 @@
#!/usr/bin/env python
'''
Copyright (C) 2005,2007 Aaron Spike, aaron@ekips.org
- template dxf_outlines.dxf added Feb 2008 by Alvin Penner, penner@vaxxine.com
- layers, transformation, flattening added April 2008 by Bob Cook, bob@bobcookdev.com
- bug fix for xpath() calls added February 2009 by Bob Cook, bob@bobcookdev.com
- max value of 10 on path flattening, August 2011 by Bob Cook, bob@bobcookdev.com
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
'''
import inkex, simplepath, simpletransform, cubicsuperpath, cspsubdiv, dxf_templates, re
class MyEffect(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.dxf = ''
self.handle = 255
self.flatness = 0.1
def output(self):
print self.dxf
def dxf_add(self, str):
self.dxf += str
def dxf_insert_code(self, code, value):
self.dxf += code + "\n" + value + "\n"
def dxf_line(self,layer,csp):
self.dxf_insert_code( '0', 'LINE' )
self.dxf_insert_code( '8', layer )
self.dxf_insert_code( '62', '4' )
self.dxf_insert_code( '5', '%x' % self.handle )
self.dxf_insert_code( '100', 'AcDbEntity' )
self.dxf_insert_code( '100', 'AcDbLine' )
self.dxf_insert_code( '10', '%f' % csp[0][0] )
self.dxf_insert_code( '20', '%f' % csp[0][1] )
self.dxf_insert_code( '30', '0.0' )
self.dxf_insert_code( '11', '%f' % csp[1][0] )
self.dxf_insert_code( '21', '%f' % csp[1][1] )
self.dxf_insert_code( '31', '0.0' )
def dxf_point(self,layer,x,y):
self.dxf_insert_code( '0', 'POINT' )
self.dxf_insert_code( '8', layer )
self.dxf_insert_code( '62', '4' )
self.dxf_insert_code( '5', '%x' % self.handle )
self.dxf_insert_code( '100', 'AcDbEntity' )
self.dxf_insert_code( '100', 'AcDbPoint' )
self.dxf_insert_code( '10', '%f' % x )
self.dxf_insert_code( '20', '%f' % y )
self.dxf_insert_code( '30', '0.0' )
def dxf_path_to_lines(self,layer,p):
f = self.flatness
is_flat = 0
while is_flat < 1:
if f > 10:
break
try:
cspsubdiv.cspsubdiv(p, f)
is_flat = 1
except:
f += 0.1
for sub in p:
for i in range(len(sub)-1):
self.handle += 1
s = sub[i]
e = sub[i+1]
self.dxf_line(layer,[s[1],e[1]])
def dxf_path_to_point(self,layer,p):
bbox = simpletransform.roughBBox(p)
x = (bbox[0] + bbox[1]) / 2
y = (bbox[2] + bbox[3]) / 2
self.dxf_point(layer,x,y)
def effect(self):
self.dxf_insert_code( '999', 'Inkscape export via "Better DXF Output" (bob@bobcookdev.com)' )
self.dxf_add( dxf_templates.r14_header )
scale = 25.4/90.0
h = inkex.unittouu(self.document.getroot().xpath('@height',namespaces=inkex.NSS)[0])
path = '//svg:path'
for node in self.document.getroot().xpath(path,namespaces=inkex.NSS):
layer = node.getparent().get(inkex.addNS('label','inkscape'))
if layer == None:
layer = 'Layer 1'
d = node.get('d')
p = cubicsuperpath.parsePath(d)
t = node.get('transform')
if t != None:
m = simpletransform.parseTransform(t)
simpletransform.applyTransformToPath(m,p)
m = [[scale,0,0],[0,-scale,h*scale]]
simpletransform.applyTransformToPath(m,p)
if re.search('drill$',layer,re.I) == None:
self.dxf_path_to_lines(layer,p)
else:
self.dxf_path_to_point(layer,p)
self.dxf_add( dxf_templates.r14_footer )

View file

@ -243,7 +243,6 @@ def beziertatlength(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)), l = 0.5, toleranc
bezierlength = bezierlengthSimpson
if __name__ == '__main__':
import timing
#print linebezierintersect(((,),(,)),((,),(,),(,),(,)))
#print linebezierintersect(((0,1),(0,-1)),((-1,0),(-.5,0),(.5,0),(1,0)))
tol = 0.00000001
@ -271,4 +270,4 @@ if __name__ == '__main__':
print beziertatlength(curve,0.5)
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99

View file

@ -46,8 +46,8 @@ def ArcToPath(p1,params):
rx,ry,teta,longflag,sweepflag,x2,y2=params[:]
teta = teta*pi/180.0
B=[x2,y2]
if rx==0 or ry==0:
return([[A,A,A],[B,B,B]])
if rx==0 or ry==0 or A==B:
return([[A[:],A[:],A[:]],[B[:],B[:],B[:]]])
mat=matprod((rotmat(teta),[[1/rx,0],[0,1/ry]],rotmat(-teta)))
applymat(mat, A)
applymat(mat, B)
@ -166,4 +166,4 @@ def formatPath(p):
return simplepath.formatPath(unCubicSuperPath(p))
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99

View file

@ -0,0 +1,62 @@
0
ENDSEC
0
SECTION
2
OBJECTS
0
DICTIONARY
5
C
330
0
100
AcDbDictionary
3
ACAD_GROUP
350
D
3
ACAD_MLINESTYLE
350
17
0
DICTIONARY
5
D
330
C
100
AcDbDictionary
0
DICTIONARY
5
1A
330
C
100
AcDbDictionary
0
DICTIONARY
5
17
330
C
100
AcDbDictionary
3
STANDARD
350
18
0
DICTIONARY
5
19
330
C
100
AcDbDictionary
0
ENDSEC
0
EOF

View file

@ -0,0 +1,580 @@
0
SECTION
2
HEADER
9
$ACADVER
1
AC1014
9
$HANDSEED
5
FFFF
0
ENDSEC
0
SECTION
2
TABLES
0
TABLE
2
VPORT
5
8
330
0
100
AcDbSymbolTable
70
4
0
VPORT
5
2E
330
8
100
AcDbSymbolTableRecord
100
AcDbViewportTableRecord
2
*ACTIVE
70
0
10
0.0
20
0.0
11
1.0
21
1.0
12
4.25
22
5.5
13
0.0
23
0.0
14
10.0
24
10.0
15
10.0
25
10.0
16
0.0
26
0.0
36
1.0
17
0.0
27
0.0
37
0.0
40
11
41
1.24
42
50.0
43
0.0
44
0.0
50
0.0
51
0.0
71
0
72
100
73
1
74
3
75
0
76
0
77
0
78
0
0
ENDTAB
0
TABLE
2
LTYPE
5
5
330
0
100
AcDbSymbolTable
70
1
0
LTYPE
5
14
330
5
100
AcDbSymbolTableRecord
100
AcDbLinetypeTableRecord
2
BYBLOCK
70
0
3
72
65
73
0
40
0.0
0
LTYPE
5
15
330
5
100
AcDbSymbolTableRecord
100
AcDbLinetypeTableRecord
2
BYLAYER
70
0
3
72
65
73
0
40
0.0
0
LTYPE
5
16
330
5
100
AcDbSymbolTableRecord
100
AcDbLinetypeTableRecord
2
CONTINUOUS
70
0
3
Solid line
72
65
73
0
40
0.0
0
ENDTAB
0
TABLE
2
LAYER
5
2
330
0
100
AcDbSymbolTable
70
1
0
LAYER
5
10
330
2
100
AcDbSymbolTableRecord
100
AcDbLayerTableRecord
2
0
70
0
62
7
6
CONTINUOUS
0
ENDTAB
0
TABLE
2
STYLE
5
3
330
0
100
AcDbSymbolTable
70
1
0
STYLE
5
11
330
3
100
AcDbSymbolTableRecord
100
AcDbTextStyleTableRecord
2
STANDARD
70
0
40
0.0
41
1.0
50
0.0
71
0
42
2.5
3
txt
4
0
ENDTAB
0
TABLE
2
VIEW
5
6
330
0
100
AcDbSymbolTable
70
0
0
ENDTAB
0
TABLE
2
UCS
5
7
330
0
100
AcDbSymbolTable
70
0
0
ENDTAB
0
TABLE
2
APPID
5
9
330
0
100
AcDbSymbolTable
70
2
0
APPID
5
12
330
9
100
AcDbSymbolTableRecord
100
AcDbRegAppTableRecord
2
ACAD
70
0
0
ENDTAB
0
TABLE
2
DIMSTYLE
5
A
330
0
100
AcDbSymbolTable
70
1
0
DIMSTYLE
105
27
330
A
100
AcDbSymbolTableRecord
100
AcDbDimStyleTableRecord
2
ISO-25
70
0
3
4
5
6
7
40
1.0
41
2.5
42
0.625
43
3.75
44
1.25
45
0.0
46
0.0
47
0.0
48
0.0
140
2.5
141
2.5
142
0.0
143
0.03937007874016
144
1.0
145
0.0
146
1.0
147
0.625
71
0
72
0
73
0
74
0
75
0
76
0
77
1
78
8
170
0
171
3
172
1
173
0
174
0
175
0
176
0
177
0
178
0
270
2
271
2
272
2
273
2
274
3
340
11
275
0
280
0
281
0
282
0
283
0
284
8
285
0
286
0
287
3
288
0
0
ENDTAB
0
TABLE
2
BLOCK_RECORD
5
1
330
0
100
AcDbSymbolTable
70
1
0
BLOCK_RECORD
5
1F
330
1
100
AcDbSymbolTableRecord
100
AcDbBlockTableRecord
2
*MODEL_SPACE
0
BLOCK_RECORD
5
1B
330
1
100
AcDbSymbolTableRecord
100
AcDbBlockTableRecord
2
*PAPER_SPACE
0
ENDTAB
0
ENDSEC
0
SECTION
2
BLOCKS
0
BLOCK
5
20
330
1F
100
AcDbEntity
8
0
100
AcDbBlockBegin
2
*MODEL_SPACE
70
0
10
0.0
20
0.0
30
0.0
3
*MODEL_SPACE
1
0
ENDBLK
5
21
330
1F
100
AcDbEntity
8
0
100
AcDbBlockEnd
0
BLOCK
5
1C
330
1B
100
AcDbEntity
67
1
8
0
100
AcDbBlockBegin
2
*PAPER_SPACE
1
0
ENDBLK
5
1D
330
1B
100
AcDbEntity
67
1
8
0
100
AcDbBlockEnd
0
ENDSEC
0
SECTION
2
ENTITIES

View file

@ -1,645 +0,0 @@
r14_header = ''' 0
SECTION
2
HEADER
9
$ACADVER
1
AC1014
9
$HANDSEED
5
FFFF
0
ENDSEC
0
SECTION
2
TABLES
0
TABLE
2
VPORT
5
8
330
0
100
AcDbSymbolTable
70
4
0
VPORT
5
2E
330
8
100
AcDbSymbolTableRecord
100
AcDbViewportTableRecord
2
*ACTIVE
70
0
10
0.0
20
0.0
11
1.0
21
1.0
12
4.25
22
5.5
13
0.0
23
0.0
14
10.0
24
10.0
15
10.0
25
10.0
16
0.0
26
0.0
36
1.0
17
0.0
27
0.0
37
0.0
40
11
41
1.24
42
50.0
43
0.0
44
0.0
50
0.0
51
0.0
71
0
72
100
73
1
74
3
75
0
76
0
77
0
78
0
0
ENDTAB
0
TABLE
2
LTYPE
5
5
330
0
100
AcDbSymbolTable
70
1
0
LTYPE
5
14
330
5
100
AcDbSymbolTableRecord
100
AcDbLinetypeTableRecord
2
BYBLOCK
70
0
3
72
65
73
0
40
0.0
0
LTYPE
5
15
330
5
100
AcDbSymbolTableRecord
100
AcDbLinetypeTableRecord
2
BYLAYER
70
0
3
72
65
73
0
40
0.0
0
LTYPE
5
16
330
5
100
AcDbSymbolTableRecord
100
AcDbLinetypeTableRecord
2
CONTINUOUS
70
0
3
Solid line
72
65
73
0
40
0.0
0
ENDTAB
0
TABLE
2
LAYER
5
2
330
0
100
AcDbSymbolTable
70
1
0
LAYER
5
10
330
2
100
AcDbSymbolTableRecord
100
AcDbLayerTableRecord
2
0
70
0
62
7
6
CONTINUOUS
0
ENDTAB
0
TABLE
2
STYLE
5
3
330
0
100
AcDbSymbolTable
70
1
0
STYLE
5
11
330
3
100
AcDbSymbolTableRecord
100
AcDbTextStyleTableRecord
2
STANDARD
70
0
40
0.0
41
1.0
50
0.0
71
0
42
2.5
3
txt
4
0
ENDTAB
0
TABLE
2
VIEW
5
6
330
0
100
AcDbSymbolTable
70
0
0
ENDTAB
0
TABLE
2
UCS
5
7
330
0
100
AcDbSymbolTable
70
0
0
ENDTAB
0
TABLE
2
APPID
5
9
330
0
100
AcDbSymbolTable
70
2
0
APPID
5
12
330
9
100
AcDbSymbolTableRecord
100
AcDbRegAppTableRecord
2
ACAD
70
0
0
ENDTAB
0
TABLE
2
DIMSTYLE
5
A
330
0
100
AcDbSymbolTable
70
1
0
DIMSTYLE
105
27
330
A
100
AcDbSymbolTableRecord
100
AcDbDimStyleTableRecord
2
ISO-25
70
0
3
4
5
6
7
40
1.0
41
2.5
42
0.625
43
3.75
44
1.25
45
0.0
46
0.0
47
0.0
48
0.0
140
2.5
141
2.5
142
0.0
143
0.03937007874016
144
1.0
145
0.0
146
1.0
147
0.625
71
0
72
0
73
0
74
0
75
0
76
0
77
1
78
8
170
0
171
3
172
1
173
0
174
0
175
0
176
0
177
0
178
0
270
2
271
2
272
2
273
2
274
3
340
11
275
0
280
0
281
0
282
0
283
0
284
8
285
0
286
0
287
3
288
0
0
ENDTAB
0
TABLE
2
BLOCK_RECORD
5
1
330
0
100
AcDbSymbolTable
70
1
0
BLOCK_RECORD
5
1F
330
1
100
AcDbSymbolTableRecord
100
AcDbBlockTableRecord
2
*MODEL_SPACE
0
BLOCK_RECORD
5
1B
330
1
100
AcDbSymbolTableRecord
100
AcDbBlockTableRecord
2
*PAPER_SPACE
0
ENDTAB
0
ENDSEC
0
SECTION
2
BLOCKS
0
BLOCK
5
20
330
1F
100
AcDbEntity
8
0
100
AcDbBlockBegin
2
*MODEL_SPACE
70
0
10
0.0
20
0.0
30
0.0
3
*MODEL_SPACE
1
0
ENDBLK
5
21
330
1F
100
AcDbEntity
8
0
100
AcDbBlockEnd
0
BLOCK
5
1C
330
1B
100
AcDbEntity
67
1
8
0
100
AcDbBlockBegin
2
*PAPER_SPACE
1
0
ENDBLK
5
1D
330
1B
100
AcDbEntity
67
1
8
0
100
AcDbBlockEnd
0
ENDSEC
0
SECTION
2
ENTITIES
'''
r14_footer = ''' 0
ENDSEC
0
SECTION
2
OBJECTS
0
DICTIONARY
5
C
330
0
100
AcDbDictionary
3
ACAD_GROUP
350
D
3
ACAD_MLINESTYLE
350
17
0
DICTIONARY
5
D
330
C
100
AcDbDictionary
0
DICTIONARY
5
1A
330
C
100
AcDbDictionary
0
DICTIONARY
5
17
330
C
100
AcDbDictionary
3
STANDARD
350
18
0
DICTIONARY
5
19
330
C
100
AcDbDictionary
0
ENDSEC
0
EOF'''

View file

@ -0,0 +1,175 @@
"""
Based on code from Aaron Spike. See http://www.bobcookdev.com/inkscape/inkscape-dxf.html
"""
import pkgutil, re
from . import inkex, simpletransform, cubicsuperpath, cspsubdiv
def _get_unit_factors_map():
# Fluctuates somewhat between Inkscape releases.
pixels_per_inch = 96.
pixels_per_mm = pixels_per_inch / 25.4
return {
'px': 1.0,
'mm': pixels_per_mm,
'cm': pixels_per_mm * 10,
'm' : pixels_per_mm * 1e3,
'km': pixels_per_mm * 1e6,
'pt': pixels_per_inch / 72,
'pc': pixels_per_inch / 6,
'in': pixels_per_inch,
'ft': pixels_per_inch * 12,
'yd': pixels_per_inch * 36 }
class DXFExportEffect(inkex.Effect):
_unit_factors = _get_unit_factors_map()
def __init__(self):
inkex.Effect.__init__(self)
self._dxf_instructions = []
self._handle = 255
self._flatness = 0.1
def _get_user_unit(self):
"""
Return the size in pixels of the unit used for measures without an explicit unit.
"""
document_height = self._measure_to_pixels(self._get_document_height_attr())
view_box_attr = self.document.getroot().get('viewBox')
if view_box_attr:
_, _, _, view_box_height = map(float, view_box_attr.split())
else:
view_box_height = document_height
return document_height / view_box_height
def _get_document_unit(self):
"""
Return the size in pixels that the user is working with in Inkscape.
"""
inkscape_unit_attrs = self.document.getroot().xpath('./sodipodi:namedview/@inkscape:document-units', namespaces = inkex.NSS)
if inkscape_unit_attrs:
unit = inkscape_unit_attrs[0]
else:
_, unit = self._parse_measure(self._get_document_height_attr())
return self._get_unit_factor(unit)
def _get_document_height_attr(self):
return self.document.getroot().xpath('@height', namespaces = inkex.NSS)[0]
def _add_instruction(self, code, value):
self._dxf_instructions.append((code, str(value)))
def _add_dxf_line(self, layer, csp):
self._add_instruction(0, 'LINE')
self._add_instruction(8, layer)
self._add_instruction(62, 4)
self._add_instruction(5, '{:x}'.format(self._handle))
self._add_instruction(100, 'AcDbEntity')
self._add_instruction(100, 'AcDbLine')
self._add_instruction(10, repr(csp[0][0]))
self._add_instruction(20, repr(csp[0][1]))
self._add_instruction(30, 0.0)
self._add_instruction(11, repr(csp[1][0]))
self._add_instruction(21, repr(csp[1][1]))
self._add_instruction(31, 0.0)
def _add_dxf_path(self, layer, path):
cspsubdiv.cspsubdiv(path, self._flatness)
for sub in path:
for i in range(len(sub) - 1):
self._handle += 1
s = sub[i]
e = sub[i + 1]
self._add_dxf_line(layer, [s[1], e[1]])
def _add_dxf_shape(self, node, document_transform, element_transform):
layer = self._get_inkscape_layer(node)
path = cubicsuperpath.parsePath(node.get('d'))
transform = simpletransform.composeTransform(
document_transform,
simpletransform.composeParents(node, element_transform))
simpletransform.applyTransformToPath(transform, path)
self._add_dxf_path(layer, path)
def effect(self):
user_unit = self._get_user_unit()
document_unit = self._get_document_unit()
height = self._measure_to_pixels(self._get_document_height_attr())
document_transform = simpletransform.composeTransform(
[[1 / document_unit, 0, 0], [0, 1 / document_unit, 0]],
[[1, 0, 0], [0, -1, height]])
element_transform = [[user_unit, 0, 0], [0, user_unit, 0]]
for node in self.document.getroot().xpath('//svg:path', namespaces = inkex.NSS):
self._add_dxf_shape(node, document_transform, element_transform)
def write(self, file):
file.write(pkgutil.get_data(__name__, 'dxf_header.txt'))
for code, value in self._dxf_instructions:
print >> file, code
print >> file, value
file.write(pkgutil.get_data(__name__, 'dxf_footer.txt'))
@classmethod
def _parse_measure(cls, string):
value_match = re.match(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)', string)
unit_match = re.search('(%s)$' % '|'.join(cls._unit_factors.keys()), string)
value = float(string[value_match.start():value_match.end()])
if unit_match:
unit = string[unit_match.start():unit_match.end()]
else:
unit = None
return value, unit
@classmethod
def _measure_to_pixels(cls, string, default_unit_factor = None):
"""
Parse a string containing a measure and return it's value converted to pixels. If the measure has no unit, it will be assumed that the unit has the size of the specified number of pixels.
"""
value, unit = cls._parse_measure(string)
return value * cls._get_unit_factor(unit, default_unit_factor)
@classmethod
def _get_inkscape_layer(cls, node):
while node is not None:
layer = node.get(inkex.addNS('label', 'inkscape'))
if layer is not None:
return layer
node = node.getparent()
return ''
@classmethod
def _get_unit_factor(cls, unit, default = None):
if unit is None:
if default is None:
default = 1
return default
else:
return cls._unit_factors[unit]

View file

@ -138,4 +138,4 @@ def dot(s1, s2):
return s1.delta_x() * s2.delta_x() + s1.delta_y() * s2.delta_y()
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99

View file

@ -1,9 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
inkex.py
A helper module for creating Inkscape extensions
Copyright (C) 2005,2007 Aaron Spike, aaron@ekips.org
Copyright (C) 2005,2010 Aaron Spike <aaron@ekips.org> and contributors
Contributors:
Aurélio A. Heckert <aurium(a)gmail.com>
Bulia Byak <buliabyak@users.sf.net>
Nicolas Dufour, nicoduf@yahoo.fr
Peter J. R. Moulder <pjrm@users.sourceforge.net>
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
@ -19,10 +26,14 @@ 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
"""
import sys, copy, optparse, random, re
import copy
import gettext
import optparse
import os
import random
import re
import sys
from math import *
_ = gettext.gettext
#a dictionary of all of the xmlns prefixes in a standard inkscape doc
NSS = {
@ -37,34 +48,35 @@ u'xlink' :u'http://www.w3.org/1999/xlink',
u'xml' :u'http://www.w3.org/XML/1998/namespace'
}
#a dictionary of unit to user unit conversion factors
uuconv = {'in':90.0, 'pt':1.25, 'px':1, 'mm':3.5433070866, 'cm':35.433070866, 'm':3543.3070866,
'km':3543307.0866, 'pc':15.0, 'yd':3240 , 'ft':1080}
def unittouu(string):
'''Returns userunits given a string representation of units in another system'''
unit = re.compile('(%s)$' % '|'.join(uuconv.keys()))
param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
p = param.match(string)
u = unit.search(string)
if p:
retval = float(p.string[p.start():p.end()])
else:
retval = 0.0
if u:
def localize():
domain = 'inkscape'
if sys.platform.startswith('win'):
import locale
current_locale, encoding = locale.getdefaultlocale()
os.environ['LANG'] = current_locale
try:
return retval * uuconv[u.string[u.start():u.end()]]
localdir = os.environ['INKSCAPE_LOCALEDIR'];
trans = gettext.translation(domain, localdir, [current_locale], fallback=True)
except KeyError:
pass
return retval
def uutounit(val, unit):
return val/uuconv[unit]
try:
from lxml import etree
except:
sys.exit(_('The fantastic lxml wrapper for libxml2 is required by inkex.py and therefore this extension. Please download and install the latest version from http://cheeseshop.python.org/pypi/lxml/, or install it through your package manager by a command like: sudo apt-get install python-lxml'))
trans = gettext.translation(domain, fallback=True)
elif sys.platform.startswith('darwin'):
try:
localdir = os.environ['INKSCAPE_LOCALEDIR'];
trans = gettext.translation(domain, localdir, fallback=True)
except KeyError:
try:
localdir = os.environ['PACKAGE_LOCALE_DIR'];
trans = gettext.translation(domain, localdir, fallback=True)
except KeyError:
trans = gettext.translation(domain, fallback=True)
else:
try:
localdir = os.environ['PACKAGE_LOCALE_DIR'];
trans = gettext.translation(domain, localdir, fallback=True)
except KeyError:
trans = gettext.translation(domain, fallback=True)
#sys.stderr.write(str(localdir) + "\n")
trans.install()
def debug(what):
sys.stderr.write(str(what) + "\n")
@ -79,13 +91,31 @@ def errormsg(msg):
Note that this should always be combined with translation:
import gettext
_ = gettext.gettext
import inkex
inkex.localize()
...
inkex.errormsg(_("This extension requires two selected paths."))
"""
sys.stderr.write((unicode(msg) + "\n").encode("UTF-8"))
if isinstance(msg, unicode):
sys.stderr.write(msg.encode("UTF-8") + "\n")
else:
sys.stderr.write((unicode(msg, "utf-8", errors='replace') + "\n").encode("UTF-8"))
def are_near_relative(a, b, eps):
if (a-b <= a*eps) and (a-b >= -a*eps):
return True
else:
return False
# third party library
try:
from lxml import etree
except Exception, e:
localize()
errormsg(_("The fantastic lxml wrapper for libxml2 is required by inkex.py and therefore this extension. Please download and install the latest version from http://cheeseshop.python.org/pypi/lxml/, or install it through your package manager by a command like: sudo apt-get install python-lxml\n\nTechnical details:\n%s" % (e,)))
sys.exit()
def check_inkbool(option, opt, value):
if str(value).capitalize() == 'True':
return True
@ -128,20 +158,37 @@ class Effect:
"""Collect command line arguments"""
self.options, self.args = self.OptionParser.parse_args(args)
def parse(self,file=None):
def parse(self, filename=None):
"""Parse document in specified file or on stdin"""
try:
# First try to open the file from the function argument
if filename != None:
try:
stream = open(file,'r')
except:
stream = open(self.svg_file,'r')
except:
stream = open(filename, 'r')
except Exception:
errormsg(_("Unable to open specified file: %s") % filename)
sys.exit()
# If it wasn't specified, try to open the file specified as
# an object member
elif self.svg_file != None:
try:
stream = open(self.svg_file, 'r')
except Exception:
errormsg(_("Unable to open object member file: %s") % self.svg_file)
sys.exit()
# Finally, if the filename was not specified anywhere, use
# standard input stream
else:
stream = sys.stdin
p = etree.XMLParser(huge_tree=True)
self.document = etree.parse(stream, parser=p)
self.original_document = copy.deepcopy(self.document)
stream.close()
# defines view_center in terms of document units
def getposinlayer(self):
#defaults
self.current_layer = self.document.getroot()
@ -156,10 +203,10 @@ class Effect:
xattr = self.document.xpath('//sodipodi:namedview/@inkscape:cx', namespaces=NSS)
yattr = self.document.xpath('//sodipodi:namedview/@inkscape:cy', namespaces=NSS)
doc_height = unittouu(self.document.getroot().get('height'))
if xattr and yattr:
x = xattr[0]
y = yattr[0]
x = self.unittouu( xattr[0] + 'px' )
y = self.unittouu( yattr[0] + 'px')
doc_height = self.unittouu(self.document.getroot().get('height'))
if x and y:
self.view_center = (float(x), doc_height - float(y)) # FIXME: y-coordinate flip, eliminate it when it's gone in Inkscape
@ -236,6 +283,88 @@ class Effect:
errormsg(_("No matching node for expression: %s") % path)
retval = None
return retval
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
#a dictionary of unit to user unit conversion factors
__uuconv = {'in':96.0, 'pt':1.33333333333, 'px':1.0, 'mm':3.77952755913, 'cm':37.7952755913,
'm':3779.52755913, 'km':3779527.55913, 'pc':16.0, 'yd':3456.0 , 'ft':1152.0}
# Function returns the unit used for the values in SVG.
# For lack of an attribute in SVG that explicitly defines what units are used for SVG coordinates,
# try to calculate the unit from the SVG width and SVG viewbox.
# Defaults to 'px' units.
def getDocumentUnit(self):
svgunit = 'px' #default to pixels
svgwidth = self.document.getroot().get('width')
viewboxstr = self.document.getroot().get('viewBox')
if viewboxstr:
unitmatch = re.compile('(%s)$' % '|'.join(self.__uuconv.keys()))
param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
p = param.match(svgwidth)
u = unitmatch.search(svgwidth)
width = 100 #default
viewboxwidth = 100 #default
svgwidthunit = 'px' #default assume 'px' unit
if p:
width = float(p.string[p.start():p.end()])
else:
errormsg(_("SVG Width not set correctly! Assuming width = 100"))
if u:
svgwidthunit = u.string[u.start():u.end()]
viewboxnumbers = []
for t in viewboxstr.split():
try:
viewboxnumbers.append(float(t))
except ValueError:
pass
if len(viewboxnumbers) == 4: #check for correct number of numbers
viewboxwidth = viewboxnumbers[2]
svgunitfactor = self.__uuconv[svgwidthunit] * width / viewboxwidth
# try to find the svgunitfactor in the list of units known. If we don't find something, ...
eps = 0.01 #allow 1% error in factor
for key in self.__uuconv:
if are_near_relative(self.__uuconv[key], svgunitfactor, eps):
#found match!
svgunit = key;
return svgunit
def unittouu(self, string):
'''Returns userunits given a string representation of units in another system'''
unit = re.compile('(%s)$' % '|'.join(self.__uuconv.keys()))
param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
p = param.match(string)
u = unit.search(string)
if p:
retval = float(p.string[p.start():p.end()])
else:
retval = 0.0
if u:
try:
return retval * (self.__uuconv[u.string[u.start():u.end()]] / self.__uuconv[self.getDocumentUnit()])
except KeyError:
pass
else: # default assume 'px' unit
return retval / self.__uuconv[self.getDocumentUnit()]
return retval
def uutounit(self, val, unit):
return val / (self.__uuconv[unit] / self.__uuconv[self.getDocumentUnit()])
def addDocumentUnit(self, value):
''' Add document unit when no unit is specified in the string '''
try:
float(value)
return value + self.getDocumentUnit()
except ValueError:
return value
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99

View file

@ -209,4 +209,4 @@ def rotatePath(p, a, cx = 0, cy = 0):
params[i + 1] = (r * math.sin(theta)) + cy
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99

View file

@ -1,244 +0,0 @@
#!/usr/bin/env python
"""
simplestyle.py
Two simple functions for working with inline css
and some color handling on top.
Copyright (C) 2005 Aaron Spike, aaron@ekips.org
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
"""
svgcolors={
'aliceblue':'#f0f8ff',
'antiquewhite':'#faebd7',
'aqua':'#00ffff',
'aquamarine':'#7fffd4',
'azure':'#f0ffff',
'beige':'#f5f5dc',
'bisque':'#ffe4c4',
'black':'#000000',
'blanchedalmond':'#ffebcd',
'blue':'#0000ff',
'blueviolet':'#8a2be2',
'brown':'#a52a2a',
'burlywood':'#deb887',
'cadetblue':'#5f9ea0',
'chartreuse':'#7fff00',
'chocolate':'#d2691e',
'coral':'#ff7f50',
'cornflowerblue':'#6495ed',
'cornsilk':'#fff8dc',
'crimson':'#dc143c',
'cyan':'#00ffff',
'darkblue':'#00008b',
'darkcyan':'#008b8b',
'darkgoldenrod':'#b8860b',
'darkgray':'#a9a9a9',
'darkgreen':'#006400',
'darkgrey':'#a9a9a9',
'darkkhaki':'#bdb76b',
'darkmagenta':'#8b008b',
'darkolivegreen':'#556b2f',
'darkorange':'#ff8c00',
'darkorchid':'#9932cc',
'darkred':'#8b0000',
'darksalmon':'#e9967a',
'darkseagreen':'#8fbc8f',
'darkslateblue':'#483d8b',
'darkslategray':'#2f4f4f',
'darkslategrey':'#2f4f4f',
'darkturquoise':'#00ced1',
'darkviolet':'#9400d3',
'deeppink':'#ff1493',
'deepskyblue':'#00bfff',
'dimgray':'#696969',
'dimgrey':'#696969',
'dodgerblue':'#1e90ff',
'firebrick':'#b22222',
'floralwhite':'#fffaf0',
'forestgreen':'#228b22',
'fuchsia':'#ff00ff',
'gainsboro':'#dcdcdc',
'ghostwhite':'#f8f8ff',
'gold':'#ffd700',
'goldenrod':'#daa520',
'gray':'#808080',
'grey':'#808080',
'green':'#008000',
'greenyellow':'#adff2f',
'honeydew':'#f0fff0',
'hotpink':'#ff69b4',
'indianred':'#cd5c5c',
'indigo':'#4b0082',
'ivory':'#fffff0',
'khaki':'#f0e68c',
'lavender':'#e6e6fa',
'lavenderblush':'#fff0f5',
'lawngreen':'#7cfc00',
'lemonchiffon':'#fffacd',
'lightblue':'#add8e6',
'lightcoral':'#f08080',
'lightcyan':'#e0ffff',
'lightgoldenrodyellow':'#fafad2',
'lightgray':'#d3d3d3',
'lightgreen':'#90ee90',
'lightgrey':'#d3d3d3',
'lightpink':'#ffb6c1',
'lightsalmon':'#ffa07a',
'lightseagreen':'#20b2aa',
'lightskyblue':'#87cefa',
'lightslategray':'#778899',
'lightslategrey':'#778899',
'lightsteelblue':'#b0c4de',
'lightyellow':'#ffffe0',
'lime':'#00ff00',
'limegreen':'#32cd32',
'linen':'#faf0e6',
'magenta':'#ff00ff',
'maroon':'#800000',
'mediumaquamarine':'#66cdaa',
'mediumblue':'#0000cd',
'mediumorchid':'#ba55d3',
'mediumpurple':'#9370db',
'mediumseagreen':'#3cb371',
'mediumslateblue':'#7b68ee',
'mediumspringgreen':'#00fa9a',
'mediumturquoise':'#48d1cc',
'mediumvioletred':'#c71585',
'midnightblue':'#191970',
'mintcream':'#f5fffa',
'mistyrose':'#ffe4e1',
'moccasin':'#ffe4b5',
'navajowhite':'#ffdead',
'navy':'#000080',
'oldlace':'#fdf5e6',
'olive':'#808000',
'olivedrab':'#6b8e23',
'orange':'#ffa500',
'orangered':'#ff4500',
'orchid':'#da70d6',
'palegoldenrod':'#eee8aa',
'palegreen':'#98fb98',
'paleturquoise':'#afeeee',
'palevioletred':'#db7093',
'papayawhip':'#ffefd5',
'peachpuff':'#ffdab9',
'peru':'#cd853f',
'pink':'#ffc0cb',
'plum':'#dda0dd',
'powderblue':'#b0e0e6',
'purple':'#800080',
'red':'#ff0000',
'rosybrown':'#bc8f8f',
'royalblue':'#4169e1',
'saddlebrown':'#8b4513',
'salmon':'#fa8072',
'sandybrown':'#f4a460',
'seagreen':'#2e8b57',
'seashell':'#fff5ee',
'sienna':'#a0522d',
'silver':'#c0c0c0',
'skyblue':'#87ceeb',
'slateblue':'#6a5acd',
'slategray':'#708090',
'slategrey':'#708090',
'snow':'#fffafa',
'springgreen':'#00ff7f',
'steelblue':'#4682b4',
'tan':'#d2b48c',
'teal':'#008080',
'thistle':'#d8bfd8',
'tomato':'#ff6347',
'turquoise':'#40e0d0',
'violet':'#ee82ee',
'wheat':'#f5deb3',
'white':'#ffffff',
'whitesmoke':'#f5f5f5',
'yellow':'#ffff00',
'yellowgreen':'#9acd32'
}
def parseStyle(s):
"""Create a dictionary from the value of an inline style attribute"""
if s is None:
return {}
else:
return dict([[x.strip() for x in i.split(":")] for i in s.split(";") if len(i.strip())])
def formatStyle(a):
"""Format an inline style attribute from a dictionary"""
return ";".join([att+":"+str(val) for att,val in a.iteritems()])
def isColor(c):
"""Determine if its a color we can use. If not, leave it unchanged."""
if c.startswith('#') and (len(c)==4 or len(c)==7):
return True
if c.lower() in svgcolors.keys():
return True
#might be "none" or some undefined color constant or rgb()
#however, rgb() shouldnt occur at this point
return False
def parseColor(c):
"""Creates a rgb int array"""
tmp = svgcolors.get(c.lower())
if tmp is not None:
c = tmp
elif c.startswith('#') and len(c)==4:
c='#'+c[1:2]+c[1:2]+c[2:3]+c[2:3]+c[3:]+c[3:]
elif c.startswith('rgb('):
# remove the rgb(...) stuff
tmp = c.strip()[4:-1]
numbers = [number.strip() for number in tmp.split(',')]
converted_numbers = []
if len(numbers) == 3:
for num in numbers:
if num.endswith(r'%'):
converted_numbers.append(int(float(num[0:-1])*255/100))
else:
converted_numbers.append(int(num))
return tuple(converted_numbers)
else:
return (0,0,0)
try:
r=int(c[1:3],16)
g=int(c[3:5],16)
b=int(c[5:],16)
except:
# unknown color ...
# Return a default color. Maybe not the best thing to do but probably
# better than raising an exception.
return(0,0,0)
return (r,g,b)
def formatColoria(a):
"""int array to #rrggbb"""
return '#%02x%02x%02x' % (a[0],a[1],a[2])
def formatColorfa(a):
"""float array to #rrggbb"""
return '#%02x%02x%02x' % (int(round(a[0]*255)),int(round(a[1]*255)),int(round(a[2]*255)))
def formatColor3i(r,g,b):
"""3 ints to #rrggbb"""
return '#%02x%02x%02x' % (r,g,b)
def formatColor3f(r,g,b):
"""3 floats to #rrggbb"""
return '#%02x%02x%02x' % (int(round(r*255)),int(round(g*255)),int(round(b*255)))
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99

View file

@ -21,8 +21,8 @@ barraud@math.univ-lille1.fr
This code defines several functions to make handling of transform
attribute easier.
'''
import inkex, cubicsuperpath, bezmisc, simplestyle
import copy, math, re
import inkex, cubicsuperpath
import math, re
def parseTransform(transf,mat=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]):
if transf=="" or transf==None:

View file

@ -1,6 +1,10 @@
import contextlib, subprocess, tempfile, shutil, re, os
class UserError(Exception):
pass
@contextlib.contextmanager
def TemporaryDirectory():
dir = tempfile.mkdtemp()
@ -15,7 +19,8 @@ def command(args):
process = subprocess.Popen(args)
process.wait()
assert not process.returncode
if process.returncode:
raise UserError('Command failed: {}'.format(' '.join(args)))
def bash_escape_string(string):

View file

@ -21,17 +21,32 @@ def main(in_path, out_path, deps_path):
temp_mk_path = os.path.join(temp_dir, 'mk')
temp_files_path = os.path.join(temp_dir, 'files')
_openscad(in_path, out_path, temp_deps_path)
# OpenSCAD requires the output file name to end in .stl
temp_stl_path = os.path.join(temp_dir, 'out.stl')
_openscad(in_path, temp_stl_path, temp_deps_path)
mk_content = '%:; echo "$@" >> {}'.format(util.bash_escape_string(temp_files_path))
# Use make to parse the dependency makefile written by OpenSCAD
util.write_file(temp_mk_path, mk_content.encode())
util.command(['make', '-s', '-B', '-f', temp_mk_path, '-f', temp_deps_path])
# All dependencies as paths relative to the project root.
deps = set(map(relpath, util.read_file(temp_files_path).decode().splitlines()))
ignored_files = set(map(relpath, [temp_deps_path, temp_mk_path, in_path, out_path]))
# Relative paths to all files that should not appear in the dependency makefile.
ignored_files = set(map(relpath, [in_path, temp_deps_path, temp_mk_path, temp_stl_path]))
# Write output files.
_write_dependencies(deps_path, relpath(out_path), deps - ignored_files)
os.rename(temp_stl_path, out_path)
main(*sys.argv[1:])
try:
main(*sys.argv[1:])
except util.UserError as e:
print 'Error:', e
sys.exit(1)
except KeyboardInterrupt:
sys.exit(2)