From 4c906e3d54b4fb79779c32165758ab1aa58919be Mon Sep 17 00:00:00 2001 From: Eric Van Albert Date: Fri, 9 Jun 2017 21:01:40 -0400 Subject: [PATCH] add inkscape extension to git and improve it to generate complex polygons, re-generate SC4 --- keygen.scad | 39 +- paths2openscad.inx | 63 ++++ paths2openscad.py | 886 +++++++++++++++++++++++++++++++++++++++++++++ sc4.scad | 37 +- sc4.svg | 26 +- sc4_polygons.scad | 18 + 6 files changed, 1003 insertions(+), 66 deletions(-) create mode 100755 paths2openscad.inx create mode 100755 paths2openscad.py create mode 100644 sc4_polygons.scad diff --git a/keygen.scad b/keygen.scad index 7356217..b88c827 100644 --- a/keygen.scad +++ b/keygen.scad @@ -1,21 +1,9 @@ $inf=1000; $eps=.01; -/* -BROKEN -function key_make_complex_polygon(paths, reversed) = [ - [for (a=paths, b=a) b], - [for(i=[0:len(reversed)]) - reversed[i] - ? [for(e=[len(paths[i])-1:-1:0]) e] - : [for(e=[0:len(paths[i])-1]) e] - ] -]; -*/ - -function key_move_and_scale(points, scale, offset) = [ - for(p=points) [scale[0] * (p[0] + offset[0]), - scale[1] * (p[1] + offset[1])] +function key_move(points, offset) = [ + for(p=points) [p[0] + offset[0], + p[1] + offset[1]] ]; module key_outline(outline_points, thickness, outline_paths=undef) { @@ -70,7 +58,7 @@ module key_warding_cutter(warding, blade_height, cutter_radius, left) { } module key_emboss(emboss_points, emboss_depth, left, thickness, emboss_paths=undef) { - translate([(left ? 1 : -1) * 0.5*thickness, 0, 0]) rotate(-90, [0, 1, 0]) rotate(-90, [0, 0, 1]) // Translate and rotate into the correct soot + translate([(left ? -1 : 1) * 0.5*thickness, 0, 0]) rotate(-90, [0, 1, 0]) rotate(-90, [0, 0, 1]) // Translate and rotate into the correct soot linear_extrude(height=2*emboss_depth, center=true) // Extrude the key outline polygon(points=emboss_points, paths=emboss_paths); // Draw the outline } @@ -85,9 +73,8 @@ module key_blank(outline_points, bow_thickness=0, emboss_depth=.1, plug_diameter=0, - scale=[1/96, -1/96], offset=[0, 0], - cutter_radius=.25) { + cutter_radius=18) { // Find the bounding box of the warding warding_min = [min([for(e=warding) e[0]]), min([for(e=warding) e[1]])]; @@ -95,20 +82,20 @@ module key_blank(outline_points, // Apply the given offset to the outline, // holes, and emboss - outline_adj = key_move_and_scale(outline_points, scale, offset); - emboss_left_adj = key_move_and_scale(emboss_left_points, scale, offset); - emboss_right_adj = key_move_and_scale(emboss_right_points, scale, offset); + outline_adj = key_move(outline_points, offset); + emboss_left_adj = key_move(emboss_left_points, offset); + emboss_right_adj = key_move(emboss_right_points, offset); // Move the warding profile // so that it is centered in X // and non-negative in Y warding_offset = [-0.5 * (warding_min[0] + warding_max[0]), - -warding_max[1]]; - warding_adj = key_move_and_scale(warding, scale, warding_offset); + -warding_min[1]]; + warding_adj = key_move(warding, warding_offset); // Infer various key properties - thickness = (bow_thickness == 0) ? abs(scale[0] * (warding_max[0] - warding_min[0])) : bow_thickness; - blade_height = abs(scale[1] * (warding_max[1] - warding_min[1])); + thickness = (bow_thickness == 0) ? abs(warding_max[0] - warding_min[0]) : bow_thickness; + blade_height = abs(warding_max[1] - warding_min[1]); // Cut out the warding milling artifacts // from the bow @@ -129,7 +116,7 @@ module key_blank(outline_points, translate([0, $inf/2, 0]) cube([$inf, $inf, $inf], center=true); - key_blade(warding_adj, plug_diameter, $inf); + key_blade(warding_adj, plug_diameter); } } // Draw the milling wheels that cut the warding diff --git a/paths2openscad.inx b/paths2openscad.inx new file mode 100755 index 0000000..79dfb3f --- /dev/null +++ b/paths2openscad.inx @@ -0,0 +1,63 @@ + + + <_name>Paths to OpenSCAD + command.extrude.openscad + org.inkscape.output.svg.inkscape + paths2openscad.py + inkex.py + simpletransform.py + cubicsuperpath.py + cspsubdiv.py + bezmisc.py + + + + <_param name="header" type="description" xml:space="preserve"> + + +The smoothing parameter describes +how smoothly to render curves. Use +smaller values for smoother curves. + + + + + + ~/inkscape.scad + 0.02 + + + <_param name="aboutpage" type="description" xml:space="preserve"> +This extension converts Inkscape paths to +extruded polygons in OpenSCAD. Before +using, first convert objects to paths +with the "Path > Object to Path" +menu item. + +Note that the paths must be polygonal. +Non-polygonal paths will not render well +in OpenSCAD. Thus, while you can convert +text to a path, letters with closed loops +will not appear as you expect in OpenSCAD +(e.g., the letter "o"). + +Inkscape's units of pixels are converted +to millimeters using the SVG Standard's +definition of 90 pixels = 1 inch. + +v0.9 +Dan Newman (dan newman @ mtbaldy us) + + + + + + all + + + + + + diff --git a/paths2openscad.py b/paths2openscad.py new file mode 100755 index 0000000..52100b5 --- /dev/null +++ b/paths2openscad.py @@ -0,0 +1,886 @@ +#!/usr/bin/env python + +# openscad.py + +# This is an Inkscape extension to output paths to extruded OpenSCAD polygons +# The Inkscape objects must first be converted to paths (Path > Object to Path). +# Some paths may not work well -- the paths have to be polygons. As such, +# paths derived from text may meet with mixed results. + +# Written by Daniel C. Newman ( dan dot newman at mtbaldy dot us ) +# 10 June 2012 + +# 15 June 2012 +# Updated by Dan Newman to handle a single level of polygon nesting. +# This is sufficient to handle most fonts. +# If you want to nest two polygons, combine them into a single path +# within Inkscape with "Path > Combine Path". + +# 9 June 2017 +# Modified by Eric Van Albert to output complex polygons instead of +# using OpenSCAD's difference() + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import math +import os.path +import inkex +import simplepath +import simplestyle +import simpletransform +import cubicsuperpath +import cspsubdiv +import bezmisc +import re + +DEFAULT_WIDTH = 100 +DEFAULT_HEIGHT = 100 + +def parseLengthWithUnits( str ): + + ''' + Parse an SVG value which may or may not have units attached + This version is greatly simplified in that it only allows: no units, + units of px, and units of %. Everything else, it returns None for. + There is a more general routine to consider in scour.py if more + generality is ever needed. + ''' + + u = 'px' + s = str.strip() + if s[-2:] == 'px': + s = s[:-2] + elif s[-1:] == '%': + u = '%' + s = s[:-1] + + try: + v = float( s ) + except: + return None, None + + return v, u + +def pointInBBox( pt, bbox ): + + ''' + Determine if the point pt=[x, y] lies on or within the bounding + box bbox=[xmin, xmax, ymin, ymax]. + ''' + + # if ( x < xmin ) or ( x > xmax ) or ( y < ymin ) or ( y > ymax ) + if ( pt[0] < bbox[0] ) or ( pt[0] > bbox[1] ) or \ + ( pt[1] < bbox[2] ) or ( pt[1] > bbox[3] ): + return False + else: + return True + +def bboxInBBox( bbox1, bbox2 ): + + ''' + Determine if the bounding box bbox1 lies on or within the + bounding box bbox2. NOTE: we do not test for strict enclosure. + + Structure of the bounding boxes is + + bbox1 = [ xmin1, xmax1, ymin1, ymax1 ] + bbox2 = [ xmin2, xmax2, ymin2, ymax2 ] + ''' + + # if ( xmin1 < xmin2 ) or ( xmax1 > xmax2 ) or ( ymin1 < ymin2 ) or ( ymax1 > ymax2 ) + + if ( bbox1[0] < bbox2[0] ) or ( bbox1[1] > bbox2[1] ) or \ + ( bbox1[2] < bbox2[2] ) or ( bbox1[3] > bbox2[3] ): + return False + else: + return True + +def pointInPoly( p, poly, bbox=None ): + + ''' + Use a ray casting algorithm to see if the point p = [x, y] lies within + the polygon poly = [[x1,y1],[x2,y2],...]. Returns True if the point + is within poly, lies on an edge of poly, or is a vertex of poly. + ''' + + if ( p is None ) or ( poly is None ): + return False + + # Check to see if the point lies outside the polygon's bounding box + if not bbox is None: + if not pointInBBox( p, bbox ): + return False + + # Check to see if the point is a vertex + if p in poly: + return True + + # Handle a boundary case associated with the point + # lying on a horizontal edge of the polygon + x = p[0] + y = p[1] + p1 = poly[0] + p2 = poly[1] + for i in range( len( poly ) ): + if i != 0: + p1 = poly[i-1] + p2 = poly[i] + if ( y == p1[1] ) and ( p1[1] == p2[1] ) and \ + ( x > min( p1[0], p2[0] ) ) and ( x < max( p1[0], p2[0] ) ): + return True + + n = len( poly ) + inside = False + + p1_x,p1_y = poly[0] + for i in range( n + 1 ): + p2_x,p2_y = poly[i % n] + if y > min( p1_y, p2_y ): + if y <= max( p1_y, p2_y ): + if x <= max( p1_x, p2_x ): + if p1_y != p2_y: + intersect = p1_x + (y - p1_y) * (p2_x - p1_x) / (p2_y - p1_y) + if x <= intersect: + inside = not inside + else: + inside = not inside + p1_x,p1_y = p2_x,p2_y + + return inside + +def polyInPoly( poly1, bbox1, poly2, bbox2 ): + + ''' + Determine if polygon poly2 = [[x1,y1],[x2,y2],...] + contains polygon poly1. + + The bounding box information, bbox=[xmin, xmax, ymin, ymax] + is optional. When supplied it can be used to perform rejections. + Note that one bounding box containing another is not sufficient + to imply that one polygon contains another. It's necessary, but + not sufficient. + ''' + + # See if poly1's bboundin box is NOT contained by poly2's bounding box + # if it isn't, then poly1 cannot be contained by poly2. + + if ( not bbox1 is None ) and ( not bbox2 is None ): + if not bboxInBBox( bbox1, bbox2 ): + return False + + # To see if poly1 is contained by poly2, we need to ensure that each + # vertex of poly1 lies on or within poly2 + + for p in poly1: + if not pointInPoly( p, poly2, bbox2 ): + return False + + # Looks like poly1 is contained on or in Poly2 + + return True + +def subdivideCubicPath( sp, flat, i=1 ): + + ''' + [ Lifted from eggbot.py with impunity ] + + Break up a bezier curve into smaller curves, each of which + is approximately a straight line within a given tolerance + (the "smoothness" defined by [flat]). + + This is a modified version of cspsubdiv.cspsubdiv(): rewritten + because recursion-depth errors on complicated line segments + could occur with cspsubdiv.cspsubdiv(). + ''' + + while True: + while True: + if i >= len( sp ): + return + + p0 = sp[i - 1][1] + p1 = sp[i - 1][2] + p2 = sp[i][0] + p3 = sp[i][1] + + b = ( p0, p1, p2, p3 ) + + if cspsubdiv.maxdist( b ) > flat: + break + + i += 1 + + one, two = bezmisc.beziersplitatt( b, 0.5 ) + sp[i - 1][2] = one[1] + sp[i][0] = two[2] + p = [one[2], one[3], two[1]] + sp[i:1] = [p] + +class OpenSCAD( inkex.Effect ): + + def __init__( self ): + + inkex.Effect.__init__( self ) + + self.OptionParser.add_option( "--tab", #NOTE: value is not used. + action="store", type="string", + dest="tab", default="splash", + help="The active tab when Apply was pressed" ) + + self.OptionParser.add_option('--smoothness', dest='smoothness', + type='float', default=float( 0.2 ), action='store', + help='Curve smoothing (less for more)' ) + + self.OptionParser.add_option('--fname', dest='fname', + type='string', default='~/inkscape.scad', + action='store', + help='Curve smoothing (less for more)' ) + + self.cx = float( DEFAULT_WIDTH ) / 2.0 + self.cy = float( DEFAULT_HEIGHT ) / 2.0 + self.xmin, self.xmax = ( 1.0E70, -1.0E70 ) + self.ymin, self.ymax = ( 1.0E70, -1.0E70 ) + + # Dictionary of paths we will construct. It's keyed by the SVG node + # it came from. Such keying isn't too useful in this specific case, + # but it can be useful in other applications when you actually want + # to go back and update the SVG document + self.paths = {} + + # Output file handling + self.call_list = [] + self.pathid = int( 0 ) + + # Output file + self.f = None + + # For handling an SVG viewbox attribute, we will need to know the + # values of the document's width and height attributes as well + # as establishing a transform from the viewbox to the display. + + self.docWidth = float( DEFAULT_WIDTH ) + self.docHeight = float( DEFAULT_HEIGHT ) + self.docTransform = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] + + # Dictionary of warnings issued. This to prevent from warning + # multiple times about the same problem + self.warnings = {} + + def getLength( self, name, default ): + + ''' + Get the attribute with name "name" and default value "default" + Parse the attribute into a value and associated units. Then, accept + units of cm, ft, in, m, mm, pc, or pt. Convert to pixels. + + Note that SVG defines 90 px = 1 in = 25.4 mm. + ''' + + str = self.document.getroot().get( name ) + if str: + v, u = parseLengthWithUnits( str ) + if not v: + # Couldn't parse the value + return None + elif ( u == 'mm' ): + return float( v ) * ( 90.0 / 25.4 ) + elif ( u == 'cm' ): + return float( v ) * ( 90.0 * 10.0 / 25.4 ) + elif ( u == 'm' ): + return float( v ) * ( 90.0 * 1000.0 / 25.4 ) + elif ( u == 'in' ): + return float( v ) * 90.0 + elif ( u == 'ft' ): + return float( v ) * 12.0 * 90.0 + elif ( u == 'pt' ): + # Use modern "Postscript" points of 72 pt = 1 in instead + # of the traditional 72.27 pt = 1 in + return float( v ) * ( 90.0 / 72.0 ) + elif ( u == 'pc' ): + return float( v ) * ( 90.0 / 6.0 ) + elif ( u == 'px' ): + return float( v ) + else: + # Unsupported units + return None + else: + # No width specified; assume the default value + return float( default ) + + def getDocProps( self ): + + ''' + Get the document's height and width attributes from the tag. + Use a default value in case the property is not present or is + expressed in units of percentages. + ''' + + self.docHeight = self.getLength( 'height', DEFAULT_HEIGHT ) + self.docWidth = self.getLength( 'width', DEFAULT_WIDTH ) + if ( self.docHeight == None ) or ( self.docWidth == None ): + return False + else: + return True + + def handleViewBox( self ): + + ''' + Set up the document-wide transform in the event that the document has an SVG viewbox + ''' + + if self.getDocProps(): + viewbox = self.document.getroot().get( 'viewBox' ) + if viewbox: + vinfo = viewbox.strip().replace( ',', ' ' ).split( ' ' ) + if ( vinfo[2] != 0 ) and ( vinfo[3] != 0 ): + sx = self.docWidth / float( vinfo[2] ) + sy = self.docHeight / float( vinfo[3] ) + self.docTransform = simpletransform.parseTransform( 'scale(%f,%f)' % (sx, sy) ) + + def getPathVertices( self, path, node=None, transform=None ): + + ''' + Decompose the path data from an SVG element into individual + subpaths, each subpath consisting of absolute move to and line + to coordinates. Place these coordinates into a list of polygon + vertices. + ''' + + if ( not path ) or ( len( path ) == 0 ): + # Nothing to do + return None + + # parsePath() may raise an exception. This is okay + sp = simplepath.parsePath( path ) + if ( not sp ) or ( len( sp ) == 0 ): + # Path must have been devoid of any real content + return None + + # Get a cubic super path + p = cubicsuperpath.CubicSuperPath( sp ) + if ( not p ) or ( len( p ) == 0 ): + # Probably never happens, but... + return None + + if transform: + simpletransform.applyTransformToPath( transform, p ) + + # Now traverse the cubic super path + subpath_list = [] + subpath_vertices = [] + + for sp in p: + + # We've started a new subpath + # See if there is a prior subpath and whether we should keep it + if len( subpath_vertices ): + subpath_list.append( [ subpath_vertices, [ sp_xmin, sp_xmax, sp_ymin, sp_ymax ] ] ) + + subpath_vertices = [] + subdivideCubicPath( sp, float( self.options.smoothness ) ) + + # Note the first point of the subpath + first_point = sp[0][1] + subpath_vertices.append( first_point ) + sp_xmin = first_point[0] + sp_xmax = first_point[0] + sp_ymin = first_point[1] + sp_ymax = first_point[1] + + # See if the first and last points are identical + # OpenSCAD doesn't mind if we duplicate the first and last + # vertex, but our polygon in polygon algorithm may + n = len( sp ) + last_point = sp[n-1][1] + if ( first_point[0] == last_point[0] ) and ( first_point[1] == last_point[1] ): + n = n - 1 + + # Traverse each point of the subpath + for csp in sp[1:n]: + + # Append the vertex to our list of vertices + pt = csp[1] + subpath_vertices.append( pt ) + + # Track the bounding box of this subpath + if pt[0] < sp_xmin: + sp_xmin = pt[0] + elif pt[0] > sp_xmax: + sp_xmax = pt[0] + if pt[1] < sp_ymin: + sp_ymin = pt[1] + elif pt[1] > sp_ymax: + sp_ymax = pt[1] + + # Track the bounding box of the overall drawing + # This is used for centering the polygons in OpenSCAD around the (x,y) origin + if sp_xmin < self.xmin: + self.xmin = sp_xmin + if sp_xmax > self.xmax: + self.xmax = sp_xmax + if sp_ymin < self.ymin: + self.ymin = sp_ymin + if sp_ymax > self.ymax: + self.ymax = sp_ymax + + # Handle the final subpath + if len( subpath_vertices ): + subpath_list.append( [ subpath_vertices, [ sp_xmin, sp_xmax, sp_ymin, sp_ymax ] ] ) + + if len( subpath_list ) > 0: + self.paths[node] = subpath_list + + def convertPath( self, node ): + + path = self.paths[node] + if ( path is None ) or ( len( path ) == 0 ): + return + + # Determine which polys contain which + + contains = [ [] for i in xrange( len( path ) ) ] + contained_by = [ [] for i in xrange( len( path ) ) ] + + for i in range( 0, len( path ) ): + for j in range( i + 1, len( path ) ): + if polyInPoly( path[j][0], path[j][1], path[i][0], path[i][1] ): + # subpath i contains subpath j + contains[i].append( j ) + # subpath j is contained in subpath i + contained_by[j].append( i ) + elif polyInPoly( path[i][0], path[i][1], path[j][0], path[j][1] ): + # subpath j contains subpath i + contains[j].append( i ) + # subpath i is containd in subpath j + contained_by[i].append( j ) + + # Generate an OpenSCAD module for this path + id = node.get ( 'id', '' ) + if ( id is None ) or ( id == '' ): + id = str( self.pathid ) + 'x' + self.pathid += 1 + else: + id = re.sub( '[^A-Za-z0-9_]+', '', id ) + scale = (1, -1) # We appear to be working in mm by this point + + # And add the call to the call list + self.call_list.append( '//polygon(points={0}_points, paths={0}_paths);\n'.format(id) ) + + points = [] + paths = [] + + for i in range( 0, len( path ) ): + + # Skip this subpath if it is contained by another one + if len( contained_by[i] ) != 0: + continue + + subpath = path[i][0] + bbox = path[i][1] + + one_path = [] + for point in subpath: + one_path.append(len(points)) + points.append((point[0] - self.cx, point[1] - self.cy )) + + paths.append(one_path) + + if len( contains[i] ) != 0: + for j in contains[i]: + one_path = [] + for point in path[j][0]: + one_path.append(len(points)) + points.append((point[0] - self.cx, point[1] - self.cy )) + + paths.append(list(reversed(one_path))) + + points_str = "[{}]".format(", ".join(["[{:8f}, {:8f}]".format(x * scale[0], y * scale[1]) for (x, y) in points])) + paths_str = "[{}]".format(", ".join(["[{}]".format(", ".join(["{:d}".format(i) for i in indices])) for indices in paths])) + + self.f.write( '{}_points = {};\n'.format(id, points_str) ) + self.f.write( '{}_paths = {};\n'.format(id, paths_str) ) + + def recursivelyTraverseSvg( self, aNodeList, + matCurrent=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], + parent_visibility='visible' ): + + ''' + [ This too is largely lifted from eggbot.py ] + + Recursively walk the SVG document, building polygon vertex lists + for each graphical element we support. + + Rendered SVG elements: + , , , , , , + + Supported SVG elements: + , + + Ignored SVG elements: + , , , , , + processing directives + + All other SVG elements trigger an error (including ) + ''' + + for node in aNodeList: + + # Ignore invisible nodes + v = node.get( 'visibility', parent_visibility ) + if v == 'inherit': + v = parent_visibility + if v == 'hidden' or v == 'collapse': + pass + + # First apply the current matrix transform to this node's tranform + matNew = simpletransform.composeTransform( matCurrent, simpletransform.parseTransform( node.get( "transform" ) ) ) + + if node.tag == inkex.addNS( 'g', 'svg' ) or node.tag == 'g': + + self.recursivelyTraverseSvg( node, matNew, v ) + + elif node.tag == inkex.addNS( 'use', 'svg' ) or node.tag == 'use': + + # A element refers to another SVG element via an xlink:href="#blah" + # attribute. We will handle the element by doing an XPath search through + # the document, looking for the element with the matching id="blah" + # attribute. We then recursively process that element after applying + # any necessary (x,y) translation. + # + # Notes: + # 1. We ignore the height and width attributes as they do not apply to + # path-like elements, and + # 2. Even if the use element has visibility="hidden", SVG still calls + # for processing the referenced element. The referenced element is + # hidden only if its visibility is "inherit" or "hidden". + + refid = node.get( inkex.addNS( 'href', 'xlink' ) ) + if not refid: + pass + + # [1:] to ignore leading '#' in reference + path = '//*[@id="%s"]' % refid[1:] + refnode = node.xpath( path ) + if refnode: + x = float( node.get( 'x', '0' ) ) + y = float( node.get( 'y', '0' ) ) + # Note: the transform has already been applied + if ( x != 0 ) or (y != 0 ): + matNew2 = composeTransform( matNew, parseTransform( 'translate(%f,%f)' % (x,y) ) ) + else: + matNew2 = matNew + v = node.get( 'visibility', v ) + self.recursivelyTraverseSvg( refnode, matNew2, v ) + + elif node.tag == inkex.addNS( 'path', 'svg' ): + + path_data = node.get( 'd') + if path_data: + self.getPathVertices( path_data, node, matNew ) + + elif node.tag == inkex.addNS( 'rect', 'svg' ) or node.tag == 'rect': + + # Manually transform + # + # + # + # into + # + # + # + # I.e., explicitly draw three sides of the rectangle and the + # fourth side implicitly + + # Create a path with the outline of the rectangle + x = float( node.get( 'x' ) ) + y = float( node.get( 'y' ) ) + if ( not x ) or ( not y ): + pass + w = float( node.get( 'width', '0' ) ) + h = float( node.get( 'height', '0' ) ) + a = [] + a.append( ['M ', [x, y]] ) + a.append( [' l ', [w, 0]] ) + a.append( [' l ', [0, h]] ) + a.append( [' l ', [-w, 0]] ) + a.append( [' Z', []] ) + self.getPathVertices( simplepath.formatPath( a ), node, matNew ) + + elif node.tag == inkex.addNS( 'line', 'svg' ) or node.tag == 'line': + + # Convert + # + # + + x1 = float( node.get( 'x1' ) ) + y1 = float( node.get( 'y1' ) ) + x2 = float( node.get( 'x2' ) ) + y2 = float( node.get( 'y2' ) ) + if ( not x1 ) or ( not y1 ) or ( not x2 ) or ( not y2 ): + pass + a = [] + a.append( ['M ', [x1, y1]] ) + a.append( [' L ', [x2, y2]] ) + self.getPathVertices( simplepath.formatPath( a ), node, matNew ) + + elif node.tag == inkex.addNS( 'polyline', 'svg' ) or node.tag == 'polyline': + + # Convert + # + # + # + # to + # + # + # + # Note: we ignore polylines with no points + + pl = node.get( 'points', '' ).strip() + if pl == '': + pass + + pa = pl.split() + d = "".join( ["M " + pa[i] if i == 0 else " L " + pa[i] for i in range( 0, len( pa ) )] ) + self.getPathVertices( d, node, matNew ) + + elif node.tag == inkex.addNS( 'polygon', 'svg' ) or node.tag == 'polygon': + + # Convert + # + # + # + # to + # + # + # + # Note: we ignore polygons with no points + + pl = node.get( 'points', '' ).strip() + if pl == '': + pass + + pa = pl.split() + d = "".join( ["M " + pa[i] if i == 0 else " L " + pa[i] for i in range( 0, len( pa ) )] ) + d += " Z" + self.getPathVertices( d, node, matNew ) + + elif node.tag == inkex.addNS( 'ellipse', 'svg' ) or \ + node.tag == 'ellipse' or \ + node.tag == inkex.addNS( 'circle', 'svg' ) or \ + node.tag == 'circle': + + # Convert circles and ellipses to a path with two 180 degree arcs. + # In general (an ellipse), we convert + # + # + # + # to + # + # + # + # where + # + # X1 = CX - RX + # X2 = CX + RX + # + # Note: ellipses or circles with a radius attribute of value 0 are ignored + + if node.tag == inkex.addNS( 'ellipse', 'svg' ) or node.tag == 'ellipse': + rx = float( node.get( 'rx', '0' ) ) + ry = float( node.get( 'ry', '0' ) ) + else: + rx = float( node.get( 'r', '0' ) ) + ry = rx + if rx == 0 or ry == 0: + pass + + cx = float( node.get( 'cx', '0' ) ) + cy = float( node.get( 'cy', '0' ) ) + x1 = cx - rx + x2 = cx + rx + d = 'M %f,%f ' % ( x1, cy ) + \ + 'A %f,%f ' % ( rx, ry ) + \ + '0 1 0 %f,%f ' % ( x2, cy ) + \ + 'A %f,%f ' % ( rx, ry ) + \ + '0 1 0 %f,%f' % ( x1, cy ) + self.mapPathVertices( d, node, matNew ) + + elif node.tag == inkex.addNS( 'pattern', 'svg' ) or node.tag == 'pattern': + + pass + + elif node.tag == inkex.addNS( 'metadata', 'svg' ) or node.tag == 'metadata': + + pass + + elif node.tag == inkex.addNS( 'defs', 'svg' ) or node.tag == 'defs': + + pass + + elif node.tag == inkex.addNS( 'desc', 'svg' ) or node.tag == 'desc': + + pass + + elif node.tag == inkex.addNS( 'namedview', 'sodipodi' ) or node.tag == 'namedview': + + pass + + elif node.tag == inkex.addNS( 'eggbot', 'svg' ) or node.tag == 'eggbot': + + pass + + elif node.tag == inkex.addNS( 'text', 'svg' ) or node.tag == 'text': + + inkex.errormsg( 'Warning: unable to draw text, please convert it to a path first.' ) + + pass + + elif node.tag == inkex.addNS( 'title', 'svg' ) or node.tag == 'title': + + pass + + elif node.tag == inkex.addNS( 'image', 'svg' ) or node.tag == 'image': + + if not self.warnings.has_key( 'image' ): + inkex.errormsg( 'Warning: unable to draw bitmap images; ' + + 'please convert them to line art first. Consider using the "Trace bitmap..." ' + + 'tool of the "Path" menu. Mac users please note that some X11 settings may ' + + 'cause cut-and-paste operations to paste in bitmap copies.' ) + self.warnings['image'] = 1 + pass + + elif node.tag == inkex.addNS( 'pattern', 'svg' ) or node.tag == 'pattern': + + pass + + elif node.tag == inkex.addNS( 'radialGradient', 'svg' ) or node.tag == 'radialGradient': + + # Similar to pattern + pass + + elif node.tag == inkex.addNS( 'linearGradient', 'svg' ) or node.tag == 'linearGradient': + + # Similar in pattern + pass + + elif node.tag == inkex.addNS( 'style', 'svg' ) or node.tag == 'style': + + # This is a reference to an external style sheet and not the value + # of a style attribute to be inherited by child elements + pass + + elif node.tag == inkex.addNS( 'cursor', 'svg' ) or node.tag == 'cursor': + + pass + + elif node.tag == inkex.addNS( 'color-profile', 'svg' ) or node.tag == 'color-profile': + + # Gamma curves, color temp, etc. are not relevant to single color output + pass + + elif not isinstance( node.tag, basestring ): + + # This is likely an XML processing instruction such as an XML + # comment. lxml uses a function reference for such node tags + # and as such the node tag is likely not a printable string. + # Further, converting it to a printable string likely won't + # be very useful. + + pass + + else: + + inkex.errormsg( 'Warning: unable to draw object <%s>, please convert it to a path first.' % node.tag ) + pass + + + def recursivelyGetEnclosingTransform( self, node ): + + ''' + Determine the cumulative transform which node inherits from + its chain of ancestors. + ''' + node = node.getparent() + if node is not None: + parent_transform = self.recursivelyGetEnclosingTransform( node ) + node_transform = node.get( 'transform', None ) + if node_transform is None: + return parent_transform + else: + tr = simpletransform.parseTransform( node_transform ) + if parent_transform is None: + return tr + else: + return simpletransform.composeTransform( parent_transform, tr ) + else: + return self.docTransform + + def effect( self ): + + # Viewbox handling + self.handleViewBox() + + # First traverse the document (or selected items), reducing + # everything to line segments. If working on a selection, + # then determine the selection's bounding box in the process. + # (Actually, we just need to know it's extrema on the x-axis.) + + if self.options.ids: + # Traverse the selected objects + for id in self.options.ids: + transform = self.recursivelyGetEnclosingTransform( self.selected[id] ) + self.recursivelyTraverseSvg( [self.selected[id]], transform ) + else: + # Traverse the entire document building new, transformed paths + self.recursivelyTraverseSvg( self.document.getroot(), self.docTransform ) + + # Determine the center of the drawing's bounding box + self.cx = self.xmin + ( self.xmax - self.xmin ) / 2.0 + self.cy = self.ymin + ( self.ymax - self.ymin ) / 2.0 + + # Determine which polygons lie entirely within other polygons + try: + if '/' == os.sep: + self.f = open( os.path.expanduser( self.options.fname ), 'w') + else: + self.f = open( os.path.expanduser( self.options.fname ).replace('/', os.sep), 'w') + + self.f.write('''// Automatically generated using the Inkscape to OpenSCAD Converter +// Variable names are of the form _points and +// _paths. As a result, you can associate a polygon in this +// OpenSCAD program with the corresponding SVG element in the Inkscape document +// by looking for the XML element with the attribute id=\"inkscape-path-id\". +''' ) + + for key in self.paths: + self.f.write( '\n' ) + self.convertPath( key ) + + # Now output the list of modules to call + self.f.write( '\n' ) + for call in self.call_list: + self.f.write( call ) + + except IOError: + inkex.errormsg( 'Unable to open the file ' + self.options.fname ) + +if __name__ == '__main__': + + e = OpenSCAD() + e.affect() diff --git a/sc4.scad b/sc4.scad index a3fd14d..b4325c8 100644 --- a/sc4.scad +++ b/sc4.scad @@ -1,30 +1,11 @@ use +include -outline = [[-12.048077,5.767320],[-12.531197,6.197850],[-16.188977,6.288450],[-16.558561,6.416520],[-16.805557,6.775740],[-17.059163,7.256309],[-17.405057,7.424420],[-19.734005,7.436020],[-20.190575,7.678950],[-21.005688,8.407776],[-21.584899,8.714600],[-21.950541,9.552540],[-22.161920,10.454470],[-22.664611,10.714781],[-23.160009,10.840370],[-23.513154,11.702385],[-23.804236,12.163606],[-24.352135,12.364150],[-26.715326,12.364150],[-27.280510,12.190678],[-27.452528,11.780981],[-27.624588,10.917230],[-28.773427,10.437810],[-29.001442,9.531850],[-29.229456,8.625890],[-29.751429,8.441815],[-30.273401,8.257560],[-30.510304,7.256071],[-30.624192,6.261970],[-31.166270,5.957583],[-31.684671,5.815030],[-35.921242,3.350340],[-36.420288,2.840467],[-36.507637,2.275740],[-36.507626,-2.275740],[-36.420275,-2.840468],[-35.921231,-3.350340],[-31.684660,-5.815030],[-31.166259,-5.957590],[-30.624181,-6.261970],[-30.510293,-7.256071],[-30.273390,-8.257560],[-29.751418,-8.441725],[-29.229445,-8.625890],[-29.001431,-9.531850],[-28.773416,-10.437810],[-27.624577,-10.917230],[-27.452517,-11.780981],[-27.280499,-12.190678],[-26.715315,-12.364150],[-24.352124,-12.364150],[-23.804225,-12.163606],[-23.513143,-11.702385],[-23.159998,-10.840370],[-22.664600,-10.714774],[-22.161909,-10.454470],[-21.950531,-9.552540],[-21.584888,-8.714600],[-21.005677,-8.407776],[-20.190564,-7.678950],[-19.733994,-7.436020],[-17.405047,-7.424420],[-17.059153,-7.256309],[-16.805547,-6.775740],[-16.558555,-6.416520],[-16.188967,-6.288450],[-12.531187,-6.197850],[-12.048067,-5.767320],[-12.026867,-4.485730],[13.592243,-4.486130],[18.673063,0.615172],[18.673063,1.913110],[16.250473,4.230870],[-12.047637,4.226870]]; - -hole = [[-28.090168,3.691950],[-28.320547,4.292520],[-29.442927,4.261420],[-33.529647,1.963740],[-34.149378,1.203692],[-34.149367,-1.203652],[-33.529636,-1.963700],[-29.442916,-4.261380],[-28.320536,-4.292480],[-28.090157,-3.691910]]; - -warding = [[34.597676,-4.486348],[34.597676,-0.102120],[35.469973,0.280288],[35.751594,0.538961],[35.618801,0.933478],[34.597676,2.619678],[34.597676,4.230951],[36.507637,4.230951],[36.507637,3.395860],[35.935063,3.114221],[35.700383,2.824692],[35.746960,2.487905],[36.507637,1.173773],[36.507637,-0.570308],[35.618801,-0.848327],[35.618801,-4.486348],[34.597676,-4.486348]]; - -emboss_points = [[-23.687656,-12.029039],[-23.687656,12.028407],[-23.424104,11.446530],[-23.424104,-11.447162],[-23.687656,-12.029039]]; -/* -Additional Embossing - linear_extrude(height=h) - polygon([[-27.515846,-11.120050],[-27.624364,-10.917479],[-27.781461,-10.800690],[-27.781461,10.800574],[-27.624364,10.916847],[-27.515846,11.119418],[-27.515846,-11.120050]]); - linear_extrude(height=h) - polygon([[-22.144595,-10.426037],[-22.144595,10.425405],[-21.878980,9.172251],[-21.878980,-9.172883],[-22.144595,-10.426037]]); - linear_extrude(height=h) - polygon([[-29.082158,-8.830269],[-29.229435,-8.626145],[-29.347773,-8.533128],[-29.347773,-4.304446],[-29.082158,-4.375760],[-29.082158,-8.830269]]); - linear_extrude(height=h) - polygon([[-20.716776,-8.206016],[-20.716776,8.205384],[-20.453227,7.968190],[-20.453227,-7.968822],[-20.716776,-8.206016]]); - linear_extrude(height=h) - polygon([[-17.000209,-7.198327],[-17.000209,7.197695],[-16.805388,6.775496],[-16.734591,6.580678],[-16.734591,-6.581310],[-16.805388,-6.776131],[-17.000209,-7.198327]]); - linear_extrude(height=h) - polygon([[-29.347773,4.303814],[-29.347773,8.532496],[-29.229435,8.625513],[-29.082158,8.829637],[-29.082158,4.375128],[-29.347773,4.303814]]); - */ - -outline_points = concat(outline, hole); -outline_paths = [[for(i=[0:len(outline)-1]) i], - [for(i=[len(hole)-1:-1:0]) i+len(outline)]]; - -key_blank(outline_points, warding, outline_paths=outline_paths, offset=-outline_points[len(outline)-1]); \ No newline at end of file +key_blank(outline_points, + warding_points, + outline_paths=outline_paths, + emboss_right_points=emboss_points, + emboss_right_paths=emboss_paths, + emboss_left_points=emboss_points, + emboss_left_paths=emboss_paths, + offset=-outline_points[187]); \ No newline at end of file diff --git a/sc4.svg b/sc4.svg index fb0554b..08252e8 100644 --- a/sc4.svg +++ b/sc4.svg @@ -50,8 +50,8 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="16" - inkscape:cx="371.70151" - inkscape:cy="761.25519" + inkscape:cx="363.99281" + inkscape:cy="776.68608" inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="false" @@ -59,7 +59,8 @@ inkscape:window-height="1767" inkscape:window-x="0" inkscape:window-y="33" - inkscape:window-maximized="0" /> + inkscape:window-maximized="0" + units="mm" /> @@ -68,7 +69,7 @@ image/svg+xml - + @@ -103,16 +104,17 @@ inkscape:connector-curvature="0" d="m 89.968469,102.00306 c 0.0066,0.39155 -0.05364,0.4569 -0.230379,0.60057 -0.176738,0.14367 -0.807458,0.15071 -1.12238,-0.0311 -0.314922,-0.18182 -3.6919,-2.06973 -4.08672,-2.29768 -0.39482,-0.22795 -0.619731,-0.44483 -0.619731,-0.760048 l 1.1e-5,-2.407344 c 0,-0.315218 0.224911,-0.532098 0.619731,-0.760048 0.39482,-0.22795 3.771798,-2.11586 4.08672,-2.29768 0.314922,-0.18182 0.945642,-0.17478 1.12238,-0.0311 0.176738,0.14367 0.23696,0.20902 0.230379,0.60057 z m 16.042091,2.07537 c 0,0.22836 -0.23197,0.43053 -0.48312,0.43053 -0.25115,0 -3.48023,0.0906 -3.65778,0.0906 -0.17756,0 -0.60247,0.17909 -0.61658,0.48729 -0.0191,0.41661 -0.45735,0.64868 -0.5995,0.64868 -0.14215,0 -1.570935,0.009 -2.328948,0.0116 -0.144144,4.3e-4 -0.356676,0.12247 -0.45657,0.24293 -0.664839,0.80167 -1.044022,0.79665 -1.394324,1.03565 -0.529527,0.36129 -0.253177,1.29326 -0.577021,1.73987 -0.323844,0.44662 -0.683968,0.11891 -0.998089,0.3859 -0.314121,0.267 -0.230223,1.52378 -1.192126,1.52378 h -2.363191 c -1.127636,0 -0.535148,-1.07281 -0.909262,-1.44692 -0.347955,-0.34796 -0.800885,-0.13146 -1.148839,-0.47942 -0.52905,-0.52905 0.07302,-1.28287 -0.456029,-1.81192 -0.370084,-0.37009 -0.673861,0.002 -1.043945,-0.36833 -0.49379,-0.49379 -0.02102,-1.51165 -0.350791,-1.99559 -0.246109,-0.36116 -0.845938,-0.30156 -1.060479,-0.44694 -0.10727,-0.0727 -3.687643,-2.13726 -4.236571,-2.46469 -0.548928,-0.32743 -0.586395,-0.67403 -0.586395,-1.0746 l 1.1e-5,-4.55148 c 0,-0.40057 0.03747,-0.74717 0.586395,-1.0746 0.548928,-0.32743 4.129301,-2.39199 4.236571,-2.46469 0.214541,-0.14538 0.81437,-0.0858 1.060479,-0.44694 0.329771,-0.48394 -0.142999,-1.5018 0.350791,-1.99559 0.370084,-0.37033 0.673861,0.002 1.043945,-0.36833 0.529049,-0.52905 -0.07302,-1.28287 0.456029,-1.81192 0.347954,-0.34796 0.800884,-0.13146 1.148839,-0.47942 0.374114,-0.37411 -0.218374,-1.44692 0.909262,-1.44692 h 2.363191 c 0.961903,0 0.878005,1.25678 1.192126,1.52378 0.314121,0.26699 0.674245,-0.0607 0.998089,0.3859 0.323844,0.44661 0.04749,1.37858 0.577021,1.73987 0.350302,0.239 0.729485,0.23398 1.394324,1.03565 0.09989,0.12046 0.312426,0.2425 0.45657,0.24293 0.758013,0.003 2.186797,0.0116 2.328947,0.0116 0.14215,0 0.5804,0.23207 0.5995,0.64868 0.0141,0.3082 0.43902,0.48729 0.61658,0.48729 0.17755,0 3.40663,0.0906 3.65778,0.0906 0.25115,0 0.48312,0.20217 0.48312,0.43053 0,0.22836 0.0212,1.28159 0.0212,1.28159 l 25.61911,-4e-4 5.08082,5.101302 v 1.297938 l -2.42259,2.31776 -28.29811,-0.004 z" style="opacity:0.46400003;fill:#f90000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - id="path4725-7" /> + id="outline" /> + id="warding" + inkscape:connector-curvature="0" /> + id="emboss" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.51999996;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#00ff00;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.05;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" + d="m 101.012,85.660311 v 25.443359 h 0.26563 V 85.660311 Z m -3.703732,-0.01793 v 25.443359 h 0.265625 V 85.642382 Z m -1.438672,0.01654 v 25.443349 h 0.265625 V 85.658919 Z m -1.438671,-0.04134 V 111.06094 H 94.69655 V 85.617577 Z m -4.191993,0.0083 V 111.0692 h 0.265625 V 85.625845 Z m -1.549479,0.01087 v 25.443359 h 0.265625 V 85.636719 Z" + inkscape:connector-curvature="0" /> diff --git a/sc4_polygons.scad b/sc4_polygons.scad new file mode 100644 index 0000000..1e488d7 --- /dev/null +++ b/sc4_polygons.scad @@ -0,0 +1,18 @@ +// Automatically generated using the Inkscape to OpenSCAD Converter +// Variable names are of the form _points and +// _paths. As a result, you can associate a polygon in this +// OpenSCAD program with the corresponding SVG element in the Inkscape document +// by looking for the XML element with the attribute id="inkscape-path-id". + +emboss_points = [[-17.046637, 12.700313], [-17.046637, -12.743046], [-16.781007, -12.743046], [-16.781007, 12.700313], [-20.750369, 12.718243], [-20.750369, -12.725116], [-20.484744, -12.725116], [-20.484744, 12.718242], [-22.189041, 12.701703], [-22.189041, -12.741646], [-21.923416, -12.741646], [-21.923416, 12.701705], [-23.627712, 12.743043], [-23.627712, -12.700316], [-23.362087, -12.700316], [-23.362087, 12.743047], [-27.819705, 12.734743], [-27.819705, -12.708576], [-27.554080, -12.708576], [-27.554080, 12.734779], [-29.369184, 12.723873], [-29.369184, -12.719486], [-29.103559, -12.719486], [-29.103559, 12.723905]]; +emboss_paths = [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]; + +outline_points = [[-12.048077, -5.717807], [-12.088247, -5.881416], [-12.195456, -6.018707], [-12.349756, -6.113179], [-12.531197, -6.148337], [-16.188977, -6.238937], [-16.358242, -6.271735], [-16.558561, -6.367007], [-16.728233, -6.520066], [-16.782295, -6.616801], [-16.805557, -6.726227], [-16.830970, -6.873712], [-16.887297, -7.003340], [-17.059163, -7.206795], [-17.254102, -7.332136], [-17.405057, -7.374907], [-19.734005, -7.386507], [-19.852107, -7.407706], [-19.978884, -7.462960], [-20.190575, -7.629437], [-20.639656, -8.095852], [-21.005688, -8.358263], [-21.312745, -8.515173], [-21.584899, -8.665087], [-21.748442, -8.825258], [-21.852912, -9.026556], [-21.914786, -9.256604], [-21.950541, -9.503027], [-22.009604, -9.995495], [-22.065866, -10.216789], [-22.161920, -10.404957], [-22.284901, -10.538816], [-22.410320, -10.616126], [-22.664611, -10.665268], [-22.917078, -10.680729], [-23.040218, -10.715938], [-23.160009, -10.790857], [-23.261967, -10.932990], [-23.343531, -11.141588], [-23.513154, -11.652872], [-23.635053, -11.902903], [-23.804236, -12.114093], [-24.037623, -12.260113], [-24.183681, -12.300457], [-24.352135, -12.314637], [-26.715326, -12.314637], [-27.062806, -12.267809], [-27.185912, -12.213166], [-27.280510, -12.141164], [-27.399922, -11.955464], [-27.452528, -11.731468], [-27.483260, -11.251625], [-27.524357, -11.037299], [-27.624588, -10.867717], [-27.759582, -10.761486], [-27.901957, -10.694943], [-28.199008, -10.628007], [-28.496059, -10.561070], [-28.638434, -10.494527], [-28.773427, -10.388297], [-28.923218, -10.180245], [-28.993477, -9.956389], [-29.010715, -9.721997], [-29.001442, -9.482337], [-28.992169, -9.242676], [-29.009407, -9.008284], [-29.079665, -8.784428], [-29.229456, -8.576377], [-29.365388, -8.469483], [-29.496659, -8.414771], [-29.751429, -8.392302], [-30.006199, -8.369787], [-30.137469, -8.315019], [-30.273401, -8.208047], [-30.416720, -8.000376], [-30.490156, -7.755972], [-30.514440, -7.487233], [-30.510304, -7.206558], [-30.499698, -6.658990], [-30.534692, -6.416895], [-30.624192, -6.212457], [-30.731620, -6.095522], [-30.863549, -6.010702], [-31.166270, -5.908069], [-31.463071, -5.845895], [-31.684671, -5.765517], [-35.921242, -3.300826], [-36.244445, -3.051116], [-36.420288, -2.790954], [-36.493206, -2.517078], [-36.507637, -2.226226], [-36.507626, 2.325254], [-36.493194, 2.616105], [-36.420275, 2.889981], [-36.244433, 3.150143], [-35.921231, 3.399854], [-31.684660, 5.864544], [-31.463060, 5.944925], [-31.166259, 6.007104], [-30.863538, 6.109737], [-30.731609, 6.194555], [-30.624181, 6.311484], [-30.534681, 6.515922], [-30.499687, 6.758017], [-30.510293, 7.305585], [-30.514429, 7.586260], [-30.490145, 7.854999], [-30.416709, 8.099403], [-30.273390, 8.307074], [-30.137458, 8.414036], [-30.006188, 8.468780], [-29.751418, 8.491239], [-29.496648, 8.513697], [-29.365377, 8.568441], [-29.229445, 8.675404], [-29.079654, 8.883455], [-29.009395, 9.107311], [-28.992158, 9.341703], [-29.001431, 9.581364], [-29.010703, 9.821024], [-28.993466, 10.055416], [-28.923207, 10.279272], [-28.773416, 10.487324], [-28.638423, 10.593554], [-28.496048, 10.660097], [-28.198997, 10.727034], [-27.901946, 10.793970], [-27.759571, 10.860513], [-27.624577, 10.966744], [-27.524346, 11.136326], [-27.483249, 11.350652], [-27.452517, 11.830495], [-27.399911, 12.054491], [-27.280499, 12.240191], [-27.185901, 12.312193], [-27.062795, 12.366836], [-26.715315, 12.413664], [-24.352124, 12.413664], [-24.183670, 12.399484], [-24.037612, 12.359140], [-23.804225, 12.213120], [-23.635042, 12.001930], [-23.513143, 11.751899], [-23.343520, 11.240615], [-23.261956, 11.032017], [-23.159998, 10.889884], [-23.040207, 10.814964], [-22.917067, 10.779753], [-22.664600, 10.764287], [-22.410309, 10.715144], [-22.284890, 10.637837], [-22.161909, 10.503984], [-22.065856, 10.315816], [-22.009593, 10.094522], [-21.950531, 9.602053], [-21.914777, 9.355631], [-21.852903, 9.125583], [-21.748433, 8.924285], [-21.584888, 8.764114], [-21.312734, 8.614200], [-21.005677, 8.457290], [-20.639645, 8.194879], [-20.190564, 7.728464], [-19.978874, 7.561987], [-19.852097, 7.506733], [-19.733994, 7.485534], [-17.405047, 7.473934], [-17.254092, 7.431163], [-17.059153, 7.305822], [-16.887287, 7.102367], [-16.830960, 6.972739], [-16.805547, 6.825254], [-16.782288, 6.715828], [-16.728227, 6.619093], [-16.558555, 6.466033], [-16.358233, 6.370762], [-16.188967, 6.337964], [-12.531187, 6.247364], [-12.349746, 6.212206], [-12.195446, 6.117733], [-12.088237, 5.980443], [-12.048067, 5.816834], [-12.026867, 4.535244], [13.592243, 4.535644], [18.673063, -0.565658], [18.673063, -1.863596], [16.250473, -4.181356], [-12.047637, -4.177356], [-28.090168, -3.642437], [-28.098526, -3.881257], [-28.136605, -4.035677], [-28.209060, -4.143618], [-28.320547, -4.243007], [-28.526194, -4.324325], [-28.829918, -4.349512], [-29.159551, -4.313671], [-29.442927, -4.211907], [-33.529647, -1.914227], [-33.793045, -1.743630], [-33.987570, -1.566928], [-34.108067, -1.373863], [-34.149378, -1.154179], [-34.149367, 1.253165], [-34.108056, 1.472850], [-33.987559, 1.665915], [-33.793034, 1.842617], [-33.529636, 2.013213], [-29.442916, 4.310893], [-29.159540, 4.412663], [-28.829907, 4.448506], [-28.526183, 4.423318], [-28.320536, 4.341993], [-28.209053, 4.242605], [-28.136602, 4.134663], [-28.098524, 3.980244], [-28.090157, 3.741423]]; +outline_paths = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187], [215, 214, 213, 212, 211, 210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190, 189, 188]]; + +warding_points = [[34.597676, 4.535861], [34.597676, 0.151633], [35.469973, -0.230774], [35.667138, -0.352517], [35.751594, -0.489447], [35.732447, -0.660339], [35.618801, -0.883964], [34.597676, -2.570165], [34.597676, -4.181437], [36.507637, -4.181437], [36.507637, -3.346346], [35.935063, -3.064708], [35.779957, -2.936419], [35.700383, -2.775179], [35.691122, -2.602124], [35.746960, -2.438392], [36.507637, -1.124259], [36.507637, 0.619821], [35.618801, 0.897840], [35.618801, 4.535861]]; +warding_paths = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]]; + +//polygon(points=emboss_points, paths=emboss_paths); +//polygon(points=outline_points, paths=outline_paths); +//polygon(points=warding_points, paths=warding_paths);