├── LICENSE
├── README.md
└── src
├── .gitignore
├── unicorn.inx
├── unicorn.py
└── unicorn
├── __init__.py
├── context.py
├── entities.py
└── svg_parser.py
/LICENSE:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (c) 2011 MakerBot Industries
3 | #
4 | # This program is free software; you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation; either version 2 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program; if not, write to the Free Software
16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 | #
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | MakerBot Unicorn G-Code Output for Inkscape
2 | ===========================================
3 |
4 | Notice
5 | ------
6 |
7 | **This extension is no longer supported or maintained. The last tested version of Inkscape is 0.48.5.**
8 |
9 | **Please feel free to fork and improve this extension for your own needs!**
10 |
11 | This is an Inkscape extension that allows you to save your Inkscape drawings as
12 | G-Code files suitable for plotting with the [MakerBot Unicorn Pen Plotter](http://store.makerbot.com/makerbot-unicorn-pen-plotter-kit.html).
13 |
14 | **Users who use this extension to generate G-Code for a machine other than a MakerBot CupCake CNC with a Unicorn Pen Plotter attachment do so at their own risk.**
15 |
16 | Author: [Marty McGuire](http://github.com/martymcguire)
17 |
18 | Website: [http://github.com/martymcguire/inkscape-unicorn](http://github.com/martymcguire/inkscape-unicorn)
19 |
20 | Credits
21 | =======
22 |
23 | * Marty McGuire pulled this all together into an Inkscape extension.
24 | * [Inkscape](http://www.inkscape.org/) is an awesome open source vector graphics app.
25 | * [Scribbles](https://github.com/makerbot/Makerbot/tree/master/Unicorn/Scribbles%20Scripts) is the original DXF-to-Unicorn Python script.
26 | * [The Egg-Bot Driver for Inkscape](http://code.google.com/p/eggbotcode/) provided inspiration and good examples for working with Inkscape's extensions API.
27 |
28 | Install
29 | =======
30 |
31 | Copy the contents of `src/` to your Inkscape `extensions/` folder.
32 |
33 | Typical locations include:
34 |
35 | * OS X - `/Applications/Inkscape.app/Contents/Resources/extensions`
36 | * Linux - `/usr/share/inkscape/extensions`
37 | * Windows - `C:\Program Files\Inkscape\share\extensions`
38 |
39 | Usage
40 | =====
41 |
42 | * Size and locate your image appropriately:
43 | * The CupCake CNC build platform size is 100mm x 100mm.
44 | * Setting units to **mm** in Inkscape makes it easy to size your drawing.
45 | * The extension will automatically attempt to center everything.
46 | * Convert all text to paths:
47 | * Select all text objects.
48 | * Choose **Path | Object to Path**.
49 | * Save as G-Code:
50 | * **File | Save a Copy**.
51 | * Select **MakerBot Unicorn G-Code (\*.gcode)**.
52 | * Save your file.
53 | * Preview
54 | * For OS X, [Pleasant3D](http://www.pleasantsoftware.com/developer/pleasant3d/index.shtml) is great for this.
55 | * For other operating systems... I don't know!
56 | * Print!
57 | * Open your `.gcode` file in [ReplicatorG](http://replicat.org/)
58 | * Set up your Unicorn and pen.
59 | * Center your build platform.
60 | * Click the **Build** button!
61 |
62 | TODOs
63 | =====
64 |
65 | * Rename `*PolyLine` stuff to `*Path` to be less misleading.
66 | * Formalize "home" to be a reasonable place to change pages/pens.
67 | * Parameterize smoothness for curve approximation.
68 | * Use native curve G-Codes instead of converting to paths?
69 | * Include example templates?
70 |
--------------------------------------------------------------------------------
/src/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 |
--------------------------------------------------------------------------------
/src/unicorn.inx:
--------------------------------------------------------------------------------
1 |
2 |
3 | <_name>MakerBot Unicorn G-Code Output
4 | com.makerbot.unicorn.gcode
5 | org.inkscape.output.svg.inkscape
6 | unicorn.py
7 | inkex.py
8 |
9 |
10 | 50
11 | 30
12 | 150.0
13 | 150.0
14 | 3500.0
15 | 150.0
16 | 0.0
17 | 0.0
18 |
19 |
20 | <_param name="reg_help" type="description" xml:space="preserve">Pen Registration Check
21 |
22 | This feature adds a quick pen-down/pen-up at the beginning of a plot so you can check whether the pen hits the paper.
23 |
24 | When plotting, ReplicatorG will pause to ask if registration was successful. If you say "No", it will simply abort the plot so you can restart.
25 | true
26 |
27 |
28 | <_param name="homing_help" type="description" xml:space="preserve">Where do you like to set your platform when you start a plot?
29 |
30 | Some examples:
31 | - X = 0, Y = 0 if it starts centered under the pen.
32 | - X = 50, Y = 50 if it starts in the front-left corner of a Cupcake CNC.
33 |
34 | Note: Double-check the orientation of your axes when changing these values!
35 | 0.00
36 | 0.00
37 |
38 |
39 | <_param name="copies_help" type="description" xml:space="preserve">Add page-changing prompts so you can plot multiple copies!
40 | 1
41 | false
42 |
43 |
44 | false
45 |
46 |
47 | <_param name="ext_help" type="description" xml:space="preserve">MakerBot Unicorn G-Code Output.
48 |
49 | - All text must be converted to paths.
50 | - Curves are approximated with line segments.
51 |
52 | More Info: http://github.com/martymcguire/inkscape-unicorn/
53 |
54 |
55 |
56 |
63 |
66 |
67 |
--------------------------------------------------------------------------------
/src/unicorn.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | '''
3 | Copyright (c) 2010 MakerBot Industries
4 |
5 | This program is free software; you can redistribute it and/or modify
6 | it under the terms of the GNU General Public License as published by
7 | the Free Software Foundation; either version 2 of the License, or
8 | (at your option) any later version.
9 |
10 | This program is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU General Public License for more details.
14 |
15 | You should have received a copy of the GNU General Public License
16 | along with this program; if not, write to the Free Software
17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 | '''
19 | import sys,os
20 | import inkex
21 | from math import *
22 | import getopt
23 | from unicorn.context import GCodeContext
24 | from unicorn.svg_parser import SvgParser
25 |
26 | class MyEffect(inkex.Effect):
27 | def __init__(self):
28 | inkex.Effect.__init__(self)
29 | self.OptionParser.add_option("--pen-up-angle",
30 | action="store", type="float",
31 | dest="pen_up_angle", default="50.0",
32 | help="Pen Up Angle")
33 | self.OptionParser.add_option("--pen-down-angle",
34 | action="store", type="float",
35 | dest="pen_down_angle", default="30.0",
36 | help="Pen Down Angle")
37 | self.OptionParser.add_option("--start-delay",
38 | action="store", type="float",
39 | dest="start_delay", default="150.0",
40 | help="Delay after pen down command before movement in milliseconds")
41 | self.OptionParser.add_option("--stop-delay",
42 | action="store", type="float",
43 | dest="stop_delay", default="150.0",
44 | help="Delay after pen up command before movement in milliseconds")
45 | self.OptionParser.add_option("--xy-feedrate",
46 | action="store", type="float",
47 | dest="xy_feedrate", default="3500.0",
48 | help="XY axes feedrate in mm/min")
49 | self.OptionParser.add_option("--z-feedrate",
50 | action="store", type="float",
51 | dest="z_feedrate", default="150.0",
52 | help="Z axis feedrate in mm/min")
53 | self.OptionParser.add_option("--z-height",
54 | action="store", type="float",
55 | dest="z_height", default="0.0",
56 | help="Z axis print height in mm")
57 | self.OptionParser.add_option("--finished-height",
58 | action="store", type="float",
59 | dest="finished_height", default="0.0",
60 | help="Z axis height after printing in mm")
61 | self.OptionParser.add_option("--register-pen",
62 | action="store", type="string",
63 | dest="register_pen", default="true",
64 | help="Add pen registration check(s)")
65 | self.OptionParser.add_option("--x-home",
66 | action="store", type="float",
67 | dest="x_home", default="0.0",
68 | help="Starting X position")
69 | self.OptionParser.add_option("--y-home",
70 | action="store", type="float",
71 | dest="y_home", default="0.0",
72 | help="Starting Y position")
73 | self.OptionParser.add_option("--num-copies",
74 | action="store", type="int",
75 | dest="num_copies", default="1")
76 | self.OptionParser.add_option("--continuous",
77 | action="store", type="string",
78 | dest="continuous", default="false",
79 | help="Plot continuously until stopped.")
80 | self.OptionParser.add_option("--pause-on-layer-change",
81 | action="store", type="string",
82 | dest="pause_on_layer_change", default="false",
83 | help="Pause on layer changes.")
84 | self.OptionParser.add_option("--tab",
85 | action="store", type="string",
86 | dest="tab")
87 |
88 | def output(self):
89 | self.context.generate()
90 |
91 | def effect(self):
92 | self.context = GCodeContext(self.options.xy_feedrate, self.options.z_feedrate,
93 | self.options.start_delay, self.options.stop_delay,
94 | self.options.pen_up_angle, self.options.pen_down_angle,
95 | self.options.z_height, self.options.finished_height,
96 | self.options.x_home, self.options.y_home,
97 | self.options.register_pen,
98 | self.options.num_copies,
99 | self.options.continuous,
100 | self.svg_file)
101 | parser = SvgParser(self.document.getroot(), self.options.pause_on_layer_change)
102 | parser.parse()
103 | for entity in parser.entities:
104 | entity.get_gcode(self.context)
105 |
106 | if __name__ == '__main__': #pragma: no cover
107 | e = MyEffect()
108 | e.affect()
109 |
--------------------------------------------------------------------------------
/src/unicorn/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martymcguire/inkscape-unicorn/5bf0f6691c08b8e3b07ecace2567413941cd8957/src/unicorn/__init__.py
--------------------------------------------------------------------------------
/src/unicorn/context.py:
--------------------------------------------------------------------------------
1 | from math import *
2 | import sys
3 |
4 | class GCodeContext:
5 | def __init__(self, xy_feedrate, z_feedrate, start_delay, stop_delay, pen_up_angle, pen_down_angle, z_height, finished_height, x_home, y_home, register_pen, num_pages, continuous, file):
6 | self.xy_feedrate = xy_feedrate
7 | self.z_feedrate = z_feedrate
8 | self.start_delay = start_delay
9 | self.stop_delay = stop_delay
10 | self.pen_up_angle = pen_up_angle
11 | self.pen_down_angle = pen_down_angle
12 | self.z_height = z_height
13 | self.finished_height = finished_height
14 | self.x_home = x_home
15 | self.y_home = y_home
16 | self.register_pen = register_pen
17 | self.num_pages = num_pages
18 | self.continuous = continuous
19 | self.file = file
20 |
21 | self.drawing = False
22 | self.last = None
23 |
24 | self.preamble = [
25 | "(Scribbled version of %s @ %.2f)" % (self.file, self.xy_feedrate),
26 | "( %s )" % " ".join(sys.argv),
27 | "G21 (metric ftw)",
28 | "G90 (absolute mode)",
29 | "G92 X%.2f Y%.2f Z%.2f (you are here)" % (self.x_home, self.y_home, self.z_height),
30 | ""
31 | ]
32 |
33 | self.postscript = [
34 | "",
35 | "(end of print job)",
36 | "M300 S%0.2F (pen up)" % self.pen_up_angle,
37 | "G4 P%d (wait %dms)" % (self.stop_delay, self.stop_delay),
38 | "M300 S255 (turn off servo)",
39 | "G1 X0 Y0 F%0.2F" % self.xy_feedrate,
40 | "G1 Z%0.2F F%0.2F (go up to finished level)" % (self.finished_height, self.z_feedrate),
41 | "G1 X%0.2F Y%0.2F F%0.2F (go home)" % (self.x_home, self.y_home, self.xy_feedrate),
42 | "M18 (drives off)",
43 | ]
44 |
45 | self.registration = [
46 | "M300 S%d (pen down)" % (self.pen_down_angle),
47 | "G4 P%d (wait %dms)" % (self.start_delay, self.start_delay),
48 | "M300 S%d (pen up)" % (self.pen_up_angle),
49 | "G4 P%d (wait %dms)" % (self.stop_delay, self.stop_delay),
50 | "M18 (disengage drives)",
51 | "M01 (Was registration test successful?)",
52 | "M17 (engage drives if YES, and continue)",
53 | ""
54 | ]
55 |
56 | self.sheet_header = [
57 | "(start of sheet header)",
58 | "G92 X%.2f Y%.2f Z%.2f (you are here)" % (self.x_home, self.y_home, self.z_height),
59 | ]
60 | if self.register_pen == 'true':
61 | self.sheet_header.extend(self.registration)
62 | self.sheet_header.append("(end of sheet header)")
63 |
64 | self.sheet_footer = [
65 | "(Start of sheet footer.)",
66 | "M300 S%d (pen up)" % (self.pen_up_angle),
67 | "G4 P%d (wait %dms)" % (self.stop_delay, self.stop_delay),
68 | "G91 (relative mode)",
69 | "G0 Z15 F%0.2f" % (self.z_feedrate),
70 | "G90 (absolute mode)",
71 | "G0 X%0.2f Y%0.2f F%0.2f" % (self.x_home, self.y_home, self.xy_feedrate),
72 | "M01 (Have you retrieved the print?)",
73 | "(machine halts until 'okay')",
74 | "G4 P%d (wait %dms)" % (self.start_delay, self.start_delay),
75 | "G91 (relative mode)",
76 | "G0 Z-15 F%0.2f (return to start position of current sheet)" % (self.z_feedrate),
77 | "G0 Z-0.01 F%0.2f (move down one sheet)" % (self.z_feedrate),
78 | "G90 (absolute mode)",
79 | "M18 (disengage drives)",
80 | "(End of sheet footer)",
81 | ]
82 |
83 | self.loop_forever = [ "M30 (Plot again?)" ]
84 |
85 | self.codes = []
86 |
87 | def generate(self):
88 | if self.continuous == 'true':
89 | self.num_pages = 1
90 |
91 | codesets = [self.preamble]
92 | if (self.continuous == 'true' or self.num_pages > 1):
93 | codesets.append(self.sheet_header)
94 | elif self.register_pen == 'true':
95 | codesets.append(self.registration)
96 | codesets.append(self.codes)
97 | if (self.continuous == 'true' or self.num_pages > 1):
98 | codesets.append(self.sheet_footer)
99 |
100 | if self.continuous == 'true':
101 | codesets.append(self.loop_forever)
102 | for codeset in codesets:
103 | for line in codeset:
104 | print line
105 | else:
106 | for p in range(0,self.num_pages):
107 | for codeset in codesets:
108 | for line in codeset:
109 | print line
110 | for line in self.postscript:
111 | print line
112 |
113 | def start(self):
114 | self.codes.append("M300 S%0.2F (pen down)" % self.pen_down_angle)
115 | self.codes.append("G4 P%d (wait %dms)" % (self.start_delay, self.start_delay))
116 | self.drawing = True
117 |
118 | def stop(self):
119 | self.codes.append("M300 S%0.2F (pen up)" % self.pen_up_angle)
120 | self.codes.append("G4 P%d (wait %dms)" % (self.stop_delay, self.stop_delay))
121 | self.drawing = False
122 |
123 | def go_to_point(self, x, y, stop=False):
124 | if self.last == (x,y):
125 | return
126 | if stop:
127 | return
128 | else:
129 | if self.drawing:
130 | self.codes.append("M300 S%0.2F (pen up)" % self.pen_up_angle)
131 | self.codes.append("G4 P%d (wait %dms)" % (self.stop_delay, self.stop_delay))
132 | self.drawing = False
133 | self.codes.append("G1 X%.2f Y%.2f F%.2f" % (x,y, self.xy_feedrate))
134 | self.last = (x,y)
135 |
136 | def draw_to_point(self, x, y, stop=False):
137 | if self.last == (x,y):
138 | return
139 | if stop:
140 | return
141 | else:
142 | if self.drawing == False:
143 | self.codes.append("M300 S%0.2F (pen down)" % self.pen_up_angle)
144 | self.codes.append("G4 P%d (wait %dms)" % (self.start_delay, self.start_delay))
145 | self.drawing = True
146 | self.codes.append("G1 X%0.2f Y%0.2f F%0.2f" % (x,y, self.xy_feedrate))
147 | self.last = (x,y)
148 |
--------------------------------------------------------------------------------
/src/unicorn/entities.py:
--------------------------------------------------------------------------------
1 | from math import cos, sin, radians
2 | import pprint
3 |
4 | class Entity:
5 | def get_gcode(self,context):
6 | #raise NotImplementedError()
7 | return "NIE"
8 |
9 | class Line(Entity):
10 | def __str__(self):
11 | return "Line from [%.2f, %.2f] to [%.2f, %.2f]" % (self.start[0], self.start[1], self.end[0], self.end[1])
12 | def get_gcode(self,context):
13 | "Emit gcode for drawing line"
14 | context.codes.append("(" + str(self) + ")")
15 | context.go_to_point(self.start[0],self.start[1])
16 | context.draw_to_point(self.end[0],self.end[1])
17 | context.codes.append("")
18 |
19 | class Circle(Entity):
20 | def __str__(self):
21 | return "Circle at [%.2f,%.2f], radius %.2f" % (self.center[0], self.center[1], self.radius)
22 | def get_gcode(self,context):
23 | "Emit gcode for drawing arc"
24 | start = (self.center[0] - self.radius, self.center[1])
25 | arc_code = "G3 I%.2f J0 F%.2f" % (self.radius, context.xy_feedrate)
26 |
27 | context.codes.append("(" + str(self) + ")")
28 | context.go_to_point(start[0],start[1])
29 | context.start()
30 | context.codes.append(arc_code)
31 | context.stop()
32 | context.codes.append("")
33 |
34 | class Arc(Entity):
35 | def __str__(self):
36 | return "Arc at [%.2f, %.2f], radius %.2f, from %.2f to %.2f" % (self.center[0], self.center[1], self.radius, self.start_angle, self.end_angle)
37 |
38 | def find_point(self,proportion):
39 | "Find point at the given proportion along the arc."
40 | delta = self.end_angle - self.start_angle
41 | angle = self.start_angle + delta*proportion
42 |
43 | return (self.center[0] + self.radius*cos(angle), self.center[1] + self.radius*sin(angle))
44 |
45 | def get_gcode(self,context):
46 | "Emit gcode for drawing arc"
47 | start = self.find_point(0)
48 | end = self.find_point(1)
49 | delta = self.end_angle - self.start_angle
50 |
51 | if (delta < 0):
52 | arc_code = "G3"
53 | else:
54 | arc_code = "G3"
55 | arc_code = arc_code + " X%.2f Y%.2f I%.2f J%.2f F%.2f" % (end[0], end[1], self.center[0] - start[0], self.center[1] - start[1], context.xy_feedrate)
56 |
57 | context.codes.append("(" + str(self) + ")")
58 | context.go_to_point(start[0],start[1])
59 | context.last = end
60 | context.start()
61 | context.codes.append(arc_code)
62 | context.stop()
63 | context.codes.append("")
64 |
65 | class Ellipse(Entity):
66 | #NOT YET IMPLEMENTED
67 | def __str__(self):
68 | return "Ellipse at [%.2f, %.2f], major [%.2f, %.2f], minor/major %.2f" + " start %.2f end %.2f" % \
69 | (self.center[0], self.center[1], self.major[0], self.major[1], self.minor_to_major, self.start_param, self.end_param)
70 |
71 | class PolyLine(Entity):
72 | def __str__(self):
73 | return "Polyline consisting of %d segments." % len(self.segments)
74 |
75 | def get_gcode(self,context):
76 | "Emit gcode for drawing polyline"
77 | if hasattr(self, 'segments'):
78 | for points in self.segments:
79 | start = points[0]
80 |
81 | context.codes.append("(" + str(self) + ")")
82 | context.go_to_point(start[0],start[1])
83 | context.start()
84 | for point in points[1:]:
85 | context.draw_to_point(point[0],point[1])
86 | context.last = point
87 | context.stop()
88 | context.codes.append("")
89 |
90 |
--------------------------------------------------------------------------------
/src/unicorn/svg_parser.py:
--------------------------------------------------------------------------------
1 | import inkex, cubicsuperpath, simplepath, simplestyle, cspsubdiv
2 | from simpletransform import *
3 | from bezmisc import *
4 | import entities
5 | from math import radians
6 | import sys, pprint
7 |
8 | def parseLengthWithUnits( str ):
9 | '''
10 | Parse an SVG value which may or may not have units attached
11 | This version is greatly simplified in that it only allows: no units,
12 | units of px, and units of %. Everything else, it returns None for.
13 | There is a more general routine to consider in scour.py if more
14 | generality is ever needed.
15 | '''
16 | u = 'px'
17 | s = str.strip()
18 | if s[-2:] == 'px':
19 | s = s[:-2]
20 | elif s[-1:] == '%':
21 | u = '%'
22 | s = s[:-1]
23 | try:
24 | v = float( s )
25 | except:
26 | return None, None
27 | return v, u
28 |
29 | def subdivideCubicPath( sp, flat, i=1 ):
30 | """
31 | Break up a bezier curve into smaller curves, each of which
32 | is approximately a straight line within a given tolerance
33 | (the "smoothness" defined by [flat]).
34 |
35 | This is a modified version of cspsubdiv.cspsubdiv(). I rewrote the recursive
36 | call because it caused recursion-depth errors on complicated line segments.
37 | """
38 |
39 | while True:
40 | while True:
41 | if i >= len( sp ):
42 | return
43 |
44 | p0 = sp[i - 1][1]
45 | p1 = sp[i - 1][2]
46 | p2 = sp[i][0]
47 | p3 = sp[i][1]
48 |
49 | b = ( p0, p1, p2, p3 )
50 |
51 | if cspsubdiv.maxdist( b ) > flat:
52 | break
53 |
54 | i += 1
55 |
56 | one, two = beziersplitatt( b, 0.5 )
57 | sp[i - 1][2] = one[1]
58 | sp[i][0] = two[2]
59 | p = [one[2], one[3], two[1]]
60 | sp[i:1] = [p]
61 |
62 | class SvgIgnoredEntity:
63 | def load(self,node,mat):
64 | self.tag = node.tag
65 | def __str__(self):
66 | return "Ignored '%s' tag" % self.tag
67 | def get_gcode(self,context):
68 | #context.codes.append("(" + str(self) + ")")
69 | #context.codes.append("")
70 | return
71 |
72 | class SvgPath(entities.PolyLine):
73 | def load(self, node, mat):
74 | d = node.get('d')
75 | if len(simplepath.parsePath(d)) == 0:
76 | return
77 | p = cubicsuperpath.parsePath(d)
78 | applyTransformToPath(mat, p)
79 |
80 | # p is now a list of lists of cubic beziers [ctrl p1, ctrl p2, endpoint]
81 | # where the start-point is the last point in the previous segment
82 | self.segments = []
83 | for sp in p:
84 | points = []
85 | subdivideCubicPath(sp,0.2) # TODO: smoothness preference
86 | for csp in sp:
87 | points.append((csp[1][0],csp[1][1]))
88 | self.segments.append(points)
89 |
90 | def new_path_from_node(self, node):
91 | newpath = inkex.etree.Element(inkex.addNS('path','svg'))
92 | s = node.get('style')
93 | if s:
94 | newpath.set('style',s)
95 | t = node.get('transform')
96 | if t:
97 | newpath.set('transform',t)
98 | return newpath
99 |
100 | class SvgRect(SvgPath):
101 | def load(self, node, mat):
102 | newpath = self.new_path_from_node(node)
103 | x = float(node.get('x'))
104 | y = float(node.get('y'))
105 | w = float(node.get('width'))
106 | h = float(node.get('height'))
107 | a = []
108 | a.append(['M ', [x,y]])
109 | a.append([' l ', [w,0]])
110 | a.append([' l ', [0,h]])
111 | a.append([' l ', [-w,0]])
112 | a.append([' Z', []])
113 | newpath.set('d', simplepath.formatPath(a))
114 | SvgPath.load(self,newpath,mat)
115 |
116 | class SvgLine(SvgPath):
117 | def load(self, node, mat):
118 | newpath = self.new_path_from_node(node)
119 | x1 = float(node.get('x1'))
120 | y1 = float(node.get('y1'))
121 | x2 = float(node.get('x2'))
122 | y2 = float(node.get('y2'))
123 | a = []
124 | a.append(['M ', [x1,y1]])
125 | a.append([' L ', [x2,y2]])
126 | newpath.set('d', simplepath.formatPath(a))
127 | SvgPath.load(self,newpath,mat)
128 |
129 | class SvgPolyLine(SvgPath):
130 | def load(self, node, mat):
131 | newpath = self.new_path_from_node(node)
132 | pl = node.get('points','').strip()
133 | if pl == '':
134 | return
135 | pa = pl.split()
136 | if not len(pa):
137 | return
138 |
139 | d = "M " + pa[0]
140 | for i in range(1, len(pa)):
141 | d += " L " + pa[i]
142 | newpath.set('d',d)
143 | SvgPath.load(self,newpath,mat)
144 |
145 | class SvgEllipse(SvgPath):
146 | def load(self, node,mat):
147 | rx = float(node.get('rx','0'))
148 | ry = float(node.get('ry','0'))
149 | SvgPath.load(self,self.make_ellipse_path(rx,ry,node), mat)
150 | def make_ellipse_path(self, rx, ry, node):
151 | if rx == 0 or ry == 0:
152 | return None
153 | cx = float(node.get('cx','0'))
154 | cy = float(node.get('cy','0'))
155 | x1 = cx - rx
156 | x2 = cx + rx
157 | d = 'M %f,%f ' % (x1,cy) + \
158 | 'A %f,%f ' % (rx,ry) + \
159 | '0 1 0 %f, %f ' % (x2,cy) + \
160 | 'A %f,%f ' % (rx,ry) + \
161 | '0 1 0 %f,%f' % (x1,cy)
162 | newpath = self.new_path_from_node(node)
163 | newpath.set('d',d)
164 | return newpath
165 |
166 | class SvgCircle(SvgEllipse):
167 | def load(self, node,mat):
168 | rx = float(node.get('r','0'))
169 | SvgPath.load(self,self.make_ellipse_path(rx,rx,node), mat)
170 |
171 | class SvgText(SvgIgnoredEntity):
172 | def load(self,node,mat):
173 | inkex.errormsg('Warning: unable to draw text. please convert it to a path first.')
174 | SvgIgnoredEntity.load(self,node,mat)
175 |
176 | class SvgLayerChange():
177 | def __init__(self,layer_name):
178 | self.layer_name = layer_name
179 | def get_gcode(self,context):
180 | context.codes.append("M01 (Plotting layer '%s')" % self.layer_name)
181 |
182 | class SvgParser:
183 |
184 | entity_map = {
185 | 'path': SvgPath,
186 | 'rect': SvgRect,
187 | 'line': SvgLine,
188 | 'polyline': SvgPolyLine,
189 | 'polygon': SvgPolyLine,
190 | 'circle': SvgCircle,
191 | 'ellipse': SvgEllipse,
192 | 'pattern': SvgIgnoredEntity,
193 | 'metadata': SvgIgnoredEntity,
194 | 'defs': SvgIgnoredEntity,
195 | 'eggbot': SvgIgnoredEntity,
196 | ('namedview','sodipodi'): SvgIgnoredEntity,
197 | 'text': SvgText
198 | }
199 |
200 | def __init__(self, svg, pause_on_layer_change='false'):
201 | self.svg = svg
202 | self.pause_on_layer_change = pause_on_layer_change
203 | self.entities = []
204 |
205 | def getLength( self, name, default ):
206 | '''
207 | Get the