From 9fd45ce71e8ec6f7386d87bc1472e7e5efe265c9 Mon Sep 17 00:00:00 2001 From: Michael Gale Date: Wed, 15 Nov 2023 20:07:06 -0500 Subject: [PATCH] Added new console generation scripts to make baseplates and boxes from the command line --- CHANGELOG.md | 2 + README.md | 69 +++++++- cqgridfinity/__init__.py | 2 +- cqgridfinity/scripts/__init__.py | 0 cqgridfinity/scripts/gridfinitybase.py | 92 ++++++++++ cqgridfinity/scripts/gridfinitybox.py | 225 +++++++++++++++++++++++++ relint.sh | 1 + setup.py | 6 + 8 files changed, 389 insertions(+), 8 deletions(-) create mode 100644 cqgridfinity/scripts/__init__.py create mode 100644 cqgridfinity/scripts/gridfinitybase.py create mode 100644 cqgridfinity/scripts/gridfinitybox.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b46191b..01da710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,5 @@ - v.0.2.1 - Added new unsupported magnet hole types - v.0.2.2 - Added SVG export and integrated STL exporter - v.0.2.3 - Updated to python build tools to make distribution +- v.0.3.0 - Added console generator scripts: gridfinitybox and gridfinitybase +- \ No newline at end of file diff --git a/README.md b/README.md index 51e4c32..4f2f24b 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,58 @@ An example of the package can be seen below: # gf_box_3x2x5_holes_scoops_labels.stl ``` +# Console Generator Scripts + +This package can be used to make your own python scripts to generate Gridfinity objects. This gives the flexibility to customize the object and combine with other code to add custom cutouts, add text labels, etc. + +However, for simple generation of standard objects such as baseplates and boxes, console scripts can be used for quick command line usage. These console scripts are installed automatically into the path of your python environment and should be accessible from your terminal shell. + +## `gridfinitybox` + +Make a customized/parameterized Gridfinity compatible box with many optional features. + +``` +usage: gridfinitybox [-h] [-m] [-u] [-n] [-s] [-l] [-e] [-d] [-r RATIO] [-ld LENGTHDIV] [-wd WIDTHDIV] + [-f FORMAT] [-o OUTPUT] + length width height +``` +Type `gridfinitybox -h` or `gridfinitybox --help` for details on optional arguments and usage. +The section below describing the `GridfinityBox` class gives greater detail about the features that can be included with a generated box. + +Examples: + +```shell + # 2x3x5 box with magnet holes saved to STL file with default filename: + $ gridfinitybox 2 3 5 -m -f stl + + # 1x3x4 box with scoops, label strip, 3 internal partitions and specified name: + $ gridfinitybox 1 3 4 -s -l -ld 3 -o MyBox.step + + # Solid 3x3x3 box with 50% fill, unsupported magnet holes and no top lip: + $ gridfinitybox 3 3 3 -d -r 0.5 -u -n + + # Lite style box 3x2x3 with label strip, partitions, output to default SVG file: + $ gridfinitybox.py 3 2 3 -e -l -ld 2 -f svg +``` + +## `gridfinitybase` + +Make a customized/parameterized Gridfinity compatible simple baseplate. + +``` +usage: gridfinitybase [-h] [-f FORMAT] [-o OUTPUT] length width +``` +Type `gridfinitybase -h` or `gridfinitybase --help` for details on optional arguments and usage. + +Examples: + +```shell + # 6 x 3 baseplate to default STL file: + $ gridfinitybase 6 3 -f stl +``` + +# Classes + ## `GridfinityBaseplate` Gridfinity baseplates can be made with the `GridfinityBaseplate` class. The baseplate style is the basic style initially proposed by Zach Freedman. Therefore, it does not have magnet or mounting holes. An example usage is as follows: @@ -253,14 +305,16 @@ The `GridfinityObject` is the base class for `GridfinityBox`, `GridfinityBasepla # MyBox_3x2x5_holes box.filename(path="./outputfiles") # ./outputfiles/gf_box_3x2x5_holes - box = GridfinityBox(4, 3, 3, holes=True, length_div=2, width_div=1) + box2 = GridfinityBox(4, 3, 3, holes=True, length_div=2, width_div=1) + box2.filename() # gf_box_4x3x3_holes_div2x1 ``` ```python - # Export object to STEP or STL file - obj.save_step_file(self, filename=None, path=None, prefix=None) - obj.save_stl_file(self, filename=None, path=None, prefix=None) + # Export object to STEP, STL, or SVG file + obj.save_step_file(filename=None, path=None, prefix=None) + obj.save_stl_file(filename=None, path=None, prefix=None) + obj.save_svg_file(filename=None, path=None, prefix=None) ``` ### Useful properties @@ -271,13 +325,13 @@ The `GridfinityObject` is the base class for `GridfinityBox`, `GridfinityBasepla ```obj.height``` returns height in mm ```obj.top_ref_height``` returns the height of the top surface of a solid box or the floor height of an empty box. This can be useful for making custom boxes with cutouts since the reference height can be used to orient the cutting solid to the correct height. -## To-do +# To-do - add example scripts - add more baseplate variants, e.g. with holes, alignment features, etc. - add parameterized "rugged" toolbox -## Releases +# Releases - v.0.1.0 - First release on PyPI - v.0.1.1 - Fixed release @@ -285,8 +339,9 @@ The `GridfinityObject` is the base class for `GridfinityBox`, `GridfinityBasepla - v.0.2.1 - Added new unsupported magnet hole types - v.0.2.2 - Added SVG export and integrated STL exporter - v.0.2.3 - Updated to python build tools to make distribution +- v.0.3.0 - Added console generator scripts: gridfinitybox and gridfinitybase -## References +# References - [Zach Freedman's YouTube Channel](https://www.youtube.com/c/ZackFreedman) - [The video that started it all!](https://youtu.be/ra_9zU-mnl8?si=EOT1LFV65VZfiepi) diff --git a/cqgridfinity/__init__.py b/cqgridfinity/__init__.py index 911428d..9843285 100644 --- a/cqgridfinity/__init__.py +++ b/cqgridfinity/__init__.py @@ -4,7 +4,7 @@ # fmt: off __project__ = 'cqgridfinity' -__version__ = '0.2.3' +__version__ = '0.3.0' # fmt: on VERSION = __project__ + "-" + __version__ diff --git a/cqgridfinity/scripts/__init__.py b/cqgridfinity/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cqgridfinity/scripts/gridfinitybase.py b/cqgridfinity/scripts/gridfinitybase.py new file mode 100644 index 0000000..75321e4 --- /dev/null +++ b/cqgridfinity/scripts/gridfinitybase.py @@ -0,0 +1,92 @@ +#! /usr/bin/env python3 +""" +command line script to make a Gridfinity baseplate +""" +import argparse + +from cqgridfinity import * + +title = """ + _____ _ _ __ _ _ _ ____ + / ____| (_) | |/ _(_) (_) | | _ \\ +| | __ _ __ _ __| | |_ _ _ __ _| |_ _ _ | |_) | __ _ ___ ___ +| | |_ | '__| |/ _` | _| | '_ \\| | __| | | | | _ < / _` / __|/ _ \\ +| |__| | | | | (_| | | | | | | | | |_| |_| | | |_) | (_| \\__ \\ __/ + \\_____|_| |_|\\__,_|_| |_|_| |_|_|\\__|\\__, | |____/ \\__,_|___/\\___| + __/ | + |___/ +""" + +DESC = """ +Make a customized/parameterized Gridfinity compatible simple baseplate. +""" + +EPILOG = """ +example usage: + + 6 x 3 baseplate to default STL file: + $ gridfinitybase 6 3 -f stl +""" + + +def main(): + parser = argparse.ArgumentParser( + description=DESC, + epilog=EPILOG, + prefix_chars="-+", + formatter_class=argparse.RawTextHelpFormatter, + ) + + parser.add_argument( + "length", metavar="length", type=str, help="Box length in U (1U = 42 mm)" + ) + parser.add_argument( + "width", metavar="width", type=str, help="Box width in U (1U = 42 mm)" + ) + parser.add_argument( + "-f", + "--format", + default="step", + help="Output file format (STEP, STL, SVG) default=STEP", + ) + parser.add_argument( + "-o", + "--output", + default=None, + help="Output filename (inferred output file format with extension)", + ) + args = parser.parse_args() + argsd = vars(args) + base = GridfinityBaseplate( + length_u=int(argsd["length"]), width_u=int(argsd["width"]), + ) + print(title) + print( + "Gridfinity baseplate: %dU x %dU (%.1f mm x %.1f mm)" + % (base.length_u, base.width_u, base.length, base.width,) + ) + if argsd["output"] is not None: + fn = argsd["output"] + else: + fn = base.filename() + s = ["\nBaseplate generated and saved as"] + if argsd["format"].lower() == "stl" or fn.lower().endswith(".stl"): + if not fn.endswith(".stl"): + fn = fn + ".stl" + base.save_stl_file(filename=argsd["output"]) + s.append("%s in STL format" % (fn)) + elif argsd["format"].lower() == "svg" or fn.lower().endswith(".svg"): + if not fn.endswith(".svg"): + fn = fn + ".svg" + base.save_svg_file(filename=argsd["output"]) + s.append("%s in SVG format" % (fn)) + else: + if not fn.endswith(".step"): + fn = fn + ".step" + base.save_step_file(filename=argsd["output"]) + s.append("%s in STEP format" % (fn)) + print(" ".join(s)) + + +if __name__ == "__main__": + main() diff --git a/cqgridfinity/scripts/gridfinitybox.py b/cqgridfinity/scripts/gridfinitybox.py new file mode 100644 index 0000000..175c200 --- /dev/null +++ b/cqgridfinity/scripts/gridfinitybox.py @@ -0,0 +1,225 @@ +#! /usr/bin/env python3 +""" +command line script to make a Gridfinity box +""" +import argparse + +from cqgridfinity import * + +title = """ + _____ _ _ __ _ _ _ ____ + / ____| (_) | |/ _(_) (_) | | _ \\ +| | __ _ __ _ __| | |_ _ _ __ _| |_ _ _ | |_) | _____ __ +| | |_ | '__| |/ _` | _| | '_ \\| | __| | | | | _ < / _ \\ \\/ / +| |__| | | | | (_| | | | | | | | | |_| |_| | | |_) | (_) > < + \\_____|_| |_|\\__,_|_| |_|_| |_|_|\\__|\\__, | |____/ \\___/_/\\_\\ + __/ | + |___/ +""" + +DESC = """ +Make a customized/parameterized Gridfinity compatible box with many optional features. +""" + +EPILOG = """ +example usages: + + 2x3x5 box with magnet holes saved to STL file with default filename: + $ gridfinitybox 2 3 5 -m -f stl + + 1x3x4 box with scoops, label strip, 3 internal partitions and specified name: + $ gridfinitybox 1 3 4 -s -l -ld 3 -o MyBox.step + + Solid 3x3x3 box with 50% fill, unsupported magnet holes and no top lip: + $ gridfinitybox 3 3 3 -d -r 0.5 -u -n + + Lite style box 3x2x3 with label strip, partitions, output to default SVG file: + $ gridfinitybox.py 3 2 3 -e -l -ld 2 -f svg +""" + + +def main(): + parser = argparse.ArgumentParser( + description=DESC, + epilog=EPILOG, + prefix_chars="-+", + formatter_class=argparse.RawTextHelpFormatter, + ) + + parser.add_argument( + "length", metavar="length", type=str, help="Box length in U (1U = 42 mm)" + ) + parser.add_argument( + "width", metavar="width", type=str, help="Box width in U (1U = 42 mm)" + ) + parser.add_argument( + "height", metavar="height", type=str, help="Box height in U (1U = 7 mm)" + ) + parser.add_argument( + "-m", + "--magnetholes", + action="store_true", + default=False, + help="Add bottom magnet/mounting holes", + ) + parser.add_argument( + "-u", + "--unsupported", + action="store_true", + default=False, + help="Add bottom magnet holes with 3D printer friendly strips without support", + ) + parser.add_argument( + "-n", + "--nolip", + action="store_true", + default=False, + help="Do not add mating lip to the top perimeter", + ) + parser.add_argument( + "-s", + "--scoops", + action="store_true", + default=False, + help="Add finger scoops against each length-wise back wall", + ) + parser.add_argument( + "-l", + "--labels", + action="store_true", + default=False, + help="Add label strips against each length-wise front wall", + ) + parser.add_argument( + "-e", + "--ecolite", + action="store_true", + default=False, + help="Make economy / lite style box with no elevated floor", + ) + parser.add_argument( + "-d", + "--solid", + action="store_true", + default=False, + help="Make solid (filled) box for customized storage", + ) + parser.add_argument( + "-r", + "--ratio", + action="store", + default=1.0, + help="Solid box fill ratio 0.0 = minimum, 1.0 = full height", + ) + parser.add_argument( + "-ld", + "--lengthdiv", + action="store", + default=0, + help="Split box length-wise with specified number of divider walls", + ) + parser.add_argument( + "-wd", + "--widthdiv", + action="store", + default=0, + help="Split box width-wise with specified number of divider walls", + ) + parser.add_argument( + "-f", + "--format", + default="step", + help="Output file format (STEP, STL, SVG) default=STEP", + ) + parser.add_argument( + "-o", + "--output", + default=None, + help="Output filename (inferred output file format with extension)", + ) + args = parser.parse_args() + argsd = vars(args) + solid_ratio = float(argsd["ratio"]) + length_div = int(argsd["lengthdiv"]) + width_div = int(argsd["widthdiv"]) + box = GridfinityBox( + length_u=int(argsd["length"]), + width_u=int(argsd["width"]), + height_u=int(argsd["height"]), + holes=argsd["magnetholes"] or argsd["unsupported"], + unsupported_holes=argsd["unsupported"], + no_lip=argsd["nolip"], + scoops=argsd["scoops"], + labels=argsd["labels"], + lite_style=argsd["ecolite"], + solid=argsd["solid"], + solid_ratio=solid_ratio, + length_div=length_div, + width_div=width_div, + ) + if argsd["ecolite"]: + bs = "lite " + elif argsd["solid"]: + bs = "solid " + else: + bs = "" + print(title) + print( + "Gridfinity %sbox: %dU x %dU x %dU (%.1f mm x %.1f mm x %.1f mm)" + % ( + bs, + box.length_u, + box.width_u, + box.height_u, + box.length, + box.width, + box.height, + ) + ) + if argsd["solid"]: + print( + " solid height ratio: %.2f top height: %.2f mm / %.2f mm" + % (solid_ratio, box.top_ref_height, box.max_height + GR_BOT_H) + ) + s = [] + if argsd["unsupported"]: + s.append("holes with no support") + elif argsd["magnetholes"]: + s.append("holes") + if argsd["nolip"]: + s.append("no lip") + if argsd["scoops"]: + s.append("scoops") + if argsd["labels"]: + s.append("label strips") + if length_div: + s.append("%d length-wise walls" % (length_div)) + if width_div: + s.append("%d width-wise walls" % (width_div)) + if len(s): + print(" with options: %s" % (", ".join(s))) + if argsd["output"] is not None: + fn = argsd["output"] + else: + fn = box.filename() + s = ["\nBox generated and saved as"] + if argsd["format"].lower() == "stl" or fn.lower().endswith(".stl"): + if not fn.endswith(".stl"): + fn = fn + ".stl" + box.save_stl_file(filename=argsd["output"]) + s.append("%s in STL format" % (fn)) + elif argsd["format"].lower() == "svg" or fn.lower().endswith(".svg"): + if not fn.endswith(".svg"): + fn = fn + ".svg" + box.save_svg_file(filename=argsd["output"]) + s.append("%s in SVG format" % (fn)) + else: + if not fn.endswith(".step"): + fn = fn + ".step" + box.save_step_file(filename=argsd["output"]) + s.append("%s in STEP format" % (fn)) + print(" ".join(s)) + + +if __name__ == "__main__": + main() diff --git a/relint.sh b/relint.sh index ff8d8c0..e19303e 100755 --- a/relint.sh +++ b/relint.sh @@ -1,3 +1,4 @@ #!/usr/bin/env bash black cqgridfinity/*.py +black cqgridfinity/scripts/*.py black tests/*.py diff --git a/setup.py b/setup.py index 46abc06..f5fd952 100644 --- a/setup.py +++ b/setup.py @@ -49,4 +49,10 @@ def read_package_variable(key, filename="__init__.py"): ], install_requires=required, dependency_links=dependency_links, + entry_points={ + "console_scripts": [ + "gridfinitybox=cqgridfinity.scripts.gridfinitybox:main", + "gridfinitybase=cqgridfinity.scripts.gridfinitybase:main", + ], + }, )