From 8583d4f8c238899de20a56abf3a6802dc3207b9b Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 22 Jun 2017 01:01:34 +0100 Subject: [PATCH 01/51] add silhouette_multi extension to perform multiple actions (cut, pen) in sequence --- silhouette_multi.inx | 367 +++++++++++++++++++++++++++++++++++++++++++ silhouette_multi.py | 178 +++++++++++++++++++++ 2 files changed, 545 insertions(+) create mode 100644 silhouette_multi.inx create mode 100755 silhouette_multi.py diff --git a/silhouette_multi.inx b/silhouette_multi.inx new file mode 100644 index 00000000..2440a712 --- /dev/null +++ b/silhouette_multi.inx @@ -0,0 +1,367 @@ + + + <_name>Silhouette Multiple Actions + com.github.jnweiger.inskscape-silhouette-multi + org.inkscape.output.svg.inkscape + inkex.py + silhouette_multi.py + + + + 0.0 + 0.0 + + No action + Media default + Pen + Cut + + 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. +Pressure values of 19 or more make the machine misbehave. Beware. + + + + [P>0 S>0] Custom: use non-zero Pressure and Speed below + [P=27,S=10] SCard without Craft Paper Backing + [P=27,S=10] Card with Craft Paper Backing + [P=10,S=10] Vinyl Sticker + [P=14,S=10] Film Labels + [P=27,S=10] Thick Media + [P= 2,S=10] Thin Media + [P=10,S=10] Pen + [P=30,S=10] Bond Paper 13-28 lbs (105g) + [P=30,S=10] Bristol Paper 57-67 lbs (145g) + [P=30,S=10] Cardstock 40-60 lbs (90g) + [P=30,S=10] Cover 40-60 lbs (170g) + [P= 1,S=10] Film, Double Matte Translucent + [P= 1,S=10] Film, Vinyl With Adhesive Back + [P= 1,S=10] Film, Window With Kling Adhesive + [P=30,S=10] Index 90 lbs (165g) + [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) + [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) + [P=30,S= 3] Magnetic Sheet + [P=30,S=10] Offset 24-60 lbs (90g) + [P= 5,S=10] Print Paper Light Weight + [P=25,S=10] Print Paper Medium Weight + [P=20,S=10] Sticker Sheet + [P=20,S=10] Tag 100 lbs (275g) + [P=30,S=10] Text Paper 24-70 lbs (105g) + [P=30,S=10] Vellum Bristol 57-67 lbs (145g) + [P=30,S=10] Writing Paper 24-70 lbs (105g) + + 0 + 0 + Use speed=0, pressure=0 to take the media defaults. + false Convert paths with dashed strokes to separate subpaths for perforated cuts. + false Shift to the top lefthand corner, then do offsets. + false + To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) + 0.5 + 1 + false + + Start Position + Below Cut-Out + + Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. + + + + + 0.0 + 0.0 + + No action + Media default + Pen + Cut + + 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. +Pressure values of 19 or more make the machine misbehave. Beware. + + + + [P>0 S>0] Custom: use non-zero Pressure and Speed below + [P=27,S=10] SCard without Craft Paper Backing + [P=27,S=10] Card with Craft Paper Backing + [P=10,S=10] Vinyl Sticker + [P=14,S=10] Film Labels + [P=27,S=10] Thick Media + [P= 2,S=10] Thin Media + [P=10,S=10] Pen + [P=30,S=10] Bond Paper 13-28 lbs (105g) + [P=30,S=10] Bristol Paper 57-67 lbs (145g) + [P=30,S=10] Cardstock 40-60 lbs (90g) + [P=30,S=10] Cover 40-60 lbs (170g) + [P= 1,S=10] Film, Double Matte Translucent + [P= 1,S=10] Film, Vinyl With Adhesive Back + [P= 1,S=10] Film, Window With Kling Adhesive + [P=30,S=10] Index 90 lbs (165g) + [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) + [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) + [P=30,S= 3] Magnetic Sheet + [P=30,S=10] Offset 24-60 lbs (90g) + [P= 5,S=10] Print Paper Light Weight + [P=25,S=10] Print Paper Medium Weight + [P=20,S=10] Sticker Sheet + [P=20,S=10] Tag 100 lbs (275g) + [P=30,S=10] Text Paper 24-70 lbs (105g) + [P=30,S=10] Vellum Bristol 57-67 lbs (145g) + [P=30,S=10] Writing Paper 24-70 lbs (105g) + + 0 + 0 + Use speed=0, pressure=0 to take the media defaults. + false Convert paths with dashed strokes to separate subpaths for perforated cuts. + false Shift to the top lefthand corner, then do offsets. + false + To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) + 0.5 + 1 + false + + Start Position + Below Cut-Out + + Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. + + + + + 0.0 + 0.0 + + No action + Media default + Pen + Cut + + 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. +Pressure values of 19 or more make the machine misbehave. Beware. + + + + [P>0 S>0] Custom: use non-zero Pressure and Speed below + [P=27,S=10] SCard without Craft Paper Backing + [P=27,S=10] Card with Craft Paper Backing + [P=10,S=10] Vinyl Sticker + [P=14,S=10] Film Labels + [P=27,S=10] Thick Media + [P= 2,S=10] Thin Media + [P=10,S=10] Pen + [P=30,S=10] Bond Paper 13-28 lbs (105g) + [P=30,S=10] Bristol Paper 57-67 lbs (145g) + [P=30,S=10] Cardstock 40-60 lbs (90g) + [P=30,S=10] Cover 40-60 lbs (170g) + [P= 1,S=10] Film, Double Matte Translucent + [P= 1,S=10] Film, Vinyl With Adhesive Back + [P= 1,S=10] Film, Window With Kling Adhesive + [P=30,S=10] Index 90 lbs (165g) + [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) + [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) + [P=30,S= 3] Magnetic Sheet + [P=30,S=10] Offset 24-60 lbs (90g) + [P= 5,S=10] Print Paper Light Weight + [P=25,S=10] Print Paper Medium Weight + [P=20,S=10] Sticker Sheet + [P=20,S=10] Tag 100 lbs (275g) + [P=30,S=10] Text Paper 24-70 lbs (105g) + [P=30,S=10] Vellum Bristol 57-67 lbs (145g) + [P=30,S=10] Writing Paper 24-70 lbs (105g) + + 0 + 0 + Use speed=0, pressure=0 to take the media defaults. + false Convert paths with dashed strokes to separate subpaths for perforated cuts. + false Shift to the top lefthand corner, then do offsets. + false + To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) + 0.5 + 1 + false + + Start Position + Below Cut-Out + + Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. + + + + + 0.0 + 0.0 + + No action + Media default + Pen + Cut + + 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. +Pressure values of 19 or more make the machine misbehave. Beware. + + + + [P>0 S>0] Custom: use non-zero Pressure and Speed below + [P=27,S=10] SCard without Craft Paper Backing + [P=27,S=10] Card with Craft Paper Backing + [P=10,S=10] Vinyl Sticker + [P=14,S=10] Film Labels + [P=27,S=10] Thick Media + [P= 2,S=10] Thin Media + [P=10,S=10] Pen + [P=30,S=10] Bond Paper 13-28 lbs (105g) + [P=30,S=10] Bristol Paper 57-67 lbs (145g) + [P=30,S=10] Cardstock 40-60 lbs (90g) + [P=30,S=10] Cover 40-60 lbs (170g) + [P= 1,S=10] Film, Double Matte Translucent + [P= 1,S=10] Film, Vinyl With Adhesive Back + [P= 1,S=10] Film, Window With Kling Adhesive + [P=30,S=10] Index 90 lbs (165g) + [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) + [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) + [P=30,S= 3] Magnetic Sheet + [P=30,S=10] Offset 24-60 lbs (90g) + [P= 5,S=10] Print Paper Light Weight + [P=25,S=10] Print Paper Medium Weight + [P=20,S=10] Sticker Sheet + [P=20,S=10] Tag 100 lbs (275g) + [P=30,S=10] Text Paper 24-70 lbs (105g) + [P=30,S=10] Vellum Bristol 57-67 lbs (145g) + [P=30,S=10] Writing Paper 24-70 lbs (105g) + + 0 + 0 + Use speed=0, pressure=0 to take the media defaults. + false Convert paths with dashed strokes to separate subpaths for perforated cuts. + false Shift to the top lefthand corner, then do offsets. + false + To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) + 0.5 + 1 + false + + Start Position + Below Cut-Out + + Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. + + + + + 0.0 + 0.0 + + No action + Media default + Pen + Cut + + 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. +Pressure values of 19 or more make the machine misbehave. Beware. + + + + [P>0 S>0] Custom: use non-zero Pressure and Speed below + [P=27,S=10] SCard without Craft Paper Backing + [P=27,S=10] Card with Craft Paper Backing + [P=10,S=10] Vinyl Sticker + [P=14,S=10] Film Labels + [P=27,S=10] Thick Media + [P= 2,S=10] Thin Media + [P=10,S=10] Pen + [P=30,S=10] Bond Paper 13-28 lbs (105g) + [P=30,S=10] Bristol Paper 57-67 lbs (145g) + [P=30,S=10] Cardstock 40-60 lbs (90g) + [P=30,S=10] Cover 40-60 lbs (170g) + [P= 1,S=10] Film, Double Matte Translucent + [P= 1,S=10] Film, Vinyl With Adhesive Back + [P= 1,S=10] Film, Window With Kling Adhesive + [P=30,S=10] Index 90 lbs (165g) + [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) + [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) + [P=30,S= 3] Magnetic Sheet + [P=30,S=10] Offset 24-60 lbs (90g) + [P= 5,S=10] Print Paper Light Weight + [P=25,S=10] Print Paper Medium Weight + [P=20,S=10] Sticker Sheet + [P=20,S=10] Tag 100 lbs (275g) + [P=30,S=10] Text Paper 24-70 lbs (105g) + [P=30,S=10] Vellum Bristol 57-67 lbs (145g) + [P=30,S=10] Writing Paper 24-70 lbs (105g) + + 0 + 0 + Use speed=0, pressure=0 to take the media defaults. + false Convert paths with dashed strokes to separate subpaths for perforated cuts. + false Shift to the top lefthand corner, then do offsets. + false + To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) + 0.5 + 1 + false + + Start Position + Below Cut-Out + + Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. + + + + + false + The document contains printed registration marks + false + Search for the registration marks automatically. Seems on some devices there is another mode, where you can set the references manually. + 180 + 230 + 15 + 20 + Distance of the registration mark edges + + + + false + Keep dialog open until device becomes idle again. + false + Subdivide, sort, and choose cut directions, so that most a cutting mat is not needed in most cases. + 5 + 1 + false + + + + +Always use the least amount of blade possible. + +1) Take a sheet of the media you are trying to cut and fold it in half. + +2) Take the blade out of the machine, set it to 1 and hold it in your hand as you would a pen but held vertically as it would be in the machine. + +3) Get your folded media and with your blade held like a pen but kept vertically press firmly down on the media and 'draw' a line. + +4) Next have a look at the media; with the correct setting you should have just cut a line through the top layer of the folded card without cutting in to the back layer. If you have not cut through the media, increase the blade by 1 position and repeat from step 3. + +5) Keep doing this until you reach the correct setting to cut the top layer without cutting the back. + +6) Once this is done the blade can be put back in to the machine. + + + + + inkscape-silhouette extension from https://github.com/jnweiger/inkscape-silhouette by Jürgen Weigert [juewei@fabmail.org] and contributors + + Version 1.19 + + + + + all + + + + + + + diff --git a/silhouette_multi.py b/silhouette_multi.py new file mode 100755 index 00000000..6407460e --- /dev/null +++ b/silhouette_multi.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python + +import sys, os +from collections import defaultdict +import math +import struct +import time +import inkex +import simplestyle + +class SilhouetteMulti(inkex.Effect): + def parse_args(self, argv): + args = {} + ignored = [] + + for arg in argv: + if arg.startswith('--'): + arg = arg[2:] + else: + ignored.append(arg) + continue + + k, v = arg.split('=', 1) + + + if k == "id": + # ignore selected items, because we're going to create selections + # based on colors + continue + + args[k] = v + + return args, ignored + + def mock_options(self): + class FakeOptions: + ids = [] + + self.options = FakeOptions() + + def getoptions(self, args=sys.argv[1:]): + # getoptions() is called automatically by affect() and uses optparse. + # We just want to store the args to pass through, so we override it. + + self.mock_options() + + self.globals = {} + self.extra_args = [] + self.actions = defaultdict(dict) + + args, self.extra_args = self.parse_args(args) + + for k, v in args.iteritems(): + if k.startswith('action'): + # example: action1_color + action_name, k = k.split('_', 1) + action_num = int(action_name[6:]) + + self.actions[action_num][k] = v + else: + self.globals[k] = v + + self.tolerance = float(self.globals.pop('tolerance', 0)) + self.pause = float(self.globals.pop('pause', 0)) + self.debug = self.globals.pop('debug') == "true" + + + def integer_to_rgb(self, color): + # takes a packed 4-byte signed integer color like -16711681 and unpacks + # it into a tuple of (red, green, blue) as unsigned integers + # in the range 0-255. Alpha is discarded. + return struct.unpack("BBBB", struct.pack(">i", color))[0:3] + + def colors_match(self, color1, color2): + # Do these colors match? + # + # Compare using self.tolerance, which specifies the maximum allowed + # "distance". + # + # Colors are (r, g, b) tuples + + distance = math.sqrt((color1[0] - color2[0])**2 + \ + (color1[1] - color2[1])**2 + \ + (color1[2] - color2[2])**2) + + return distance <= self.tolerance + + def get_node_stroke_color(self, node): + if node.get('style') is None: + return None + + style = simplestyle.parseStyle(node.get('style')) + + if not style.get('stroke'): + return None + + return simplestyle.parseColor(style.get('stroke')) + + def get_nodes_by_color(self, color, element=None, parent_visibility="visible"): + if element is None: + element = self.document.getroot() + + nodes_by_color = [] + + for node in element: + visibility = node.get('visibility', parent_visibility) + + if visibility == 'inherit': + visibility = parent_visibility + + if visibility in ('hidden', 'collapse'): + return [] + + node_color = self.get_node_stroke_color(node) + + if node_color and self.colors_match(color, node_color): + nodes_by_color.append(node) + + nodes_by_color.extend(self.get_nodes_by_color(color, node, visibility)) + + return nodes_by_color + + def format_args(self, args): + if isinstance(args, dict): + args = args.iteritems() + + return " ".join(("--%s=%s" % (k, v) for k, v in args)) + + def global_args(self): + return self.format_args(self.globals) + + def id_args(self, nodes): + return self.format_args(("id", node.get("id")) for node in nodes) + + def effect(self): + # any output on stdout crashes inkscape, so let's avoid that + old_stdout = sys.stdout + sys.stdout = sys.stderr + + commands = [] + + for action_num in sorted(self.actions.keys()): + action = self.actions[action_num] + + if action['tool'] == 'none': + continue + + color = self.integer_to_rgb(int(action.pop('color'))) + nodes = self.get_nodes_by_color(color) + + if not nodes: + continue + + command = ("python sendto_silhouette.py" + " " + + self.global_args() + " " + + self.format_args(action) + " " + + self.id_args(nodes) + " " + + " ".join(self.extra_args)) + + commands.append(command) + + if self.debug: + print >> sys.stderr, "\n".join(commands) + else: + for command in commands: + os.system(command) + time.sleep(self.pause) + + sys.stdout = old_stdout + + +if __name__ == "__main__": + #print >> sys.stderr, " ".join(sys.argv) + #sys.exit(0) + + s = SilhouetteMulti() + s.affect() + From 9fe59ec5b1f4d53ec2f1b7159d3a5e43d5fce10c Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 22 Jun 2017 03:18:18 +0100 Subject: [PATCH 02/51] bugfix: selecting paths inside groups causes group transform to be ignored --- sendto_silhouette.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/sendto_silhouette.py b/sendto_silhouette.py index ebc67302..6cb3ccc1 100644 --- a/sendto_silhouette.py +++ b/sendto_silhouette.py @@ -126,6 +126,8 @@ N_PAGE_WIDTH = 3200 N_PAGE_HEIGHT = 800 +IDENTITY_TRANSFORM = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] + def px2mm(px): ''' @@ -447,7 +449,6 @@ def DoWePlotLayer( self, strLayerName ): def recursivelyTraverseSvg( self, aNodeList, - 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 @@ -470,8 +471,8 @@ def recursivelyTraverseSvg( self, aNodeList, if v == 'hidden' or v == 'collapse': pass - # first apply the current matrix transform to this node's tranform - matNew = composeTransform( matCurrent, parseTransform( node.get( "transform" ) ) ) + # calculate this object's transform + transform = composeTransform(self.docTransform, composeParents(node, IDENTITY_TRANSFORM)) if node.tag == inkex.addNS( 'g', 'svg' ) or node.tag == 'g': @@ -485,7 +486,7 @@ def recursivelyTraverseSvg( self, aNodeList, if not self.allLayers: # inkex.errormsg('Plotting layer named: ' + node.get(inkex.addNS('label', 'inkscape'))) self.DoWePlotLayer( node.get( inkex.addNS( 'label', 'inkscape' ) ) ) - self.recursivelyTraverseSvg( node, matNew, parent_visibility=v ) + self.recursivelyTraverseSvg( node, parent_visibility=v ) elif node.tag == inkex.addNS( 'use', 'svg' ) or node.tag == 'use': @@ -512,11 +513,9 @@ def recursivelyTraverseSvg( self, aNodeList, 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 + transform = composeTransform( transform, parseTransform( 'translate(%f,%f)' % (x,y) ) ) v = node.get( 'visibility', v ) - self.recursivelyTraverseSvg( refnode, matNew2, parent_visibility=v ) + self.recursivelyTraverseSvg( refnode, parent_visibility=v ) else: pass else: @@ -539,7 +538,7 @@ def recursivelyTraverseSvg( self, aNodeList, if self.resumeMode and ( self.pathcount < self.svgLastPath ): pass else: - self.plotPath( node, matNew ) + self.plotPath( node, transform ) if ( not self.bStopped ): #an "index" for resuming plots quickly-- record last complete path self.svgLastPath += 1 self.svgLastPathNC = self.nodeCount @@ -587,7 +586,7 @@ def recursivelyTraverseSvg( self, aNodeList, a.append( [' l ', [-w, 0]] ) a.append( [' Z', []] ) newpath.set( 'd', simplepath.formatPath( a ) ) - self.plotPath( newpath, matNew ) + self.plotPath( newpath, transform ) elif node.tag == inkex.addNS( 'line', 'svg' ) or node.tag == 'line': @@ -627,7 +626,7 @@ def recursivelyTraverseSvg( self, aNodeList, a.append( ['M ', [x1, y1]] ) a.append( [' L ', [x2, y2]] ) newpath.set( 'd', simplepath.formatPath( a ) ) - self.plotPath( newpath, matNew ) + self.plotPath( newpath, transform ) if ( not self.bStopped ): #an "index" for resuming plots quickly-- record last complete path self.svgLastPath += 1 self.svgLastPathNC = self.nodeCount @@ -678,7 +677,7 @@ def recursivelyTraverseSvg( self, aNodeList, t = node.get( 'transform' ) if t: newpath.set( 'transform', t ) - self.plotPath( newpath, matNew ) + self.plotPath( newpath, transform ) if ( not self.bStopped ): #an "index" for resuming plots quickly-- record last complete path self.svgLastPath += 1 self.svgLastPathNC = self.nodeCount @@ -730,7 +729,7 @@ def recursivelyTraverseSvg( self, aNodeList, t = node.get( 'transform' ) if t: newpath.set( 'transform', t ) - self.plotPath( newpath, matNew ) + self.plotPath( newpath, transform ) if ( not self.bStopped ): #an "index" for resuming plots quickly-- record last complete path self.svgLastPath += 1 self.svgLastPathNC = self.nodeCount @@ -794,7 +793,7 @@ def recursivelyTraverseSvg( self, aNodeList, t = node.get( 'transform' ) if t: newpath.set( 'transform', t ) - self.plotPath( newpath, matNew ) + self.plotPath( newpath, transform ) if ( not self.bStopped ): #an "index" for resuming plots quickly-- record last complete path self.svgLastPath += 1 self.svgLastPathNC = self.nodeCount @@ -978,10 +977,10 @@ def write_progress(done, total, msg): if self.options.ids: # Traverse the selected objects for id in self.options.ids: - self.recursivelyTraverseSvg( [self.selected[id]], self.docTransform ) + self.recursivelyTraverseSvg( [self.selected[id]] ) else: # Traverse the entire document - self.recursivelyTraverseSvg( self.document.getroot(), self.docTransform ) + self.recursivelyTraverseSvg( self.document.getroot() ) self.pen=None if self.options.tool == 'pen': self.pen=True From 71830958fa3317ac0427337bd8f33a0ced406656 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Fri, 23 Jun 2017 03:51:36 +0100 Subject: [PATCH 03/51] gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2f78cf5b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc + From d45d68ae2176ab94ac01c8bbae03356d9b4c66b5 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sat, 8 Jul 2017 00:46:44 +0100 Subject: [PATCH 04/51] handle svg:use properly --- sendto_silhouette.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sendto_silhouette.py b/sendto_silhouette.py index 6cb3ccc1..97605cfc 100644 --- a/sendto_silhouette.py +++ b/sendto_silhouette.py @@ -449,7 +449,8 @@ def DoWePlotLayer( self, strLayerName ): def recursivelyTraverseSvg( self, aNodeList, - parent_visibility='visible' ): + parent_visibility='visible', + extra_transform=IDENTITY_TRANSFORM ): """ Recursively traverse the svg file to plot out all of the paths. The function keeps track of the composite transformation @@ -472,7 +473,9 @@ def recursivelyTraverseSvg( self, aNodeList, pass # calculate this object's transform - transform = composeTransform(self.docTransform, composeParents(node, IDENTITY_TRANSFORM)) + transform = composeParents(node, IDENTITY_TRANSFORM) + transform = composeTransform(self.docTransform, transform) + transform = composeTransform(extra_transform, transform) if node.tag == inkex.addNS( 'g', 'svg' ) or node.tag == 'g': @@ -515,7 +518,7 @@ def recursivelyTraverseSvg( self, aNodeList, if ( x != 0 ) or (y != 0 ): transform = composeTransform( transform, parseTransform( 'translate(%f,%f)' % (x,y) ) ) v = node.get( 'visibility', v ) - self.recursivelyTraverseSvg( refnode, parent_visibility=v ) + self.recursivelyTraverseSvg( refnode, parent_visibility=v, extra_transform=transform ) else: pass else: From f936f1b1de9fe161a37e2f79a33b38339e499984 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Tue, 11 Jul 2017 21:52:56 +0100 Subject: [PATCH 05/51] set max pressure to correct value 33 --- sendto_silhouette.inx | 2 +- silhouette_multi.inx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sendto_silhouette.inx b/sendto_silhouette.inx index 21b49b9f..649d5b59 100644 --- a/sendto_silhouette.inx +++ b/sendto_silhouette.inx @@ -48,7 +48,7 @@ Pressure values of 19 or more make the machine misbehave. Beware. [P=30,S=10] Writing Paper 24-70 lbs (105g) 0 - 0 + 0 Use speed=0, pressure=0 to take the media defaults. false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. diff --git a/silhouette_multi.inx b/silhouette_multi.inx index 2440a712..52b705d0 100644 --- a/silhouette_multi.inx +++ b/silhouette_multi.inx @@ -50,7 +50,7 @@ Pressure values of 19 or more make the machine misbehave. Beware. [P=30,S=10] Writing Paper 24-70 lbs (105g) 0 - 0 + 0 Use speed=0, pressure=0 to take the media defaults. false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. @@ -110,7 +110,7 @@ Pressure values of 19 or more make the machine misbehave. Beware. [P=30,S=10] Writing Paper 24-70 lbs (105g) 0 - 0 + 0 Use speed=0, pressure=0 to take the media defaults. false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. @@ -170,7 +170,7 @@ Pressure values of 19 or more make the machine misbehave. Beware. [P=30,S=10] Writing Paper 24-70 lbs (105g) 0 - 0 + 0 Use speed=0, pressure=0 to take the media defaults. false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. @@ -230,7 +230,7 @@ Pressure values of 19 or more make the machine misbehave. Beware. [P=30,S=10] Writing Paper 24-70 lbs (105g) 0 - 0 + 0 Use speed=0, pressure=0 to take the media defaults. false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. @@ -290,7 +290,7 @@ Pressure values of 19 or more make the machine misbehave. Beware. [P=30,S=10] Writing Paper 24-70 lbs (105g) 0 - 0 + 0 Use speed=0, pressure=0 to take the media defaults. false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. From 255db42f52ce5f138c2ea3cd6a52b0aa5d97c8d1 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Tue, 11 Jul 2017 21:53:10 +0100 Subject: [PATCH 06/51] abort if an action fails --- silhouette_multi.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index 6407460e..eb4bb9be 100755 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -163,7 +163,12 @@ def effect(self): print >> sys.stderr, "\n".join(commands) else: for command in commands: - os.system(command) + status = os.system(command) + + if status != 0: + print >> sys.stderr, "command returned exit status %s: %s" % (status, command) + break + time.sleep(self.pause) sys.stdout = old_stdout From 2369114a0e78895b2f8e2f95471e61928c06f19c Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 12 Jul 2017 20:35:04 +0100 Subject: [PATCH 07/51] replace silhouette_multi entirely with a full gui version --- silhouette_multi.inx | 348 --------------------- silhouette_multi.py | 723 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 608 insertions(+), 463 deletions(-) mode change 100755 => 100644 silhouette_multi.py diff --git a/silhouette_multi.inx b/silhouette_multi.inx index 52b705d0..1f1ba6f1 100644 --- a/silhouette_multi.inx +++ b/silhouette_multi.inx @@ -5,354 +5,6 @@ org.inkscape.output.svg.inkscape inkex.py silhouette_multi.py - - - - 0.0 - 0.0 - - No action - Media default - Pen - Cut - - 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. -Pressure values of 19 or more make the machine misbehave. Beware. - - - - [P>0 S>0] Custom: use non-zero Pressure and Speed below - [P=27,S=10] SCard without Craft Paper Backing - [P=27,S=10] Card with Craft Paper Backing - [P=10,S=10] Vinyl Sticker - [P=14,S=10] Film Labels - [P=27,S=10] Thick Media - [P= 2,S=10] Thin Media - [P=10,S=10] Pen - [P=30,S=10] Bond Paper 13-28 lbs (105g) - [P=30,S=10] Bristol Paper 57-67 lbs (145g) - [P=30,S=10] Cardstock 40-60 lbs (90g) - [P=30,S=10] Cover 40-60 lbs (170g) - [P= 1,S=10] Film, Double Matte Translucent - [P= 1,S=10] Film, Vinyl With Adhesive Back - [P= 1,S=10] Film, Window With Kling Adhesive - [P=30,S=10] Index 90 lbs (165g) - [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) - [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) - [P=30,S= 3] Magnetic Sheet - [P=30,S=10] Offset 24-60 lbs (90g) - [P= 5,S=10] Print Paper Light Weight - [P=25,S=10] Print Paper Medium Weight - [P=20,S=10] Sticker Sheet - [P=20,S=10] Tag 100 lbs (275g) - [P=30,S=10] Text Paper 24-70 lbs (105g) - [P=30,S=10] Vellum Bristol 57-67 lbs (145g) - [P=30,S=10] Writing Paper 24-70 lbs (105g) - - 0 - 0 - Use speed=0, pressure=0 to take the media defaults. - false Convert paths with dashed strokes to separate subpaths for perforated cuts. - false Shift to the top lefthand corner, then do offsets. - false - To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) - 0.5 - 1 - false - - Start Position - Below Cut-Out - - Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. - - - - - 0.0 - 0.0 - - No action - Media default - Pen - Cut - - 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. -Pressure values of 19 or more make the machine misbehave. Beware. - - - - [P>0 S>0] Custom: use non-zero Pressure and Speed below - [P=27,S=10] SCard without Craft Paper Backing - [P=27,S=10] Card with Craft Paper Backing - [P=10,S=10] Vinyl Sticker - [P=14,S=10] Film Labels - [P=27,S=10] Thick Media - [P= 2,S=10] Thin Media - [P=10,S=10] Pen - [P=30,S=10] Bond Paper 13-28 lbs (105g) - [P=30,S=10] Bristol Paper 57-67 lbs (145g) - [P=30,S=10] Cardstock 40-60 lbs (90g) - [P=30,S=10] Cover 40-60 lbs (170g) - [P= 1,S=10] Film, Double Matte Translucent - [P= 1,S=10] Film, Vinyl With Adhesive Back - [P= 1,S=10] Film, Window With Kling Adhesive - [P=30,S=10] Index 90 lbs (165g) - [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) - [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) - [P=30,S= 3] Magnetic Sheet - [P=30,S=10] Offset 24-60 lbs (90g) - [P= 5,S=10] Print Paper Light Weight - [P=25,S=10] Print Paper Medium Weight - [P=20,S=10] Sticker Sheet - [P=20,S=10] Tag 100 lbs (275g) - [P=30,S=10] Text Paper 24-70 lbs (105g) - [P=30,S=10] Vellum Bristol 57-67 lbs (145g) - [P=30,S=10] Writing Paper 24-70 lbs (105g) - - 0 - 0 - Use speed=0, pressure=0 to take the media defaults. - false Convert paths with dashed strokes to separate subpaths for perforated cuts. - false Shift to the top lefthand corner, then do offsets. - false - To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) - 0.5 - 1 - false - - Start Position - Below Cut-Out - - Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. - - - - - 0.0 - 0.0 - - No action - Media default - Pen - Cut - - 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. -Pressure values of 19 or more make the machine misbehave. Beware. - - - - [P>0 S>0] Custom: use non-zero Pressure and Speed below - [P=27,S=10] SCard without Craft Paper Backing - [P=27,S=10] Card with Craft Paper Backing - [P=10,S=10] Vinyl Sticker - [P=14,S=10] Film Labels - [P=27,S=10] Thick Media - [P= 2,S=10] Thin Media - [P=10,S=10] Pen - [P=30,S=10] Bond Paper 13-28 lbs (105g) - [P=30,S=10] Bristol Paper 57-67 lbs (145g) - [P=30,S=10] Cardstock 40-60 lbs (90g) - [P=30,S=10] Cover 40-60 lbs (170g) - [P= 1,S=10] Film, Double Matte Translucent - [P= 1,S=10] Film, Vinyl With Adhesive Back - [P= 1,S=10] Film, Window With Kling Adhesive - [P=30,S=10] Index 90 lbs (165g) - [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) - [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) - [P=30,S= 3] Magnetic Sheet - [P=30,S=10] Offset 24-60 lbs (90g) - [P= 5,S=10] Print Paper Light Weight - [P=25,S=10] Print Paper Medium Weight - [P=20,S=10] Sticker Sheet - [P=20,S=10] Tag 100 lbs (275g) - [P=30,S=10] Text Paper 24-70 lbs (105g) - [P=30,S=10] Vellum Bristol 57-67 lbs (145g) - [P=30,S=10] Writing Paper 24-70 lbs (105g) - - 0 - 0 - Use speed=0, pressure=0 to take the media defaults. - false Convert paths with dashed strokes to separate subpaths for perforated cuts. - false Shift to the top lefthand corner, then do offsets. - false - To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) - 0.5 - 1 - false - - Start Position - Below Cut-Out - - Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. - - - - - 0.0 - 0.0 - - No action - Media default - Pen - Cut - - 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. -Pressure values of 19 or more make the machine misbehave. Beware. - - - - [P>0 S>0] Custom: use non-zero Pressure and Speed below - [P=27,S=10] SCard without Craft Paper Backing - [P=27,S=10] Card with Craft Paper Backing - [P=10,S=10] Vinyl Sticker - [P=14,S=10] Film Labels - [P=27,S=10] Thick Media - [P= 2,S=10] Thin Media - [P=10,S=10] Pen - [P=30,S=10] Bond Paper 13-28 lbs (105g) - [P=30,S=10] Bristol Paper 57-67 lbs (145g) - [P=30,S=10] Cardstock 40-60 lbs (90g) - [P=30,S=10] Cover 40-60 lbs (170g) - [P= 1,S=10] Film, Double Matte Translucent - [P= 1,S=10] Film, Vinyl With Adhesive Back - [P= 1,S=10] Film, Window With Kling Adhesive - [P=30,S=10] Index 90 lbs (165g) - [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) - [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) - [P=30,S= 3] Magnetic Sheet - [P=30,S=10] Offset 24-60 lbs (90g) - [P= 5,S=10] Print Paper Light Weight - [P=25,S=10] Print Paper Medium Weight - [P=20,S=10] Sticker Sheet - [P=20,S=10] Tag 100 lbs (275g) - [P=30,S=10] Text Paper 24-70 lbs (105g) - [P=30,S=10] Vellum Bristol 57-67 lbs (145g) - [P=30,S=10] Writing Paper 24-70 lbs (105g) - - 0 - 0 - Use speed=0, pressure=0 to take the media defaults. - false Convert paths with dashed strokes to separate subpaths for perforated cuts. - false Shift to the top lefthand corner, then do offsets. - false - To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) - 0.5 - 1 - false - - Start Position - Below Cut-Out - - Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. - - - - - 0.0 - 0.0 - - No action - Media default - Pen - Cut - - 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. -Pressure values of 19 or more make the machine misbehave. Beware. - - - - [P>0 S>0] Custom: use non-zero Pressure and Speed below - [P=27,S=10] SCard without Craft Paper Backing - [P=27,S=10] Card with Craft Paper Backing - [P=10,S=10] Vinyl Sticker - [P=14,S=10] Film Labels - [P=27,S=10] Thick Media - [P= 2,S=10] Thin Media - [P=10,S=10] Pen - [P=30,S=10] Bond Paper 13-28 lbs (105g) - [P=30,S=10] Bristol Paper 57-67 lbs (145g) - [P=30,S=10] Cardstock 40-60 lbs (90g) - [P=30,S=10] Cover 40-60 lbs (170g) - [P= 1,S=10] Film, Double Matte Translucent - [P= 1,S=10] Film, Vinyl With Adhesive Back - [P= 1,S=10] Film, Window With Kling Adhesive - [P=30,S=10] Index 90 lbs (165g) - [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) - [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) - [P=30,S= 3] Magnetic Sheet - [P=30,S=10] Offset 24-60 lbs (90g) - [P= 5,S=10] Print Paper Light Weight - [P=25,S=10] Print Paper Medium Weight - [P=20,S=10] Sticker Sheet - [P=20,S=10] Tag 100 lbs (275g) - [P=30,S=10] Text Paper 24-70 lbs (105g) - [P=30,S=10] Vellum Bristol 57-67 lbs (145g) - [P=30,S=10] Writing Paper 24-70 lbs (105g) - - 0 - 0 - Use speed=0, pressure=0 to take the media defaults. - false Convert paths with dashed strokes to separate subpaths for perforated cuts. - false Shift to the top lefthand corner, then do offsets. - false - To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) - 0.5 - 1 - false - - Start Position - Below Cut-Out - - Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. - - - - - false - The document contains printed registration marks - false - Search for the registration marks automatically. Seems on some devices there is another mode, where you can set the references manually. - 180 - 230 - 15 - 20 - Distance of the registration mark edges - - - - false - Keep dialog open until device becomes idle again. - false - Subdivide, sort, and choose cut directions, so that most a cutting mat is not needed in most cases. - 5 - 1 - false - - - - -Always use the least amount of blade possible. - -1) Take a sheet of the media you are trying to cut and fold it in half. - -2) Take the blade out of the machine, set it to 1 and hold it in your hand as you would a pen but held vertically as it would be in the machine. - -3) Get your folded media and with your blade held like a pen but kept vertically press firmly down on the media and 'draw' a line. - -4) Next have a look at the media; with the correct setting you should have just cut a line through the top layer of the folded card without cutting in to the back layer. If you have not cut through the media, increase the blade by 1 position and repeat from step 3. - -5) Keep doing this until you reach the correct setting to cut the top layer without cutting the back. - -6) Once this is done the blade can be put back in to the machine. - - - - - inkscape-silhouette extension from https://github.com/jnweiger/inkscape-silhouette by Jürgen Weigert [juewei@fabmail.org] and contributors - - Version 1.19 - - all diff --git a/silhouette_multi.py b/silhouette_multi.py old mode 100755 new mode 100644 index eb4bb9be..c9a19e0b --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -1,124 +1,621 @@ #!/usr/bin/env python - -import sys, os +# -*- coding: UTF-8 -*- + +import os +import sys +import cPickle +from tempfile import NamedTemporaryFile +from collections import defaultdict, OrderedDict +import xmltodict +import traceback +from cStringIO import StringIO +import wx +from wx.lib.scrolledpanel import ScrolledPanel +from wx.lib.agw import ultimatelistctrl as ulc +from wx.lib.embeddedimage import PyEmbeddedImage from collections import defaultdict -import math -import struct -import time import inkex import simplestyle +from functools import partial +from itertools import groupby -class SilhouetteMulti(inkex.Effect): - def parse_args(self, argv): - args = {} - ignored = [] - for arg in argv: - if arg.startswith('--'): - arg = arg[2:] +small_up_arrow = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADxJ" + "REFUOI1jZGRiZqAEMFGke2gY8P/f3/9kGwDTjM8QnAaga8JlCG3CAJdt2MQxDCAUaOjyjKMp" + "cRAYAABS2CPsss3BWQAAAABJRU5ErkJggg==") + +small_down_arrow = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAEhJ" + "REFUOI1jZGRiZqAEMFGke9QABgYGBgYWdIH///7+J6SJkYmZEacLkCUJacZqAD5DsInTLhDR" + "bcPlKrwugGnCFy6Mo3mBAQChDgRlP4RC7wAAAABJRU5ErkJggg==") + + + +def presets_path(): + try: + import appdirs + config_path = appdirs.user_config_dir('inkscape-silhouette') + except ImportError: + config_path = os.path.expanduser('~/.inkscape-silhouette') + + if not os.path.exists(config_path): + os.makedirs(config_path) + return os.path.join(config_path, 'presets.cPickle') + +def load_presets(): + try: + with open(presets_path(), 'r') as presets: + presets = cPickle.load(presets) + return presets + except: + return {} + +def save_presets(presets): + #print "saving presets", presets + with open(presets_path(), 'w') as presets_file: + cPickle.dump(presets, presets_file) + + +def load_preset(name): + return load_presets().get(name) + + +def save_preset(name, data): + presets = load_presets() + presets[name] = data + save_presets(presets) + + +def delete_preset(name): + presets = load_presets() + presets.pop(name, None) + save_presets(presets) + + +def confirm_dialog(parent, question, caption = 'Silhouette Multiple Actions'): + dlg = wx.MessageDialog(parent, question, caption, wx.YES_NO | wx.ICON_QUESTION) + result = dlg.ShowModal() == wx.ID_YES + dlg.Destroy() + return result + + +def info_dialog(parent, message, caption = 'Silhouette Multiple Actions'): + dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + +class ParamsNotebook(wx.Notebook): + """Handle a notebook of tabs that contain params. + + Each param has a name, and all names are globally unique across all tabs. + """ + + def __init__(self, *args, **kwargs): + wx.Notebook.__init__(self, *args, **kwargs) + + self.load_inx() + self.create_tabs() + self.add_tabs() + + def load_inx(self): + with open('sendto_silhouette.inx') as inx_file: + self.inx = xmltodict.parse(inx_file, force_list=('param',)) + + def create_tabs(self): + self.notebook = self.inx['inkscape-extension']['param'][0] + + if self.notebook['@type'] != 'notebook': + print >> sys.stderr, "unexpected INX format" + return + + self.tabs = [] + for page in self.notebook['page']: + self.tabs.append(ParamsTab(self, wx.ID_ANY, name=page['@name'], title=page['@_gui-text'], params=page['param'])) + + def add_tabs(self): + for tab in self.tabs: + self.AddPage(tab, tab.title) + + def get_values(self): + values = {} + + for tab in self.tabs: + values.update(tab.get_values()) + + return values + + def get_defaults(self): + values = {} + + for tab in self.tabs: + values.update(tab.get_defaults()) + + return values + + + def set_values(self, values): + for tab in self.tabs: + tab.set_values(values) + + + def set_defaults(self): + for tab in self.tabs: + tab.set_defaults() + +class ParamsTab(ScrolledPanel): + def __init__(self, *args, **kwargs): + self.params = kwargs.pop('params', []) + self.name = kwargs.pop('name', None) + self.title = kwargs.pop('title', None) + kwargs["style"] = wx.TAB_TRAVERSAL + ScrolledPanel.__init__(self, *args, **kwargs) + self.SetupScrolling() + + self.param_inputs = {} + self.choices_by_label = {} + self.choices_by_value = {} + self.defaults = {} + + self.settings_grid = wx.GridBagSizer(hgap=0, vgap=0) + self.settings_grid.AddGrowableCol(0, 1) + self.settings_grid.SetFlexibleDirection(wx.HORIZONTAL) + + self.__set_properties() + self.__do_layout() + + def get_values(self): + values = {} + + for name, input in self.param_inputs.iteritems(): + if isinstance(input, wx.Choice): + choice = input.GetSelection() + + if choice == wx.NOT_FOUND: + values[name] = None + else: + values[name] = self.choices_by_label[name][input.GetString(choice)] else: - ignored.append(arg) + values[name] = input.GetValue() + return values + + def get_defaults(self): + return self.defaults + + def set_values(self, values): + for name, value in values.iteritems(): + if name not in self.param_inputs: + # ignore params not contained in this tab continue - k, v = arg.split('=', 1) + input = self.param_inputs[name] + if isinstance(input, wx.Choice): + if value is None: + input.SetSelection(wx.NOT_FOUND) + else: + label = self.choices_by_value[name][value] + input.SetStringSelection(label) + else: + input.SetValue(value) + + def set_defaults(self): + self.set_values(self.defaults) + + def __set_properties(self): + # begin wxGlade: SatinPane.__set_properties + # end wxGlade + pass + + def __do_layout(self): + # just to add space around the settings + box = wx.BoxSizer(wx.VERTICAL) + + for row, param in enumerate(self.params): + param_type = param['@type'] + param_name = param['@name'] + if param_type == 'description': + self.settings_grid.Add(wx.StaticText(self, label=param.get('#text', '')), + pos=(row, 0), span=(1, 2), flag=wx.EXPAND|wx.LEFT|wx.ALIGN_TOP, border=10) + else: + self.settings_grid.Add(wx.StaticText(self, label=param.get('@_gui-text', '')), + pos=(row, 0), flag=wx.EXPAND|wx.TOP|wx.ALIGN_TOP, border=5) + + if param_type == 'boolean': + input = wx.CheckBox(self) + elif param_type == 'float': + input = wx.SpinCtrlDouble(self, wx.ID_ANY, min=float(param.get('@min', 0.0)), max=float(param.get('@max', 2.0**32)), inc=0.1, value=param.get('#text', '')) + elif param_type == 'int': + input = wx.SpinCtrl(self, wx.ID_ANY, min=int(param.get('@min', 0)), max=int(param.get('@max', 2**32)), value=param.get('#text', '')) + elif param_type == 'enum': + choices = OrderedDict((item['#text'], item['@value']) for item in param['item']) + self.choices_by_label[param_name] = choices + self.choices_by_value[param_name] = { v: k for k, v in choices.iteritems() } + input = wx.Choice(self, wx.ID_ANY, choices=choices.keys(), style=wx.LB_SINGLE) + input.SetStringSelection(choices.keys()[0]) + else: + # not sure what else to do here... + continue + + self.param_inputs[param_name] = input + + self.settings_grid.Add(input, pos=(row, 1), flag=wx.ALIGN_BOTTOM|wx.TOP, border=5) + + self.defaults = self.get_values() + + box.Add(self.settings_grid, proportion=1, flag=wx.ALL, border=10) + self.SetSizer(box) + + self.Layout() + + +class SilhouetteMultiFrame(wx.Frame): + def __init__(self, *args, **kwargs): + # begin wxGlade: MyFrame.__init__ + self.colors = kwargs.pop('colors', []) + self.run_callback = kwargs.pop('run_callback') + wx.Frame.__init__(self, None, wx.ID_ANY, + "Silhouette Multi-Action" + ) + + self.selected = None + self.color_settings = {} + self.color_enabled = {} + self.notebook = ParamsNotebook(self, wx.ID_ANY) + self.up_button = wx.Button(self, wx.ID_UP) + self.down_button = wx.Button(self, wx.ID_DOWN) + self.run_button = wx.Button(self, wx.ID_EXECUTE) + self.cancel_button = wx.Button(self, wx.ID_CANCEL, "Cancel") + + self.presets_box = wx.StaticBox(self, wx.ID_ANY, label="Presets") + self.preset_chooser = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_SORT) + self.load_preset_button = wx.Button(self, wx.ID_REVERT_TO_SAVED, "Load") + self.add_preset_button = wx.Button(self, wx.ID_SAVE, "Add") + self.overwrite_preset_button = wx.Button(self, wx.ID_SAVEAS, "Overwrite") + self.delete_preset_button = wx.Button(self, wx.ID_DELETE, "Delete") + + self.update_preset_list() + self.init_actions() + + self.Bind(wx.EVT_BUTTON, self.move_up, self.up_button) + self.Bind(wx.EVT_BUTTON, self.move_down, self.down_button) + self.Bind(wx.EVT_BUTTON, self.run, self.run_button) + self.Bind(wx.EVT_BUTTON, self.load_preset, self.load_preset_button) + self.Bind(wx.EVT_BUTTON, self.add_preset, self.add_preset_button) + self.Bind(wx.EVT_BUTTON, self.overwrite_preset, self.overwrite_preset_button) + self.Bind(wx.EVT_BUTTON, self.delete_preset, self.delete_preset_button) + self.Bind(wx.EVT_BUTTON, self.close, self.cancel_button) + + self._load_preset('__LAST__') + + self.__set_properties() + self.__do_layout() + # end wxGlade + + def close(self, event): + self.Close() + + def load_preset(self, event): + preset_name = self.get_preset_name() + if not preset_name: + return + + self._load_preset(preset_name) + + def add_preset(self, event, overwrite=False): + preset_name = self.get_preset_name() + if not preset_name: + return + + if not overwrite and load_preset(preset_name): + info_dialog(self, 'Preset "%s" already exists. Please use another name or press "Overwrite"' % preset_name, caption='Preset') + + self.save_color_settings() + save_preset(preset_name, self.get_preset_data()) + self.update_preset_list() + + event.Skip() + + def overwrite_preset(self, event): + self.add_preset(event, overwrite=True) + + def delete_preset(self, event): + preset_name = self.get_preset_name() + if not preset_name: + return + + preset = self.check_and_load_preset(preset_name) + if not preset: + return + + delete_preset(preset_name) + self.update_preset_list() + self.preset_chooser.SetValue("") - if k == "id": - # ignore selected items, because we're going to create selections - # based on colors - continue + event.Skip() - args[k] = v + def check_and_load_preset(self, preset_name): + preset = load_preset(preset_name) + if not preset: + info_dialog(self, 'Preset "%s" not found.' % preset_name, caption='Preset') - return args, ignored + return preset - def mock_options(self): - class FakeOptions: - ids = [] + def get_preset_name(self): + preset_name = self.preset_chooser.GetValue().strip() + if preset_name: + return preset_name + else: + info_dialog(self, "Please enter or select a preset name first.", caption='Preset') + return - self.options = FakeOptions() + def update_preset_list(self): + preset_names = load_presets().keys() + preset_names = [preset for preset in preset_names if not preset.startswith("__")] + self.preset_chooser.SetItems(preset_names) - def getoptions(self, args=sys.argv[1:]): - # getoptions() is called automatically by affect() and uses optparse. - # We just want to store the args to pass through, so we override it. + def _load_preset(self, preset_name): + preset = load_preset(preset_name) - self.mock_options() + if not preset: + return - self.globals = {} - self.extra_args = [] - self.actions = defaultdict(dict) + if self.selected: + self.actions.Select(self.selected, False) - args, self.extra_args = self.parse_args(args) + self.colors = preset['colors'] + self.color_enabled = preset['color_enabled'] + self.color_settings = preset['color_settings'] - for k, v in args.iteritems(): - if k.startswith('action'): - # example: action1_color - action_name, k = k.split('_', 1) - action_num = int(action_name[6:]) + self.refresh_actions() - self.actions[action_num][k] = v - else: - self.globals[k] = v + def _save_preset(self, preset_name): + self.save_color_settings() - self.tolerance = float(self.globals.pop('tolerance', 0)) - self.pause = float(self.globals.pop('pause', 0)) - self.debug = self.globals.pop('debug') == "true" + preset = self.get_preset_data() + save_preset(preset_name, preset) + def get_preset_data(self): + return { 'colors': self.colors, + 'color_enabled': self.color_enabled, + 'color_settings': self.color_settings } - def integer_to_rgb(self, color): - # takes a packed 4-byte signed integer color like -16711681 and unpacks - # it into a tuple of (red, green, blue) as unsigned integers - # in the range 0-255. Alpha is discarded. - return struct.unpack("BBBB", struct.pack(">i", color))[0:3] + def run(self, event): + self.save_color_settings() - def colors_match(self, color1, color2): - # Do these colors match? - # - # Compare using self.tolerance, which specifies the maximum allowed - # "distance". - # - # Colors are (r, g, b) tuples + actions = [] - distance = math.sqrt((color1[0] - color2[0])**2 + \ - (color1[1] - color2[1])**2 + \ - (color1[2] - color2[2])**2) + for color in self.colors: + if self.color_enabled.get(color, True): + actions.append((color, self.color_settings.get(color) or self.notebook.get_defaults())) - return distance <= self.tolerance + if actions: + if not confirm_dialog(self, "About to perform %d actions, continue?" % len(actions)): + return + else: + info_dialog("No colors were enabled, so no actions can be performed.") + return - def get_node_stroke_color(self, node): - if node.get('style') is None: - return None + self._save_preset('__LAST__') + self.run_callback(actions) - style = simplestyle.parseStyle(node.get('style')) + def move_up(self, event): + if self.selected is None or self.selected == 0: + return - if not style.get('stroke'): - return None + this = self.selected + prev = this - 1 - return simplestyle.parseColor(style.get('stroke')) + self.colors[this], self.colors[prev] = self.colors[prev], self.colors[this] + self.actions.Select(this, False) + self.actions.Select(prev) - def get_nodes_by_color(self, color, element=None, parent_visibility="visible"): - if element is None: - element = self.document.getroot() + self.refresh_actions() - nodes_by_color = [] + def move_down(self, event): + if self.selected is None or self.selected == len(self.colors) - 1: + return - for node in element: - visibility = node.get('visibility', parent_visibility) + this = self.selected + next = this + 1 + + self.colors[this], self.colors[next] = self.colors[next], self.colors[this] + self.actions.Select(this, False) + self.actions.Select(next) + + self.refresh_actions() + + def action_selected(self, event=None): + # first, save the settings for the color they were previously working on + self.save_color_settings() + + # then load the settings for the newly-selected color + self.selected = event.m_itemIndex + self.load_color_settings() + + self.up_button.Enable() + self.down_button.Enable() + self.notebook.Enable() + + def action_deselected(self, event=None): + self.save_color_settings() + + self.selected = None + + self.up_button.Disable() + self.down_button.Disable() + self.notebook.Disable() + + def load_color_settings(self): + color = self.colors[self.selected] + settings = self.color_settings.get(color) + + if settings: + self.notebook.set_values(settings) + else: + self.notebook.set_defaults() + + def save_color_settings(self): + #print "save:", self.selected + + if self.selected is None: + return + + color = self.colors[self.selected] + settings = self.notebook.get_values() + self.color_settings[color] = settings + + #print "settings:", settings + + def item_checked(self, event): + item = event.m_itemIndex + checked = self.actions.IsItemChecked(item, 2) + self.color_enabled[self.colors[item]] = checked + + def init_actions(self): + self.actions = ulc.UltimateListCtrl(self, size=(300, 150), agwStyle=wx.LC_REPORT|ulc.ULC_HRULES|ulc.ULC_SINGLE_SEL) + + self.Bind(ulc.EVT_LIST_ITEM_SELECTED, self.action_selected, self.actions) + self.Bind(ulc.EVT_LIST_ITEM_DESELECTED, self.action_deselected, self.actions) + self.Bind(ulc.EVT_LIST_ITEM_CHECKED, self.item_checked, self.actions) + self.action_deselected() + + self.actions.InsertColumn(0, "Step") + self.actions.InsertColumn(1, "Color") + self.actions.InsertColumn(2, "Perform Action?") + self.actions.SetColumnWidth(2, ulc.ULC_AUTOSIZE_FILL) + + self.action_checkboxes = [] + + for i, color in enumerate(self.colors): + self.actions.InsertStringItem(i, "%d." % (i + 1)) + + item = self.actions.GetItem(i, 2) + item.SetKind(1) # "a checkbox-like item" + item.SetMask(ulc.ULC_MASK_KIND) + self.actions.SetItem(item) + + self.refresh_actions() + + def refresh_actions(self): + for i, color in enumerate(self.colors): + item = self.actions.GetItem(i, 1) + item.SetMask(ulc.ULC_MASK_BACKCOLOUR) + item.SetBackgroundColour(wx.Colour(*color)) + self.actions.SetItem(item) + + item = self.actions.GetItem(i, 2) + item.Check(self.color_enabled.get(color, True)) + item.SetMask(ulc.ULC_MASK_CHECK) + self.actions.SetItem(item) + + + def __set_properties(self): + # begin wxGlade: MyFrame.__set_properties + self.SetTitle("Silhouette Multi-Action") + self.notebook.SetMinSize((800, 500)) + # end wxGlade + + def __do_layout(self): + # begin wxGlade: MyFrame.__do_layout + sizer_1 = wx.BoxSizer(wx.VERTICAL) + + sizer_2 = wx.BoxSizer(wx.HORIZONTAL) + sizer_2.Add(self.actions, 0, flag=wx.ALL|wx.EXPAND, border=10) + + sizer_3 = wx.BoxSizer(wx.VERTICAL) + sizer_3.Add(self.up_button, 0, border=10) + sizer_3.Add(self.down_button, 0, border=10) + + sizer_2.Add(sizer_3, 0, flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=10) + + sizer_4 = wx.StaticBoxSizer(self.presets_box, wx.VERTICAL) + sizer_4.Add(self.preset_chooser, 0, flag=wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.TOP|wx.EXPAND, border=10) + + sizer_5 = wx.BoxSizer(wx.HORIZONTAL) + sizer_5.Add(self.load_preset_button, 0, flag=wx.RIGHT|wx.LEFT|wx.BOTTOM, border=10) + sizer_5.Add(self.add_preset_button, 0, flag=wx.RIGHT, border=10) + sizer_5.Add(self.overwrite_preset_button, 0, flag=wx.RIGHT, border=10) + sizer_5.Add(self.delete_preset_button, 0, flag=wx.RIGHT, border=10) + + sizer_4.Add(sizer_5, 0) + sizer_2.Add(sizer_4, 0, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=30) + + sizer_6 = wx.BoxSizer(wx.VERTICAL) + sizer_6.Add(self.run_button, 0, flag=wx.ALIGN_RIGHT|wx.BOTTOM, border=10) + sizer_6.Add(self.cancel_button, 0, flag=wx.ALIGN_RIGHT|wx.EXPAND) + sizer_2.Add(sizer_6, 0, flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=30) + + sizer_1.Add(sizer_2, 0, flag=wx.EXPAND|wx.ALL, border=10) + + sizer_1.Add(self.notebook, 1, wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, 10) + self.SetSizer(sizer_1) + sizer_1.Fit(self) + self.Layout() + # end wxGlade + +class SilhouetteMulti(inkex.Effect): + def get_style(self, element): + if element.get('style') is not None: + return simplestyle.parseStyle(element.get('style')) + else: + return {} + + return style + + def get_stroke_color(self, element): + stroke = self.get_style(element).get('stroke') + + if stroke is not None and stroke != 'none': + return simplestyle.parseColor(stroke) + else: + return None + + def load_selected_objects(self): + self.selected_objects = set() + + def traverse_element(element, selected=False, parent_visibility="visible"): + if self.get_style(element).get('display') == 'none': + return + + visibility = element.get('visibility', parent_visibility) if visibility == 'inherit': visibility = parent_visibility - if visibility in ('hidden', 'collapse'): - return [] + if element.get('id') in self.selected: + selected = True + + if selected and visibility not in ('hidden', 'collapse'): + self.selected_objects.add(element) - node_color = self.get_node_stroke_color(node) + for child in element: + traverse_element(child, visibility) - if node_color and self.colors_match(color, node_color): - nodes_by_color.append(node) + traverse_element(self.document.getroot()) - nodes_by_color.extend(self.get_nodes_by_color(color, node, visibility)) + def split_objects_by_color(self): + self.objects_by_color = defaultdict(list) + self.load_selected_objects() - return nodes_by_color + for obj in self.selected_objects: + color = self.get_stroke_color(obj) + if color: + self.objects_by_color[color].append(obj) + + def effect(self): + app = wx.App() + self.split_objects_by_color() + self.frame = SilhouetteMultiFrame(colors=self.objects_by_color.keys(), run_callback=self.run) + self.frame.Show() + app.MainLoop() + + def save_copy(self): + f = NamedTemporaryFile(suffix='.svg', prefix='silhouette-multiple-actions') + self.document.write(f) + f.flush() + return f def format_args(self, args): if isinstance(args, dict): @@ -126,58 +623,54 @@ def format_args(self, args): return " ".join(("--%s=%s" % (k, v) for k, v in args)) - def global_args(self): - return self.format_args(self.globals) - def id_args(self, nodes): return self.format_args(("id", node.get("id")) for node in nodes) - def effect(self): - # any output on stdout crashes inkscape, so let's avoid that - old_stdout = sys.stdout - sys.stdout = sys.stderr + def run(self, actions): + svg_file = self.save_copy() - commands = [] + for color, settings in actions: + command = "python sendto_silhouette.py " + command += self.format_args(settings) + command += self.id_args(self.objects_by_color[color]) + command += " " + svg_file.name - for action_num in sorted(self.actions.keys()): - action = self.actions[action_num] + #print >> sys.stderr, command - if action['tool'] == 'none': - continue + status = os.system(command) - color = self.integer_to_rgb(int(action.pop('color'))) - nodes = self.get_nodes_by_color(color) + if status != 0: + print >> sys.stderr, "command returned exit status %s: %s" % (status, command) + break - if not nodes: - continue - command = ("python sendto_silhouette.py" + " " + - self.global_args() + " " + - self.format_args(action) + " " + - self.id_args(nodes) + " " + - " ".join(self.extra_args)) - commands.append(command) + self.frame.Close() - if self.debug: - print >> sys.stderr, "\n".join(commands) - else: - for command in commands: - status = os.system(command) - if status != 0: - print >> sys.stderr, "command returned exit status %s: %s" % (status, command) - break +def save_stderr(): + # GTK likes to spam stderr, which inkscape will show in a dialog. + null = open('/dev/null', 'w') + sys.stderr_dup = os.dup(sys.stderr.fileno()) + os.dup2(null.fileno(), 2) + sys.stderr_backup = sys.stderr + sys.stderr = StringIO() - time.sleep(self.pause) - sys.stdout = old_stdout +def restore_stderr(): + os.dup2(sys.stderr_dup, 2) + sys.stderr_backup.write(sys.stderr.getvalue()) + sys.sys.stderr = stderr_backup +# end of class MyFrame if __name__ == "__main__": - #print >> sys.stderr, " ".join(sys.argv) - #sys.exit(0) + save_stderr() - s = SilhouetteMulti() - s.affect() + try: + e = SilhouetteMulti() + e.affect() + except: + traceback.print_exc() + restore_stderr() From f27f9aa231703d8512b50e208bf3ba40d66b9f59 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 12 Jul 2017 21:04:52 +0100 Subject: [PATCH 08/51] try to reassign colors when loading preset --- silhouette_multi.py | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index c9a19e0b..8c67b7a7 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -364,9 +364,42 @@ def _load_preset(self, preset_name): if self.selected: self.actions.Select(self.selected, False) - self.colors = preset['colors'] - self.color_enabled = preset['color_enabled'] - self.color_settings = preset['color_settings'] + old_colors = self.colors + self.colors = [] + extra_colors = [] + + for color in preset['colors']: + if color in old_colors: + old_colors.remove(color) + self.colors.append(color) + self.color_enabled[color] = preset['color_enabled'].get(color, True) + self.color_settings[color] = preset['color_settings'].get(color, {}) + else: + extra_colors.append(color) + + reassigned = 0 + # If there are any leftover colors in this SVG that weren't in the + # preset, we have to add them back into the list. Let's try to + # use the settings from one of the "unclaimed" colors in the preset. + + for color in old_colors: + if extra_colors: + reassigned += 1 + assigned_color = extra_colors.pop(0) + self.colors.append(color) + self.color_enabled[color] = preset['color_enabled'].get(assigned_color, True) + self.color_settings[color] = preset['color_settings'].get(assigned_color, {}) + + message = [] + + if reassigned: + message.append("%d colors were reassigned." % reassigned) + + if extra_colors: + message.append("%d colors from the preset were not used." % len(extra_colors)) + + if message: + info_dialog(self, "Colors in the preset and this SVG did not match fully. " + " ".join(message)) self.refresh_actions() @@ -514,7 +547,7 @@ def refresh_actions(self): def __set_properties(self): # begin wxGlade: MyFrame.__set_properties self.SetTitle("Silhouette Multi-Action") - self.notebook.SetMinSize((800, 500)) + self.notebook.SetMinSize((800, 800)) # end wxGlade def __do_layout(self): From 38b11f79bcd6a7e17403d46fc610c634fcd78f99 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 13 Jul 2017 01:12:07 +0100 Subject: [PATCH 09/51] add dry run mode and a couple small bugfixes --- silhouette_multi.inx | 2 ++ silhouette_multi.py | 45 ++++++++++++++++++++++++++++---------------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/silhouette_multi.inx b/silhouette_multi.inx index 1f1ba6f1..bd780fa1 100644 --- a/silhouette_multi.inx +++ b/silhouette_multi.inx @@ -6,6 +6,8 @@ inkex.py silhouette_multi.py + false + all diff --git a/silhouette_multi.py b/silhouette_multi.py index 8c67b7a7..28d81a2f 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -254,6 +254,7 @@ class SilhouetteMultiFrame(wx.Frame): def __init__(self, *args, **kwargs): # begin wxGlade: MyFrame.__init__ self.colors = kwargs.pop('colors', []) + self.options = kwargs.pop('options') self.run_callback = kwargs.pop('run_callback') wx.Frame.__init__(self, None, wx.ID_ANY, "Silhouette Multi-Action" @@ -287,7 +288,7 @@ def __init__(self, *args, **kwargs): self.Bind(wx.EVT_BUTTON, self.delete_preset, self.delete_preset_button) self.Bind(wx.EVT_BUTTON, self.close, self.cancel_button) - self._load_preset('__LAST__') + self._load_preset('__LAST__', silent=True) self.__set_properties() self.__do_layout() @@ -355,9 +356,11 @@ def update_preset_list(self): preset_names = [preset for preset in preset_names if not preset.startswith("__")] self.preset_chooser.SetItems(preset_names) - def _load_preset(self, preset_name): + def _load_preset(self, preset_name, silent=False): preset = load_preset(preset_name) + #print >> sys.stderr, preset + if not preset: return @@ -383,22 +386,26 @@ def _load_preset(self, preset_name): # use the settings from one of the "unclaimed" colors in the preset. for color in old_colors: + self.colors.append(color) if extra_colors: reassigned += 1 assigned_color = extra_colors.pop(0) - self.colors.append(color) self.color_enabled[color] = preset['color_enabled'].get(assigned_color, True) self.color_settings[color] = preset['color_settings'].get(assigned_color, {}) message = [] + #print >> sys.stderr, reassigned, extra_colors + #print >> sys.stderr, self.colors + #print >> sys.stderr, self.color_settings + if reassigned: message.append("%d colors were reassigned." % reassigned) if extra_colors: message.append("%d colors from the preset were not used." % len(extra_colors)) - if message: + if message and not silent: info_dialog(self, "Colors in the preset and this SVG did not match fully. " + " ".join(message)) self.refresh_actions() @@ -424,10 +431,11 @@ def run(self, event): actions.append((color, self.color_settings.get(color) or self.notebook.get_defaults())) if actions: - if not confirm_dialog(self, "About to perform %d actions, continue?" % len(actions)): - return + if not self.options.dry_run: + if not confirm_dialog(self, "About to perform %d actions, continue?" % len(actions)): + return else: - info_dialog("No colors were enabled, so no actions can be performed.") + info_dialog(self, "No colors were enabled, so no actions can be performed.") return self._save_preset('__LAST__') @@ -589,6 +597,11 @@ def __do_layout(self): # end wxGlade class SilhouetteMulti(inkex.Effect): + def __init__(self, *args, **kwargs): + inkex.Effect.__init__(self, *args, **kwargs) + + self.OptionParser.add_option("-d", "--dry_run", type='inkbool', default=False) + def get_style(self, element): if element.get('style') is not None: return simplestyle.parseStyle(element.get('style')) @@ -624,7 +637,7 @@ def traverse_element(element, selected=False, parent_visibility="visible"): self.selected_objects.add(element) for child in element: - traverse_element(child, visibility) + traverse_element(child, selected, visibility) traverse_element(self.document.getroot()) @@ -640,7 +653,7 @@ def split_objects_by_color(self): def effect(self): app = wx.App() self.split_objects_by_color() - self.frame = SilhouetteMultiFrame(colors=self.objects_by_color.keys(), run_callback=self.run) + self.frame = SilhouetteMultiFrame(colors=self.objects_by_color.keys(), run_callback=self.run, options=self.options) self.frame.Show() app.MainLoop() @@ -669,14 +682,14 @@ def run(self, actions): command += " " + svg_file.name #print >> sys.stderr, command + if self.options.dry_run: + print >> sys.stderr, command + else: + status = os.system(command) - status = os.system(command) - - if status != 0: - print >> sys.stderr, "command returned exit status %s: %s" % (status, command) - break - - + if status != 0: + print >> sys.stderr, "command returned exit status %s: %s" % (status, command) + break self.frame.Close() From 04bedc3848a383d373af11b2bdccaf00c378319f Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 13 Jul 2017 03:02:07 +0100 Subject: [PATCH 10/51] several bugfixes --- silhouette_multi.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index 28d81a2f..95226915 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -619,7 +619,7 @@ def get_stroke_color(self, element): return None def load_selected_objects(self): - self.selected_objects = set() + self.selected_objects = [] def traverse_element(element, selected=False, parent_visibility="visible"): if self.get_style(element).get('display') == 'none': @@ -634,12 +634,18 @@ def traverse_element(element, selected=False, parent_visibility="visible"): selected = True if selected and visibility not in ('hidden', 'collapse'): - self.selected_objects.add(element) + self.selected_objects.append(element) for child in element: traverse_element(child, selected, visibility) - traverse_element(self.document.getroot()) + # if they haven't selected specific objects, then process all objects + if self.selected: + select_all = False + else: + select_all = True + + traverse_element(self.document.getroot(), selected=select_all) def split_objects_by_color(self): self.objects_by_color = defaultdict(list) @@ -673,12 +679,15 @@ def id_args(self, nodes): return self.format_args(("id", node.get("id")) for node in nodes) def run(self, actions): + self.frame.Close() + restore_stderr() + svg_file = self.save_copy() for color, settings in actions: - command = "python sendto_silhouette.py " - command += self.format_args(settings) - command += self.id_args(self.objects_by_color[color]) + command = "python sendto_silhouette.py" + command += " " + self.format_args(settings) + command += " " + self.id_args(self.objects_by_color[color]) command += " " + svg_file.name #print >> sys.stderr, command @@ -689,9 +698,9 @@ def run(self, actions): if status != 0: print >> sys.stderr, "command returned exit status %s: %s" % (status, command) + print >> sys.stderr, os.getcwd() break - self.frame.Close() def save_stderr(): @@ -704,9 +713,12 @@ def save_stderr(): def restore_stderr(): - os.dup2(sys.stderr_dup, 2) - sys.stderr_backup.write(sys.stderr.getvalue()) - sys.sys.stderr = stderr_backup + if hasattr(sys, "stderr_backup"): + os.dup2(sys.stderr_dup, 2) + sys.stderr_backup.write(sys.stderr.getvalue()) + sys.stderr = sys.stderr_backup + + del sys.stderr_backup # end of class MyFrame From 52d65235c93d0c4d083fea726161a67bdf428859 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 13 Jul 2017 17:59:04 +0100 Subject: [PATCH 11/51] add "progress" dialog --- silhouette_multi.py | 82 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index 95226915..91b08e6c 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -3,7 +3,10 @@ import os import sys +import time import cPickle +import subprocess +from threading import Thread from tempfile import NamedTemporaryFile from collections import defaultdict, OrderedDict import xmltodict @@ -664,10 +667,9 @@ def effect(self): app.MainLoop() def save_copy(self): - f = NamedTemporaryFile(suffix='.svg', prefix='silhouette-multiple-actions') - self.document.write(f) - f.flush() - return f + self.svg_file = NamedTemporaryFile(suffix='.svg', prefix='silhouette-multiple-actions') + self.document.write(self.svg_file) + self.svg_file.flush() def format_args(self, args): if isinstance(args, dict): @@ -678,30 +680,74 @@ def format_args(self, args): def id_args(self, nodes): return self.format_args(("id", node.get("id")) for node in nodes) - def run(self, actions): - self.frame.Close() - restore_stderr() - - svg_file = self.save_copy() + def format_commands(self, actions): + commands = [] for color, settings in actions: command = "python sendto_silhouette.py" command += " " + self.format_args(settings) command += " " + self.id_args(self.objects_by_color[color]) - command += " " + svg_file.name + command += " " + self.svg_file.name + + commands.append(command) + + return commands + + def run(self, actions): + self.frame.Close() + restore_stderr() + + self.save_copy() - #print >> sys.stderr, command - if self.options.dry_run: + commands = self.format_commands(actions) + + if self.options.dry_run: + print >> sys.stderr, "\n\n".join(commands) + else: + self.run_commands_with_dialog(commands) + + def run_commands_with_dialog(self, commands): + for i, command in enumerate(commands): + if not self.run_command_with_dialog(command, step=i + 1, total=len(commands)): + info_dialog(None, "Action failed.") + print >> sys.stderr, "The command that failed:" print >> sys.stderr, command - else: - status = os.system(command) - if status != 0: - print >> sys.stderr, "command returned exit status %s: %s" % (status, command) - print >> sys.stderr, os.getcwd() - break + sys.exit(1) + + def run_command_with_dialog(self, command, step, total): + process = subprocess.Popen(command, shell=True) + + dialog = wx.ProgressDialog(style=wx.PD_APP_MODAL|wx.PD_CAN_ABORT|wx.PD_ELAPSED_TIME, + message="Performing action %d of %d..." % (step, total), + title="Silhouette Multiple Actions") + + last_tick = time.time() + + while process.returncode is None: + if time.time() - last_tick > 0.5: + dialog.Pulse() + last_tick = time.time() + + process.poll() + wx.Yield() + time.sleep(0.1) + + if dialog.WasCancelled(): + def cancel(): + process.terminate() + process.wait() + + Thread(target=cancel).start() + dialog.Destroy() + wx.Yield() + info_dialog(None, "Action aborted. It may take awhile for the machine to cancel its operation.") + sys.exit(1) + dialog.Destroy() + wx.Yield() + return process.returncode == 0 def save_stderr(): # GTK likes to spam stderr, which inkscape will show in a dialog. From d1891e4cb0a07e634858c2a9ac6007cb2e64f9e0 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 13 Jul 2017 17:59:34 +0100 Subject: [PATCH 12/51] disable colors not in the preset when loading --- silhouette_multi.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/silhouette_multi.py b/silhouette_multi.py index 91b08e6c..3aef6a8d 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -390,11 +390,14 @@ def _load_preset(self, preset_name, silent=False): for color in old_colors: self.colors.append(color) + if extra_colors: reassigned += 1 assigned_color = extra_colors.pop(0) self.color_enabled[color] = preset['color_enabled'].get(assigned_color, True) self.color_settings[color] = preset['color_settings'].get(assigned_color, {}) + else: + self.color_enabled[color] = False message = [] From 306ffb0c5b419470a9fd0016350e2afb0088d3b8 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Mon, 28 Aug 2017 01:09:03 +0100 Subject: [PATCH 13/51] send SIGKILL instead of SIGTERM --- silhouette_multi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index 3aef6a8d..800186bf 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -738,7 +738,7 @@ def run_command_with_dialog(self, command, step, total): if dialog.WasCancelled(): def cancel(): - process.terminate() + process.kill() process.wait() Thread(target=cancel).start() From 02d703cfeff781509b824b20472410dc3277a7fe Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 28 Sep 2017 01:23:54 +0100 Subject: [PATCH 14/51] fix cancel --- silhouette_multi.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index 800186bf..aa02d7c4 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -719,7 +719,9 @@ def run_commands_with_dialog(self, commands): sys.exit(1) def run_command_with_dialog(self, command, step, total): - process = subprocess.Popen(command, shell=True) + # exec ensures that the shell gets replaced so that we can terminate the + # actual python script if the user cancels + process = subprocess.Popen("exec " + command, shell=True) dialog = wx.ProgressDialog(style=wx.PD_APP_MODAL|wx.PD_CAN_ABORT|wx.PD_ELAPSED_TIME, message="Performing action %d of %d..." % (step, total), @@ -738,7 +740,7 @@ def run_command_with_dialog(self, command, step, total): if dialog.WasCancelled(): def cancel(): - process.kill() + process.terminate() process.wait() Thread(target=cancel).start() From 7ed0f30e56661b3444b6264d3c89cc058aa69ba9 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sun, 5 Nov 2017 01:04:01 +0000 Subject: [PATCH 15/51] use fill color if stroke not set --- silhouette_multi.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index aa02d7c4..47ce5c88 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -616,11 +616,14 @@ def get_style(self, element): return style - def get_stroke_color(self, element): - stroke = self.get_style(element).get('stroke') + def get_color(self, element): + color = self.get_style(element).get('stroke', 'none') - if stroke is not None and stroke != 'none': - return simplestyle.parseColor(stroke) + if color == 'none': + color = self.get_style(element).get('fill', 'none') + + if color != 'none': + return simplestyle.parseColor(color) else: return None @@ -658,7 +661,7 @@ def split_objects_by_color(self): self.load_selected_objects() for obj in self.selected_objects: - color = self.get_stroke_color(obj) + color = self.get_color(obj) if color: self.objects_by_color[color].append(obj) @@ -774,12 +777,20 @@ def restore_stderr(): # end of class MyFrame if __name__ == "__main__": - save_stderr() - try: - e = SilhouetteMulti() - e.affect() - except: - traceback.print_exc() + pid = os.fork() + if pid == 0: + # Forking and closing stdout and stderr allows inkscape to continue on + # while the silhouette machine is cutting. This is useful if you're + # cutting something really big and want to work on another document. + os.close(1) + os.close(2) + + try: + e = SilhouetteMulti() + e.affect() + except: + traceback.print_exc() + - restore_stderr() + sys.exit(0) From 5dbd018c860766940324cbd6e9210d33ec214ce9 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Tue, 5 Dec 2017 19:53:42 +0000 Subject: [PATCH 16/51] ignore stroke set on group (silhouette_multi) --- silhouette_multi.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/silhouette_multi.py b/silhouette_multi.py index 47ce5c88..ab47131d 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -617,6 +617,12 @@ def get_style(self, element): return style def get_color(self, element): + if element.tag == inkex.addNS( 'g', 'svg'): + # Sometimes Inkscape sets a stroke style on a group, which seems to + # have no visual effect. If we didn't ignore those, we'd cut those + # objects twice. + return None + color = self.get_style(element).get('stroke', 'none') if color == 'none': From b24f0199f3ecd89d20947d288e5218c12bd7ea6d Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Tue, 5 Dec 2017 19:53:59 +0000 Subject: [PATCH 17/51] properly detect closed paths We flatten paths using cspsubdiv, and this can mean that the starting and ending points of a closed path are not actually the exact same floating point values. Instead, compare with a slop factor. --- sendto_silhouette.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sendto_silhouette.py b/sendto_silhouette.py index 97605cfc..b10c6645 100644 --- a/sendto_silhouette.py +++ b/sendto_silhouette.py @@ -122,6 +122,7 @@ from silhouette.Graphtec import SilhouetteCameo from silhouette.Strategy import MatFree from silhouette.convert2dashes import splitPath +from silhouette.Geometry import dist_sq, XY_a N_PAGE_WIDTH = 3200 N_PAGE_HEIGHT = 800 @@ -946,6 +947,9 @@ def handleViewBox( self ): self.docTransform = parseTransform( 'scale(%f,%f)' % (sx, sy) ) + def is_closed_path(self, path): + return dist_sq(XY_a(path[0]), XY_a(path[-1])) < 0.01 + def effect(self): if self.options.version: print __version__ @@ -1008,21 +1012,22 @@ def write_progress(done, total, msg): multipath = [] multipath.extend(mm_path) + for i in range(1,self.options.multipass): # if reverse continue path without lifting, instead turn with rotating knife if (self.options.reversetoggle): mm_path = list(reversed(mm_path)) multipath.extend(mm_path[1:]) # if closed path (end = start) continue path without lifting - elif (mm_path[0] == mm_path[-1]): + elif self.is_closed_path(mm_path): multipath.extend(mm_path[1:]) # else start a new path - else: + else: cut.append(mm_path) # on a closed path some overlapping doesn't harm, limited to a maximum of one additional round overcut = self.options.overcut - if (overcut > 0) and (mm_path[0] == mm_path[-1]): + if (overcut > 0) and self.is_closed_path(mm_path): pfrom = mm_path[0] for pnext in mm_path[1:]: dx = pnext[0] - pfrom[0] From 40748298449076c5285d4d7166aa6baa344fffd2 Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Wed, 25 Jul 2018 12:08:36 -0400 Subject: [PATCH 18/51] - change Cameo3 page width to exacty 12" --- silhouette/Graphtec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index f589d05b..dc93aff2 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -138,7 +138,7 @@ { 'vendor_id': 0x0b4d, 'product_id': 0x112f, 'name': 'Silhouette Cameo3', # margin_top_mm is just for safety when moving backwards with thin media # margin_left_mm is a physical limit, but is relative to width_mm! - 'width_mm': 304, 'length_mm': 3000, 'margin_left_mm':5, 'margin_top_mm':15.5, 'regmark': True }, + 'width_mm': 304.8, 'length_mm': 3000, 'margin_left_mm':5, 'margin_top_mm':15.5, 'regmark': True }, { 'vendor_id': 0x0b4d, 'product_id': 0x110a, 'name': 'Craft Robo CC200-20', 'width_mm': 200, 'length_mm': 1000, 'regmark': True }, { 'vendor_id': 0x0b4d, 'product_id': 0x111a, 'name': 'Craft Robo CC300-20' }, From 08a3cfeec2321b68df667d80843b551f272eecff Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Fri, 7 Sep 2018 10:56:59 -0400 Subject: [PATCH 19/51] - add and use vendor/product id definitions --- silhouette/Graphtec.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index dc93aff2..b68c8911 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -120,30 +120,33 @@ PRODUCT_ID_SILHOUETTE_SD_1 = 0x111c PRODUCT_ID_SILHOUETTE_SD_2 = 0x111d PRODUCT_ID_SILHOUETTE_CAMEO = 0x1121 +PRODUCT_ID_SILHOUETTE_CAMEO2 = 0x112b +PRODUCT_ID_SILHOUETTE_CAMEO3 = 0x112f PRODUCT_ID_SILHOUETTE_PORTRAIT = 0x1123 +PRODUCT_ID_SILHOUETTE_PORTRAIT2 = 0x1132 DEVICE = [ - { 'vendor_id': 0x0b4d, 'product_id': 0x1123, 'name': 'Silhouette Portrait', + { 'vendor_id': VENDOR_ID_GRAPHTEC, 'product_id': PRODUCT_ID_SILHOUETTE_PORTRAIT, 'name': 'Silhouette Portrait', 'width_mm': 203, 'length_mm': 3000, 'regmark': True }, - { 'vendor_id': 0x0b4d, 'product_id': 0x1132, 'name': 'Silhouette Portrait2', + { 'vendor_id': VENDOR_ID_GRAPHTEC, 'product_id': PRODUCT_ID_SILHOUETTE_PORTRAIT2, 'name': 'Silhouette Portrait2', 'width_mm': 203, 'length_mm': 3000, 'regmark': True }, - { 'vendor_id': 0x0b4d, 'product_id': 0x1121, 'name': 'Silhouette Cameo', + { 'vendor_id': VENDOR_ID_GRAPHTEC, 'product_id': PRODUCT_ID_SILHOUETTE_CAMEO, 'name': 'Silhouette Cameo', # margin_top_mm is just for safety when moving backwards with thin media # margin_left_mm is a physical limit, but is relative to width_mm! 'width_mm': 304, 'length_mm': 3000, 'margin_left_mm':9.0, 'margin_top_mm':1.0, 'regmark': True }, - { 'vendor_id': 0x0b4d, 'product_id': 0x112b, 'name': 'Silhouette Cameo2', + { 'vendor_id': VENDOR_ID_GRAPHTEC, 'product_id': PRODUCT_ID_SILHOUETTE_CAMEO2, 'name': 'Silhouette Cameo2', # margin_top_mm is just for safety when moving backwards with thin media # margin_left_mm is a physical limit, but is relative to width_mm! 'width_mm': 304, 'length_mm': 3000, 'margin_left_mm':9.0, 'margin_top_mm':1.0, 'regmark': True }, - { 'vendor_id': 0x0b4d, 'product_id': 0x112f, 'name': 'Silhouette Cameo3', + { 'vendor_id': VENDOR_ID_GRAPHTEC, 'product_id': PRODUCT_ID_SILHOUETTE_CAMEO3, 'name': 'Silhouette Cameo3', # margin_top_mm is just for safety when moving backwards with thin media # margin_left_mm is a physical limit, but is relative to width_mm! 'width_mm': 304.8, 'length_mm': 3000, 'margin_left_mm':5, 'margin_top_mm':15.5, 'regmark': True }, - { 'vendor_id': 0x0b4d, 'product_id': 0x110a, 'name': 'Craft Robo CC200-20', + { 'vendor_id': VENDOR_ID_GRAPHTEC, 'product_id': PRODUCT_ID_CC200_20, 'name': 'Craft Robo CC200-20', 'width_mm': 200, 'length_mm': 1000, 'regmark': True }, - { 'vendor_id': 0x0b4d, 'product_id': 0x111a, 'name': 'Craft Robo CC300-20' }, - { 'vendor_id': 0x0b4d, 'product_id': 0x111c, 'name': 'Silhouette SD 1' }, - { 'vendor_id': 0x0b4d, 'product_id': 0x111d, 'name': 'Silhouette SD 2' }, + { 'vendor_id': VENDOR_ID_GRAPHTEC, 'product_id': PRODUCT_ID_CC300_20, 'name': 'Craft Robo CC300-20' }, + { 'vendor_id': VENDOR_ID_GRAPHTEC, 'product_id': PRODUCT_ID_SILHOUETTE_SD_1, 'name': 'Silhouette SD 1' }, + { 'vendor_id': VENDOR_ID_GRAPHTEC, 'product_id': PRODUCT_ID_SILHOUETTE_SD_2, 'name': 'Silhouette SD 2' }, ] def _bbox_extend(bb, x, y): From ad55657f8696a893abca82948ce51e2862511d04 Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Fri, 7 Sep 2018 11:41:27 -0400 Subject: [PATCH 20/51] - add functionality to select tool --- sendto_silhouette.inx | 4 ++++ sendto_silhouette.py | 5 +++++ silhouette/Graphtec.py | 26 +++++++++++++++++--------- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/sendto_silhouette.inx b/sendto_silhouette.inx index caa5a98e..13af1f00 100644 --- a/sendto_silhouette.inx +++ b/sendto_silhouette.inx @@ -9,6 +9,10 @@ 0.0 0.0 + + Red (left tool) + Blue (right tool) + Media default Pen diff --git a/sendto_silhouette.py b/sendto_silhouette.py index f498f7d4..e5c3e2ec 100644 --- a/sendto_silhouette.py +++ b/sendto_silhouette.py @@ -307,6 +307,8 @@ def __init__(self): dest="smoothness", default=.2, help="Smoothness of curves" ) self.OptionParser.add_option('-t', '--tool', action = 'store', choices=('cut', 'pen','default'), dest = 'tool', default = None, help="Optimize for pen or knive") + self.OptionParser.add_option('-T', '--toolholder', action = 'store', + choices=('1', '2'), dest = 'toolholder', default = None, help="[1..2]") self.OptionParser.add_option('-V', '--version', action = 'store_const', const=True, dest = 'version', default = False, help='Just print version number ("'+__version__+'") and exit.') @@ -996,6 +998,8 @@ def write_progress(done, total, msg): # Traverse the entire document self.recursivelyTraverseSvg( self.document.getroot() ) + if self.options.toolholder is not None: + self.options.toolholder = int(self.options.toolholder) self.pen=None if self.options.tool == 'pen': self.pen=True if self.options.tool == 'cut': self.pen=False @@ -1079,6 +1083,7 @@ def write_progress(done, total, msg): if self.options.pressure == 0: self.options.pressure = None if self.options.speed == 0: self.options.speed = None dev.setup(media=int(self.options.media,10), pen=self.pen, + toolholder=self.options.toolholder, bladediameter=self.options.bladediameter, pressure=self.options.pressure, speed=self.options.speed) diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index b68c8911..bb5c4b92 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -536,7 +536,7 @@ def get_version(s): return resp[0:-2] # chop of 0x03 - def setup(s, media=132, speed=None, pressure=None, pen=None, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None): + def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None): """media range is [100..300], default 132, "Print Paper Light Weight" speed range is [1..10], default None, from paper (132 -> 10) pressure range is [1..33], default None, from paper (132 -> 5) @@ -557,7 +557,8 @@ def setup(s, media=132, speed=None, pressure=None, pen=None, trackenhancing=Fals if media is not None: if media < 100 or media > 300: media = 300 - s.write("FW%d\x03" % media); + # Silhouette Studio does not appear to issue this command + #s.write("FW%d\x03" % media); if pen is None: if media == 113: pen = True @@ -569,16 +570,21 @@ def setup(s, media=132, speed=None, pressure=None, pen=None, trackenhancing=Fals if pressure is None: pressure = i[1] if speed is None: speed = i[2] + if toolholder is None: + toolholder = 1 + s.write("J%d\x03" % toolholder) + print("toolholder: %d" % toolholder, file=s.log) + if speed is not None: if speed < 1: speed = 1 if speed > 10: speed = 10 - s.write("!%d\x03" % speed); + s.write("!%d,%d\x03" % (speed, toolholder)); print("speed: %d" % speed, file=s.log) if pressure is not None: if pressure < 1: pressure = 1 if pressure > 33: pressure = 33 - s.write("FX%d\x03" % pressure); + s.write("FX%d,%d\x03" % (pressure, toolholder)); # s.write("FX%d,0\x03" % pressure); # oops, graphtecprint does it like this print("pressure: %d" % pressure, file=s.log) @@ -587,6 +593,8 @@ def setup(s, media=132, speed=None, pressure=None, pen=None, trackenhancing=Fals else: print("Loaded media is expected right-aligned.", file=s.log) + s.write("FF1,0,%d\x03FF1,1,%d\x03" % (toolholder, toolholder)) + # robocut/Plotter.cpp:393 says: # It is 0 for the pen, 18 for cutting. Default diameter of a blade is 0.9mm # C possible stands for curvature. Not that any of the other letters make sense... @@ -596,8 +604,8 @@ def setup(s, media=132, speed=None, pressure=None, pen=None, trackenhancing=Fals if pen: circle = 0 else: - circle = bladediameter * 20 - s.write("FC%d\x03" % circle) + circle = 0.5 + bladediameter * 20 + s.write("FC0,1,%d\x03FC%d,1,%d\x03" % (toolholder, circle, toolholder)) # if enabled, rollers three times forward and back. # needs a pressure of 19 or more, else nothing will happen @@ -617,7 +625,7 @@ def setup(s, media=132, speed=None, pressure=None, pen=None, trackenhancing=Fals s.write("FN0\x03TB50,0\x03") # Don't lift plotter head between paths - s.write("FE0,0\x03") + #s.write("FE0,0\x03") def find_bbox(s, cut): """Find the bounding box of the cut, returns (xmin,ymin,xmax,ymax)""" @@ -883,7 +891,7 @@ def plot(s, mediawidth=210.0, mediaheight=297.0, margintop=None, #p = "FU%d,%d\x03" % (height,width) # optional #s.write(p) - p = "\\0,0\x03Z%d,%d\x03L0\x03FE0,0\x03FF0,0,0\x03" % (height, width) + p = "\\%d,%d\x03Z%d,%d\x03" % (0, 0, 6096, 6096) s.write(p) bbox['clip'] = {'urx':width, 'ury':top, 'llx':left, 'lly':height} @@ -909,7 +917,7 @@ def plot(s, mediawidth=210.0, mediaheight=297.0, margintop=None, if not 'urx' in bbox: bbox['urx'] = 0 if not 'ury' in bbox: bbox['ury'] = 0 if endposition == 'start': - new_home = "H\x03" + new_home = "J0\x03H\x03L0\x03FN0\x03TB50,0\x03" else: #includes 'below' new_home = "M%d,%d\x03SO0\x03" % (int(0.5+bbox['lly']+end_paper_offset*20.), 0) #! axis swapped when using Cameo-system #new_home += "FN0\x03TB50,0\x03" From fe9a82df67bb3a8cf09dfa7d8ef1f6aa97dc3fdb Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Fri, 7 Sep 2018 11:54:56 -0400 Subject: [PATCH 21/51] - add option to use cutting mat --- sendto_silhouette.inx | 1 + sendto_silhouette.py | 4 ++++ silhouette/Graphtec.py | 8 +++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/sendto_silhouette.inx b/sendto_silhouette.inx index 13af1f00..ccab55c3 100644 --- a/sendto_silhouette.inx +++ b/sendto_silhouette.inx @@ -52,6 +52,7 @@ 0 0 Use speed=0, pressure=0 to take the media defaults. Pressure values of 19 or more could trigger the trackenhancing feature, which means a movement along the full media height before start. Beware. + true false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. false diff --git a/sendto_silhouette.py b/sendto_silhouette.py index e5c3e2ec..2e92b044 100644 --- a/sendto_silhouette.py +++ b/sendto_silhouette.py @@ -275,6 +275,9 @@ def __init__(self): self.OptionParser.add_option('-c', '--bladediameter', action = 'store', dest = 'bladediameter', type = 'float', default = 0.9, help="[0..2.3] diameter of the used blade [mm], default = 0.9") + self.OptionParser.add_option('-C', '--cuttingmat', + action = 'store', dest = 'cuttingmat', type = 'inkbool', default = True, + help='Use cutting mat') self.OptionParser.add_option('--dummy', action = 'store', dest = 'dummy', type = 'inkbool', default = False, help="Dump raw data to "+self.dumpname+" instead of cutting.") @@ -1084,6 +1087,7 @@ def write_progress(done, total, msg): if self.options.speed == 0: self.options.speed = None dev.setup(media=int(self.options.media,10), pen=self.pen, toolholder=self.options.toolholder, + cuttingmat=self.options.cuttingmat, bladediameter=self.options.bladediameter, pressure=self.options.pressure, speed=self.options.speed) diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index bb5c4b92..34289877 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -536,7 +536,7 @@ def get_version(s): return resp[0:-2] # chop of 0x03 - def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None): + def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cuttingmat=True, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None): """media range is [100..300], default 132, "Print Paper Light Weight" speed range is [1..10], default None, from paper (132 -> 10) pressure range is [1..33], default None, from paper (132 -> 5) @@ -615,6 +615,12 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, tr else: s.write("FY1\x03") + if 'product_id' in s.hardware and s.hardware['product_id'] == PRODUCT_ID_SILHOUETTE_CAMEO3: + if cuttingmat: + s.write("TG1\x03") + else: + s.write("TG0\x03") + #FNx, x = 0 seem to be some kind of reset, x = 1: plotter head moves to other # side of media (boundary check?), but next cut run will stall #TB50,x: x = 1 landscape mode, x = 0 portrait mode From 7ad1ca00b651bf384be3a367a5d8d9fe373aa194 Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Fri, 7 Sep 2018 12:03:40 -0400 Subject: [PATCH 22/51] - add option to lift plotter head at sharp corners --- sendto_silhouette.inx | 1 + sendto_silhouette.py | 4 ++++ silhouette/Graphtec.py | 8 +++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/sendto_silhouette.inx b/sendto_silhouette.inx index ccab55c3..7f2b3f54 100644 --- a/sendto_silhouette.inx +++ b/sendto_silhouette.inx @@ -57,6 +57,7 @@ false Shift to the top lefthand corner, then do offsets. false To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) + false Lift head at sharp corners 0.5 1 false diff --git a/sendto_silhouette.py b/sendto_silhouette.py index 2e92b044..ab3bf69c 100644 --- a/sendto_silhouette.py +++ b/sendto_silhouette.py @@ -300,6 +300,9 @@ def __init__(self): self.OptionParser.add_option('-p', '--pressure', action = 'store', dest = 'pressure', type = 'int', default = 10, help="[1..18], or 0 for media default") + self.OptionParser.add_option('-P', '--sharpencorners', + action = 'store', dest = 'sharpencorners', type = 'inkbool', default = False, + help='Lift head at sharp corners') self.OptionParser.add_option('-r', '--reversetoggle', action = 'store', dest = 'reversetoggle', type = 'inkbool', default = False, help="Cut each path the other direction. Affects every second pass when multipass.") @@ -1088,6 +1091,7 @@ def write_progress(done, total, msg): dev.setup(media=int(self.options.media,10), pen=self.pen, toolholder=self.options.toolholder, cuttingmat=self.options.cuttingmat, + sharpencorners=self.options.sharpencorners, bladediameter=self.options.bladediameter, pressure=self.options.pressure, speed=self.options.speed) diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index 34289877..8527d811 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -536,7 +536,7 @@ def get_version(s): return resp[0:-2] # chop of 0x03 - def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cuttingmat=True, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None): + def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cuttingmat=True, sharpencorners=False, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None): """media range is [100..300], default 132, "Print Paper Light Weight" speed range is [1..10], default None, from paper (132 -> 10) pressure range is [1..33], default None, from paper (132 -> 5) @@ -593,6 +593,12 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu else: print("Loaded media is expected right-aligned.", file=s.log) + # Lift plotter head at sharp corners + if sharpencorners: + s.write("FE1,%d\x03" % toolholder) + else: + s.write("FE0,%d\x03" % toolholder) + s.write("FF1,0,%d\x03FF1,1,%d\x03" % (toolholder, toolholder)) # robocut/Plotter.cpp:393 says: From 7e86b7da6005f02995a5e6fd469579002f112ac8 Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Fri, 7 Sep 2018 12:13:51 -0400 Subject: [PATCH 23/51] - add support for auto-blade --- sendto_silhouette.inx | 6 ++-- sendto_silhouette.py | 12 +++++++- silhouette/Graphtec.py | 70 ++++++++++++++++++++++++------------------ 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/sendto_silhouette.inx b/sendto_silhouette.inx index 7f2b3f54..6fd8793b 100644 --- a/sendto_silhouette.inx +++ b/sendto_silhouette.inx @@ -17,6 +17,7 @@ Media default Pen Cut + AutoBlade 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. @@ -24,7 +25,7 @@ [P>0 S>0] Custom: use non-zero Pressure and Speed below [P=27,S=10] SCard without Craft Paper Backing [P=27,S=10] Card with Craft Paper Backing - [P=10,S=10] Vinyl Sticker + [P=10,S=10,D=1] Vinyl Sticker [P=14,S=10] Film Labels [P=27,S=10] Thick Media [P= 2,S=10] Thin Media @@ -51,7 +52,8 @@ 0 0 - Use speed=0, pressure=0 to take the media defaults. Pressure values of 19 or more could trigger the trackenhancing feature, which means a movement along the full media height before start. Beware. + -1 + Use speed=0, pressure=0, depth=-1 to take the media defaults. Pressure values of 19 or more could trigger the trackenhancing feature, which means a movement along the full media height before start. Beware. true false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. diff --git a/sendto_silhouette.py b/sendto_silhouette.py index ab3bf69c..dbb5c6ce 100644 --- a/sendto_silhouette.py +++ b/sendto_silhouette.py @@ -278,6 +278,9 @@ def __init__(self): self.OptionParser.add_option('-C', '--cuttingmat', action = 'store', dest = 'cuttingmat', type = 'inkbool', default = True, help='Use cutting mat') + self.OptionParser.add_option('-D', '--depth', + action = 'store', dest = 'depth', type = 'int', default = -1, + help="[0..10], or -1 for media default") self.OptionParser.add_option('--dummy', action = 'store', dest = 'dummy', type = 'inkbool', default = False, help="Dump raw data to "+self.dumpname+" instead of cutting.") @@ -312,7 +315,7 @@ def __init__(self): self.OptionParser.add_option( "-S", "--smoothness", action="store", type="float", dest="smoothness", default=.2, help="Smoothness of curves" ) self.OptionParser.add_option('-t', '--tool', action = 'store', - choices=('cut', 'pen','default'), dest = 'tool', default = None, help="Optimize for pen or knive") + choices=('autoblade', 'cut', 'pen','default'), dest = 'tool', default = None, help="Optimize for pen or knive") self.OptionParser.add_option('-T', '--toolholder', action = 'store', choices=('1', '2'), dest = 'toolholder', default = None, help="[1..2]") self.OptionParser.add_option('-V', '--version', @@ -1007,8 +1010,12 @@ def write_progress(done, total, msg): if self.options.toolholder is not None: self.options.toolholder = int(self.options.toolholder) self.pen=None + self.autoblade=False if self.options.tool == 'pen': self.pen=True if self.options.tool == 'cut': self.pen=False + if self.options.tool == 'autoblade': + self.pen=False + self.autoblade=True if self.options.strategy == 'matfree': mf = MatFree('default', scale=px2mm(1.0), pen=self.pen) @@ -1088,10 +1095,13 @@ def write_progress(done, total, msg): if self.options.pressure == 0: self.options.pressure = None if self.options.speed == 0: self.options.speed = None + if self.options.depth == -1: self.options.depth = None dev.setup(media=int(self.options.media,10), pen=self.pen, toolholder=self.options.toolholder, cuttingmat=self.options.cuttingmat, sharpencorners=self.options.sharpencorners, + autoblade=self.autoblade, + depth=self.options.depth, bladediameter=self.options.bladediameter, pressure=self.options.pressure, speed=self.options.speed) diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index 8527d811..a6a2255c 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -83,34 +83,34 @@ MEDIA = [ # CAUTION: keep in sync with sendto_silhouette.inx -# media, pressure, speed, cap-color, name - ( 100, 27, 10, "yellow", "Card without Craft Paper Backing"), - ( 101, 27, 10, "yellow", "Card with Craft Paper Backing"), - ( 102, 10, 10, "blue", "Vinyl Sticker"), - ( 106, 14, 10, "blue", "Film Labels"), - ( 111, 27, 10, "yellow", "Thick Media"), - ( 112, 2, 10, "blue", "Thin Media"), - ( 113, 10, 10, "pen", "Pen"), - ( 120, 30, 10, "blue", "Bond Paper 13-28 lbs (105g)"), - ( 121, 30, 10, "yellow", "Bristol Paper 57-67 lbs (145g)"), - ( 122, 30, 10, "yellow", "Cardstock 40-60 lbs (90g)"), - ( 123, 30, 10, "yellow", "Cover 40-60 lbs (170g)"), - ( 124, 1, 10, "blue", "Film, Double Matte Translucent"), - ( 125, 1, 10, "blue", "Film, Vinyl With Adhesive Back"), - ( 126, 1, 10, "blue", "Film, Window With Kling Adhesive"), - ( 127, 30, 10, "red", "Index 90 lbs (165g)"), - ( 128, 20, 10, "yellow", "Inkjet Photo Paper 28-44 lbs (70g)"), - ( 129, 27, 10, "red", "Inkjet Photo Paper 45-75 lbs (110g)"), - ( 130, 30, 3, "red", "Magnetic Sheet"), - ( 131, 30, 10, "blue", "Offset 24-60 lbs (90g)"), - ( 132, 5, 10, "blue", "Print Paper Light Weight"), - ( 133, 25, 10, "yellow", "Print Paper Medium Weight"), - ( 134, 20, 10, "blue", "Sticker Sheet"), - ( 135, 20, 10, "red", "Tag 100 lbs (275g)"), - ( 136, 30, 10, "blue", "Text Paper 24-70 lbs (105g)"), - ( 137, 30, 10, "yellow", "Vellum Bristol 57-67 lbs (145g)"), - ( 138, 30, 10, "blue", "Writing Paper 24-70 lbs (105g)"), - ( 300, None, None, "custom", "Custom"), +# media, pressure, speed, depth, cap-color, name + ( 100, 27, 10, 1, "yellow", "Card without Craft Paper Backing"), + ( 101, 27, 10, 1, "yellow", "Card with Craft Paper Backing"), + ( 102, 10, 10, 1, "blue", "Vinyl Sticker"), + ( 106, 14, 10, 1, "blue", "Film Labels"), + ( 111, 27, 10, 1, "yellow", "Thick Media"), + ( 112, 2, 10, 1, "blue", "Thin Media"), + ( 113, 10, 10,None, "pen", "Pen"), + ( 120, 30, 10, 1, "blue", "Bond Paper 13-28 lbs (105g)"), + ( 121, 30, 10, 1, "yellow", "Bristol Paper 57-67 lbs (145g)"), + ( 122, 30, 10, 1, "yellow", "Cardstock 40-60 lbs (90g)"), + ( 123, 30, 10, 1, "yellow", "Cover 40-60 lbs (170g)"), + ( 124, 1, 10, 1, "blue", "Film, Double Matte Translucent"), + ( 125, 1, 10, 1, "blue", "Film, Vinyl With Adhesive Back"), + ( 126, 1, 10, 1, "blue", "Film, Window With Kling Adhesive"), + ( 127, 30, 10, 1, "red", "Index 90 lbs (165g)"), + ( 128, 20, 10, 1, "yellow", "Inkjet Photo Paper 28-44 lbs (70g)"), + ( 129, 27, 10, 1, "red", "Inkjet Photo Paper 45-75 lbs (110g)"), + ( 130, 30, 3, 1, "red", "Magnetic Sheet"), + ( 131, 30, 10, 1, "blue", "Offset 24-60 lbs (90g)"), + ( 132, 5, 10, 1, "blue", "Print Paper Light Weight"), + ( 133, 25, 10, 1, "yellow", "Print Paper Medium Weight"), + ( 134, 20, 10, 1, "blue", "Sticker Sheet"), + ( 135, 20, 10, 1, "red", "Tag 100 lbs (275g)"), + ( 136, 30, 10, 1, "blue", "Text Paper 24-70 lbs (105g)"), + ( 137, 30, 10, 1, "yellow", "Vellum Bristol 57-67 lbs (145g)"), + ( 138, 30, 10, 1, "blue", "Writing Paper 24-70 lbs (105g)"), + ( 300, None, None,None, "custom", "Custom"), ] # robocut/Plotter.h:53 ff @@ -536,7 +536,7 @@ def get_version(s): return resp[0:-2] # chop of 0x03 - def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cuttingmat=True, sharpencorners=False, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None): + def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cuttingmat=True, sharpencorners=False, autoblade=False, depth=None, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None): """media range is [100..300], default 132, "Print Paper Light Weight" speed range is [1..10], default None, from paper (132 -> 10) pressure range is [1..33], default None, from paper (132 -> 5) @@ -566,15 +566,25 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu pen = False for i in MEDIA: if i[0] == media: - print("Media=%d, cap='%s', name='%s'" % (media, i[3], i[4]), file=s.log) + print("Media=%d, cap='%s', name='%s'" % (media, i[4], i[5]), file=s.log) if pressure is None: pressure = i[1] if speed is None: speed = i[2] + if depth is None: depth = i[3] + break if toolholder is None: toolholder = 1 s.write("J%d\x03" % toolholder) print("toolholder: %d" % toolholder, file=s.log) + if autoblade and depth is not None: + if 'product_id' in s.hardware and s.hardware['product_id'] == PRODUCT_ID_SILHOUETTE_CAMEO3: + if toolholder == 1: + if depth < 0: depth = 0 + if depth > 10: depth = 10 + s.write("TF%d,%d\x03" % (depth, toolholder)); + print("depth: %d" % depth, file=s.log) + if speed is not None: if speed < 1: speed = 1 if speed > 10: speed = 10 From 7d96cfda65074c50db8752af63d84b4c8126d43e Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Fri, 7 Sep 2018 12:16:19 -0400 Subject: [PATCH 24/51] - remove cameo3 margins (handled by cutting mat option) --- silhouette/Graphtec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index a6a2255c..dbff9bd3 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -141,7 +141,7 @@ { 'vendor_id': VENDOR_ID_GRAPHTEC, 'product_id': PRODUCT_ID_SILHOUETTE_CAMEO3, 'name': 'Silhouette Cameo3', # margin_top_mm is just for safety when moving backwards with thin media # margin_left_mm is a physical limit, but is relative to width_mm! - 'width_mm': 304.8, 'length_mm': 3000, 'margin_left_mm':5, 'margin_top_mm':15.5, 'regmark': True }, + 'width_mm': 304.8, 'length_mm': 3000, 'margin_left_mm':0.0, 'margin_top_mm':0.0, 'regmark': True }, { 'vendor_id': VENDOR_ID_GRAPHTEC, 'product_id': PRODUCT_ID_CC200_20, 'name': 'Craft Robo CC200-20', 'width_mm': 200, 'length_mm': 1000, 'regmark': True }, { 'vendor_id': VENDOR_ID_GRAPHTEC, 'product_id': PRODUCT_ID_CC300_20, 'name': 'Craft Robo CC300-20' }, From fe3a5a92ccc4869cc444fbdcf3b70ee580f557bf Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Fri, 7 Sep 2018 12:19:09 -0400 Subject: [PATCH 25/51] - fixes to ignore invisible nodes --- sendto_silhouette.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sendto_silhouette.py b/sendto_silhouette.py index dbb5c6ce..472901cf 100644 --- a/sendto_silhouette.py +++ b/sendto_silhouette.py @@ -485,11 +485,18 @@ def recursivelyTraverseSvg( self, aNodeList, for node in aNodeList: # Ignore invisible nodes - v = node.get( 'visibility', parent_visibility ) + v = None + style = node.get('style') + if style is not None: + kvs = {k.strip():v.strip() for k,v in [x.split(':', 1) for x in style.split(';')]} + if 'display' in kvs and kvs['display'] == 'none': + v = 'hidden' + if v is None: + v = node.get( 'visibility', parent_visibility ) if v == 'inherit': v = parent_visibility if v == 'hidden' or v == 'collapse': - pass + continue # calculate this object's transform transform = composeParents(node, IDENTITY_TRANSFORM) From ad1671b81e7a4203efff3f5704059c51cee1d4ac Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Wed, 12 Sep 2018 00:04:10 -0400 Subject: [PATCH 26/51] - add wireshark caps from silhouette studio --- ...silhouette_cameo3_12x24_cutting_mat.pcapng.gz | Bin 0 -> 2430 bytes .../silhouette_cameo3_autoblade_depth5.pcapng.gz | Bin 0 -> 2916 bytes .../silhouette_cameo3_default_settings.pcapng.gz | Bin 0 -> 3499 bytes ...houette_cameo3_line_segment_overcut.pcapng.gz | Bin 0 -> 2788 bytes ...lhouette_cameo3_max_speed_max_force.pcapng.gz | Bin 0 -> 2741 bytes logs/silhouette_cameo3_no_cutting_mat.pcapng.gz | Bin 0 -> 2207 bytes logs/silhouette_cameo3_pen.pcapng.gz | Bin 0 -> 2444 bytes logs/silhouette_cameo3_ratchet_blade.pcapng.gz | Bin 0 -> 2181 bytes logs/silhouette_cameo3_tool_2.pcapng.gz | Bin 0 -> 2773 bytes logs/silhouette_cameo3_track_enhancing.pcapng.gz | Bin 0 -> 2730 bytes 10 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 logs/silhouette_cameo3_12x24_cutting_mat.pcapng.gz create mode 100644 logs/silhouette_cameo3_autoblade_depth5.pcapng.gz create mode 100644 logs/silhouette_cameo3_default_settings.pcapng.gz create mode 100644 logs/silhouette_cameo3_line_segment_overcut.pcapng.gz create mode 100644 logs/silhouette_cameo3_max_speed_max_force.pcapng.gz create mode 100644 logs/silhouette_cameo3_no_cutting_mat.pcapng.gz create mode 100644 logs/silhouette_cameo3_pen.pcapng.gz create mode 100644 logs/silhouette_cameo3_ratchet_blade.pcapng.gz create mode 100644 logs/silhouette_cameo3_tool_2.pcapng.gz create mode 100644 logs/silhouette_cameo3_track_enhancing.pcapng.gz diff --git a/logs/silhouette_cameo3_12x24_cutting_mat.pcapng.gz b/logs/silhouette_cameo3_12x24_cutting_mat.pcapng.gz new file mode 100644 index 0000000000000000000000000000000000000000..b7844c51111afcb9347ac608e4f7eef842718f9d GIT binary patch literal 2430 zcmV-^34!(>iwFP!000001FhN%R20`42k?PG1cX3nN~{7It%;!QI0dniDaJMzIlt)?jSZ_>Nf~_UKV*l-^ku zy&zL}&cAyOJ9pjvzTfxVduL$RwR3Q=`y0bBu>s>pSde3n>1N6JGSTx&baOm3o?K|* ze4U3j)|1owDtvu;K57~lnvlc=aVn)B9~rfn^C;Js6mW@&;S-fAPljdO8TCX(j=qGO zuAi4zSiXQ0crPxeR6i$=6MO~li40@KD4DR*xkb6zMV!ZU{e0bmg6#RTJxNBjQpI_c z8IKBHF;mF#&ei4Rz@XC?W0WUQBAEEyjrJzbezq%YE?m-pq$)GaI_*)zpw zk`hA_$XLi!@q!;eNe~1-mA4>}cw3UqnjG!TsF^Xj;_JhDA2M4?hcK1dEru~M@sMuH9WWrbt1+)+Zt;O-z#zSi&!Kw@`gwb1Uo6wx zTV-!=q%Gs2uQ%G)<Be|`Uis>8M_&0!RgBF8GW&~ zy>nv0ioqj?nBoS;r>(q9#W#BDQl7;d?WM+eo|TB-`R!$cI~ltV5^wUw(1=L(Sv-BN zsE1uOD9BhL?ThD%X$HAuL8<0Ba$}ii=L%g9Gya-nKjzHG<>u?qeBAx$hJhnvPTCh~ zZHTYH_Qgk0@p}7PhOuNkR@%20(~}$dbs^rf#`uACXq308bs2PD7}q73S{J$whc!P@ zb@Y#yQ^#<}JLc4(4V9?lh=bw(Qb%4zJG3t%Pj^b5XXfmS%6s=9UzP9x^4XGKpzFl2 z)+f+;v1Xc8JK|qSv~EXyVgE~zUzqF!`PpeMlJVyFsco-H)^Co#c)tSjCvBbp`KC`9-9AS`O_f3 zXzFyxUvfJI@<*M`fc)}VS&;8>Iv+a!)_Uoo^Y2p0JjgHJTLk${J*Ck67w`+A`LBEb zUFiIq{7D7mZ~en^X#U+N{$8?vDL;#f_o-Oj9!SQ4(*9`jKpUxIBjR{g8{)%z@oc(y zm!{??slCKIc8b87@(z3-QCzjIUFv;=DZltWVw8`Uz(%A9ym$x~@0n-sBl4(xj(K(M zA!HmPolpFeG$KOqGUg&jK|F@?rla6z9B9QS*xuX&=k!j;Mrcj;Kip)_ng~*R;-7Oq zfcE5uMSq6;8sB=T|J#eVLVjm%1LV5}?t=X7%0|dP`T0J`|E2IC*rR(gzgk6DrVbxX0KlkG;$iHTP2lAJC zbV2^UKi-A>$he1)|6@WoH+!e51x>}z=enWx(osGn@{*c{x!}Y@_Vd;Apg|1V957b z76tjPx+&)P{oWHTqvG$l#dKtoakjJ{K_0~NUMijq2^VVKA@{1=qiR;)-Bv4ADam6g{!-z;%rnfq_^ z#X4yIzxCTh@BgMFGX2+wZ=tWh>wUHI{H2HMA>aD$4toB(PsyDBn1BYz|IqSdIzDKR zO#kgCjr93j`<+a`DChvx|K_Gcke_`2b1J^_K#NSjF61cW-}&-5;LJRN02{6(F5)OTxW|;srt?FI}TcRBL1xPHjuw;#Y>X$=J@e0PLlPT z1@hl(8xHx)v)mznS{euWd(OQ9t$(tsC$#?Er<9N%x0#3hT_32ac&Db1W%@kl2l*!& z-h}$sT@8Z#qS+CUKQ%83@>AEuLGwT5W<2EIC`*QX&z&id|7l(Zi_Y{%aE`9 z`3Cg9>e19&{r)bolli^R(v|H{{|(=CK>arq+=qP6ng@`-JNPl=GtNDbzfm=$3-KTP z$+`>i&yI0`{E%cv$PfJYE0BNlI~T~WUgQe-b*b)9kgvV?I^;Y2%8QDZfBoI^ zI|VlX25+eUcV1INe%0HPq5em-1VX+L9|`sE>KG0A!J0V8e|cR3i_@% literal 0 HcmV?d00001 diff --git a/logs/silhouette_cameo3_autoblade_depth5.pcapng.gz b/logs/silhouette_cameo3_autoblade_depth5.pcapng.gz new file mode 100644 index 0000000000000000000000000000000000000000..d84345fcbe4374b9657888d69f576f9e456a39dd GIT binary patch literal 2916 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 literal 0 HcmV?d00001 diff --git a/logs/silhouette_cameo3_max_speed_max_force.pcapng.gz b/logs/silhouette_cameo3_max_speed_max_force.pcapng.gz new file mode 100644 index 0000000000000000000000000000000000000000..d6c359f09ccb191953af530e9fc718206203a29c GIT binary patch literal 2741 zcmV;m3QF}KiwFP!000001Ff11SQA$k#|Oewpi*jGMa4m_0);REk|1Ck2oGU-89-1Y z$Ri-dDkA7YjHOho%ga^@YApi3_|8pNRnPG^H&5->RMMe0%X>Wwu9{tOhl2W1bb5--46wb7KL5|8P zHo}?Cl)6c!3^R2aEsu_)WweLTn+cg(KsznT%$-Ze!~}T>J)9{4Zi+)^@g>G;g~ zX$zJtqD71=osyS1FO3#SMV?+1WkLz5z`S>}Q1u56x#=;x6xUy3rO#FSDgRAy&ptCUOnVkN2ml8d4zv44q+ z35Z60QIZEE@@6KBM54)zho=r!_;VQkF)6`WQ+q2E9R@?}z$+17cWEMr@2iW16*Yi5hf# z#KY1Ag*7)arp!%^jAayRir7Y}kL`!WR-xFVb#Xakvj@h0gLuBK%Q)$6UW z*Bi2>Yx=z5{NJi@zF~})@lC3E*A^vU_lzHn#i&3rgs3m%_HW0gVrRsT4<(DRa(|VL zhW@6D)87vp?~m5A&cKh%s%;jRA0vUKiQw5>3CCiDZBpmEqQ2{Z7|-~&tRGu}H(D7Y zw2~#1zdlBPFYj$K7@NUjaeArRWZ=a&=-IfAGMF1%b&5NJJ}>LA80$8x^B1E2!T~X! z@#AzeEiS@8GA`I(9vi0!QuxGV&YQa+PnDai@{NcJ3<~#=`$q&t(Vo8YaQ~PXpJ+vB zWNeVaH!vtht_Y2e4ULM#By``0^||z~$%D-XH5n`?gPO=N?ZF8}`D0LjjIIi_swuWl zYuQ^9BLOQOWvbWp*DuIhj_ErJdS*V?>)1s)JtN@I*CwsVcR+oILG*e)D-8}2JlErE zW%2Bl`EIE1)~mlKjvv5h*NVP^^mLI>!qDg?pq{T4JfBMyt2q7esfTZEF{mDm6HD@G z)Tg=mEIKwoDiZYRr(*iArDge(P=69P-<(-)3BkU+*SF|Nb(b7IFU$oL;! zYy$bOGCu|KKdj+&b}3 zycxtdS-Bm=Kdspb;&&u}4&pcW><00F3Hdvwx2;(=opAj%902h*4X+3B|0O>R;-CAr z0mN@SehkE){&5p%{ku$U;mn_lKL^u`3t9@Mp#BtY{y20H8zK?}M>2xg05Rjr2$byG zObIht;>x}W!bAdn-2V3wjTC1J`tG@-?{{yE8Cc)f(1D|faYcj#PfxZ9Vh^6*M@+-w zsh!Uk1fYI^KHt*B0rA*dQo+F@S6xKZir7}p=v$Gut`p1tLC_m}z`fJZi&#wu&H066 zqOiKm=bR+sniCplZap9V?vfuteAf|IK>lx)UIp>*``rNXt%kOP_>($*1@X^oJ3xHL z_D&H0hT;*3?_b^x;@><=sUiG@*=A}8-z?q&#NVC&3a&pCLins=$;F|V-j<%0K*oQv za5%0%vWL*WY4r#YUz_1zpl>0)CqJ6z%%2Z`OVUITzv16bApW=ELJe z@2v^>yRJz={0GXn@cD0Vt0d!h?T~@^M^l4Ae8Hrtob}_wZ(1J-;!kXk2Js)yn2za> zY$zcd|7lqXAbzJ)B8XpdI|;=19hc6zfAQhB&CdYMzhLY6Abu^A4f3xUyBJje#ru|k z_*U06ApS9ZHthLZ80Hn)QKuYGzIuRkLq(>&_zcWK`<$XXPl0OlJPa#E=*r&8b!GO9gcZo;NOMNf7dYMrI>!mTtfbya{^qytAud= zCHf4(^tRetgy-KAmDV`_od<;b@7;1coNxO!;rRbLau|qT8f1^_t2PnV|6KJb5dXG% z9Eg8%_5={$Nb7>>Yj)fv+<)q>yb0pJa=`;1|LiWp^N;&BFA!h7)5oCxJ%s*S$NPc! z-^>cY^<6s&uYWG?l!N&9!oxWAeE3f?!g2k~j=`T#bMpUFq2Sc>T7T^^Apb>wn~CYQ zsb3C!-t+XAim|Q91!0sCl|zT z4J!cg|8Z9h;tyY1gzN2V2+w~D4=xAsXZRO`_}$V{K77vSTBk^&o!F zss@}t$A)lUo&4c35Z~eYcQ{`rCtQDy7A+ut_pQ?)zG?9fpncUp_7bT6H>_H5e$#p- z8DCa)6?9%T`)3=j|8L?9GXCpc?V#(*%Tc!szL#ea@=rS40r@|A`gc(Mw;X;5x~^@E z>EfI}eE7;*N`w1vCgh)ZU7&&R)3PlzknulTIRw`md@tw2k4UxU)brtQ8)*mf|M7!A zaq9W-3#&(h{5xM9tAWh_gfWgFe)RrHp!yeIamMRsaQ(uEFa5+F#Q$@$h_imY=KmBa zsQyxuVYm(N4+@zcH%M#gWi4F&lh|3MVU|B1=daJ|8OAwK*) v3GtluU|~)jFJhGg|YE-)-wKUjHL!9HB}uV3kslU%7;>f1m+k^ znRH`8ZsD2|hGV5n&MISmF2jX#!66i7P06U(RV#{?Wfw92>Bg1%5<~XNUj&eh3Rw{2 zzuGhgvVqIFoZw~p+?-_rl%Am!AvD83s zn+AF#ZJCC_-h}?MiNpM1IbP;Z^$_|+ukKtVy0)Z%anJo^L5`y&hm6c+FXVXM|2V#s zaT5OcP)d!{EO$N|dCin(WIueqp?*fqC)!W*uzu=K{3Bhg9|`SXO+Pz8^O_j9eKUxsnxBv^mGfUie%?y^NYmw3mgG#Cn-L?A)j}McktYd#M!U zaP{nLDJJt`aX-y;rlo136Vu|NRob)^bxK64G2c+QN?%&4pRGxXRnLo1MQhYaOz>>g zyy(=_h~$)nM6EhycC0#8m6DLGO-M=CPMy9-T|tmj(v%yZ1~N#4iorVxixq@`rp)kPPyl{&g1Q_qi${zyF;u z$X|Re3i53nm5~4Oduqr(^=>@mdl@v4U*nPl`5SeaqVZ$#A2#a*@oqhdb1n1jpJYS+ zgOpsz@6Z|`UtgIA`ES@23H`jpC6@W~C#N;!0Yh$UJJ4c(d4m)*9q>UM(vNkkD4l%a&%k<`BRe&YJN)% z-ACmL@)b)qx5kipjCel&BRMXPlbUjok>f`dYaY2U(?rX^Ko8^|xu&O^%b;UYKY8m| zjtM7rPTc7dj~|Qg>b)KEe>J%R^0!~xA$(6us2%-YRn#}8S3>^&tUZulcH)J1# z{I!{fA^-cEMmh$(<`+U=`C6J~{-R?S zAb+QKv+(`z{ZF$j^D_&+gnYL>?T|0+>45yEbvGct;r>m?Uy<=&$Ujip4f(NM_aOgU z#RJH{UiVlSKlMus?};1go?_>Jr=$(>Z$;2;i0}J~Bjn%y!3pxi|Ll(W8^=Sw)s0Dz ze^==R`CB7>1@TIo^XZoPoek3<|7`Hj1o3V?xfb^Suv|J8f7ssy<$`#&QP!42<~ibi zJb9vFrGa5A9i!%UttZ={uBp4;yLY0fZ|9}J0{*v$cF|-PxttCA(R~JNzJ_gr8vLSy z+YDrG5U)YQ*C3aNg}uBC(_SyFWZNifnHZ{ojz!%Uq0qT-Yi~H@f3j^h5zZ%fDZDD-d_Uw(bIDw|Bdl)LcWVf zKIBL2UIF<_YD*zsn!XzHIpjz0+Y0%{BRe3!eg7`VZ@aV`@()kh z3;CCh?ia)#^jkjPGXL%B4d3xkbkQC?~s49^RgiR_^FR9{NBE5MKk1Qb+rkv|3BJV_`j&SSsjpnWy^KQ z_qcxp^6#(hg8YRiZ$o}}We?MSOdM z1LSXvbb|cX%iJNq{MmTOuPXF}{Mvdi$hY~}7xMST_>ILE{eCptFaz=*sRAIsxu8%-^jXNZ3=<lX>)l~#=zmig?J#gOl3)I)yG zSIZ$^mXQzn|Cvw#`A097LVn}b)zJHDQ`!c|*Zk&f$oIcd2KhI?-U9i@KYJJQFY78G z-z)b6$Zz_l67s9G`yk)BdOzfQO!)}%|LI!;`P#v`OGQ006X)ja>i$ literal 0 HcmV?d00001 diff --git a/logs/silhouette_cameo3_pen.pcapng.gz b/logs/silhouette_cameo3_pen.pcapng.gz new file mode 100644 index 0000000000000000000000000000000000000000..8513ce933b2394ff1170018d0379a6714a86b6d8 GIT binary patch literal 2444 zcmV;733K)ziwFP!000001FhQ$SQBR&2k;@m1Fed#qE?{sXoV2c8H6M%Sgs(r1Oo|& z8U#6th$3nbb=9I$MC*-bv8-67h;GGW>xI=m^<1=Cx2>!0)^nBOs-S(IidNlkLeP(6 zH1qtM=OOcrSt|C-C2i zHU#R)m`m_tISo6Q<2a2sJIIH`JCJN`$@VQ9YG$l1|N7vDU1n2u8)ouKvwQ#<^Iy(P zuIg0G$0dAS4AYS>){#`$-V)oEl*%~R1zU5Ns2rZF%yM5c_HB`)J!8k`$d$>u@VuqSET*-4moZR{le4B5=*HzsRtUn;kf?}-H?O9B{hpy?1m~6%(CwwKr*cTvnB__`R<_9Tvj1*; zDc$V&`{rMUI!gBj1msR71`Tj-1IVCcv7bc`y5#^wMfB!Cp=X_R^=x+9%2+ezaw!Me710qC*0LjL}irs3C^*iQ^|{r<%>FLvoP8O~eHz)p7Y~|1MqWUTjE)%K>X1w3*@e3>?-W@p&`Si zuj1)*g;C_oJ;~Tp*yqm`>kP_bIHih}k%yFdb*|9$94lTV*pDsqv0=&*Xg;n7E|ts3 zSSIZAv@uZ4NgLxssd(=GGWkF<9w_X;#iu6^^5cSkmRjPQ*THpJP92w4>#!V`0P48V zb!^OB`G?eT=f^cv9a$&y;##f47$i`~-=D6d>S!7-rjEEd8=!UZFsq1)Z?%rsu8VW_ zC6IsN;bzD$+)@Vld(GvLU;b>jV7x7UaL!)(I``>Tk>};fZx2C!`ryNmzirM*$p5JF zG~|DA^8(~2z4!t0cNPB&@@F%bAwQ?+7UXYUa2xV36#oMGb$NFoKjB6#$C(!k0W{I5#;@6LmdLX`jV;3r3{Pj1qb63IoZMy!a^nm&0GLj9Na9tQQ_)))-= z*+p8&pXV12`P<_oAisyR5%L#ZjfVUw5){ELeVo5b%I`8iR> z&NYx7~lsJD@;AVRMvnG!c=PUPY5v<=9U-3aX6~8H`UgYt2 z@Yx|)zb$@o=iOBNin#VhalZY&J&^x(^#MBGv4hC@zpgn1`9X)jp^sn9#&~gl*Qeh? zzWdbEkpI8gRgm9H`#tR+JQyp^uX=s~^4Isi1o>sTmmxpv@iplDe-?OCuzp+o__NiJ zzyDYblg(x|0fnpsCe=3|Lgo5 z1mkV-yXZO!)-U8IQt{g*T@-#~>?iE^BRj_7oHQhYl^O$mSOqIJ@lT^w>|m9Gf0B;i zq}II6@8c9L?WSP)0)+GTAUl0IRzZX~PQ!14$PU>y-p8TuIc|B_UBTaicNfmrne2pU zE%`!7oeE2BjrSbsxm#p#g67_EzNam7&j}rSZ+$Ol?d{aR4f#&HU7-H&M7{(0{dc^p z5W05R;(yZJP4wE&qvF3f;I0@)#^Z$j5VE6V6<(|~P|K~DO`g4~`>t40w=3(}@-v>9 z{J(BlAtkTyQL&oWzQ$_vH55=a6qYFzX=I#cso|*h%$OE6@JmP-?FP;whfo?M(|4$d^Qt{WMQ6jH@8)|YP|Fft0bo`JK zk?#WvCl)|{#kpnl`rEW$WdEC$D)ARUI@n9^mlmw zu=5nE-xj}5pLSGy?SwWW{lstw$X|4%Gwlz#D)K%^?stxmUm)!X`FedXTYSOK0e2qm z4b6XAS7*_F92Gyn&(%{!#;S&RwzB2=??ZONt)Knr_@@yp{h44xz81gN?&wF?)mq-y z_BG<41EAv?+06~|PyOHy`Bih3ke}(qLjJF=D#&+UuZDb=gkg|hIococ@68N>{FHwW zhy2e+jD-A!#d^qJ78MKm`H7<-|Lo`l$dA8~2whjdtoji04JqRxzgKxCe@=rMBL;i#v3n4$bY^h+pP5-|^xm>V*oBqCU?i#57xY~74 z|6M112KfymuTr|F*iEf%-pOeHQA!Z^lK)|Fg#>$p1?HGvurPb`AUaC;J6D z{yi#c1m73hbp5TWh5Db*J%swNne-U)Q}@(E{_G$|iTEqCB}&9EtZWbY=hPiV`Tqwu Kae-CHLI41vb}$$K literal 0 HcmV?d00001 diff --git a/logs/silhouette_cameo3_ratchet_blade.pcapng.gz b/logs/silhouette_cameo3_ratchet_blade.pcapng.gz new file mode 100644 index 0000000000000000000000000000000000000000..309a06149185dd9c451bb659cd170a4e93e3af61 GIT binary patch literal 2181 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 literal 0 HcmV?d00001 From a1304f8cb8eb04e910299da30185b04a85090202 Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Wed, 12 Sep 2018 01:37:01 -0400 Subject: [PATCH 27/51] - enable printer initialization/shutdown commands (to match those from silhouette studio) --- silhouette/Graphtec.py | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index dbff9bd3..624d0301 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -506,21 +506,31 @@ def initialize(s): #except: # pass - #s.write("TB71\x03") # asks for something, no idea, just repeating sniffed communication - #try: - # resp = s.read(timeout=1000) - # if len(resp) > 1: - # print("TB71: '%s'" % (resp[:-1]), file=s.log) - #except: - # pass + if 'product_id' in s.hardware and s.hardware['product_id'] == PRODUCT_ID_SILHOUETTE_CAMEO3: - #s.write("FA\x03") # asks for something, not sure, current position? - #try: - # resp = s.read(timeout=1000) - # if len(resp) > 1: - # print("FA: '%s'" % (resp[:-1]), file=s.log) # response '0,0' - #except: - # pass + s.write("TB71\x03") # asks for something, no idea, just repeating sniffed communication + try: + resp = s.read(timeout=1000) + if len(resp) > 1: + print("TB71: '%s'" % (resp[:-1]), file=s.log) + except: + pass + + s.write("FA\x03") # asks for something, not sure, current position? + try: + resp = s.read(timeout=1000) + if len(resp) > 1: + print("FA: '%s'" % (resp[:-1]), file=s.log) # response '0,0' + except: + pass + + s.write("TC\x03") + try: + resp = s.read(timeout=1000) + if len(resp) > 1: + print("TC: '%s'" % (resp[:-1]), file=s.log) # response '0,0' + except: + pass def get_version(s): """Retrieve the firmware version string from the device.""" @@ -939,7 +949,7 @@ def plot(s, mediawidth=210.0, mediaheight=297.0, margintop=None, if not 'urx' in bbox: bbox['urx'] = 0 if not 'ury' in bbox: bbox['ury'] = 0 if endposition == 'start': - new_home = "J0\x03H\x03L0\x03FN0\x03TB50,0\x03" + new_home = "L0\x03\\0,0\x03M0,0\x03J0\x03FN0\x03TB50,0\x03" else: #includes 'below' new_home = "M%d,%d\x03SO0\x03" % (int(0.5+bbox['lly']+end_paper_offset*20.), 0) #! axis swapped when using Cameo-system #new_home += "FN0\x03TB50,0\x03" From 5fc641b4ed15d1ca2982d02b14ec8cd5d7c0b915 Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Wed, 12 Sep 2018 01:40:41 -0400 Subject: [PATCH 28/51] - change order of setup commands (to match order of silhouette studio) - disable FY1 command --- silhouette/Graphtec.py | 69 +++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index 624d0301..ff89cc3f 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -565,6 +565,21 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu s.initialize() + if cuttingmat: + s.write("TG1\x03") + else: + s.write("TG0\x03") + + #FNx, x = 0 seem to be some kind of reset, x = 1: plotter head moves to other + # side of media (boundary check?), but next cut run will stall + #TB50,x: x = 1 landscape mode, x = 0 portrait mode + s.write("FN0\x03TB50,0\x03") + + if cuttingmat: + s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, 6096, 6096)) + else: + s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, 6096, 5900)) # TODO: Height should come from media height + if media is not None: if media < 100 or media > 300: media = 300 # Silhouette Studio does not appear to issue this command @@ -587,14 +602,6 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu s.write("J%d\x03" % toolholder) print("toolholder: %d" % toolholder, file=s.log) - if autoblade and depth is not None: - if 'product_id' in s.hardware and s.hardware['product_id'] == PRODUCT_ID_SILHOUETTE_CAMEO3: - if toolholder == 1: - if depth < 0: depth = 0 - if depth > 10: depth = 10 - s.write("TF%d,%d\x03" % (depth, toolholder)); - print("depth: %d" % depth, file=s.log) - if speed is not None: if speed < 1: speed = 1 if speed > 10: speed = 10 @@ -608,6 +615,9 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu # s.write("FX%d,0\x03" % pressure); # oops, graphtecprint does it like this print("pressure: %d" % pressure, file=s.log) + if pen: + s.write("FC0,1,%d\x03" % (toolholder)) + if s.leftaligned: print("Loaded media is expected left-aligned.", file=s.log) else: @@ -619,7 +629,10 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu else: s.write("FE0,%d\x03" % toolholder) - s.write("FF1,0,%d\x03FF1,1,%d\x03" % (toolholder, toolholder)) + if pen: + s.write("FF0,0,%d\x03" % (toolholder)) + else: + s.write("FF1,0,%d\x03FF1,1,%d\x03" % (toolholder, toolholder)) # robocut/Plotter.cpp:393 says: # It is 0 for the pen, 18 for cutting. Default diameter of a blade is 0.9mm @@ -627,34 +640,34 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu # C possible stands for circle. # This value is the circle diameter which is exectuted on direction changes on corners to adjust the blade. # Seems to be limited to 46 or 47. Values above does keep the last setting on the device. - if pen: - circle = 0 - else: + if not pen: circle = 0.5 + bladediameter * 20 - s.write("FC0,1,%d\x03FC%d,1,%d\x03" % (toolholder, circle, toolholder)) + s.write("FC0,1,%d\x03FC%d,1,%d\x03" % (toolholder, circle, toolholder)) + + if autoblade and depth is not None: + if 'product_id' in s.hardware and s.hardware['product_id'] == PRODUCT_ID_SILHOUETTE_CAMEO3: + if toolholder == 1: + if depth < 0: depth = 0 + if depth > 10: depth = 10 + s.write("TF%d,%d\x03" % (depth, toolholder)); + print("depth: %d" % depth, file=s.log) # if enabled, rollers three times forward and back. # needs a pressure of 19 or more, else nothing will happen if trackenhancing is not None: if trackenhancing: s.write("FY0\x03") - else: - s.write("FY1\x03") - - if 'product_id' in s.hardware and s.hardware['product_id'] == PRODUCT_ID_SILHOUETTE_CAMEO3: - if cuttingmat: - s.write("TG1\x03") - else: - s.write("TG0\x03") + # else: + # s.write("FY1\x03") #FNx, x = 0 seem to be some kind of reset, x = 1: plotter head moves to other # side of media (boundary check?), but next cut run will stall #TB50,x: x = 1 landscape mode, x = 0 portrait mode - if landscape is not None: - if landscape: - s.write("FN0\x03TB50,1\x03") - else: - s.write("FN0\x03TB50,0\x03") + #if landscape is not None: + # if landscape: + # s.write("FN0\x03TB50,1\x03") + # else: + # s.write("FN0\x03TB50,0\x03") # Don't lift plotter head between paths #s.write("FE0,0\x03") @@ -923,8 +936,8 @@ def plot(s, mediawidth=210.0, mediaheight=297.0, margintop=None, #p = "FU%d,%d\x03" % (height,width) # optional #s.write(p) - p = "\\%d,%d\x03Z%d,%d\x03" % (0, 0, 6096, 6096) - s.write(p) + #p = "\\%d,%d\x03Z%d,%d\x03" % (0, 0, 6096, 6096) + #s.write(p) bbox['clip'] = {'urx':width, 'ury':top, 'llx':left, 'lly':height} bbox['only'] = bboxonly From b116e26c73d2685f300159f9df17bf6a127005fe Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Wed, 12 Sep 2018 01:42:29 -0400 Subject: [PATCH 29/51] - change speed/pressure settings for vinyl and pen (to match silhouette studio) --- sendto_silhouette.inx | 4 ++-- silhouette/Graphtec.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sendto_silhouette.inx b/sendto_silhouette.inx index 6fd8793b..0d16e815 100644 --- a/sendto_silhouette.inx +++ b/sendto_silhouette.inx @@ -25,11 +25,11 @@ [P>0 S>0] Custom: use non-zero Pressure and Speed below [P=27,S=10] SCard without Craft Paper Backing [P=27,S=10] Card with Craft Paper Backing - [P=10,S=10,D=1] Vinyl Sticker + [P=10,S= 5,D=1] Vinyl Sticker [P=14,S=10] Film Labels [P=27,S=10] Thick Media [P= 2,S=10] Thin Media - [P=10,S=10] Pen + [P=18,S=10] Pen [P=30,S=10] Bond Paper 13-28 lbs (105g) [P=30,S=10] Bristol Paper 57-67 lbs (145g) [P=30,S=10] Cardstock 40-60 lbs (90g) diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index ff89cc3f..985d61c8 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -86,11 +86,11 @@ # media, pressure, speed, depth, cap-color, name ( 100, 27, 10, 1, "yellow", "Card without Craft Paper Backing"), ( 101, 27, 10, 1, "yellow", "Card with Craft Paper Backing"), - ( 102, 10, 10, 1, "blue", "Vinyl Sticker"), + ( 102, 10, 5, 1, "blue", "Vinyl Sticker"), ( 106, 14, 10, 1, "blue", "Film Labels"), ( 111, 27, 10, 1, "yellow", "Thick Media"), ( 112, 2, 10, 1, "blue", "Thin Media"), - ( 113, 10, 10,None, "pen", "Pen"), + ( 113, 18, 10,None, "pen", "Pen"), ( 120, 30, 10, 1, "blue", "Bond Paper 13-28 lbs (105g)"), ( 121, 30, 10, 1, "yellow", "Bristol Paper 57-67 lbs (145g)"), ( 122, 30, 10, 1, "yellow", "Cardstock 40-60 lbs (90g)"), From d18d4e4d58619263d65c66bc2e22a7c85e47a453 Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Wed, 12 Sep 2018 02:05:50 -0400 Subject: [PATCH 30/51] - cameo3 appears to be able to use full width in 'no cutting mat' mode --- silhouette/Graphtec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index 985d61c8..226a9cd7 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -578,7 +578,7 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu if cuttingmat: s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, 6096, 6096)) else: - s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, 6096, 5900)) # TODO: Height should come from media height + s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, 6096, 6096)) # TODO: Height should come from media height if media is not None: if media < 100 or media > 300: media = 300 From bc3f9478de8d51b16e1ac70ee72a13f0689cb63f Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Wed, 12 Sep 2018 02:52:38 -0400 Subject: [PATCH 31/51] - add support for 12x24 cutting mat - allow full length cutting in matless mode --- sendto_silhouette.inx | 6 +++++- sendto_silhouette.py | 4 ++-- silhouette/Graphtec.py | 14 ++++++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/sendto_silhouette.inx b/sendto_silhouette.inx index 0d16e815..6345323c 100644 --- a/sendto_silhouette.inx +++ b/sendto_silhouette.inx @@ -54,7 +54,11 @@ 0 -1 Use speed=0, pressure=0, depth=-1 to take the media defaults. Pressure values of 19 or more could trigger the trackenhancing feature, which means a movement along the full media height before start. Beware. - true + + Cameo 12x12 + Cameo 12x24 + None + false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. false diff --git a/sendto_silhouette.py b/sendto_silhouette.py index 472901cf..05c962dd 100644 --- a/sendto_silhouette.py +++ b/sendto_silhouette.py @@ -275,8 +275,8 @@ def __init__(self): self.OptionParser.add_option('-c', '--bladediameter', action = 'store', dest = 'bladediameter', type = 'float', default = 0.9, help="[0..2.3] diameter of the used blade [mm], default = 0.9") - self.OptionParser.add_option('-C', '--cuttingmat', - action = 'store', dest = 'cuttingmat', type = 'inkbool', default = True, + self.OptionParser.add_option('-C', '--cuttingmat', action = 'store', + choices=('cameo_12x12', 'cameo_12x24', 'no_mat'), dest = 'cuttingmat', default = 'cameo_12x12', help='Use cutting mat') self.OptionParser.add_option('-D', '--depth', action = 'store', dest = 'depth', type = 'int', default = -1, diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index 226a9cd7..40dcb6fe 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -546,7 +546,7 @@ def get_version(s): return resp[0:-2] # chop of 0x03 - def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cuttingmat=True, sharpencorners=False, autoblade=False, depth=None, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None): + def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cuttingmat=None, sharpencorners=False, autoblade=False, depth=None, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None, mediawidth=210.0, mediaheight=297.0): """media range is [100..300], default 132, "Print Paper Light Weight" speed range is [1..10], default None, from paper (132 -> 10) pressure range is [1..33], default None, from paper (132 -> 5) @@ -565,8 +565,10 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu s.initialize() - if cuttingmat: + if cuttingmat == 'cameo_12x12': s.write("TG1\x03") + elif cuttingmat == 'cameo_12x24': + s.write("TG2\x03") else: s.write("TG0\x03") @@ -575,10 +577,14 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu #TB50,x: x = 1 landscape mode, x = 0 portrait mode s.write("FN0\x03TB50,0\x03") - if cuttingmat: + if cuttingmat == 'cameo_12x12': s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, 6096, 6096)) + elif cuttingmat == 'cameo_12x24': + s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, 12192, 6096)) else: - s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, 6096, 6096)) # TODO: Height should come from media height + width = s.hardware['width_mm'] if 'width_mm' in s.hardware else mediawidth + height = s.hardware['length_mm'] if 'length_mm' in s.hardware else mediaheight + s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, height * 20.0, width * 20.0)) if media is not None: if media < 100 or media > 300: media = 300 From a27c0eba9578ba7d88df915ae3b8b01db7dc8317 Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Wed, 12 Sep 2018 10:04:37 -0400 Subject: [PATCH 32/51] - change precision of x/y offset fields to match precision of printer units --- sendto_silhouette.inx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sendto_silhouette.inx b/sendto_silhouette.inx index 6345323c..48a30e00 100644 --- a/sendto_silhouette.inx +++ b/sendto_silhouette.inx @@ -7,8 +7,8 @@ sendto_silhouette.py - 0.0 - 0.0 + 0.0 + 0.0 Red (left tool) Blue (right tool) From 084e1357c16f0f8a0e6ebfdda06feeccad4921c1 Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Thu, 13 Sep 2018 17:37:13 -0400 Subject: [PATCH 33/51] - add conversion utilities for printer's binary encoding commands --- silhouette/beutil.py | 164 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 silhouette/beutil.py diff --git a/silhouette/beutil.py b/silhouette/beutil.py new file mode 100644 index 00000000..41828b68 --- /dev/null +++ b/silhouette/beutil.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python + +# Conversion utilities for printer's binary encoding commands +# (c) 2018 D. Bajar + +from __future__ import print_function +import sys + +def to_BE(x, y): + if abs(x) < 112 and abs(y) < 112: + index = 224 * (x + 112) + (y + 112) + d2 = index // 224 + index -= d2 * 224 + d1 = index + be1 = "%02X%02X" % (d1 + 0x20, d2 + 0x20) + # print("BE1:", be1) + return ("BE1", be1) + elif abs(x) < 1676 and abs(y) < 1676: + index = 3352 * (x + 1676) + (y + 1676) + d3 = index // (224 * 224) + index -= d3 * (224 * 224) + d2 = index // 224 + index -= d2 * 224 + d1 = index + be2 = "%02X%02X%02X" % (d1 + 0x20, d2 + 0x20, d3 + 0x20) + # print("BE2:", be2) + return ("BE2", be2) + else: + # TODO: Implement to_BE3 + raise RuntimeError("to_BE3 not supported") + # end if +# end def to_BE + +def from_BE(be_stream): + + if len(be_stream) == 4: + d1 = int(be_stream[:2], 16) + d2 = int(be_stream[2:], 16) + if d1 < 0x20 or d2 < 0x20: + raise ValueError("Invalid BE1 stream digit") + # end if + index = (d2 - 0x20) * 224 + (d1 - 0x20) + x = index // 224 - 112 + y = index % 224 - 112 + # print("BE1: %d,%d" % (x, y)) + return ("BE1", (x, y)) + elif len(be_stream) == 6: + d1 = int(be_stream[0:2], 16) + d2 = int(be_stream[2:4], 16) + d3 = int(be_stream[4:6], 16) + if d1 < 0x20 or d2 < 0x20 or d3 < 0x20: + raise ValueError("Invalid BE2 stream digit") + # end if + index = (d3 - 0x20) * (224 * 224) + (d2 - 0x20) * 224 + (d1 - 0x20) + x = index // 3352 - 1676 + y = index % 3352 - 1676 + # print("BE2: %d,%d" % (x, y)) + return ("BE2", (x, y)) + elif len(be_stream) == 8: + # TODO: Implement from_BE3 + raise RuntimeError("from_BE3 not supported") + else: + raise ValueError("Invalid length hex stream") + # end if + +# end def from_BE + +def test_BE(x, y, be_stream, be_enc): + + enc, stream = to_BE(x, y) + passed = True if enc == be_enc and stream == be_stream else False + print("to_BE: (%d, %d) -> '%s' %s= '%s' : %s" % (x, y, stream, '=' if passed else '!', be_stream, "PASSED" if passed else "FAILED")) + if not passed: + sys.exit(-1) + # end if + + enc, xy = from_BE(be_stream) + passed = True if enc == be_enc and xy == (x, y) else False + print("from_BE: '%s -> (%d, %d) %s= (%d, %d) : %s" % (be_stream, xy[0], xy[1], '=' if passed else '!', x, y, "PASSED" if passed else "FAILED")) + if not passed: + sys.exit(-1) + # end if + +# end def test_BE + +def test(): + + print("Running tests ...") + + test_BE( 0, 0, "9090", "BE1") + test_BE( 0, 1, "9190", "BE1") + test_BE( 1, 0, "9091", "BE1") + test_BE( 0, -1, "8F90", "BE1") + test_BE( -1, 0, "908F", "BE1") + test_BE( 1, 1, "9191", "BE1") + test_BE( 1, -1, "8F91", "BE1") + test_BE( -1, -1, "8F8F", "BE1") + test_BE( -1, 1, "918F", "BE1") + test_BE( 0, 111, "FF90", "BE1") + test_BE( 111, 0, "90FF", "BE1") + test_BE( 0, -111, "2190", "BE1") + test_BE(-111, 0, "9021", "BE1") + test_BE( 111, 111, "FFFF", "BE1") + test_BE( 111, -111, "21FF", "BE1") + test_BE(-111, -111, "2121", "BE1") + test_BE(-111, 111, "FF21", "BE1") + test_BE( 56, -27, "75C8", "BE1") + test_BE( -77, 44, "BC43", "BE1") + test_BE( -39, 106, "FA69", "BE1") + test_BE( 72, -25, "77D8", "BE1") + + test_BE( 0, 112, "3C2090", "BE2") + test_BE( 112, 0, "AC8B97", "BE2") + test_BE( 0, -112, "3CFF8F", "BE2") + test_BE( -112, 0, "AC9388", "BE2") + test_BE( 112, 112, "3C8C97", "BE2") + test_BE( 112, -112, "3C8B97", "BE2") + test_BE( -112, -112, "3C9388", "BE2") + test_BE( -112, 112, "3C9488", "BE2") + test_BE( 0, 1675, "372790", "BE2") + test_BE( 1675, 0, "D4E8FF", "BE2") + test_BE( 0, -1675, "41F88F", "BE2") + test_BE(-1675, 0, "843620", "BE2") + test_BE( 1675, 1675, "5FF0FF", "BE2") + test_BE( 1675, -1675, "69E1FF", "BE2") + test_BE(-1675, -1675, "F92E20", "BE2") + test_BE(-1675, 1675, "EF3D20", "BE2") + test_BE( 1091, 674, "B6E8D8", "BE2") + test_BE( 116, 1421, "D9CD97", "BE2") + test_BE( -702, 485, "E13861", "BE2") + test_BE(-1463, -1153, "C3552E", "BE2") + + print("All test PASSED !!!") + sys.exit(0) + +# end def test + +def main(argv): + + # test() + + if len(argv) <= 1: + print("Usage: %s | " % (argv[0])) + return 1 + # end if + + if len(argv) <= 2: + be_stream = argv[1] + res = from_BE(be_stream) + print("%s %s -> %d,%d" % (res[0], be_stream, res[1][0], res[1][1])) + else: + x = int(argv[1], 0) + y = int(argv[2], 0) + res = to_BE(x, y) + print("%d,%d -> %s %s" % (x, y, res[0], res[1])) + # end if + + return 0 + +# end def main + +if __name__ == '__main__': + sys.exit(main(sys.argv)) +# end if From 9669ae080e4585030655f8c6be5e7b82835c18f8 Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Thu, 13 Sep 2018 23:25:58 -0400 Subject: [PATCH 34/51] - implement be3 encoding --- silhouette/beutil.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/silhouette/beutil.py b/silhouette/beutil.py index 41828b68..8e58a3ca 100644 --- a/silhouette/beutil.py +++ b/silhouette/beutil.py @@ -25,9 +25,22 @@ def to_BE(x, y): be2 = "%02X%02X%02X" % (d1 + 0x20, d2 + 0x20, d3 + 0x20) # print("BE2:", be2) return ("BE2", be2) + elif abs(x) < 375482 and abs(y) < 375482: + index = 750964 * (x + 375482) + (y + 375482) + d5 = index // (224 * 224 * 224 * 224) + index -= d5 * (224 * 224 * 224 * 224) + d4 = index // (224 * 224 * 224) + index -= d4 * (224 * 224 * 224) + d3 = index // (224 * 224) + index -= d3 * (224 * 224) + d2 = index // 224 + index -= d2 * 224 + d1 = index + be3 = "%02X%02X%02X%02X%02X" % (d1 + 0x20, d2 + 0x20, d3 + 0x20, d4 + 0x20, d5 + 0x20) + # print("BE3:", be3) + return ("BE3", be3) else: - # TODO: Implement to_BE3 - raise RuntimeError("to_BE3 not supported") + raise ValueError("Invalid coordinate") # end if # end def to_BE @@ -56,9 +69,20 @@ def from_BE(be_stream): y = index % 3352 - 1676 # print("BE2: %d,%d" % (x, y)) return ("BE2", (x, y)) - elif len(be_stream) == 8: - # TODO: Implement from_BE3 - raise RuntimeError("from_BE3 not supported") + elif len(be_stream) == 10: + d1 = int(be_stream[0:2], 16) + d2 = int(be_stream[2:4], 16) + d3 = int(be_stream[4:6], 16) + d4 = int(be_stream[6:8], 16) + d5 = int(be_stream[8:10], 16) + if d1 < 0x20 or d2 < 0x20 or d3 < 0x20 or d4 < 0x20 or d5 < 0x20: + raise ValueError("Invalid BE3 stream digit") + # end if + index = (d5 - 0x20) * (224 * 224 * 224 * 224) + (d4 - 0x20) * (224 * 224 * 224) + (d3 - 0x20) * (224 * 224) + (d2 - 0x20) * 224 + (d1 - 0x20) + x = index // 750964 - 375482 + y = index % 750964 - 375482 + # print("BE3: %d,%d" % (x, y)) + return ("BE3", (x, y)) else: raise ValueError("Invalid length hex stream") # end if From 157eb0874640b1a58959af2dc979205feebe20c4 Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Fri, 14 Sep 2018 00:05:45 -0400 Subject: [PATCH 35/51] - add "Start/End Ext." options when lifting plotter head at sharp corners --- ..._cameo3_line_segment_overcut_0.9mm.pcapng.gz | Bin 0 -> 2795 bytes sendto_silhouette.inx | 16 +++++++++------- sendto_silhouette.py | 8 ++++++++ silhouette/Graphtec.py | 6 ++++-- 4 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 logs/silhouette_cameo3_line_segment_overcut_0.9mm.pcapng.gz diff --git a/logs/silhouette_cameo3_line_segment_overcut_0.9mm.pcapng.gz b/logs/silhouette_cameo3_line_segment_overcut_0.9mm.pcapng.gz new file mode 100644 index 0000000000000000000000000000000000000000..8a82b9704ea9055405307c3c64bd47fb4396ad36 GIT binary patch literal 2795 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 literal 0 HcmV?d00001 diff --git a/sendto_silhouette.inx b/sendto_silhouette.inx index 48a30e00..6c0d27d2 100644 --- a/sendto_silhouette.inx +++ b/sendto_silhouette.inx @@ -9,6 +9,11 @@ 0.0 0.0 + + Cameo 12x12 + Cameo 12x24 + None + Red (left tool) Blue (right tool) @@ -54,17 +59,10 @@ 0 -1 Use speed=0, pressure=0, depth=-1 to take the media defaults. Pressure values of 19 or more could trigger the trackenhancing feature, which means a movement along the full media height before start. Beware. - - Cameo 12x12 - Cameo 12x24 - None - false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. false To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) - false Lift head at sharp corners - 0.5 1 false @@ -89,6 +87,10 @@ false Keep dialog open until device becomes idle again. + false Lift head at sharp corners + 0.1 + 0.1 + 0.5 Z-Order Without mat diff --git a/sendto_silhouette.py b/sendto_silhouette.py index 05c962dd..7f802143 100644 --- a/sendto_silhouette.py +++ b/sendto_silhouette.py @@ -306,6 +306,12 @@ def __init__(self): self.OptionParser.add_option('-P', '--sharpencorners', action = 'store', dest = 'sharpencorners', type = 'inkbool', default = False, help='Lift head at sharp corners') + self.OptionParser.add_option('--sharpencorners_start', + action = 'store', dest = 'sharpencorners_start', type = 'float', default = 0.1, + help="Sharpen Corners - Start Ext. [mm]") + self.OptionParser.add_option('--sharpencorners_end', + action = 'store', dest = 'sharpencorners_end', type = 'float', default = 0.1, + help="Sharpen Corners - End Ext. [mm]") self.OptionParser.add_option('-r', '--reversetoggle', action = 'store', dest = 'reversetoggle', type = 'inkbool', default = False, help="Cut each path the other direction. Affects every second pass when multipass.") @@ -1107,6 +1113,8 @@ def write_progress(done, total, msg): toolholder=self.options.toolholder, cuttingmat=self.options.cuttingmat, sharpencorners=self.options.sharpencorners, + sharpencorners_start=self.options.sharpencorners_start, + sharpencorners_end=self.options.sharpencorners_end, autoblade=self.autoblade, depth=self.options.depth, bladediameter=self.options.bladediameter, diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index 40dcb6fe..5d4896cf 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -546,7 +546,7 @@ def get_version(s): return resp[0:-2] # chop of 0x03 - def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cuttingmat=None, sharpencorners=False, autoblade=False, depth=None, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None, mediawidth=210.0, mediaheight=297.0): + def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cuttingmat=None, sharpencorners=False, sharpencorners_start=0.1, sharpencorners_end=0.1, autoblade=False, depth=None, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None, mediawidth=210.0, mediaheight=297.0): """media range is [100..300], default 132, "Print Paper Light Weight" speed range is [1..10], default None, from paper (132 -> 10) pressure range is [1..33], default None, from paper (132 -> 5) @@ -638,7 +638,9 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu if pen: s.write("FF0,0,%d\x03" % (toolholder)) else: - s.write("FF1,0,%d\x03FF1,1,%d\x03" % (toolholder, toolholder)) + sharpencorners_start = int((sharpencorners_start + 0.05) * 10.0) + sharpencorners_end = int((sharpencorners_end + 0.05) * 10.0) + s.write("FF%d,0,%d\x03FF%d,%d,%d\x03" % (sharpencorners_start, toolholder, sharpencorners_start, sharpencorners_end, toolholder)) # robocut/Plotter.cpp:393 says: # It is 0 for the pen, 18 for cutting. Default diameter of a blade is 0.9mm From d90651e49c93bb9da81bc7e1a84ae2df32bbfc05 Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Fri, 14 Sep 2018 01:22:14 -0400 Subject: [PATCH 36/51] - add precut for closed paths --- sendto_silhouette.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sendto_silhouette.py b/sendto_silhouette.py index 7f802143..d7300ee5 100644 --- a/sendto_silhouette.py +++ b/sendto_silhouette.py @@ -1070,6 +1070,20 @@ def write_progress(done, total, msg): # on a closed path some overlapping doesn't harm, limited to a maximum of one additional round overcut = self.options.overcut if (overcut > 0) and self.is_closed_path(mm_path): + precut = overcut + pfrom = mm_path[-1] + for pprev in reversed(mm_path[:-1]): + dx = pprev[0] - pfrom[0] + dy = pprev[1] - pfrom[1] + dist = math.sqrt(dx*dx + dy*dy) + if (precut > dist): # Full segment needed + precut -= dist + multipath.insert(0, pprev) + pfrom = pprev + else: # only partial segement needed, create new endpoint + pprev = (pfrom[0]+dx*(precut/dist), pfrom[1]+dy*(precut/dist)) + multipath.insert(0, pprev) + break pfrom = mm_path[0] for pnext in mm_path[1:]: dx = pnext[0] - pfrom[0] From b87652149609a8bedcdc034dd0e3e5cfe38382fe Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Fri, 14 Sep 2018 01:44:22 -0400 Subject: [PATCH 37/51] - add option to disable sw clipping (cameo3 provides hardware clipping) --- sendto_silhouette.inx | 1 + sendto_silhouette.py | 4 ++++ silhouette/Graphtec.py | 7 +++++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/sendto_silhouette.inx b/sendto_silhouette.inx index 6c0d27d2..69bc133c 100644 --- a/sendto_silhouette.inx +++ b/sendto_silhouette.inx @@ -101,6 +101,7 @@ Without mat: Subdivide, sort, and choose cut directions, so that a cutting mat is not needed in most cases. Minimal Traveling: Find the nearest startpoint to minimize travel movements Minimal Traveling (fully optimized): Additionally search startpoints in closed paths + true false Used for debugging and developent only! diff --git a/sendto_silhouette.py b/sendto_silhouette.py index d7300ee5..bd7f072f 100644 --- a/sendto_silhouette.py +++ b/sendto_silhouette.py @@ -288,6 +288,9 @@ def __init__(self): action = 'store', dest = 'strategy', default = 'mintravel', choices=('mintravel','mintravelfull','matfree','zorder' ), help="Cutting Strategy: mintravel, mintravelfull, matfree or zorder") + self.OptionParser.add_option('-l', '--sw_clipping', + action = 'store', dest = 'sw_clipping', type = 'inkbool', default = True, + help='Enable software clipping') self.OptionParser.add_option('-m', '--media', '--media-id', '--media_id', action = 'store', dest = 'media', default = '132', choices=('100','101','102','106','111','112','113', @@ -1131,6 +1134,7 @@ def write_progress(done, total, msg): sharpencorners_end=self.options.sharpencorners_end, autoblade=self.autoblade, depth=self.options.depth, + sw_clipping=self.options.sw_clipping, bladediameter=self.options.bladediameter, pressure=self.options.pressure, speed=self.options.speed) diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index 5d4896cf..cee6f17a 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -290,6 +290,7 @@ def __init__(self, log=sys.stderr, no_device=False, progress_cb=None): self.regmark = False # not yet implemented. See robocut/Plotter.cpp:446 if self.dev is None or 'width_mm' in self.hardware: self.leftaligned = True + self.enable_sw_clipping = True def write(s, string, timeout=10000): """Send a command to the device. Long commands are sent in chunks of 4096 bytes. @@ -546,7 +547,7 @@ def get_version(s): return resp[0:-2] # chop of 0x03 - def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cuttingmat=None, sharpencorners=False, sharpencorners_start=0.1, sharpencorners_end=0.1, autoblade=False, depth=None, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None, mediawidth=210.0, mediaheight=297.0): + def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cuttingmat=None, sharpencorners=False, sharpencorners_start=0.1, sharpencorners_end=0.1, autoblade=False, depth=None, sw_clipping=True, trackenhancing=False, bladediameter=0.9, landscape=False, leftaligned=None, mediawidth=210.0, mediaheight=297.0): """media range is [100..300], default 132, "Print Paper Light Weight" speed range is [1..10], default None, from paper (132 -> 10) pressure range is [1..33], default None, from paper (132 -> 5) @@ -660,6 +661,8 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu s.write("TF%d,%d\x03" % (depth, toolholder)); print("depth: %d" % depth, file=s.log) + s.enable_sw_clipping = sw_clipping + # if enabled, rollers three times forward and back. # needs a pressure of 19 or more, else nothing will happen if trackenhancing is not None: @@ -811,7 +814,7 @@ def plot_cmds(s, plist, bbox, x_off_mm, y_off_mm): bbox['clip']['count'] = 1 if bbox['only'] is False: - if inside and last_inside: + if not s.enable_sw_clipping or (inside and last_inside): plotcmds.append("D%d,%d" % (int(0.5+y), int(0.5+x))) else: # // if outside the range just move From 111a628342c4cd42100caf449600427121bb1b09 Mon Sep 17 00:00:00 2001 From: "D. Bajar" Date: Sat, 6 Oct 2018 13:28:02 -0400 Subject: [PATCH 38/51] - enable new features for Cameo3 only --- silhouette/Graphtec.py | 155 +++++++++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 60 deletions(-) diff --git a/silhouette/Graphtec.py b/silhouette/Graphtec.py index cee6f17a..ab08a78f 100644 --- a/silhouette/Graphtec.py +++ b/silhouette/Graphtec.py @@ -292,6 +292,9 @@ def __init__(self, log=sys.stderr, no_device=False, progress_cb=None): self.leftaligned = True self.enable_sw_clipping = True + def product_id(s): + return s.hardware['product_id'] if 'product_id' in s.hardware else None + def write(s, string, timeout=10000): """Send a command to the device. Long commands are sent in chunks of 4096 bytes. A nonblocking read() is attempted before write(), to find spurious diagnostics.""" @@ -507,7 +510,7 @@ def initialize(s): #except: # pass - if 'product_id' in s.hardware and s.hardware['product_id'] == PRODUCT_ID_SILHOUETTE_CAMEO3: + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: s.write("TB71\x03") # asks for something, no idea, just repeating sniffed communication try: @@ -538,7 +541,7 @@ def get_version(s): if s.dev is None: return None - s.write("FG\x03") + s.write("FG\x03") # Get Version: 17 Chars try: resp = s.read(timeout=10000) # Large timeout because the plotter moves. except usb.core.USBError as e: @@ -566,31 +569,35 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu s.initialize() - if cuttingmat == 'cameo_12x12': - s.write("TG1\x03") - elif cuttingmat == 'cameo_12x24': - s.write("TG2\x03") - else: - s.write("TG0\x03") + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: + if cuttingmat == 'cameo_12x12': + s.write("TG1\x03") + elif cuttingmat == 'cameo_12x24': + s.write("TG2\x03") + else: + s.write("TG0\x03") - #FNx, x = 0 seem to be some kind of reset, x = 1: plotter head moves to other - # side of media (boundary check?), but next cut run will stall - #TB50,x: x = 1 landscape mode, x = 0 portrait mode - s.write("FN0\x03TB50,0\x03") + #FNx, x = 0 seem to be some kind of reset, x = 1: plotter head moves to other + # side of media (boundary check?), but next cut run will stall + #TB50,x: x = 1 landscape mode, x = 0 portrait mode + s.write("FN0\x03TB50,0\x03") - if cuttingmat == 'cameo_12x12': - s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, 6096, 6096)) - elif cuttingmat == 'cameo_12x24': - s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, 12192, 6096)) - else: - width = s.hardware['width_mm'] if 'width_mm' in s.hardware else mediawidth - height = s.hardware['length_mm'] if 'length_mm' in s.hardware else mediaheight - s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, height * 20.0, width * 20.0)) + if cuttingmat == 'cameo_12x12': + s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, 6096, 6096)) + elif cuttingmat == 'cameo_12x24': + s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, 12192, 6096)) + else: + width = s.hardware['width_mm'] if 'width_mm' in s.hardware else mediawidth + height = s.hardware['length_mm'] if 'length_mm' in s.hardware else mediaheight + s.write("\\%d,%d\x03Z%d,%d\x03" % (0, 0, height * 20.0, width * 20.0)) if media is not None: if media < 100 or media > 300: media = 300 - # Silhouette Studio does not appear to issue this command - #s.write("FW%d\x03" % media); + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: + # Silhouette Studio does not appear to issue this command + pass + else: + s.write("FW%d\x03" % media); if pen is None: if media == 113: pen = True @@ -606,24 +613,32 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu if toolholder is None: toolholder = 1 - s.write("J%d\x03" % toolholder) + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: + s.write("J%d\x03" % toolholder) print("toolholder: %d" % toolholder, file=s.log) if speed is not None: if speed < 1: speed = 1 if speed > 10: speed = 10 - s.write("!%d,%d\x03" % (speed, toolholder)); + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: + s.write("!%d,%d\x03" % (speed, toolholder)); + else: + s.write("!%d\x03" % speed); print("speed: %d" % speed, file=s.log) if pressure is not None: if pressure < 1: pressure = 1 if pressure > 33: pressure = 33 - s.write("FX%d,%d\x03" % (pressure, toolholder)); + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: + s.write("FX%d,%d\x03" % (pressure, toolholder)); + else: + s.write("FX%d\x03" % pressure); # s.write("FX%d,0\x03" % pressure); # oops, graphtecprint does it like this print("pressure: %d" % pressure, file=s.log) - if pen: - s.write("FC0,1,%d\x03" % (toolholder)) + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: + if pen: + s.write("FC0,1,%d\x03" % (toolholder)) if s.leftaligned: print("Loaded media is expected left-aligned.", file=s.log) @@ -631,17 +646,18 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu print("Loaded media is expected right-aligned.", file=s.log) # Lift plotter head at sharp corners - if sharpencorners: - s.write("FE1,%d\x03" % toolholder) - else: - s.write("FE0,%d\x03" % toolholder) + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: + if sharpencorners: + s.write("FE1,%d\x03" % toolholder) + else: + s.write("FE0,%d\x03" % toolholder) - if pen: - s.write("FF0,0,%d\x03" % (toolholder)) - else: - sharpencorners_start = int((sharpencorners_start + 0.05) * 10.0) - sharpencorners_end = int((sharpencorners_end + 0.05) * 10.0) - s.write("FF%d,0,%d\x03FF%d,%d,%d\x03" % (sharpencorners_start, toolholder, sharpencorners_start, sharpencorners_end, toolholder)) + if pen: + s.write("FF0,0,%d\x03" % (toolholder)) + else: + sharpencorners_start = int((sharpencorners_start + 0.05) * 10.0) + sharpencorners_end = int((sharpencorners_end + 0.05) * 10.0) + s.write("FF%d,0,%d\x03FF%d,%d,%d\x03" % (sharpencorners_start, toolholder, sharpencorners_start, sharpencorners_end, toolholder)) # robocut/Plotter.cpp:393 says: # It is 0 for the pen, 18 for cutting. Default diameter of a blade is 0.9mm @@ -649,17 +665,24 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu # C possible stands for circle. # This value is the circle diameter which is exectuted on direction changes on corners to adjust the blade. # Seems to be limited to 46 or 47. Values above does keep the last setting on the device. - if not pen: - circle = 0.5 + bladediameter * 20 - s.write("FC0,1,%d\x03FC%d,1,%d\x03" % (toolholder, circle, toolholder)) - - if autoblade and depth is not None: - if 'product_id' in s.hardware and s.hardware['product_id'] == PRODUCT_ID_SILHOUETTE_CAMEO3: - if toolholder == 1: - if depth < 0: depth = 0 - if depth > 10: depth = 10 - s.write("TF%d,%d\x03" % (depth, toolholder)); - print("depth: %d" % depth, file=s.log) + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: + if not pen: + circle = 0.5 + bladediameter * 20 + s.write("FC0,1,%d\x03FC%d,1,%d\x03" % (toolholder, circle, toolholder)) + + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: + if autoblade and depth is not None: + if toolholder == 1: + if depth < 0: depth = 0 + if depth > 10: depth = 10 + s.write("TF%d,%d\x03" % (depth, toolholder)); + print("depth: %d" % depth, file=s.log) + else: + if pen: + circle = 0 + else: + circle = bladediameter * 20 + s.write("FC%d\x03" % circle) s.enable_sw_clipping = sw_clipping @@ -668,20 +691,26 @@ def setup(s, media=132, speed=None, pressure=None, toolholder=None, pen=None, cu if trackenhancing is not None: if trackenhancing: s.write("FY0\x03") - # else: - # s.write("FY1\x03") + else: + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: + pass + else: + s.write("FY1\x03") #FNx, x = 0 seem to be some kind of reset, x = 1: plotter head moves to other # side of media (boundary check?), but next cut run will stall #TB50,x: x = 1 landscape mode, x = 0 portrait mode - #if landscape is not None: - # if landscape: - # s.write("FN0\x03TB50,1\x03") - # else: - # s.write("FN0\x03TB50,0\x03") + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: + pass + else: + if landscape is not None: + if landscape: + s.write("FN0\x03TB50,1\x03") + else: + s.write("FN0\x03TB50,0\x03") - # Don't lift plotter head between paths - #s.write("FE0,0\x03") + # Don't lift plotter head between paths + s.write("FE0,0\x03") def find_bbox(s, cut): """Find the bounding box of the cut, returns (xmin,ymin,xmax,ymax)""" @@ -947,8 +976,11 @@ def plot(s, mediawidth=210.0, mediaheight=297.0, margintop=None, #p = "FU%d,%d\x03" % (height,width) # optional #s.write(p) - #p = "\\%d,%d\x03Z%d,%d\x03" % (0, 0, 6096, 6096) - #s.write(p) + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: + pass + else: + p = "\\0,0\x03Z%d,%d\x03L0\x03FE0,0\x03FF0,0,0\x03" % (height, width) + s.write(p) bbox['clip'] = {'urx':width, 'ury':top, 'llx':left, 'lly':height} bbox['only'] = bboxonly @@ -973,7 +1005,10 @@ def plot(s, mediawidth=210.0, mediaheight=297.0, margintop=None, if not 'urx' in bbox: bbox['urx'] = 0 if not 'ury' in bbox: bbox['ury'] = 0 if endposition == 'start': - new_home = "L0\x03\\0,0\x03M0,0\x03J0\x03FN0\x03TB50,0\x03" + if s.product_id() == PRODUCT_ID_SILHOUETTE_CAMEO3: + new_home = "L0\x03\\0,0\x03M0,0\x03J0\x03FN0\x03TB50,0\x03" + else: + new_home = "H\x03" else: #includes 'below' new_home = "M%d,%d\x03SO0\x03" % (int(0.5+bbox['lly']+end_paper_offset*20.), 0) #! axis swapped when using Cameo-system #new_home += "FN0\x03TB50,0\x03" From 51fb2ad330003fae42ce3761adb954a3116e7d32 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 22 Jun 2017 01:01:34 +0100 Subject: [PATCH 39/51] add silhouette_multi extension to perform multiple actions (cut, pen) in sequence --- silhouette_multi.inx | 367 +++++++++++++++++++++++++++++++++++++++++++ silhouette_multi.py | 178 +++++++++++++++++++++ 2 files changed, 545 insertions(+) create mode 100644 silhouette_multi.inx create mode 100755 silhouette_multi.py diff --git a/silhouette_multi.inx b/silhouette_multi.inx new file mode 100644 index 00000000..2440a712 --- /dev/null +++ b/silhouette_multi.inx @@ -0,0 +1,367 @@ + + + <_name>Silhouette Multiple Actions + com.github.jnweiger.inskscape-silhouette-multi + org.inkscape.output.svg.inkscape + inkex.py + silhouette_multi.py + + + + 0.0 + 0.0 + + No action + Media default + Pen + Cut + + 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. +Pressure values of 19 or more make the machine misbehave. Beware. + + + + [P>0 S>0] Custom: use non-zero Pressure and Speed below + [P=27,S=10] SCard without Craft Paper Backing + [P=27,S=10] Card with Craft Paper Backing + [P=10,S=10] Vinyl Sticker + [P=14,S=10] Film Labels + [P=27,S=10] Thick Media + [P= 2,S=10] Thin Media + [P=10,S=10] Pen + [P=30,S=10] Bond Paper 13-28 lbs (105g) + [P=30,S=10] Bristol Paper 57-67 lbs (145g) + [P=30,S=10] Cardstock 40-60 lbs (90g) + [P=30,S=10] Cover 40-60 lbs (170g) + [P= 1,S=10] Film, Double Matte Translucent + [P= 1,S=10] Film, Vinyl With Adhesive Back + [P= 1,S=10] Film, Window With Kling Adhesive + [P=30,S=10] Index 90 lbs (165g) + [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) + [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) + [P=30,S= 3] Magnetic Sheet + [P=30,S=10] Offset 24-60 lbs (90g) + [P= 5,S=10] Print Paper Light Weight + [P=25,S=10] Print Paper Medium Weight + [P=20,S=10] Sticker Sheet + [P=20,S=10] Tag 100 lbs (275g) + [P=30,S=10] Text Paper 24-70 lbs (105g) + [P=30,S=10] Vellum Bristol 57-67 lbs (145g) + [P=30,S=10] Writing Paper 24-70 lbs (105g) + + 0 + 0 + Use speed=0, pressure=0 to take the media defaults. + false Convert paths with dashed strokes to separate subpaths for perforated cuts. + false Shift to the top lefthand corner, then do offsets. + false + To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) + 0.5 + 1 + false + + Start Position + Below Cut-Out + + Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. + + + + + 0.0 + 0.0 + + No action + Media default + Pen + Cut + + 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. +Pressure values of 19 or more make the machine misbehave. Beware. + + + + [P>0 S>0] Custom: use non-zero Pressure and Speed below + [P=27,S=10] SCard without Craft Paper Backing + [P=27,S=10] Card with Craft Paper Backing + [P=10,S=10] Vinyl Sticker + [P=14,S=10] Film Labels + [P=27,S=10] Thick Media + [P= 2,S=10] Thin Media + [P=10,S=10] Pen + [P=30,S=10] Bond Paper 13-28 lbs (105g) + [P=30,S=10] Bristol Paper 57-67 lbs (145g) + [P=30,S=10] Cardstock 40-60 lbs (90g) + [P=30,S=10] Cover 40-60 lbs (170g) + [P= 1,S=10] Film, Double Matte Translucent + [P= 1,S=10] Film, Vinyl With Adhesive Back + [P= 1,S=10] Film, Window With Kling Adhesive + [P=30,S=10] Index 90 lbs (165g) + [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) + [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) + [P=30,S= 3] Magnetic Sheet + [P=30,S=10] Offset 24-60 lbs (90g) + [P= 5,S=10] Print Paper Light Weight + [P=25,S=10] Print Paper Medium Weight + [P=20,S=10] Sticker Sheet + [P=20,S=10] Tag 100 lbs (275g) + [P=30,S=10] Text Paper 24-70 lbs (105g) + [P=30,S=10] Vellum Bristol 57-67 lbs (145g) + [P=30,S=10] Writing Paper 24-70 lbs (105g) + + 0 + 0 + Use speed=0, pressure=0 to take the media defaults. + false Convert paths with dashed strokes to separate subpaths for perforated cuts. + false Shift to the top lefthand corner, then do offsets. + false + To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) + 0.5 + 1 + false + + Start Position + Below Cut-Out + + Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. + + + + + 0.0 + 0.0 + + No action + Media default + Pen + Cut + + 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. +Pressure values of 19 or more make the machine misbehave. Beware. + + + + [P>0 S>0] Custom: use non-zero Pressure and Speed below + [P=27,S=10] SCard without Craft Paper Backing + [P=27,S=10] Card with Craft Paper Backing + [P=10,S=10] Vinyl Sticker + [P=14,S=10] Film Labels + [P=27,S=10] Thick Media + [P= 2,S=10] Thin Media + [P=10,S=10] Pen + [P=30,S=10] Bond Paper 13-28 lbs (105g) + [P=30,S=10] Bristol Paper 57-67 lbs (145g) + [P=30,S=10] Cardstock 40-60 lbs (90g) + [P=30,S=10] Cover 40-60 lbs (170g) + [P= 1,S=10] Film, Double Matte Translucent + [P= 1,S=10] Film, Vinyl With Adhesive Back + [P= 1,S=10] Film, Window With Kling Adhesive + [P=30,S=10] Index 90 lbs (165g) + [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) + [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) + [P=30,S= 3] Magnetic Sheet + [P=30,S=10] Offset 24-60 lbs (90g) + [P= 5,S=10] Print Paper Light Weight + [P=25,S=10] Print Paper Medium Weight + [P=20,S=10] Sticker Sheet + [P=20,S=10] Tag 100 lbs (275g) + [P=30,S=10] Text Paper 24-70 lbs (105g) + [P=30,S=10] Vellum Bristol 57-67 lbs (145g) + [P=30,S=10] Writing Paper 24-70 lbs (105g) + + 0 + 0 + Use speed=0, pressure=0 to take the media defaults. + false Convert paths with dashed strokes to separate subpaths for perforated cuts. + false Shift to the top lefthand corner, then do offsets. + false + To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) + 0.5 + 1 + false + + Start Position + Below Cut-Out + + Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. + + + + + 0.0 + 0.0 + + No action + Media default + Pen + Cut + + 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. +Pressure values of 19 or more make the machine misbehave. Beware. + + + + [P>0 S>0] Custom: use non-zero Pressure and Speed below + [P=27,S=10] SCard without Craft Paper Backing + [P=27,S=10] Card with Craft Paper Backing + [P=10,S=10] Vinyl Sticker + [P=14,S=10] Film Labels + [P=27,S=10] Thick Media + [P= 2,S=10] Thin Media + [P=10,S=10] Pen + [P=30,S=10] Bond Paper 13-28 lbs (105g) + [P=30,S=10] Bristol Paper 57-67 lbs (145g) + [P=30,S=10] Cardstock 40-60 lbs (90g) + [P=30,S=10] Cover 40-60 lbs (170g) + [P= 1,S=10] Film, Double Matte Translucent + [P= 1,S=10] Film, Vinyl With Adhesive Back + [P= 1,S=10] Film, Window With Kling Adhesive + [P=30,S=10] Index 90 lbs (165g) + [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) + [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) + [P=30,S= 3] Magnetic Sheet + [P=30,S=10] Offset 24-60 lbs (90g) + [P= 5,S=10] Print Paper Light Weight + [P=25,S=10] Print Paper Medium Weight + [P=20,S=10] Sticker Sheet + [P=20,S=10] Tag 100 lbs (275g) + [P=30,S=10] Text Paper 24-70 lbs (105g) + [P=30,S=10] Vellum Bristol 57-67 lbs (145g) + [P=30,S=10] Writing Paper 24-70 lbs (105g) + + 0 + 0 + Use speed=0, pressure=0 to take the media defaults. + false Convert paths with dashed strokes to separate subpaths for perforated cuts. + false Shift to the top lefthand corner, then do offsets. + false + To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) + 0.5 + 1 + false + + Start Position + Below Cut-Out + + Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. + + + + + 0.0 + 0.0 + + No action + Media default + Pen + Cut + + 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. +Pressure values of 19 or more make the machine misbehave. Beware. + + + + [P>0 S>0] Custom: use non-zero Pressure and Speed below + [P=27,S=10] SCard without Craft Paper Backing + [P=27,S=10] Card with Craft Paper Backing + [P=10,S=10] Vinyl Sticker + [P=14,S=10] Film Labels + [P=27,S=10] Thick Media + [P= 2,S=10] Thin Media + [P=10,S=10] Pen + [P=30,S=10] Bond Paper 13-28 lbs (105g) + [P=30,S=10] Bristol Paper 57-67 lbs (145g) + [P=30,S=10] Cardstock 40-60 lbs (90g) + [P=30,S=10] Cover 40-60 lbs (170g) + [P= 1,S=10] Film, Double Matte Translucent + [P= 1,S=10] Film, Vinyl With Adhesive Back + [P= 1,S=10] Film, Window With Kling Adhesive + [P=30,S=10] Index 90 lbs (165g) + [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) + [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) + [P=30,S= 3] Magnetic Sheet + [P=30,S=10] Offset 24-60 lbs (90g) + [P= 5,S=10] Print Paper Light Weight + [P=25,S=10] Print Paper Medium Weight + [P=20,S=10] Sticker Sheet + [P=20,S=10] Tag 100 lbs (275g) + [P=30,S=10] Text Paper 24-70 lbs (105g) + [P=30,S=10] Vellum Bristol 57-67 lbs (145g) + [P=30,S=10] Writing Paper 24-70 lbs (105g) + + 0 + 0 + Use speed=0, pressure=0 to take the media defaults. + false Convert paths with dashed strokes to separate subpaths for perforated cuts. + false Shift to the top lefthand corner, then do offsets. + false + To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) + 0.5 + 1 + false + + Start Position + Below Cut-Out + + Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. + + + + + false + The document contains printed registration marks + false + Search for the registration marks automatically. Seems on some devices there is another mode, where you can set the references manually. + 180 + 230 + 15 + 20 + Distance of the registration mark edges + + + + false + Keep dialog open until device becomes idle again. + false + Subdivide, sort, and choose cut directions, so that most a cutting mat is not needed in most cases. + 5 + 1 + false + + + + +Always use the least amount of blade possible. + +1) Take a sheet of the media you are trying to cut and fold it in half. + +2) Take the blade out of the machine, set it to 1 and hold it in your hand as you would a pen but held vertically as it would be in the machine. + +3) Get your folded media and with your blade held like a pen but kept vertically press firmly down on the media and 'draw' a line. + +4) Next have a look at the media; with the correct setting you should have just cut a line through the top layer of the folded card without cutting in to the back layer. If you have not cut through the media, increase the blade by 1 position and repeat from step 3. + +5) Keep doing this until you reach the correct setting to cut the top layer without cutting the back. + +6) Once this is done the blade can be put back in to the machine. + + + + + inkscape-silhouette extension from https://github.com/jnweiger/inkscape-silhouette by Jürgen Weigert [juewei@fabmail.org] and contributors + + Version 1.19 + + + + + all + + + + + + + diff --git a/silhouette_multi.py b/silhouette_multi.py new file mode 100755 index 00000000..6407460e --- /dev/null +++ b/silhouette_multi.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python + +import sys, os +from collections import defaultdict +import math +import struct +import time +import inkex +import simplestyle + +class SilhouetteMulti(inkex.Effect): + def parse_args(self, argv): + args = {} + ignored = [] + + for arg in argv: + if arg.startswith('--'): + arg = arg[2:] + else: + ignored.append(arg) + continue + + k, v = arg.split('=', 1) + + + if k == "id": + # ignore selected items, because we're going to create selections + # based on colors + continue + + args[k] = v + + return args, ignored + + def mock_options(self): + class FakeOptions: + ids = [] + + self.options = FakeOptions() + + def getoptions(self, args=sys.argv[1:]): + # getoptions() is called automatically by affect() and uses optparse. + # We just want to store the args to pass through, so we override it. + + self.mock_options() + + self.globals = {} + self.extra_args = [] + self.actions = defaultdict(dict) + + args, self.extra_args = self.parse_args(args) + + for k, v in args.iteritems(): + if k.startswith('action'): + # example: action1_color + action_name, k = k.split('_', 1) + action_num = int(action_name[6:]) + + self.actions[action_num][k] = v + else: + self.globals[k] = v + + self.tolerance = float(self.globals.pop('tolerance', 0)) + self.pause = float(self.globals.pop('pause', 0)) + self.debug = self.globals.pop('debug') == "true" + + + def integer_to_rgb(self, color): + # takes a packed 4-byte signed integer color like -16711681 and unpacks + # it into a tuple of (red, green, blue) as unsigned integers + # in the range 0-255. Alpha is discarded. + return struct.unpack("BBBB", struct.pack(">i", color))[0:3] + + def colors_match(self, color1, color2): + # Do these colors match? + # + # Compare using self.tolerance, which specifies the maximum allowed + # "distance". + # + # Colors are (r, g, b) tuples + + distance = math.sqrt((color1[0] - color2[0])**2 + \ + (color1[1] - color2[1])**2 + \ + (color1[2] - color2[2])**2) + + return distance <= self.tolerance + + def get_node_stroke_color(self, node): + if node.get('style') is None: + return None + + style = simplestyle.parseStyle(node.get('style')) + + if not style.get('stroke'): + return None + + return simplestyle.parseColor(style.get('stroke')) + + def get_nodes_by_color(self, color, element=None, parent_visibility="visible"): + if element is None: + element = self.document.getroot() + + nodes_by_color = [] + + for node in element: + visibility = node.get('visibility', parent_visibility) + + if visibility == 'inherit': + visibility = parent_visibility + + if visibility in ('hidden', 'collapse'): + return [] + + node_color = self.get_node_stroke_color(node) + + if node_color and self.colors_match(color, node_color): + nodes_by_color.append(node) + + nodes_by_color.extend(self.get_nodes_by_color(color, node, visibility)) + + return nodes_by_color + + def format_args(self, args): + if isinstance(args, dict): + args = args.iteritems() + + return " ".join(("--%s=%s" % (k, v) for k, v in args)) + + def global_args(self): + return self.format_args(self.globals) + + def id_args(self, nodes): + return self.format_args(("id", node.get("id")) for node in nodes) + + def effect(self): + # any output on stdout crashes inkscape, so let's avoid that + old_stdout = sys.stdout + sys.stdout = sys.stderr + + commands = [] + + for action_num in sorted(self.actions.keys()): + action = self.actions[action_num] + + if action['tool'] == 'none': + continue + + color = self.integer_to_rgb(int(action.pop('color'))) + nodes = self.get_nodes_by_color(color) + + if not nodes: + continue + + command = ("python sendto_silhouette.py" + " " + + self.global_args() + " " + + self.format_args(action) + " " + + self.id_args(nodes) + " " + + " ".join(self.extra_args)) + + commands.append(command) + + if self.debug: + print >> sys.stderr, "\n".join(commands) + else: + for command in commands: + os.system(command) + time.sleep(self.pause) + + sys.stdout = old_stdout + + +if __name__ == "__main__": + #print >> sys.stderr, " ".join(sys.argv) + #sys.exit(0) + + s = SilhouetteMulti() + s.affect() + From b841b4f1421ccef99219f8323b7808d6a3ae3455 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Tue, 11 Jul 2017 21:52:56 +0100 Subject: [PATCH 40/51] set max pressure to correct value 33 --- silhouette_multi.inx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/silhouette_multi.inx b/silhouette_multi.inx index 2440a712..52b705d0 100644 --- a/silhouette_multi.inx +++ b/silhouette_multi.inx @@ -50,7 +50,7 @@ Pressure values of 19 or more make the machine misbehave. Beware. [P=30,S=10] Writing Paper 24-70 lbs (105g) 0 - 0 + 0 Use speed=0, pressure=0 to take the media defaults. false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. @@ -110,7 +110,7 @@ Pressure values of 19 or more make the machine misbehave. Beware. [P=30,S=10] Writing Paper 24-70 lbs (105g) 0 - 0 + 0 Use speed=0, pressure=0 to take the media defaults. false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. @@ -170,7 +170,7 @@ Pressure values of 19 or more make the machine misbehave. Beware. [P=30,S=10] Writing Paper 24-70 lbs (105g) 0 - 0 + 0 Use speed=0, pressure=0 to take the media defaults. false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. @@ -230,7 +230,7 @@ Pressure values of 19 or more make the machine misbehave. Beware. [P=30,S=10] Writing Paper 24-70 lbs (105g) 0 - 0 + 0 Use speed=0, pressure=0 to take the media defaults. false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. @@ -290,7 +290,7 @@ Pressure values of 19 or more make the machine misbehave. Beware. [P=30,S=10] Writing Paper 24-70 lbs (105g) 0 - 0 + 0 Use speed=0, pressure=0 to take the media defaults. false Convert paths with dashed strokes to separate subpaths for perforated cuts. false Shift to the top lefthand corner, then do offsets. From c79f1b745acc9cf4e9c173b145910dd5f8bca155 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Tue, 11 Jul 2017 21:53:10 +0100 Subject: [PATCH 41/51] abort if an action fails --- silhouette_multi.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index 6407460e..eb4bb9be 100755 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -163,7 +163,12 @@ def effect(self): print >> sys.stderr, "\n".join(commands) else: for command in commands: - os.system(command) + status = os.system(command) + + if status != 0: + print >> sys.stderr, "command returned exit status %s: %s" % (status, command) + break + time.sleep(self.pause) sys.stdout = old_stdout From 39227508574441c79c15b2840e732a00e32f3c06 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 12 Jul 2017 20:35:04 +0100 Subject: [PATCH 42/51] replace silhouette_multi entirely with a full gui version --- silhouette_multi.inx | 348 --------------------- silhouette_multi.py | 723 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 608 insertions(+), 463 deletions(-) mode change 100755 => 100644 silhouette_multi.py diff --git a/silhouette_multi.inx b/silhouette_multi.inx index 52b705d0..1f1ba6f1 100644 --- a/silhouette_multi.inx +++ b/silhouette_multi.inx @@ -5,354 +5,6 @@ org.inkscape.output.svg.inkscape inkex.py silhouette_multi.py - - - - 0.0 - 0.0 - - No action - Media default - Pen - Cut - - 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. -Pressure values of 19 or more make the machine misbehave. Beware. - - - - [P>0 S>0] Custom: use non-zero Pressure and Speed below - [P=27,S=10] SCard without Craft Paper Backing - [P=27,S=10] Card with Craft Paper Backing - [P=10,S=10] Vinyl Sticker - [P=14,S=10] Film Labels - [P=27,S=10] Thick Media - [P= 2,S=10] Thin Media - [P=10,S=10] Pen - [P=30,S=10] Bond Paper 13-28 lbs (105g) - [P=30,S=10] Bristol Paper 57-67 lbs (145g) - [P=30,S=10] Cardstock 40-60 lbs (90g) - [P=30,S=10] Cover 40-60 lbs (170g) - [P= 1,S=10] Film, Double Matte Translucent - [P= 1,S=10] Film, Vinyl With Adhesive Back - [P= 1,S=10] Film, Window With Kling Adhesive - [P=30,S=10] Index 90 lbs (165g) - [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) - [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) - [P=30,S= 3] Magnetic Sheet - [P=30,S=10] Offset 24-60 lbs (90g) - [P= 5,S=10] Print Paper Light Weight - [P=25,S=10] Print Paper Medium Weight - [P=20,S=10] Sticker Sheet - [P=20,S=10] Tag 100 lbs (275g) - [P=30,S=10] Text Paper 24-70 lbs (105g) - [P=30,S=10] Vellum Bristol 57-67 lbs (145g) - [P=30,S=10] Writing Paper 24-70 lbs (105g) - - 0 - 0 - Use speed=0, pressure=0 to take the media defaults. - false Convert paths with dashed strokes to separate subpaths for perforated cuts. - false Shift to the top lefthand corner, then do offsets. - false - To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) - 0.5 - 1 - false - - Start Position - Below Cut-Out - - Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. - - - - - 0.0 - 0.0 - - No action - Media default - Pen - Cut - - 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. -Pressure values of 19 or more make the machine misbehave. Beware. - - - - [P>0 S>0] Custom: use non-zero Pressure and Speed below - [P=27,S=10] SCard without Craft Paper Backing - [P=27,S=10] Card with Craft Paper Backing - [P=10,S=10] Vinyl Sticker - [P=14,S=10] Film Labels - [P=27,S=10] Thick Media - [P= 2,S=10] Thin Media - [P=10,S=10] Pen - [P=30,S=10] Bond Paper 13-28 lbs (105g) - [P=30,S=10] Bristol Paper 57-67 lbs (145g) - [P=30,S=10] Cardstock 40-60 lbs (90g) - [P=30,S=10] Cover 40-60 lbs (170g) - [P= 1,S=10] Film, Double Matte Translucent - [P= 1,S=10] Film, Vinyl With Adhesive Back - [P= 1,S=10] Film, Window With Kling Adhesive - [P=30,S=10] Index 90 lbs (165g) - [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) - [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) - [P=30,S= 3] Magnetic Sheet - [P=30,S=10] Offset 24-60 lbs (90g) - [P= 5,S=10] Print Paper Light Weight - [P=25,S=10] Print Paper Medium Weight - [P=20,S=10] Sticker Sheet - [P=20,S=10] Tag 100 lbs (275g) - [P=30,S=10] Text Paper 24-70 lbs (105g) - [P=30,S=10] Vellum Bristol 57-67 lbs (145g) - [P=30,S=10] Writing Paper 24-70 lbs (105g) - - 0 - 0 - Use speed=0, pressure=0 to take the media defaults. - false Convert paths with dashed strokes to separate subpaths for perforated cuts. - false Shift to the top lefthand corner, then do offsets. - false - To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) - 0.5 - 1 - false - - Start Position - Below Cut-Out - - Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. - - - - - 0.0 - 0.0 - - No action - Media default - Pen - Cut - - 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. -Pressure values of 19 or more make the machine misbehave. Beware. - - - - [P>0 S>0] Custom: use non-zero Pressure and Speed below - [P=27,S=10] SCard without Craft Paper Backing - [P=27,S=10] Card with Craft Paper Backing - [P=10,S=10] Vinyl Sticker - [P=14,S=10] Film Labels - [P=27,S=10] Thick Media - [P= 2,S=10] Thin Media - [P=10,S=10] Pen - [P=30,S=10] Bond Paper 13-28 lbs (105g) - [P=30,S=10] Bristol Paper 57-67 lbs (145g) - [P=30,S=10] Cardstock 40-60 lbs (90g) - [P=30,S=10] Cover 40-60 lbs (170g) - [P= 1,S=10] Film, Double Matte Translucent - [P= 1,S=10] Film, Vinyl With Adhesive Back - [P= 1,S=10] Film, Window With Kling Adhesive - [P=30,S=10] Index 90 lbs (165g) - [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) - [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) - [P=30,S= 3] Magnetic Sheet - [P=30,S=10] Offset 24-60 lbs (90g) - [P= 5,S=10] Print Paper Light Weight - [P=25,S=10] Print Paper Medium Weight - [P=20,S=10] Sticker Sheet - [P=20,S=10] Tag 100 lbs (275g) - [P=30,S=10] Text Paper 24-70 lbs (105g) - [P=30,S=10] Vellum Bristol 57-67 lbs (145g) - [P=30,S=10] Writing Paper 24-70 lbs (105g) - - 0 - 0 - Use speed=0, pressure=0 to take the media defaults. - false Convert paths with dashed strokes to separate subpaths for perforated cuts. - false Shift to the top lefthand corner, then do offsets. - false - To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) - 0.5 - 1 - false - - Start Position - Below Cut-Out - - Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. - - - - - 0.0 - 0.0 - - No action - Media default - Pen - Cut - - 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. -Pressure values of 19 or more make the machine misbehave. Beware. - - - - [P>0 S>0] Custom: use non-zero Pressure and Speed below - [P=27,S=10] SCard without Craft Paper Backing - [P=27,S=10] Card with Craft Paper Backing - [P=10,S=10] Vinyl Sticker - [P=14,S=10] Film Labels - [P=27,S=10] Thick Media - [P= 2,S=10] Thin Media - [P=10,S=10] Pen - [P=30,S=10] Bond Paper 13-28 lbs (105g) - [P=30,S=10] Bristol Paper 57-67 lbs (145g) - [P=30,S=10] Cardstock 40-60 lbs (90g) - [P=30,S=10] Cover 40-60 lbs (170g) - [P= 1,S=10] Film, Double Matte Translucent - [P= 1,S=10] Film, Vinyl With Adhesive Back - [P= 1,S=10] Film, Window With Kling Adhesive - [P=30,S=10] Index 90 lbs (165g) - [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) - [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) - [P=30,S= 3] Magnetic Sheet - [P=30,S=10] Offset 24-60 lbs (90g) - [P= 5,S=10] Print Paper Light Weight - [P=25,S=10] Print Paper Medium Weight - [P=20,S=10] Sticker Sheet - [P=20,S=10] Tag 100 lbs (275g) - [P=30,S=10] Text Paper 24-70 lbs (105g) - [P=30,S=10] Vellum Bristol 57-67 lbs (145g) - [P=30,S=10] Writing Paper 24-70 lbs (105g) - - 0 - 0 - Use speed=0, pressure=0 to take the media defaults. - false Convert paths with dashed strokes to separate subpaths for perforated cuts. - false Shift to the top lefthand corner, then do offsets. - false - To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) - 0.5 - 1 - false - - Start Position - Below Cut-Out - - Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. - - - - - 0.0 - 0.0 - - No action - Media default - Pen - Cut - - 'pen' executes the strokes exactly as sent, 'cut' adds small serifs to help the knive find its orientation at corners. -Pressure values of 19 or more make the machine misbehave. Beware. - - - - [P>0 S>0] Custom: use non-zero Pressure and Speed below - [P=27,S=10] SCard without Craft Paper Backing - [P=27,S=10] Card with Craft Paper Backing - [P=10,S=10] Vinyl Sticker - [P=14,S=10] Film Labels - [P=27,S=10] Thick Media - [P= 2,S=10] Thin Media - [P=10,S=10] Pen - [P=30,S=10] Bond Paper 13-28 lbs (105g) - [P=30,S=10] Bristol Paper 57-67 lbs (145g) - [P=30,S=10] Cardstock 40-60 lbs (90g) - [P=30,S=10] Cover 40-60 lbs (170g) - [P= 1,S=10] Film, Double Matte Translucent - [P= 1,S=10] Film, Vinyl With Adhesive Back - [P= 1,S=10] Film, Window With Kling Adhesive - [P=30,S=10] Index 90 lbs (165g) - [P=20,S=10] Inkjet Photo Paper 28-44 lbs (70g) - [P=27,S=10] Inkjet Photo Paper 45-75 lbs (110g) - [P=30,S= 3] Magnetic Sheet - [P=30,S=10] Offset 24-60 lbs (90g) - [P= 5,S=10] Print Paper Light Weight - [P=25,S=10] Print Paper Medium Weight - [P=20,S=10] Sticker Sheet - [P=20,S=10] Tag 100 lbs (275g) - [P=30,S=10] Text Paper 24-70 lbs (105g) - [P=30,S=10] Vellum Bristol 57-67 lbs (145g) - [P=30,S=10] Writing Paper 24-70 lbs (105g) - - 0 - 0 - Use speed=0, pressure=0 to take the media defaults. - false Convert paths with dashed strokes to separate subpaths for perforated cuts. - false Shift to the top lefthand corner, then do offsets. - false - To see the used area, tick the checkmark above and use pressure=1 (or better remove tool) - 0.5 - 1 - false - - Start Position - Below Cut-Out - - Choose position of blade relative to the media after cutting. "Below Cut-Out" is ideal for using cross-cutter. - - - - - false - The document contains printed registration marks - false - Search for the registration marks automatically. Seems on some devices there is another mode, where you can set the references manually. - 180 - 230 - 15 - 20 - Distance of the registration mark edges - - - - false - Keep dialog open until device becomes idle again. - false - Subdivide, sort, and choose cut directions, so that most a cutting mat is not needed in most cases. - 5 - 1 - false - - - - -Always use the least amount of blade possible. - -1) Take a sheet of the media you are trying to cut and fold it in half. - -2) Take the blade out of the machine, set it to 1 and hold it in your hand as you would a pen but held vertically as it would be in the machine. - -3) Get your folded media and with your blade held like a pen but kept vertically press firmly down on the media and 'draw' a line. - -4) Next have a look at the media; with the correct setting you should have just cut a line through the top layer of the folded card without cutting in to the back layer. If you have not cut through the media, increase the blade by 1 position and repeat from step 3. - -5) Keep doing this until you reach the correct setting to cut the top layer without cutting the back. - -6) Once this is done the blade can be put back in to the machine. - - - - - inkscape-silhouette extension from https://github.com/jnweiger/inkscape-silhouette by Jürgen Weigert [juewei@fabmail.org] and contributors - - Version 1.19 - - all diff --git a/silhouette_multi.py b/silhouette_multi.py old mode 100755 new mode 100644 index eb4bb9be..c9a19e0b --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -1,124 +1,621 @@ #!/usr/bin/env python - -import sys, os +# -*- coding: UTF-8 -*- + +import os +import sys +import cPickle +from tempfile import NamedTemporaryFile +from collections import defaultdict, OrderedDict +import xmltodict +import traceback +from cStringIO import StringIO +import wx +from wx.lib.scrolledpanel import ScrolledPanel +from wx.lib.agw import ultimatelistctrl as ulc +from wx.lib.embeddedimage import PyEmbeddedImage from collections import defaultdict -import math -import struct -import time import inkex import simplestyle +from functools import partial +from itertools import groupby -class SilhouetteMulti(inkex.Effect): - def parse_args(self, argv): - args = {} - ignored = [] - for arg in argv: - if arg.startswith('--'): - arg = arg[2:] +small_up_arrow = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADxJ" + "REFUOI1jZGRiZqAEMFGke2gY8P/f3/9kGwDTjM8QnAaga8JlCG3CAJdt2MQxDCAUaOjyjKMp" + "cRAYAABS2CPsss3BWQAAAABJRU5ErkJggg==") + +small_down_arrow = PyEmbeddedImage( + "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAEhJ" + "REFUOI1jZGRiZqAEMFGke9QABgYGBgYWdIH///7+J6SJkYmZEacLkCUJacZqAD5DsInTLhDR" + "bcPlKrwugGnCFy6Mo3mBAQChDgRlP4RC7wAAAABJRU5ErkJggg==") + + + +def presets_path(): + try: + import appdirs + config_path = appdirs.user_config_dir('inkscape-silhouette') + except ImportError: + config_path = os.path.expanduser('~/.inkscape-silhouette') + + if not os.path.exists(config_path): + os.makedirs(config_path) + return os.path.join(config_path, 'presets.cPickle') + +def load_presets(): + try: + with open(presets_path(), 'r') as presets: + presets = cPickle.load(presets) + return presets + except: + return {} + +def save_presets(presets): + #print "saving presets", presets + with open(presets_path(), 'w') as presets_file: + cPickle.dump(presets, presets_file) + + +def load_preset(name): + return load_presets().get(name) + + +def save_preset(name, data): + presets = load_presets() + presets[name] = data + save_presets(presets) + + +def delete_preset(name): + presets = load_presets() + presets.pop(name, None) + save_presets(presets) + + +def confirm_dialog(parent, question, caption = 'Silhouette Multiple Actions'): + dlg = wx.MessageDialog(parent, question, caption, wx.YES_NO | wx.ICON_QUESTION) + result = dlg.ShowModal() == wx.ID_YES + dlg.Destroy() + return result + + +def info_dialog(parent, message, caption = 'Silhouette Multiple Actions'): + dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + + +class ParamsNotebook(wx.Notebook): + """Handle a notebook of tabs that contain params. + + Each param has a name, and all names are globally unique across all tabs. + """ + + def __init__(self, *args, **kwargs): + wx.Notebook.__init__(self, *args, **kwargs) + + self.load_inx() + self.create_tabs() + self.add_tabs() + + def load_inx(self): + with open('sendto_silhouette.inx') as inx_file: + self.inx = xmltodict.parse(inx_file, force_list=('param',)) + + def create_tabs(self): + self.notebook = self.inx['inkscape-extension']['param'][0] + + if self.notebook['@type'] != 'notebook': + print >> sys.stderr, "unexpected INX format" + return + + self.tabs = [] + for page in self.notebook['page']: + self.tabs.append(ParamsTab(self, wx.ID_ANY, name=page['@name'], title=page['@_gui-text'], params=page['param'])) + + def add_tabs(self): + for tab in self.tabs: + self.AddPage(tab, tab.title) + + def get_values(self): + values = {} + + for tab in self.tabs: + values.update(tab.get_values()) + + return values + + def get_defaults(self): + values = {} + + for tab in self.tabs: + values.update(tab.get_defaults()) + + return values + + + def set_values(self, values): + for tab in self.tabs: + tab.set_values(values) + + + def set_defaults(self): + for tab in self.tabs: + tab.set_defaults() + +class ParamsTab(ScrolledPanel): + def __init__(self, *args, **kwargs): + self.params = kwargs.pop('params', []) + self.name = kwargs.pop('name', None) + self.title = kwargs.pop('title', None) + kwargs["style"] = wx.TAB_TRAVERSAL + ScrolledPanel.__init__(self, *args, **kwargs) + self.SetupScrolling() + + self.param_inputs = {} + self.choices_by_label = {} + self.choices_by_value = {} + self.defaults = {} + + self.settings_grid = wx.GridBagSizer(hgap=0, vgap=0) + self.settings_grid.AddGrowableCol(0, 1) + self.settings_grid.SetFlexibleDirection(wx.HORIZONTAL) + + self.__set_properties() + self.__do_layout() + + def get_values(self): + values = {} + + for name, input in self.param_inputs.iteritems(): + if isinstance(input, wx.Choice): + choice = input.GetSelection() + + if choice == wx.NOT_FOUND: + values[name] = None + else: + values[name] = self.choices_by_label[name][input.GetString(choice)] else: - ignored.append(arg) + values[name] = input.GetValue() + return values + + def get_defaults(self): + return self.defaults + + def set_values(self, values): + for name, value in values.iteritems(): + if name not in self.param_inputs: + # ignore params not contained in this tab continue - k, v = arg.split('=', 1) + input = self.param_inputs[name] + if isinstance(input, wx.Choice): + if value is None: + input.SetSelection(wx.NOT_FOUND) + else: + label = self.choices_by_value[name][value] + input.SetStringSelection(label) + else: + input.SetValue(value) + + def set_defaults(self): + self.set_values(self.defaults) + + def __set_properties(self): + # begin wxGlade: SatinPane.__set_properties + # end wxGlade + pass + + def __do_layout(self): + # just to add space around the settings + box = wx.BoxSizer(wx.VERTICAL) + + for row, param in enumerate(self.params): + param_type = param['@type'] + param_name = param['@name'] + if param_type == 'description': + self.settings_grid.Add(wx.StaticText(self, label=param.get('#text', '')), + pos=(row, 0), span=(1, 2), flag=wx.EXPAND|wx.LEFT|wx.ALIGN_TOP, border=10) + else: + self.settings_grid.Add(wx.StaticText(self, label=param.get('@_gui-text', '')), + pos=(row, 0), flag=wx.EXPAND|wx.TOP|wx.ALIGN_TOP, border=5) + + if param_type == 'boolean': + input = wx.CheckBox(self) + elif param_type == 'float': + input = wx.SpinCtrlDouble(self, wx.ID_ANY, min=float(param.get('@min', 0.0)), max=float(param.get('@max', 2.0**32)), inc=0.1, value=param.get('#text', '')) + elif param_type == 'int': + input = wx.SpinCtrl(self, wx.ID_ANY, min=int(param.get('@min', 0)), max=int(param.get('@max', 2**32)), value=param.get('#text', '')) + elif param_type == 'enum': + choices = OrderedDict((item['#text'], item['@value']) for item in param['item']) + self.choices_by_label[param_name] = choices + self.choices_by_value[param_name] = { v: k for k, v in choices.iteritems() } + input = wx.Choice(self, wx.ID_ANY, choices=choices.keys(), style=wx.LB_SINGLE) + input.SetStringSelection(choices.keys()[0]) + else: + # not sure what else to do here... + continue + + self.param_inputs[param_name] = input + + self.settings_grid.Add(input, pos=(row, 1), flag=wx.ALIGN_BOTTOM|wx.TOP, border=5) + + self.defaults = self.get_values() + + box.Add(self.settings_grid, proportion=1, flag=wx.ALL, border=10) + self.SetSizer(box) + + self.Layout() + + +class SilhouetteMultiFrame(wx.Frame): + def __init__(self, *args, **kwargs): + # begin wxGlade: MyFrame.__init__ + self.colors = kwargs.pop('colors', []) + self.run_callback = kwargs.pop('run_callback') + wx.Frame.__init__(self, None, wx.ID_ANY, + "Silhouette Multi-Action" + ) + + self.selected = None + self.color_settings = {} + self.color_enabled = {} + self.notebook = ParamsNotebook(self, wx.ID_ANY) + self.up_button = wx.Button(self, wx.ID_UP) + self.down_button = wx.Button(self, wx.ID_DOWN) + self.run_button = wx.Button(self, wx.ID_EXECUTE) + self.cancel_button = wx.Button(self, wx.ID_CANCEL, "Cancel") + + self.presets_box = wx.StaticBox(self, wx.ID_ANY, label="Presets") + self.preset_chooser = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_SORT) + self.load_preset_button = wx.Button(self, wx.ID_REVERT_TO_SAVED, "Load") + self.add_preset_button = wx.Button(self, wx.ID_SAVE, "Add") + self.overwrite_preset_button = wx.Button(self, wx.ID_SAVEAS, "Overwrite") + self.delete_preset_button = wx.Button(self, wx.ID_DELETE, "Delete") + + self.update_preset_list() + self.init_actions() + + self.Bind(wx.EVT_BUTTON, self.move_up, self.up_button) + self.Bind(wx.EVT_BUTTON, self.move_down, self.down_button) + self.Bind(wx.EVT_BUTTON, self.run, self.run_button) + self.Bind(wx.EVT_BUTTON, self.load_preset, self.load_preset_button) + self.Bind(wx.EVT_BUTTON, self.add_preset, self.add_preset_button) + self.Bind(wx.EVT_BUTTON, self.overwrite_preset, self.overwrite_preset_button) + self.Bind(wx.EVT_BUTTON, self.delete_preset, self.delete_preset_button) + self.Bind(wx.EVT_BUTTON, self.close, self.cancel_button) + + self._load_preset('__LAST__') + + self.__set_properties() + self.__do_layout() + # end wxGlade + + def close(self, event): + self.Close() + + def load_preset(self, event): + preset_name = self.get_preset_name() + if not preset_name: + return + + self._load_preset(preset_name) + + def add_preset(self, event, overwrite=False): + preset_name = self.get_preset_name() + if not preset_name: + return + + if not overwrite and load_preset(preset_name): + info_dialog(self, 'Preset "%s" already exists. Please use another name or press "Overwrite"' % preset_name, caption='Preset') + + self.save_color_settings() + save_preset(preset_name, self.get_preset_data()) + self.update_preset_list() + + event.Skip() + + def overwrite_preset(self, event): + self.add_preset(event, overwrite=True) + + def delete_preset(self, event): + preset_name = self.get_preset_name() + if not preset_name: + return + + preset = self.check_and_load_preset(preset_name) + if not preset: + return + + delete_preset(preset_name) + self.update_preset_list() + self.preset_chooser.SetValue("") - if k == "id": - # ignore selected items, because we're going to create selections - # based on colors - continue + event.Skip() - args[k] = v + def check_and_load_preset(self, preset_name): + preset = load_preset(preset_name) + if not preset: + info_dialog(self, 'Preset "%s" not found.' % preset_name, caption='Preset') - return args, ignored + return preset - def mock_options(self): - class FakeOptions: - ids = [] + def get_preset_name(self): + preset_name = self.preset_chooser.GetValue().strip() + if preset_name: + return preset_name + else: + info_dialog(self, "Please enter or select a preset name first.", caption='Preset') + return - self.options = FakeOptions() + def update_preset_list(self): + preset_names = load_presets().keys() + preset_names = [preset for preset in preset_names if not preset.startswith("__")] + self.preset_chooser.SetItems(preset_names) - def getoptions(self, args=sys.argv[1:]): - # getoptions() is called automatically by affect() and uses optparse. - # We just want to store the args to pass through, so we override it. + def _load_preset(self, preset_name): + preset = load_preset(preset_name) - self.mock_options() + if not preset: + return - self.globals = {} - self.extra_args = [] - self.actions = defaultdict(dict) + if self.selected: + self.actions.Select(self.selected, False) - args, self.extra_args = self.parse_args(args) + self.colors = preset['colors'] + self.color_enabled = preset['color_enabled'] + self.color_settings = preset['color_settings'] - for k, v in args.iteritems(): - if k.startswith('action'): - # example: action1_color - action_name, k = k.split('_', 1) - action_num = int(action_name[6:]) + self.refresh_actions() - self.actions[action_num][k] = v - else: - self.globals[k] = v + def _save_preset(self, preset_name): + self.save_color_settings() - self.tolerance = float(self.globals.pop('tolerance', 0)) - self.pause = float(self.globals.pop('pause', 0)) - self.debug = self.globals.pop('debug') == "true" + preset = self.get_preset_data() + save_preset(preset_name, preset) + def get_preset_data(self): + return { 'colors': self.colors, + 'color_enabled': self.color_enabled, + 'color_settings': self.color_settings } - def integer_to_rgb(self, color): - # takes a packed 4-byte signed integer color like -16711681 and unpacks - # it into a tuple of (red, green, blue) as unsigned integers - # in the range 0-255. Alpha is discarded. - return struct.unpack("BBBB", struct.pack(">i", color))[0:3] + def run(self, event): + self.save_color_settings() - def colors_match(self, color1, color2): - # Do these colors match? - # - # Compare using self.tolerance, which specifies the maximum allowed - # "distance". - # - # Colors are (r, g, b) tuples + actions = [] - distance = math.sqrt((color1[0] - color2[0])**2 + \ - (color1[1] - color2[1])**2 + \ - (color1[2] - color2[2])**2) + for color in self.colors: + if self.color_enabled.get(color, True): + actions.append((color, self.color_settings.get(color) or self.notebook.get_defaults())) - return distance <= self.tolerance + if actions: + if not confirm_dialog(self, "About to perform %d actions, continue?" % len(actions)): + return + else: + info_dialog("No colors were enabled, so no actions can be performed.") + return - def get_node_stroke_color(self, node): - if node.get('style') is None: - return None + self._save_preset('__LAST__') + self.run_callback(actions) - style = simplestyle.parseStyle(node.get('style')) + def move_up(self, event): + if self.selected is None or self.selected == 0: + return - if not style.get('stroke'): - return None + this = self.selected + prev = this - 1 - return simplestyle.parseColor(style.get('stroke')) + self.colors[this], self.colors[prev] = self.colors[prev], self.colors[this] + self.actions.Select(this, False) + self.actions.Select(prev) - def get_nodes_by_color(self, color, element=None, parent_visibility="visible"): - if element is None: - element = self.document.getroot() + self.refresh_actions() - nodes_by_color = [] + def move_down(self, event): + if self.selected is None or self.selected == len(self.colors) - 1: + return - for node in element: - visibility = node.get('visibility', parent_visibility) + this = self.selected + next = this + 1 + + self.colors[this], self.colors[next] = self.colors[next], self.colors[this] + self.actions.Select(this, False) + self.actions.Select(next) + + self.refresh_actions() + + def action_selected(self, event=None): + # first, save the settings for the color they were previously working on + self.save_color_settings() + + # then load the settings for the newly-selected color + self.selected = event.m_itemIndex + self.load_color_settings() + + self.up_button.Enable() + self.down_button.Enable() + self.notebook.Enable() + + def action_deselected(self, event=None): + self.save_color_settings() + + self.selected = None + + self.up_button.Disable() + self.down_button.Disable() + self.notebook.Disable() + + def load_color_settings(self): + color = self.colors[self.selected] + settings = self.color_settings.get(color) + + if settings: + self.notebook.set_values(settings) + else: + self.notebook.set_defaults() + + def save_color_settings(self): + #print "save:", self.selected + + if self.selected is None: + return + + color = self.colors[self.selected] + settings = self.notebook.get_values() + self.color_settings[color] = settings + + #print "settings:", settings + + def item_checked(self, event): + item = event.m_itemIndex + checked = self.actions.IsItemChecked(item, 2) + self.color_enabled[self.colors[item]] = checked + + def init_actions(self): + self.actions = ulc.UltimateListCtrl(self, size=(300, 150), agwStyle=wx.LC_REPORT|ulc.ULC_HRULES|ulc.ULC_SINGLE_SEL) + + self.Bind(ulc.EVT_LIST_ITEM_SELECTED, self.action_selected, self.actions) + self.Bind(ulc.EVT_LIST_ITEM_DESELECTED, self.action_deselected, self.actions) + self.Bind(ulc.EVT_LIST_ITEM_CHECKED, self.item_checked, self.actions) + self.action_deselected() + + self.actions.InsertColumn(0, "Step") + self.actions.InsertColumn(1, "Color") + self.actions.InsertColumn(2, "Perform Action?") + self.actions.SetColumnWidth(2, ulc.ULC_AUTOSIZE_FILL) + + self.action_checkboxes = [] + + for i, color in enumerate(self.colors): + self.actions.InsertStringItem(i, "%d." % (i + 1)) + + item = self.actions.GetItem(i, 2) + item.SetKind(1) # "a checkbox-like item" + item.SetMask(ulc.ULC_MASK_KIND) + self.actions.SetItem(item) + + self.refresh_actions() + + def refresh_actions(self): + for i, color in enumerate(self.colors): + item = self.actions.GetItem(i, 1) + item.SetMask(ulc.ULC_MASK_BACKCOLOUR) + item.SetBackgroundColour(wx.Colour(*color)) + self.actions.SetItem(item) + + item = self.actions.GetItem(i, 2) + item.Check(self.color_enabled.get(color, True)) + item.SetMask(ulc.ULC_MASK_CHECK) + self.actions.SetItem(item) + + + def __set_properties(self): + # begin wxGlade: MyFrame.__set_properties + self.SetTitle("Silhouette Multi-Action") + self.notebook.SetMinSize((800, 500)) + # end wxGlade + + def __do_layout(self): + # begin wxGlade: MyFrame.__do_layout + sizer_1 = wx.BoxSizer(wx.VERTICAL) + + sizer_2 = wx.BoxSizer(wx.HORIZONTAL) + sizer_2.Add(self.actions, 0, flag=wx.ALL|wx.EXPAND, border=10) + + sizer_3 = wx.BoxSizer(wx.VERTICAL) + sizer_3.Add(self.up_button, 0, border=10) + sizer_3.Add(self.down_button, 0, border=10) + + sizer_2.Add(sizer_3, 0, flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=10) + + sizer_4 = wx.StaticBoxSizer(self.presets_box, wx.VERTICAL) + sizer_4.Add(self.preset_chooser, 0, flag=wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.TOP|wx.EXPAND, border=10) + + sizer_5 = wx.BoxSizer(wx.HORIZONTAL) + sizer_5.Add(self.load_preset_button, 0, flag=wx.RIGHT|wx.LEFT|wx.BOTTOM, border=10) + sizer_5.Add(self.add_preset_button, 0, flag=wx.RIGHT, border=10) + sizer_5.Add(self.overwrite_preset_button, 0, flag=wx.RIGHT, border=10) + sizer_5.Add(self.delete_preset_button, 0, flag=wx.RIGHT, border=10) + + sizer_4.Add(sizer_5, 0) + sizer_2.Add(sizer_4, 0, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=30) + + sizer_6 = wx.BoxSizer(wx.VERTICAL) + sizer_6.Add(self.run_button, 0, flag=wx.ALIGN_RIGHT|wx.BOTTOM, border=10) + sizer_6.Add(self.cancel_button, 0, flag=wx.ALIGN_RIGHT|wx.EXPAND) + sizer_2.Add(sizer_6, 0, flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=30) + + sizer_1.Add(sizer_2, 0, flag=wx.EXPAND|wx.ALL, border=10) + + sizer_1.Add(self.notebook, 1, wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, 10) + self.SetSizer(sizer_1) + sizer_1.Fit(self) + self.Layout() + # end wxGlade + +class SilhouetteMulti(inkex.Effect): + def get_style(self, element): + if element.get('style') is not None: + return simplestyle.parseStyle(element.get('style')) + else: + return {} + + return style + + def get_stroke_color(self, element): + stroke = self.get_style(element).get('stroke') + + if stroke is not None and stroke != 'none': + return simplestyle.parseColor(stroke) + else: + return None + + def load_selected_objects(self): + self.selected_objects = set() + + def traverse_element(element, selected=False, parent_visibility="visible"): + if self.get_style(element).get('display') == 'none': + return + + visibility = element.get('visibility', parent_visibility) if visibility == 'inherit': visibility = parent_visibility - if visibility in ('hidden', 'collapse'): - return [] + if element.get('id') in self.selected: + selected = True + + if selected and visibility not in ('hidden', 'collapse'): + self.selected_objects.add(element) - node_color = self.get_node_stroke_color(node) + for child in element: + traverse_element(child, visibility) - if node_color and self.colors_match(color, node_color): - nodes_by_color.append(node) + traverse_element(self.document.getroot()) - nodes_by_color.extend(self.get_nodes_by_color(color, node, visibility)) + def split_objects_by_color(self): + self.objects_by_color = defaultdict(list) + self.load_selected_objects() - return nodes_by_color + for obj in self.selected_objects: + color = self.get_stroke_color(obj) + if color: + self.objects_by_color[color].append(obj) + + def effect(self): + app = wx.App() + self.split_objects_by_color() + self.frame = SilhouetteMultiFrame(colors=self.objects_by_color.keys(), run_callback=self.run) + self.frame.Show() + app.MainLoop() + + def save_copy(self): + f = NamedTemporaryFile(suffix='.svg', prefix='silhouette-multiple-actions') + self.document.write(f) + f.flush() + return f def format_args(self, args): if isinstance(args, dict): @@ -126,58 +623,54 @@ def format_args(self, args): return " ".join(("--%s=%s" % (k, v) for k, v in args)) - def global_args(self): - return self.format_args(self.globals) - def id_args(self, nodes): return self.format_args(("id", node.get("id")) for node in nodes) - def effect(self): - # any output on stdout crashes inkscape, so let's avoid that - old_stdout = sys.stdout - sys.stdout = sys.stderr + def run(self, actions): + svg_file = self.save_copy() - commands = [] + for color, settings in actions: + command = "python sendto_silhouette.py " + command += self.format_args(settings) + command += self.id_args(self.objects_by_color[color]) + command += " " + svg_file.name - for action_num in sorted(self.actions.keys()): - action = self.actions[action_num] + #print >> sys.stderr, command - if action['tool'] == 'none': - continue + status = os.system(command) - color = self.integer_to_rgb(int(action.pop('color'))) - nodes = self.get_nodes_by_color(color) + if status != 0: + print >> sys.stderr, "command returned exit status %s: %s" % (status, command) + break - if not nodes: - continue - command = ("python sendto_silhouette.py" + " " + - self.global_args() + " " + - self.format_args(action) + " " + - self.id_args(nodes) + " " + - " ".join(self.extra_args)) - commands.append(command) + self.frame.Close() - if self.debug: - print >> sys.stderr, "\n".join(commands) - else: - for command in commands: - status = os.system(command) - if status != 0: - print >> sys.stderr, "command returned exit status %s: %s" % (status, command) - break +def save_stderr(): + # GTK likes to spam stderr, which inkscape will show in a dialog. + null = open('/dev/null', 'w') + sys.stderr_dup = os.dup(sys.stderr.fileno()) + os.dup2(null.fileno(), 2) + sys.stderr_backup = sys.stderr + sys.stderr = StringIO() - time.sleep(self.pause) - sys.stdout = old_stdout +def restore_stderr(): + os.dup2(sys.stderr_dup, 2) + sys.stderr_backup.write(sys.stderr.getvalue()) + sys.sys.stderr = stderr_backup +# end of class MyFrame if __name__ == "__main__": - #print >> sys.stderr, " ".join(sys.argv) - #sys.exit(0) + save_stderr() - s = SilhouetteMulti() - s.affect() + try: + e = SilhouetteMulti() + e.affect() + except: + traceback.print_exc() + restore_stderr() From 099eb4c18f240c802c700c1d7301b2ba6766f19d Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Wed, 12 Jul 2017 21:04:52 +0100 Subject: [PATCH 43/51] try to reassign colors when loading preset --- silhouette_multi.py | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index c9a19e0b..8c67b7a7 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -364,9 +364,42 @@ def _load_preset(self, preset_name): if self.selected: self.actions.Select(self.selected, False) - self.colors = preset['colors'] - self.color_enabled = preset['color_enabled'] - self.color_settings = preset['color_settings'] + old_colors = self.colors + self.colors = [] + extra_colors = [] + + for color in preset['colors']: + if color in old_colors: + old_colors.remove(color) + self.colors.append(color) + self.color_enabled[color] = preset['color_enabled'].get(color, True) + self.color_settings[color] = preset['color_settings'].get(color, {}) + else: + extra_colors.append(color) + + reassigned = 0 + # If there are any leftover colors in this SVG that weren't in the + # preset, we have to add them back into the list. Let's try to + # use the settings from one of the "unclaimed" colors in the preset. + + for color in old_colors: + if extra_colors: + reassigned += 1 + assigned_color = extra_colors.pop(0) + self.colors.append(color) + self.color_enabled[color] = preset['color_enabled'].get(assigned_color, True) + self.color_settings[color] = preset['color_settings'].get(assigned_color, {}) + + message = [] + + if reassigned: + message.append("%d colors were reassigned." % reassigned) + + if extra_colors: + message.append("%d colors from the preset were not used." % len(extra_colors)) + + if message: + info_dialog(self, "Colors in the preset and this SVG did not match fully. " + " ".join(message)) self.refresh_actions() @@ -514,7 +547,7 @@ def refresh_actions(self): def __set_properties(self): # begin wxGlade: MyFrame.__set_properties self.SetTitle("Silhouette Multi-Action") - self.notebook.SetMinSize((800, 500)) + self.notebook.SetMinSize((800, 800)) # end wxGlade def __do_layout(self): From 97f9ef73e8a5343b5b9c70ac0af55d4f792707b9 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 13 Jul 2017 01:12:07 +0100 Subject: [PATCH 44/51] add dry run mode and a couple small bugfixes --- silhouette_multi.inx | 2 ++ silhouette_multi.py | 45 ++++++++++++++++++++++++++++---------------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/silhouette_multi.inx b/silhouette_multi.inx index 1f1ba6f1..bd780fa1 100644 --- a/silhouette_multi.inx +++ b/silhouette_multi.inx @@ -6,6 +6,8 @@ inkex.py silhouette_multi.py + false + all diff --git a/silhouette_multi.py b/silhouette_multi.py index 8c67b7a7..28d81a2f 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -254,6 +254,7 @@ class SilhouetteMultiFrame(wx.Frame): def __init__(self, *args, **kwargs): # begin wxGlade: MyFrame.__init__ self.colors = kwargs.pop('colors', []) + self.options = kwargs.pop('options') self.run_callback = kwargs.pop('run_callback') wx.Frame.__init__(self, None, wx.ID_ANY, "Silhouette Multi-Action" @@ -287,7 +288,7 @@ def __init__(self, *args, **kwargs): self.Bind(wx.EVT_BUTTON, self.delete_preset, self.delete_preset_button) self.Bind(wx.EVT_BUTTON, self.close, self.cancel_button) - self._load_preset('__LAST__') + self._load_preset('__LAST__', silent=True) self.__set_properties() self.__do_layout() @@ -355,9 +356,11 @@ def update_preset_list(self): preset_names = [preset for preset in preset_names if not preset.startswith("__")] self.preset_chooser.SetItems(preset_names) - def _load_preset(self, preset_name): + def _load_preset(self, preset_name, silent=False): preset = load_preset(preset_name) + #print >> sys.stderr, preset + if not preset: return @@ -383,22 +386,26 @@ def _load_preset(self, preset_name): # use the settings from one of the "unclaimed" colors in the preset. for color in old_colors: + self.colors.append(color) if extra_colors: reassigned += 1 assigned_color = extra_colors.pop(0) - self.colors.append(color) self.color_enabled[color] = preset['color_enabled'].get(assigned_color, True) self.color_settings[color] = preset['color_settings'].get(assigned_color, {}) message = [] + #print >> sys.stderr, reassigned, extra_colors + #print >> sys.stderr, self.colors + #print >> sys.stderr, self.color_settings + if reassigned: message.append("%d colors were reassigned." % reassigned) if extra_colors: message.append("%d colors from the preset were not used." % len(extra_colors)) - if message: + if message and not silent: info_dialog(self, "Colors in the preset and this SVG did not match fully. " + " ".join(message)) self.refresh_actions() @@ -424,10 +431,11 @@ def run(self, event): actions.append((color, self.color_settings.get(color) or self.notebook.get_defaults())) if actions: - if not confirm_dialog(self, "About to perform %d actions, continue?" % len(actions)): - return + if not self.options.dry_run: + if not confirm_dialog(self, "About to perform %d actions, continue?" % len(actions)): + return else: - info_dialog("No colors were enabled, so no actions can be performed.") + info_dialog(self, "No colors were enabled, so no actions can be performed.") return self._save_preset('__LAST__') @@ -589,6 +597,11 @@ def __do_layout(self): # end wxGlade class SilhouetteMulti(inkex.Effect): + def __init__(self, *args, **kwargs): + inkex.Effect.__init__(self, *args, **kwargs) + + self.OptionParser.add_option("-d", "--dry_run", type='inkbool', default=False) + def get_style(self, element): if element.get('style') is not None: return simplestyle.parseStyle(element.get('style')) @@ -624,7 +637,7 @@ def traverse_element(element, selected=False, parent_visibility="visible"): self.selected_objects.add(element) for child in element: - traverse_element(child, visibility) + traverse_element(child, selected, visibility) traverse_element(self.document.getroot()) @@ -640,7 +653,7 @@ def split_objects_by_color(self): def effect(self): app = wx.App() self.split_objects_by_color() - self.frame = SilhouetteMultiFrame(colors=self.objects_by_color.keys(), run_callback=self.run) + self.frame = SilhouetteMultiFrame(colors=self.objects_by_color.keys(), run_callback=self.run, options=self.options) self.frame.Show() app.MainLoop() @@ -669,14 +682,14 @@ def run(self, actions): command += " " + svg_file.name #print >> sys.stderr, command + if self.options.dry_run: + print >> sys.stderr, command + else: + status = os.system(command) - status = os.system(command) - - if status != 0: - print >> sys.stderr, "command returned exit status %s: %s" % (status, command) - break - - + if status != 0: + print >> sys.stderr, "command returned exit status %s: %s" % (status, command) + break self.frame.Close() From faa7893cddd0ee2734854f53c30b255ff4e3512b Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 13 Jul 2017 03:02:07 +0100 Subject: [PATCH 45/51] several bugfixes --- silhouette_multi.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index 28d81a2f..95226915 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -619,7 +619,7 @@ def get_stroke_color(self, element): return None def load_selected_objects(self): - self.selected_objects = set() + self.selected_objects = [] def traverse_element(element, selected=False, parent_visibility="visible"): if self.get_style(element).get('display') == 'none': @@ -634,12 +634,18 @@ def traverse_element(element, selected=False, parent_visibility="visible"): selected = True if selected and visibility not in ('hidden', 'collapse'): - self.selected_objects.add(element) + self.selected_objects.append(element) for child in element: traverse_element(child, selected, visibility) - traverse_element(self.document.getroot()) + # if they haven't selected specific objects, then process all objects + if self.selected: + select_all = False + else: + select_all = True + + traverse_element(self.document.getroot(), selected=select_all) def split_objects_by_color(self): self.objects_by_color = defaultdict(list) @@ -673,12 +679,15 @@ def id_args(self, nodes): return self.format_args(("id", node.get("id")) for node in nodes) def run(self, actions): + self.frame.Close() + restore_stderr() + svg_file = self.save_copy() for color, settings in actions: - command = "python sendto_silhouette.py " - command += self.format_args(settings) - command += self.id_args(self.objects_by_color[color]) + command = "python sendto_silhouette.py" + command += " " + self.format_args(settings) + command += " " + self.id_args(self.objects_by_color[color]) command += " " + svg_file.name #print >> sys.stderr, command @@ -689,9 +698,9 @@ def run(self, actions): if status != 0: print >> sys.stderr, "command returned exit status %s: %s" % (status, command) + print >> sys.stderr, os.getcwd() break - self.frame.Close() def save_stderr(): @@ -704,9 +713,12 @@ def save_stderr(): def restore_stderr(): - os.dup2(sys.stderr_dup, 2) - sys.stderr_backup.write(sys.stderr.getvalue()) - sys.sys.stderr = stderr_backup + if hasattr(sys, "stderr_backup"): + os.dup2(sys.stderr_dup, 2) + sys.stderr_backup.write(sys.stderr.getvalue()) + sys.stderr = sys.stderr_backup + + del sys.stderr_backup # end of class MyFrame From 2c6f0a1b8c1ec8181b4cdd0ab8ddd4d882ffa41e Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 13 Jul 2017 17:59:04 +0100 Subject: [PATCH 46/51] add "progress" dialog --- silhouette_multi.py | 82 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index 95226915..91b08e6c 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -3,7 +3,10 @@ import os import sys +import time import cPickle +import subprocess +from threading import Thread from tempfile import NamedTemporaryFile from collections import defaultdict, OrderedDict import xmltodict @@ -664,10 +667,9 @@ def effect(self): app.MainLoop() def save_copy(self): - f = NamedTemporaryFile(suffix='.svg', prefix='silhouette-multiple-actions') - self.document.write(f) - f.flush() - return f + self.svg_file = NamedTemporaryFile(suffix='.svg', prefix='silhouette-multiple-actions') + self.document.write(self.svg_file) + self.svg_file.flush() def format_args(self, args): if isinstance(args, dict): @@ -678,30 +680,74 @@ def format_args(self, args): def id_args(self, nodes): return self.format_args(("id", node.get("id")) for node in nodes) - def run(self, actions): - self.frame.Close() - restore_stderr() - - svg_file = self.save_copy() + def format_commands(self, actions): + commands = [] for color, settings in actions: command = "python sendto_silhouette.py" command += " " + self.format_args(settings) command += " " + self.id_args(self.objects_by_color[color]) - command += " " + svg_file.name + command += " " + self.svg_file.name + + commands.append(command) + + return commands + + def run(self, actions): + self.frame.Close() + restore_stderr() + + self.save_copy() - #print >> sys.stderr, command - if self.options.dry_run: + commands = self.format_commands(actions) + + if self.options.dry_run: + print >> sys.stderr, "\n\n".join(commands) + else: + self.run_commands_with_dialog(commands) + + def run_commands_with_dialog(self, commands): + for i, command in enumerate(commands): + if not self.run_command_with_dialog(command, step=i + 1, total=len(commands)): + info_dialog(None, "Action failed.") + print >> sys.stderr, "The command that failed:" print >> sys.stderr, command - else: - status = os.system(command) - if status != 0: - print >> sys.stderr, "command returned exit status %s: %s" % (status, command) - print >> sys.stderr, os.getcwd() - break + sys.exit(1) + + def run_command_with_dialog(self, command, step, total): + process = subprocess.Popen(command, shell=True) + + dialog = wx.ProgressDialog(style=wx.PD_APP_MODAL|wx.PD_CAN_ABORT|wx.PD_ELAPSED_TIME, + message="Performing action %d of %d..." % (step, total), + title="Silhouette Multiple Actions") + + last_tick = time.time() + + while process.returncode is None: + if time.time() - last_tick > 0.5: + dialog.Pulse() + last_tick = time.time() + + process.poll() + wx.Yield() + time.sleep(0.1) + + if dialog.WasCancelled(): + def cancel(): + process.terminate() + process.wait() + + Thread(target=cancel).start() + dialog.Destroy() + wx.Yield() + info_dialog(None, "Action aborted. It may take awhile for the machine to cancel its operation.") + sys.exit(1) + dialog.Destroy() + wx.Yield() + return process.returncode == 0 def save_stderr(): # GTK likes to spam stderr, which inkscape will show in a dialog. From 7c3ecde4c1d5605d9a37f5e1cbee482fdb76f92f Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 13 Jul 2017 17:59:34 +0100 Subject: [PATCH 47/51] disable colors not in the preset when loading --- silhouette_multi.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/silhouette_multi.py b/silhouette_multi.py index 91b08e6c..3aef6a8d 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -390,11 +390,14 @@ def _load_preset(self, preset_name, silent=False): for color in old_colors: self.colors.append(color) + if extra_colors: reassigned += 1 assigned_color = extra_colors.pop(0) self.color_enabled[color] = preset['color_enabled'].get(assigned_color, True) self.color_settings[color] = preset['color_settings'].get(assigned_color, {}) + else: + self.color_enabled[color] = False message = [] From 6e66966329ab7ad8939c29a2e2f3560fb8794fd1 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Mon, 28 Aug 2017 01:09:03 +0100 Subject: [PATCH 48/51] send SIGKILL instead of SIGTERM --- silhouette_multi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index 3aef6a8d..800186bf 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -738,7 +738,7 @@ def run_command_with_dialog(self, command, step, total): if dialog.WasCancelled(): def cancel(): - process.terminate() + process.kill() process.wait() Thread(target=cancel).start() From fe8314fee9f08f918f6349cf82d22ae259869b19 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Thu, 28 Sep 2017 01:23:54 +0100 Subject: [PATCH 49/51] fix cancel --- silhouette_multi.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index 800186bf..aa02d7c4 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -719,7 +719,9 @@ def run_commands_with_dialog(self, commands): sys.exit(1) def run_command_with_dialog(self, command, step, total): - process = subprocess.Popen(command, shell=True) + # exec ensures that the shell gets replaced so that we can terminate the + # actual python script if the user cancels + process = subprocess.Popen("exec " + command, shell=True) dialog = wx.ProgressDialog(style=wx.PD_APP_MODAL|wx.PD_CAN_ABORT|wx.PD_ELAPSED_TIME, message="Performing action %d of %d..." % (step, total), @@ -738,7 +740,7 @@ def run_command_with_dialog(self, command, step, total): if dialog.WasCancelled(): def cancel(): - process.kill() + process.terminate() process.wait() Thread(target=cancel).start() From 81e159249c4dabf32f62f34a1e2894c01e4546e4 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Sun, 5 Nov 2017 01:04:01 +0000 Subject: [PATCH 50/51] use fill color if stroke not set --- silhouette_multi.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/silhouette_multi.py b/silhouette_multi.py index aa02d7c4..47ce5c88 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -616,11 +616,14 @@ def get_style(self, element): return style - def get_stroke_color(self, element): - stroke = self.get_style(element).get('stroke') + def get_color(self, element): + color = self.get_style(element).get('stroke', 'none') - if stroke is not None and stroke != 'none': - return simplestyle.parseColor(stroke) + if color == 'none': + color = self.get_style(element).get('fill', 'none') + + if color != 'none': + return simplestyle.parseColor(color) else: return None @@ -658,7 +661,7 @@ def split_objects_by_color(self): self.load_selected_objects() for obj in self.selected_objects: - color = self.get_stroke_color(obj) + color = self.get_color(obj) if color: self.objects_by_color[color].append(obj) @@ -774,12 +777,20 @@ def restore_stderr(): # end of class MyFrame if __name__ == "__main__": - save_stderr() - try: - e = SilhouetteMulti() - e.affect() - except: - traceback.print_exc() + pid = os.fork() + if pid == 0: + # Forking and closing stdout and stderr allows inkscape to continue on + # while the silhouette machine is cutting. This is useful if you're + # cutting something really big and want to work on another document. + os.close(1) + os.close(2) + + try: + e = SilhouetteMulti() + e.affect() + except: + traceback.print_exc() + - restore_stderr() + sys.exit(0) From 7f3dc0e7f369deed2d89071c795c7d125c60e206 Mon Sep 17 00:00:00 2001 From: Lex Neva Date: Tue, 5 Dec 2017 19:53:42 +0000 Subject: [PATCH 51/51] ignore stroke set on group (silhouette_multi) --- silhouette_multi.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/silhouette_multi.py b/silhouette_multi.py index 47ce5c88..ab47131d 100644 --- a/silhouette_multi.py +++ b/silhouette_multi.py @@ -617,6 +617,12 @@ def get_style(self, element): return style def get_color(self, element): + if element.tag == inkex.addNS( 'g', 'svg'): + # Sometimes Inkscape sets a stroke style on a group, which seems to + # have no visual effect. If we didn't ignore those, we'd cut those + # objects twice. + return None + color = self.get_style(element).get('stroke', 'none') if color == 'none':