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