Implement basic SVG primitives and 'path' skeleton
This commit is contained in:
@@ -71,8 +71,12 @@ class DrawingController(Node):
|
||||
xml = ET.parse(svgpath)
|
||||
svg = xml.getroot()
|
||||
#self.map_point = map_point_function(float(svg.get('width')), float(svg.get('height')), 0.1, 0.5, -0.2, 0.2)
|
||||
try:
|
||||
self.map_point = map_point_function(float(svg.get('width')), float(svg.get('height')), 0.0, 1.0, 0.0, 1.0)
|
||||
self.lines = []
|
||||
except:
|
||||
self.map_point = map_point_function(1000, 1000, 0.0, 1.0, 0.0, 1.0)
|
||||
|
||||
self.lines = [ ((0,0),(0,0)) ]
|
||||
for child in svg:
|
||||
if (child.tag == 'line'):
|
||||
p1 = (float(child.get('x1')), float(child.get('y1')))
|
||||
@@ -80,7 +84,7 @@ class DrawingController(Node):
|
||||
self.lines.append((p1,p2))
|
||||
|
||||
self.svg_processor = SVGProcessor(self.get_logger())
|
||||
self.svg_processor.process_svg(svgpath)
|
||||
print(self.svg_processor.process_svg(svgpath))
|
||||
|
||||
def send_goal(self, motion):
|
||||
self.busy = True
|
||||
|
||||
@@ -1,31 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import rclpy
|
||||
import lxml.etree as ET
|
||||
|
||||
class SVGProcessor():
|
||||
"""
|
||||
Class which reads an svg file and creates sequences of points
|
||||
|
||||
...
|
||||
|
||||
Attributes
|
||||
----------
|
||||
primitive_fns : dict
|
||||
contains a mapping of SVG primitive names to functions that produce points
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
||||
Services
|
||||
-------
|
||||
|
||||
Topics
|
||||
-------
|
||||
"""
|
||||
|
||||
def __init__(self, logger):
|
||||
self.logger = logger
|
||||
# A dict containing svg primitive names mapping to functions that handle them
|
||||
self.primitives = {
|
||||
self.primitive_fns = {
|
||||
# Reference:
|
||||
# https://developer.mozilla.org/en-US/docs/Web/SVG/Element
|
||||
#"line": lambda p: print("LINE"),
|
||||
"line": self.primitive_line,
|
||||
"polyline": self.primitive_polyline,
|
||||
"polygon": self.primitive_polygon,
|
||||
}
|
||||
|
||||
def get_primitive(self, primitive):
|
||||
log_error = lambda p: self.logger.error("'{}' not supported".format(p.tag))
|
||||
return self.primitives.get(primitive.tag, log_error)
|
||||
def get_primitive_fn(self, primitive):
|
||||
'''
|
||||
Looks up the primitive tag name in the dictionary of functions.
|
||||
|
||||
Parameters:
|
||||
primitive (lxml child): the primitive from the svg file
|
||||
Returns:
|
||||
primitive_fn ():
|
||||
'''
|
||||
def log_error(p, _):
|
||||
self.logger.error("'{}' not supported".format(p.tag))
|
||||
return []
|
||||
return self.primitive_fns.get(primitive.tag, log_error)
|
||||
|
||||
|
||||
def down_and_up(self, points):
|
||||
down = (points[0][0], points[0][1], 1)
|
||||
up = (points[-1][0], points[-1][1], 1)
|
||||
|
||||
return [down] + points + [up]
|
||||
|
||||
def primitive_line(self, child, map_point):
|
||||
p1 = map_point(float(child.get('x1')), float(child.get('y1')))
|
||||
p2 = map_point(float(child.get('x2')), float(child.get('y2')))
|
||||
return [
|
||||
(p1[0],p1[1],0),
|
||||
(p2[0],p2[1],0),
|
||||
]
|
||||
|
||||
def primitive_polyline(self, child, map_point):
|
||||
points = child.get('points').split(' ')
|
||||
points = [(map_point(float(p[0])),
|
||||
map_point(float(p[1])))
|
||||
for p in points.split(',')]
|
||||
output = []
|
||||
for p in points:
|
||||
output.append((p[0],p[1],0))
|
||||
return output
|
||||
|
||||
def primitive_polygon(self, child, map_point):
|
||||
output = self.primitive_polyline(child, map_point)
|
||||
output.append((output[0][0],output[0][1],0))
|
||||
return output
|
||||
|
||||
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
|
||||
def path_parser(self, child, map_point):
|
||||
path = child.get('d')
|
||||
self.logger.info("Parsing path :'{}...' with {} characters".format(path[:40], len(path)))
|
||||
self.logger.error("Path parser not implemented")
|
||||
return []
|
||||
|
||||
# https://stackoverflow.com/questions/30232031/how-can-i-strip-namespaces-out-of-an-lxml-tree
|
||||
def strip_ns_prefix(self, tree):
|
||||
#xpath query for selecting all element nodes in namespace
|
||||
query = "descendant-or-self::*[namespace-uri()!='']"
|
||||
#for each element returned by the above xpath query...
|
||||
for element in tree.xpath(query):
|
||||
#replace element name with its local name
|
||||
element.tag = ET.QName(element).localname
|
||||
return tree
|
||||
|
||||
def process_svg(self, svg_path):
|
||||
with open(svg_path) as svg:
|
||||
xml = ET.parse(svg)
|
||||
with open(svg_path) as svg_file:
|
||||
xml = ET.parse(svg_file)
|
||||
svg = xml.getroot()
|
||||
svg = self.strip_ns_prefix(svg)
|
||||
|
||||
map_point = self.map_point_function(1000,
|
||||
1000)
|
||||
if 'width' in svg.attrib:
|
||||
map_point = self.map_point_function(float(svg.get('width')),
|
||||
float(svg.get('height')))
|
||||
elif 'viewBox' in svg.attrib:
|
||||
# TODO parse viewBox
|
||||
pass
|
||||
else:
|
||||
self.logger.error("Unable to get SVG dimensions")
|
||||
|
||||
motions = []
|
||||
for child in svg:
|
||||
f = self.get_primitive(child)
|
||||
f(child)
|
||||
self.logger.info("Attempting to process SVG primitive:'{}'".format(child.tag))
|
||||
primitive_fn = self.primitive_line
|
||||
# path can consist of multiple primitives
|
||||
if (child.tag == 'path'):
|
||||
for m in self.path_parser(child, map_point):
|
||||
motions.append(m)
|
||||
else:
|
||||
primitive_fn = self.get_primitive_fn(child)
|
||||
motions.append(primitive_fn(child, map_point))
|
||||
|
||||
motions_refined = []
|
||||
for m in motions:
|
||||
if m == []:
|
||||
continue
|
||||
self.logger.info("Refining:'{}'".format(m))
|
||||
motions_refined.append(self.down_and_up(m))
|
||||
return motions_refined
|
||||
|
||||
def translate(self, val, lmin, lmax, rmin, rmax):
|
||||
lspan = lmax - lmin
|
||||
@@ -39,3 +144,4 @@ class SVGProcessor():
|
||||
y = self.translate(ypix, 0, y_pixels, 0, 1)
|
||||
return (x,y)
|
||||
return map_point
|
||||
|
||||
|
||||
Reference in New Issue
Block a user