Rewritten unit conversion methods of inkex.py to properly handle viewport settings.
This commit is contained in:
parent
e309a206be
commit
c74efa59dc
5 changed files with 178 additions and 19 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue