summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--readme.creole56
-rw-r--r--src/.gitignore6
-rw-r--r--support/dxf_export/__main__.py14
-rwxr-xr-xsupport/dxf_export/better_dxf_outlines.py123
-rwxr-xr-xsupport/dxf_export/bezmisc.py3
-rwxr-xr-xsupport/dxf_export/cubicsuperpath.py6
-rw-r--r--support/dxf_export/dxf_footer.txt62
-rw-r--r--support/dxf_export/dxf_header.txt580
-rwxr-xr-xsupport/dxf_export/dxf_templates.py645
-rw-r--r--support/dxf_export/effect.py175
-rwxr-xr-xsupport/dxf_export/ffgeom.py2
-rwxr-xr-xsupport/dxf_export/inkex.py211
-rwxr-xr-xsupport/dxf_export/simplepath.py2
-rwxr-xr-xsupport/dxf_export/simplestyle.py244
-rwxr-xr-xsupport/dxf_export/simpletransform.py4
-rw-r--r--support/lib/util.py7
-rw-r--r--support/openscad/__main__.py21
18 files changed, 1075 insertions, 1089 deletions
diff --git a/.gitignore b/.gitignore
index 51d2952..b01f4b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,2 @@
/config.mk
*.pyc
-*.dxf
-*.stl
-*.d
diff --git a/readme.creole b/readme.creole
index 148e6d3..673e7fb 100644
--- a/readme.creole
+++ b/readme.creole
@@ -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.
+
+* 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.
+
-* 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.
+=== Explicitly specifying paths to binaries
-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:
+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`.
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..893598b
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,6 @@
+*.dxf
+*.stl
+*.d
+
+# Generated files
+/generated/
diff --git a/support/dxf_export/__main__.py b/support/dxf_export/__main__.py
index 76c2121..c770a0e 100644
--- a/support/dxf_export/__main__.py
+++ b/support/dxf_export/__main__.py
@@ -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)
diff --git a/support/dxf_export/better_dxf_outlines.py b/support/dxf_export/better_dxf_outlines.py
deleted file mode 100755
index 19ec6c8..0000000
--- a/support/dxf_export/better_dxf_outlines.py
+++ /dev/null
@@ -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 )
diff --git a/support/dxf_export/bezmisc.py b/support/dxf_export/bezmisc.py
index e663fa6..b7f5429 100755
--- a/support/dxf_export/bezmisc.py
+++ b/support/dxf_export/bezmisc.py
@@ -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
diff --git a/support/dxf_export/cubicsuperpath.py b/support/dxf_export/cubicsuperpath.py
index af61acb..925efdb 100755
--- a/support/dxf_export/cubicsuperpath.py
+++ b/support/dxf_export/cubicsuperpath.py
@@ -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
diff --git a/support/dxf_export/dxf_footer.txt b/support/dxf_export/dxf_footer.txt
new file mode 100644
index 0000000..a225dd7
--- /dev/null
+++ b/support/dxf_export/dxf_footer.txt
@@ -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
diff --git a/support/dxf_export/dxf_header.txt b/support/dxf_export/dxf_header.txt
new file mode 100644
index 0000000..341cb1b
--- /dev/null
+++ b/support/dxf_export/dxf_header.txt
@@ -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
diff --git a/support/dxf_export/dxf_templates.py b/support/dxf_export/dxf_templates.py
deleted file mode 100755
index fb26632..0000000
--- a/support/dxf_export/dxf_templates.py
+++ /dev/null
@@ -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'''
diff --git a/support/dxf_export/effect.py b/support/dxf_export/effect.py
new file mode 100644
index 0000000..206ce9d
--- /dev/null
+++ b/support/dxf_export/effect.py
@@ -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]
diff --git a/support/dxf_export/ffgeom.py b/support/dxf_export/ffgeom.py
index 1983586..ef8799b 100755
--- a/support/dxf_export/ffgeom.py
+++ b/support/dxf_export/ffgeom.py
@@ -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
diff --git a/support/dxf_export/inkex.py b/support/dxf_export/inkex.py
index e487822..19e860b 100755
--- a/support/dxf_export/inkex.py
+++ b/support/dxf_export/inkex.py
@@ -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()])
+def localize():
+ domain = 'inkscape'
+ if sys.platform.startswith('win'):
+ import locale
+ current_locale, encoding = locale.getdefaultlocale()
+ os.environ['LANG'] = current_locale
+ try:
+ localdir = os.environ['INKSCAPE_LOCALEDIR'];
+ trans = gettext.translation(domain, localdir, [current_locale], fallback=True)
+ except KeyError:
+ 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:
- retval = 0.0
- if u:
try:
- return retval * uuconv[u.string[u.start():u.end()]]
+ localdir = os.environ['PACKAGE_LOCALE_DIR'];
+ trans = gettext.translation(domain, localdir, 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)
+ #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
+
+ #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 encoding=utf-8 textwidth=99
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
diff --git a/support/dxf_export/simplepath.py b/support/dxf_export/simplepath.py
index f62b1b4..94ab092 100755
--- a/support/dxf_export/simplepath.py
+++ b/support/dxf_export/simplepath.py
@@ -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
diff --git a/support/dxf_export/simplestyle.py b/support/dxf_export/simplestyle.py
deleted file mode 100755
index 3ec971e..0000000
--- a/support/dxf_export/simplestyle.py
+++ /dev/null
@@ -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
diff --git a/support/dxf_export/simpletransform.py b/support/dxf_export/simpletransform.py
index 47cc61e..55082ed 100755
--- a/support/dxf_export/simpletransform.py
+++ b/support/dxf_export/simpletransform.py
@@ -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:
diff --git a/support/lib/util.py b/support/lib/util.py
index bede240..8b65e58 100644
--- a/support/lib/util.py
+++ b/support/lib/util.py
@@ -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):
diff --git a/support/openscad/__main__.py b/support/openscad/__main__.py
index 08b2ab6..3c103e5 100644
--- a/support/openscad/__main__.py
+++ b/support/openscad/__main__.py
@@ -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)