From 31e91f69a24257aa3993d3d6223838b0f64c6500 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 17 May 2022 22:36:30 -0700 Subject: [PATCH] Added img2scad.py script. --- scripts/img2scad.py | 79 +++++++++++++++++++++++++++++++++++++++++++++ shapes3d.scad | 4 +++ skin.scad | 6 ++++ 3 files changed, 89 insertions(+) create mode 100755 scripts/img2scad.py diff --git a/scripts/img2scad.py b/scripts/img2scad.py new file mode 100755 index 0000000..99cc777 --- /dev/null +++ b/scripts/img2scad.py @@ -0,0 +1,79 @@ +#!env python3 + +import re +import os +import sys +import os.path +import argparse + +from PIL import Image + + +def img2scad(filename, varname, resize, outf): + indent = " " * 4 + im = Image.open(filename).convert('L') + if resize: + print("Resizing to {}x{}".format(resize[0],resize[1])) + im = im.resize(resize) + pix = im.load() + width, height = im.size + print("// Image {} ({}x{})".format(filename, width, height), file=outf) + print("{} = [".format(varname), file=outf) + line = indent + for x in range(width): + line += "[ " + for y in range(height): + line += "{:d}, ".format(pix[x,y]) + if len(line) > 60: + print(line, file=outf) + line = indent * 2 + line += " ]," + if line != indent: + print(line, file=outf) + line = indent + print("];", file=outf) + print("", file=outf) + + +def main(): + parser = argparse.ArgumentParser(prog='img2scad') + parser.add_argument('-o', '--outfile', + help='Output .scad file.') + parser.add_argument('-v', '--varname', + help='Variable to use in .scad file.') + parser.add_argument('-r', '--resize', + help='Resample image to WIDTHxHEIGHT.') + parser.add_argument('infile', help='Input image file.') + opts = parser.parse_args() + + non_alnum = re.compile(r'[^a-zA-Z0-9_]') + if not opts.varname: + if opts.outfile: + opts.varname = os.path.splitext(os.path.basename(opts.outfile))[0] + opts.varname = non_alnum.sub("", opts.varname) + else: + opts.varname = "image_data" + size_pat = re.compile(r'^([0-9][0-9]*)x([0-9][0-9]*)$') + if opts.resize: + m = size_pat.match(opts.resize) + if not m: + print("Expected WIDTHxHEIGHT resize format.", file=sys.stderr) + sys.exit(-1) + opts.resize = (int(m.group(1)), int(m.group(2))) + + if not opts.varname or non_alnum.search(opts.varname): + print("Bad variable name: {}".format(opts.varname), file=sys.stderr) + sys.exit(-1) + + if opts.outfile: + with open(opts.outfile, "w") as outf: + img2scad(opts.infile, opts.varname, opts.resize, outf) + else: + img2scad(opts.infile, opts.varname, opts.resize, sys.stdout) + + sys.exit(0) + + +if __name__ == "__main__": + main() + diff --git a/shapes3d.scad b/shapes3d.scad index 58f3b0f..4661669 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -2775,6 +2775,8 @@ module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, anchor=CENTER, spin=0, // Description: // Given a regular rectangular 2D grid of scalar values, or a function literal, generates a 3D // surface where the height at any given point is the scalar value for that position. +// One script to convert a grayscale image to a heightfield array in a .scad file can be found at: +// https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py // Arguments: // data = This is either the 2D rectangular array of heights, or a function literal that takes X and Y arguments. // size = The [X,Y] size of the surface to create. If given as a scalar, use it for both X and Y sizes. Default: `[100,100]` @@ -2902,6 +2904,8 @@ function heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04 // Given a regular rectangular 2D grid of scalar values, or a function literal of signature (x,y), generates // a cylindrical 3D surface where the height at any given point above the radius `r=`, is the scalar value // for that position. +// One script to convert a grayscale image to a heightfield array in a .scad file can be found at: +// https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py // Arguments: // data = This is either the 2D rectangular array of heights, or a function literal of signature `(x, y)`. // l = The length of the cylinder to wrap around. diff --git a/skin.scad b/skin.scad index 8bd06db..7dea509 100644 --- a/skin.scad +++ b/skin.scad @@ -2125,6 +2125,8 @@ function _get_texture(tex,n,m) = // Topics: Sweep, Extrusion, Textures, Knurling // Description: // Given a single polygon path, creates a linear extrusion of that polygon vertically, with a given texture tiled evenly over the side surfaces. +// One script to convert a grayscale image to a texture heightfield array in a .scad file can be found at: +// https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py // Arguments: // path = The path to sweep/extrude. // texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0) that define the texture to apply to vertical surfaces. @@ -2320,6 +2322,8 @@ module textured_linear_sweep( // Description: // Given a single 2D path, fully in the X+ half-plane, revolves that path around the Z axis (after rotating its Y+ to Z+). // This creates a solid from that surface of revolution, capped top and bottom, with the sides covered in a given tiled texture. +// One script to convert a grayscale image to a texture heightfield array in a .scad file can be found at: +// https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py // Arguments: // path = The path to sweep/extrude. // texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0) that define the texture to apply to vertical surfaces. See {{textured_linear_sweep()}} for what textures are supported. @@ -2454,6 +2458,8 @@ module textured_revolution( // Topics: Sweep, Extrusion, Textures, Knurling // Description: // Creates a cylinder or cone with optional chamfers or roundings, covered in a textured surface. +// One script to convert a grayscale image to a texture heightfield array in a .scad file can be found at: +// https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py // Arguments: // h | l = The height of the cylinder. // r = The radius of the cylinder.