Rewritten unit conversion methods of inkex.py to properly handle viewport settings.

This commit is contained in:
Michael Schwarz 2014-12-20 22:22:04 +01:00
parent e309a206be
commit c74efa59dc
5 changed files with 178 additions and 19 deletions

View file

@ -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

@ -2,17 +2,70 @@
Based on code from Aaron Spike. See http://www.bobcookdev.com/inkscape/inkscape-dxf.html
"""
import pkgutil
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)))
@ -40,24 +93,31 @@ class DXFExportEffect(inkex.Effect):
e = sub[i + 1]
self._add_dxf_line(layer, [s[1], e[1]])
def _add_dxf_shape(self, node, document_transformation):
def _add_dxf_shape(self, node, document_transform, element_transform):
layer = self._get_inkscape_layer(node)
path = cubicsuperpath.parsePath(node.get('d'))
transformation = simpletransform.composeTransform(
document_transformation,
simpletransform.composeParents(node, [[1, 0, 0], [0, 1, 0]]))
transform = simpletransform.composeTransform(
document_transform,
simpletransform.composeParents(node, element_transform))
simpletransform.applyTransformToPath(transformation, path)
simpletransform.applyTransformToPath(transform, path)
self._add_dxf_path(layer, path)
def effect(self):
height = self.unittouu(self.document.getroot().xpath('@height', namespaces = inkex.NSS)[0])
document_transformation = [[1, 0, 0], [0, -1, height]]
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_transformation)
self._add_dxf_shape(node, document_transform, element_transform)
def write(self, file):
file.write(pkgutil.get_data(__name__, 'dxf_header.txt'))
@ -68,6 +128,30 @@ class DXFExportEffect(inkex.Effect):
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:
@ -79,3 +163,13 @@ class DXFExportEffect(inkex.Effect):
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

@ -101,6 +101,13 @@ def errormsg(msg):
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
@ -277,16 +284,57 @@ class Effect:
retval = None
return retval
def getDocumentUnit(self):
docunit = self.document.xpath('//sodipodi:namedview/@inkscape:document-units', namespaces=NSS)
if docunit:
return docunit[0]
else:
return 'px'
#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()))

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

@ -34,4 +34,10 @@ def main(in_path, out_path, deps_path):
_write_dependencies(deps_path, relpath(out_path), deps - ignored_files)
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)