Skip to content

Instantly share code, notes, and snippets.

@wware
Last active October 20, 2018 09:23
Show Gist options
  • Save wware/37d8919a094db8987792 to your computer and use it in GitHub Desktop.
Save wware/37d8919a094db8987792 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Usage:
CMD [-D | --debug] (-T | --test)
CMD [-D | --debug] [-m | --mm] [-n <NAME> | --name <NAME>] (-f | --file) <FILENAME>
This script is used to create OpenSCAD modules from the HPGL files generated by
the Matthias Wandel's very beautiful Gear Template Generator, at
https://woodgears.ca/gear_cutting/template.html
Having created a gear file (typically with a ".plt" extension) you can pipe the
standard output of this script to a ".scad" file and do something like this.
$ ./hpgl2openscad.py -f woodgears/gear.plt > foo.scad
$ cat > bar.scad
use <foo.scad>;
Gear();
translate([3,0,0]) {
Gear();
}
^D
$ /Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD bar.scad
"""
import docopt
import logging
import pprint
import sys
logger = logging.getLogger('hpgl2openscad')
class HpglInterpreter(object):
def __init__(self):
self.paths = []
self._use_inches = True
def read(self, inf, scale=1.0):
scale /= 40.0 # HPGL dots per millimeter
if self._use_inches:
scale /= 25.4
active = False
p = []
while True:
line = inf.readline()
# logger.debug(line.strip())
if not line:
break
line = line.strip()
if line == 'SP1;':
active = True
elif line.startswith('SP'):
active = False
elif active and line.startswith('PU '):
self.add_path(p)
p = []
fields = line.replace('a', 'A')[3:-1].split(',')
x, y = map(int, fields[:2])
mt = self.create_move_to(scale * x, scale * y)
p.append(mt)
elif active and line.startswith('PD '):
fields = line.replace('a', 'A')[3:-1].split(',')
x, y = map(int, fields[:2])
lt = self.create_line_to(scale * x, scale * y)
p.append(lt)
if len(p) > 0:
self.add_path(p)
def units(self):
return self._use_inches and 'inches' or 'mm'
def add_path(self, p):
logger.debug('add_path:' + repr(p))
self.paths.append(p)
def create_move_to(self, x, y):
# logger.debug('create_move_to:{0}:{1}'.format(x, y))
return ('moveTo', x, y)
def create_line_to(self, x, y):
# logger.debug('create_line_to:{0}:{1}'.format(x, y))
return ('lineTo', x, y)
class OpenSCADPolygon(HpglInterpreter):
def __init__(self, name='Gear'):
super(OpenSCADPolygon, self).__init__()
self.name = name
def add_path(self, p):
p = p[1:]
if p:
self.paths.append(p)
def create_move_to(self, x, y):
# OpenSCAD polygons use closed paths and do not repeat the beginning
# and ending point as HPGL does, so return a None which will later be
# removed.
return None
def create_line_to(self, x, y):
# logger.debug('create_line_to:{0}:{1}'.format(x, y))
return [x, y]
def __repr__(self):
all_points = reduce(lambda x, y: x + y, self.paths)
sum_of_points = reduce(lambda p1, p2: (p1[0] + p2[0], p1[1] + p2[1]), all_points)
center = (sum_of_points[0] / len(all_points), sum_of_points[1] / len(all_points))
all_points = map(
lambda p, center=center: [p[0] - center[0], p[1] - center[1]],
all_points
)
indices = []
n = 0
for path in self.paths:
N = n + len(path)
indices.append(range(n, N))
n = N
#a, b = pprint.pformat(all_points), pprint.pformat(indices)
a, b = repr(all_points), repr(indices)
return """module {0}() {1} linear_extrude(height=1, center=true)
polygon(points={2}, paths={3});{4}""".format(
self.name, "{", a, b, "}"
) + """\n{0}();\n""".format(self.name)
def main():
ch = logging.StreamHandler()
ch.setFormatter(
logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
)
logger.addHandler(ch)
args = docopt.docopt(__doc__.replace('CMD', sys.argv[0]))
if args['-D'] or args['--debug']:
logger.setLevel(logging.DEBUG)
ch.setLevel(logging.DEBUG)
logger.debug('hello')
if args['-T'] or args['--test']:
import doctest
# verbose=True can be useful
failure_count, test_count = \
doctest.testmod(optionflags=doctest.ELLIPSIS)
sys.exit(failure_count)
if args['-n'] or args['--name']:
name = args['<NAME>']
else:
name = 'Gear'
if args['-f'] or args['--file']:
inf = open(args['<FILENAME>'])
else:
inf = sys.stdin
interp = OpenSCADPolygon(name)
if args['-m'] or args['--mm']:
interp._use_inches = False
interp.read(inf)
print interp
if __name__ == '__main__':
main()

Fun hacks with OpenSCAD

Hack the First

Use OpenSCAD to take a slice from a 3D object and convert it to a 2D DXF file, suitable for laser cutting.

Given a file foo.scad like this:

module example002()
{
        intersection() {
                difference() {
                        union() {
                                cube([30, 30, 30], center = true);
                                translate([0, 0, -25])
                                        cube([15, 15, 50], center = true);
                        }
                        union() {
                                cube([50, 10, 10], center = true);
                                cube([10, 50, 10], center = true);
                                cube([10, 10, 50], center = true);
                        }
                }
                translate([0, 0, 5])
                        cylinder(h = 50, r1 = 20, r2 = 5, center = true);
        }
}

projection(cut = true) example002();

you want to use the command line like this:

/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD -o foo.dxf foo.scad

It might also be possible to use a DXF file as a layer for 3D printing, if I can display it on a DLP projector, maybe using this Python script.

Hack the Second

Use hpgl2openscad.py to take a gear generated by Matthias Wandel's Gear Template Generator and extrude it into an OpenSCAD part.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment