'What is wrong with this inkscape extension?
I am not a programmer. I used to use an inkscape extension that was designed to change the path in inkscape to Gcode. Recently this extension stopped working giving the following error massage. The extension has 5 python files (unicorn.py, init.py, context.py, entities.py, svg_parser.py). The error massage I keep getting is:
Traceback (most recent call last): File "unicorn.py", line 23, in from unicorn.svg_parser import SvgParser File "C:\Program Files\Inkscape\share\inkscape\extensions\unicorn\svg_parser.py", line 4, in import entities ModuleNotFoundError: No module named 'entities'
Based on the error massage I think the problem is with python.py or svg_parder.py I post the unicorn.py and svg_parser.py here. Can anybody help me with this?
unicorn.py text
#!/usr/bin/env python
'''
''
import sys,os
import inkex
from math import *
import getopt
from unicorn.context import GCodeContext
from unicorn.svg_parser import SvgParser
class MyEffect(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option("--extrude_multiple",
action="store", type="float",
dest="extrude_multiple", default="1.0",
help="Extrude_multiple")
self.OptionParser.add_option("--e_r_pattern",
action="store", type="float",
dest="e_r_pattern", default="1.0",
help="E_r_pattern")
self.OptionParser.add_option("--e_r_speed",
action="store", type="float",
dest="e_r_speed", default="900.0",
help="E_r_speed")
self.OptionParser.add_option("--z_hop_enabled",
action="store", type="inkbool",
dest="z_hop_enabled", default="true",
help="Z_hop_enabled")
self.OptionParser.add_option("--z_hop_height",
action="store", type="float",
dest="z_hop_height", default="10.0",
help="Z_hop_height")
self.OptionParser.add_option("--z_hop_speed",
action="store", type="float",
dest="z_hop_speed", default="900.0",
help="Z_hop_speed")
self.OptionParser.add_option("--x_move",
action="store", type="float",
dest="x_move", default="0.1",
help="X move")
self.OptionParser.add_option("--y_move",
action="store", type="float",
dest="y_move", default="0.1",
help="Y move")
self.OptionParser.add_option("--layer_delay",
action="store", type="int",
dest="layer_delay", default="10000",
help="Layer_delay")
self.OptionParser.add_option("--retraction",
action="store", type="float",
dest="retraction", default="0.1",
help="Retraction")
self.OptionParser.add_option("--z_up",
action="store", type="float",
dest="z_up", default="0.1",
help="Z up height")
self.OptionParser.add_option("--retract_before",
action="store", type="float",
dest="retract_before", default="0.1",
help="Retract/extrude before print")
self.OptionParser.add_option("--retract_after",
action="store", type="float",
dest="retract_after", default="0.1",
help="Retract/extrude after print")
self.OptionParser.add_option("--pen-up-angle",
action="store", type="float",
dest="pen_up_angle", default="50.0",
help="Pen Up Angle")
self.OptionParser.add_option("--pen-down-angle",
action="store", type="float",
dest="pen_down_angle", default="30.0",
help="Pen Down Angle")
self.OptionParser.add_option("--start-delay",
action="store", type="float",
dest="start_delay", default="150.0",
help="Delay after pen down command before movement in milliseconds")
self.OptionParser.add_option("--stop-delay",
action="store", type="float",
dest="stop_delay", default="150.0",
help="Delay after pen up command before movement in milliseconds")
self.OptionParser.add_option("--xy-feedrate",
action="store", type="float",
dest="xy_feedrate", default="300.0",
help="XY axes feedrate in mm/min")
self.OptionParser.add_option("--z-feedrate",
action="store", type="float",
dest="z_feedrate", default="0.1",
help="Z axis feedrate in mm/min")
self.OptionParser.add_option("--z-height",
action="store", type="float",
dest="z_height", default="0.0",
help="Z axis print height in mm")
self.OptionParser.add_option("--finished-height",
action="store", type="float",
dest="finished_height", default="0.0",
help="Z axis height after printing in mm")
self.OptionParser.add_option("--register-pen",
action="store", type="string",
dest="register_pen", default="true",
help="Add pen registration check(s)")
self.OptionParser.add_option("--x-home",
action="store", type="float",
dest="x_home", default="0.0",
help="Starting X position")
self.OptionParser.add_option("--y-home",
action="store", type="float",
dest="y_home", default="0.0",
help="Starting Y position")
self.OptionParser.add_option("--num-copies",
action="store", type="int",
dest="num_copies", default="1")
self.OptionParser.add_option("--continuous",
action="store", type="string",
dest="continuous", default="false",
help="Plot continuously until stopped.")
self.OptionParser.add_option("--pause-on-layer-change",
action="store", type="string",
dest="pause_on_layer_change", default="false",
help="Pause on layer changes.")
self.OptionParser.add_option("--tab",
action="store", type="string",
dest="tab")
def output(self):
self.context.generate()
def effect(self):
self.context = GCodeContext(self.options.extrude_multiple , self.options.e_r_pattern, self.options.e_r_speed, self.options.z_hop_enabled, self.options.z_hop_height,self.options.z_hop_speed, self.options.x_move, self.options.y_move, self.options.layer_delay, self.options.retraction,
self.options.z_up, self.options.retract_before, self.options.retract_after,
self.options.xy_feedrate, self.options.z_feedrate,
self.options.start_delay, self.options.stop_delay,
self.options.pen_up_angle, self.options.pen_down_angle,
self.options.z_height, self.options.finished_height,
self.options.x_home, self.options.y_home,
self.options.register_pen,
self.options.num_copies,
self.options.continuous,
self.svg_file)
parser = SvgParser(self.document.getroot(), self.options.pause_on_layer_change)
parser.parse()
for entity in parser.entities:
entity.get_gcode(self.context)
if __name__ == '__main__': #pragma: no cover
e = MyEffect()
e.affect()
Svg_parser.py text
import inkex
import cubicsuperpath
import simplepath
import simplestyle
import cspsubdiv
from simpletransform import *
from bezmisc import *
import entities
from math import radians
import sys
import pprint
def parseLengthWithUnits(str):
'''
Parse an SVG value which may or may not have units attached
This version is greatly simplified in that it only allows: no units,
units of px, and units of %. Everything else, it returns None for.
There is a more general routine to consider in scour.py if more
generality is ever needed.
'''
u = 'px'
s = str.strip()
if s[-2:] == 'px':
s = s[:-2]
elif s[-1:] == '%':
u = '%'
s = s[:-1]
try:
v = float(s)
except:
return None, None
return v, u
def subdivideCubicPath(sp, flat, i=1):
"""
Break up a bezier curve into smaller curves, each of which
is approximately a straight line within a given tolerance
(the "smoothness" defined by [flat]).
This is a modified version of cspsubdiv.cspsubdiv(). I rewrote the recursive
call because it caused recursion-depth errors on complicated line segments.
"""
while True:
while True:
if i >= len(sp):
return
p0 = sp[i - 1][1]
p1 = sp[i - 1][2]
p2 = sp[i][0]
p3 = sp[i][1]
b = (p0, p1, p2, p3)
if cspsubdiv.maxdist(b) > flat:
break
i += 1
one, two = beziersplitatt(b, 0.5)
sp[i - 1][2] = one[1]
sp[i][0] = two[2]
p = [one[2], one[3], two[1]]
sp[i:1] = [p]
class SvgIgnoredEntity:
def load(self, node, mat):
self.tag = node.tag
def __str__(self):
return "Ignored '%s' tag" % self.tag
def get_gcode(self, context):
#context.codes.append("(" + str(self) + ")")
# context.codes.append("")
return
class SvgPath(entities.PolyLine):
def load(self, node, mat):
d = node.get('d')
if len(simplepath.parsePath(d)) == 0:
return
p = cubicsuperpath.parsePath(d)
applyTransformToPath(mat, p)
# p is now a list of lists of cubic beziers [ctrl p1, ctrl p2, endpoint]
# where the start-point is the last point in the previous segment
self.segments = []
for sp in p:
points = []
subdivideCubicPath(sp, 0.2) # TODO: smoothness preference
for csp in sp:
points.append((csp[1][0], csp[1][1]))
self.segments.append(points)
def new_path_from_node(self, node):
newpath = inkex.etree.Element(inkex.addNS('path', 'svg'))
s = node.get('style')
if s:
newpath.set('style', s)
t = node.get('transform')
if t:
newpath.set('transform', t)
return newpath
class SvgRect(SvgPath):
def load(self, node, mat):
newpath = self.new_path_from_node(node)
x = float(node.get('x'))
y = float(node.get('y'))
w = float(node.get('width'))
h = float(node.get('height'))
a = []
a.append(['M ', [x, y]])
a.append([' l ', [w, 0]])
a.append([' l ', [0, h]])
a.append([' l ', [-w, 0]])
a.append([' Z', []])
newpath.set('d', simplepath.formatPath(a))
SvgPath.load(self, newpath, mat)
class SvgLine(SvgPath):
def load(self, node, mat):
newpath = self.new_path_from_node(node)
x1 = float(node.get('x1'))
y1 = float(node.get('y1'))
x2 = float(node.get('x2'))
y2 = float(node.get('y2'))
a = []
a.append(['M ', [x1, y1]])
a.append([' L ', [x2, y2]])
newpath.set('d', simplepath.formatPath(a))
SvgPath.load(self, newpath, mat)
class SvgPolyLine(SvgPath):
def load(self, node, mat):
newpath = self.new_path_from_node(node)
pl = node.get('points', '').strip()
if pl == '':
return
pa = pl.split()
if not len(pa):
return
d = "M " + pa[0]
for i in range(1, len(pa)):
d += " L " + pa[i]
newpath.set('d', d)
SvgPath.load(self, newpath, mat)
class SvgEllipse(SvgPath):
def load(self, node, mat):
rx = float(node.get('rx', '0'))
ry = float(node.get('ry', '0'))
SvgPath.load(self, self.make_ellipse_path(rx, ry, node), mat)
def make_ellipse_path(rx, ry, node):
if rx == 0 or ry == 0:
return None
cx = float(node.get('cx', '0'))
cy = float(node.get('cy', '0'))
x1 = cx - rx
x2 = cx + rx
d = 'M %f,%f ' % (x1, cy) + \
'A %f,%f ' % (rx, ry) + \
'0 1 0 %f, %f ' % (x2, cy) + \
'A %f,%f ' % (rx, ry) + \
'0 1 0 %f,%f' % (x1, cy)
newpath = self.new_path_from_node(node)
newpath.set('d', d)
return newpath
class SvgCircle(SvgEllipse):
def load(self, node, mat):
rx = float(node.get('r', '0'))
SvgPath.load(self, self.make_ellipse_path(rx, rx, node), mat)
class SvgText(SvgIgnoredEntity):
def load(self, node, mat):
inkex.errormsg(
'Warning: unable to draw text. please convert it to a path first.')
SvgIgnoredEntity.load(self, node, mat)
class SvgLayerChange():
def __init__(self, layer_name):
self.layer_name = layer_name
def get_gcode(self, context):
context.codes.append("M01 (Plotting layer '%s')" % self.layer_name)
class SvgParser:
entity_map = {
'path': SvgPath,
'rect': SvgRect,
'line': SvgLine,
'polyline': SvgPolyLine,
'polygon': SvgPolyLine,
'circle': SvgCircle,
'ellipse': SvgEllipse,
'pattern': SvgIgnoredEntity,
'metadata': SvgIgnoredEntity,
'defs': SvgIgnoredEntity,
'eggbot': SvgIgnoredEntity,
('namedview', 'sodipodi'): SvgIgnoredEntity,
'text': SvgText
}
def __init__(self, svg, pause_on_layer_change='false'):
self.svg = svg
self.pause_on_layer_change = pause_on_layer_change
self.entities = []
def getLength(self, name, default):
'''
Get the <svg> attribute with name "name" and default value "default"
Parse the attribute into a value and associated units. Then, accept
no units (''), units of pixels ('px'), and units of percentage ('%').
'''
str = self.svg.get(name)
if str:
v, u = parseLengthWithUnits(str)
if not v:
# Couldn't parse the value
return None
elif (u == '') or (u == 'px'):
return v
elif u == '%':
return float(default) * v / 100.0
else:
# Unsupported units
return None
else:
# No width specified; assume the default value
return float(default)
def parse(self):
# 0.28222 scale determined by comparing pixels-per-mm in a default Inkscape file.
self.svgWidth = self.getLength('width', 354) * 0.28222
self.svgHeight = self.getLength('height', 354) * 0.28222
self.recursivelyTraverseSvg(self.svg, [
[0.28222, 0.0, -(self.svgWidth/2.0)], [0.0, -0.28222, (self.svgHeight/2.0)]])
# TODO: center this thing
def recursivelyTraverseSvg(self, nodeList,
matCurrent=[[1.0, 0.0, 0.0],
[0.0, -1.0, 0.0]],
parent_visibility='visible'):
"""
Recursively traverse the svg file to plot out all of the
paths. The function keeps track of the composite transformation
that should be applied to each path.
This function handles path, group, line, rect, polyline, polygon,
circle, ellipse and use (clone) elements. Notable elements not
handled include text. Unhandled elements should be converted to
paths in Inkscape.
TODO: There's a lot of inlined code in the eggbot version of this
that would benefit from the Entities method of dealing with things.
"""
for node in nodeList:
# Ignore invisible nodes
v = node.get('visibility', parent_visibility)
if v == 'inherit':
v = parent_visibility
if v == 'hidden' or v == 'collapse':
pass
# first apply the current matrix transform to this node's transform
matNew = composeTransform(
matCurrent, parseTransform(node.get("transform")))
if node.tag == inkex.addNS('g', 'svg') or node.tag == 'g':
if (node.get(inkex.addNS('groupmode', 'inkscape')) == 'layer'):
layer_name = node.get(inkex.addNS('label', 'inkscape'))
if(self.pause_on_layer_change == 'true'):
self.entities.append(SvgLayerChange(layer_name))
self.recursivelyTraverseSvg(
node, matNew, parent_visibility=v)
elif node.tag == inkex.addNS('use', 'svg') or node.tag == 'use':
refid = node.get(inkex.addNS('href', 'xlink'))
if refid:
# [1:] to ignore leading '#' in reference
path = '//*[@id="%s"]' % refid[1:]
refnode = node.xpath(path)
if refnode:
x = float(node.get('x', '0'))
y = float(node.get('y', '0'))
# Note: the transform has already been applied
if (x != 0) or (y != 0):
matNew2 = composeTransform(
matNew, parseTransform('translate(%f,%f)' % (x, y)))
else:
matNew2 = matNew
v = node.get('visibility', v)
self.recursivelyTraverseSvg(
refnode, matNew2, parent_visibility=v)
else:
pass
else:
pass
elif not isinstance(node.tag, basestring):
pass
else:
entity = self.make_entity(node, matNew)
if entity == None:
inkex.errormsg(
'Warning: unable to draw object, please convert it to a path first.')
def make_entity(self, node, mat):
for nodetype in SvgParser.entity_map.keys():
tag = nodetype
ns = 'svg'
if(type(tag) is tuple):
tag = nodetype[0]
ns = nodetype[1]
if node.tag == inkex.addNS(tag, ns) or node.tag == tag:
constructor = SvgParser.entity_map[nodetype]
entity = constructor()
entity.load(node, mat)
self.entities.append(entity)
return entity
return None
# the python
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|