From a5ba57218ddfe0c0fb09ca388d76da38b3df9e26 Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 16 Mar 2022 14:26:00 -0400 Subject: initial public repository --- .gitmodules | 3 + BOSL2 | 1 + Makefile | 23 ++++ README.md | 313 ++++++++++++++++++++++++++++++++++++++++++++++++ cubbies.scad | 209 ++++++++++++++++++++++++++++++++ cubbies.sh | 65 ++++++++++ description | 1 + shopenscad.sh | 238 ++++++++++++++++++++++++++++++++++++ svasilogianis.github.io | 1 + test_fits.sh | 28 +++++ 10 files changed, 882 insertions(+) create mode 100644 .gitmodules create mode 160000 BOSL2 create mode 100644 Makefile create mode 100644 README.md create mode 100644 cubbies.scad create mode 100755 cubbies.sh create mode 100644 description create mode 100755 shopenscad.sh create mode 160000 svasilogianis.github.io create mode 100755 test_fits.sh diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..faf109d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "BOSL2"] + path = BOSL2 + url = https://github.com/revarbat/BOSL2 diff --git a/BOSL2 b/BOSL2 new file mode 160000 index 0000000..9f5214a --- /dev/null +++ b/BOSL2 @@ -0,0 +1 @@ +Subproject commit 9f5214adc0476f29aa9437dc401c01e8f1cd5da1 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4275aa8 --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +README.html: pandoc.css README.md + pandoc --standalone --css=$^ -o $@ + cp README.html svasilogianis.github.io/index.html + +stls: reference-stls test-fit-stls + +reference-stls: bottom.stl side.stl + +bottom.stl: shopenscad.sh cubbies.scad + part='"bottom"' ./shopenscad.sh cubbies.scad -o $@ + +side.stl: shopenscad.sh cubbies.scad + part='"side"' ./shopenscad.sh cubbies.scad -o $@ + +test_fit_stls = male_tabs-40-120-90-5-5-0-10-5-0.5-true.stl \ + female_tabs-40-120-90-5-5-0-10-5-0.1-true.stl \ + female_tabs-40-120-90-5-5-0-10-5-0.2-true.stl \ + female_tabs-40-120-90-5-5-0-10-5-0.3-true.stl \ + female_tabs-40-120-90-5-5-0-10-5-0.4-true.stl \ + female_tabs-40-120-90-5-5-0-10-5-0.5-true.stl + +test-fit-stls: $(wildcard $test_fit_stls) + ./test_fits.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..10a8611 --- /dev/null +++ b/README.md @@ -0,0 +1,313 @@ +# 3D Printable Parametric Cubbies # + +::: { .parts } + +Bottom Part: +: [![Bottom Part](pictures/bottom.png "Bottom part")](pictures/side.png) + +Side Part: +: [![Side](pictures/side.png "Side part"){#fig:side}](pictures/side.png) + +::: + +3D printable parametric cubbies to store your 3D things. There are two distinct +shapes — a `bottom` and a `side` — that are generated to the dimensions of your +things, 3D printed, then assembled into modular storage units. The parts are +optimized for printing by using [sparse +structures](https://github.com/revarbat/BOSL2/wiki/walls.scad#module-sparse_strut) +(to save material) that lay flat on the print bed (for ease of printing). + +## Pictures ## + +::: { .figs } + +![[Bottom part (printed)](pictures/bottom-printed-full.jpg "Bottom part, printed (full resolution)")](pictures/bottom-printed-thumbnail.jpg) + +![[Side part (printed)](pictures/side-printed-full.jpg "Side part, printed (full resolution)")](pictures/side-printed-thumbnail.jpg "Side part, printed") + +![[The Smurftruder with a fresh print](pictures/smurfstrusion-full.jpg "Smurfstrusion (full resolution)")](pictures/smurfstrusion-thumbnail.jpg) + +![[Mounted Cubbies (empty)](pictures/cubbies-mounted-empty-full.jpg "Mounted Cubbies (full resolution)")](pictures/cubbies-mounted-empty-thumbnail.jpg) + +![[Mounted Cubbies (empty, viewed from an angle)](pictures/cubbies-mounted-empty-angle-view-full.jpg "Empty cubbies, mounted (full resolution)")](pictures/cubbies-mounted-empty-angle-view-thumbnail.jpg) + +![[Mounted Cubbies (filled)](pictures/cubbies-full-full.jpg "Mounted cubbies, filled (full resolution)")](pictures/cubbies-full-thumbnail.jpg) + +![[Cubby Development](pictures/cubby-development-full.jpg "Cubby Development (full resolution)")](pictures/cubby-development-thumbnail.jpg) + +::: + +## Instructions ## + +### 🚫 Do NOT Scale in a Slicer 🚫 ### + +All models should be generated by specifying, at minimum, the parameters +`cubby_width`, `cubby_depth`, and `cubby_height` in `cubbies.scad` (ways to do +this are described below.) The dovetail joints are generated to a constant size +(controlled through the parameters `tab_width`, `tab_padding`, and +`tab_tolerance`) and should not be expected to fit after being scaled. Cubbies +can be generated in in one of the following ways: + + * Using the Thingiverse Customizer + + * Using [OpenScad](https://openscad.org "Homepage for OpenSCAD \"The + Programmers Solid 3D Cad Modeller\"") -- either by using its builtin + customizer (choose `Window -> Customizer` from the menu bar if it is not + visible), or by modifying the variables at the top of the `cubbies.scad` + file. As mentioned above, the minimum paramaters to generate a custom sized + cubby are `cubby_width`, `cubby_depth`, and `cubby_height`. (Even those + arguments can be omitted, and your cubby will be whatever abritrary size I + decided as defaults (`40`,`120`,`90`).) + + * There are two scripts for generating cubbies from the commandline: + `cubbies.sh` (run [`./cubbies.sh -h` for details](#cubbies.sh) can generate + entire sets of cubbies, and `shopenscad.sh` (run `./shopenscad.sh -h` for + details[^1]) generates individual parts. + +For a single cubby, you will need to print the side piece twice, and the bottom +piece once. To add additional cubbies, print one bottom and one side. The bottom +and side pieces can be flipped upside down so that the bottom piece's tabs fit +into the extra unused holes of the side piece. You can also use this to control +whether the screw tabs end up on the inside or outside of the cubby. + +You can print tolerance test fit pieces (which are the minimum material +necessary to confirm that the pieces will fit with your printer and slicer +settings). These test pieces were generated using `dovetail_test_fits.sh`, which +you can use to generate your own at different tolerances (run +`dovetail_test_fits.sh -h` for details). You can also generate them in `OpenSCAD` +by specifying either `male_tabs` or `female_tabs` for the `part` paramaters (the +`cubby_depth` paramater is respected as the length of those dovetail test +pieces). + +## Parameters ## + +`part` Controls which part will be generated, and can be any of: + +: `bottom` — a bottom part whose dimensions will be `cubby_width` x `cubby_depth`. + +: `side` — a side part whose dimensions will be `cubby_depth` x `cubby_height`. + +: `female_tabs` — generates a shape consisting of the minimum amount of material + needed to test the dovetail joint fit. `cubby_depth` is respected as the + length that the tab holes (mortises) should run. (I have found that although + my printer could print a single tab accurately, spanning the entire length of + `cubby_depth` gave me the most accurate test. + +: `male_tabs` — as `female_tabs` above, except with male tabs (tenons) + +: `view_fit` — used as a debugging aid during development -- displays two sets + of tops and bottoms posiitioned to inspect tab fit. + +: `both` — a `bottom` and a `side` laid next each other. (These may look like + two seperate models, but is only one model, and obnixious to work with in a + slicer. Generating them seperately will allow you to manipulate them + individually). + +### Cubby Dimensions ### + +`cubby_width` +: How wide the cubby should be, used as one dimension of the `bottom` and `male_tabs` `part`. + +`cubby_depth` +: How deep the cubby should be, used as one dimension of the `side` and `female_tabs` `part`. + +`cubby_height` +: How deep the cubby should be, used as one dimension of the `side`. + +`thickness` +: How thick the `bottom`, `side`, or test fit pieces (`male_tabs`, + `female_tabs`) should be. When either part is eventually laid flat on the + print bed, this effectively becomes the `z` axis height. (The `tab_depth` is + dictated by this `thickness` dimension.) + +`strut_thickness` +: The thickness of the strut members in the sparse structures. Applies to both + `bottom` and `side`. + +`max_bridge` +: Maximum bridging distance between cross-braces of sparse struts. Applies to + `bottom`, and `side`. + +### Tab/Dovetail Dimensions ### + +`tab_width` +: The width of each tab. + +`tab_padding` +: The distance of the first tab from the edge, as well as between each tab. + +`tab_tolerance` +: How much bigger the hole (or mortise) should be than the tab (tenon) + +![[Dovetail Test Fit](pictures/dovetail_test_fit.jpg-full "Dovetail Test Fit (full resolution)")](pictures/dovetail_test_fit-thumbnail.jpg) + +## Source Repository ## + + The source repository is a Git repository that is hosted both on a read-only + HTTPS URL and on a read-write SSH url. + + You can push to the read-write URL simply without creating any user account. + You only need to generate a SSH public key on your own system. + +### Read-Only Clone ### + + The read-only clone command is: + + git clone --recurse-submodules https://git.cryptonomic.net/cubbies.git + +### Read-Write Clone ### + + To clone from the read-write URL, you first need to have your own ssh identity. + Then you can run `git clone`. + + # Generate an SSH key if needed + command -v ssh-keygen || { echo Please Install OpenSSH; exit; } + [ -d ~/.ssh ] || mkdir ~/.ssh + [ -e ~/.ssh/id_ed25519.pub ] || ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N + + # Add the public key for cryptonomic.net + echo cryptonomic.net ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPK7K8KsygvXtjw0yH4h43bwBGKq9oWBdCa1eY5rzT5D >> ~/.ssh/known_hosts + + # Perform the clone + git clone --recurse-submodules ssh://d@cryptonomic.net:public_git/cubbies.git + + You can push your changes back simply: + + git add . + git commit -a -m 'commit message' + git push + +## Cubbies on the Web ## + + * thingiverse + * thangs + * cults3d + +## Dependencies/Credits ## + + * [OpenSCAD](https://openscad.org "OpenSCAD") + * [Belfry OpenSCAD Library](https://github.com/revarbat/BOSL "Belfry OpenSCAD Library") + +## Contributions ## + +![Megachurch Pastor](pictures/kenneth-copeland.jpg) ![Donation Basket](pictures/basket.jpg) + + +- PayPal: +- Venmo: +- CashApp: + +## Shell Scripts ## + +### cubbies.sh ### + +``` +$ ./cubbies.sh -h +./cubbies.sh: generate sets of cubbies. You will need print the side piece twice for your +first bottom piece, then a single side piece for each additional bottom piece. + +Usage: +./cubbies.sh cubby-depth cubby-height cubby-width1 [cubby-width2 ... cubby-widthN] + +Examples: + + ./cubbies.sh 150 90 20 + + Generates two total pieces: a side piece (150mm depth, 90mm height) and a + bottom piece (150mm depth and 20mm width). You will need to print the side + piece twice and bottom piece once. + + ./cubbies.sh 180 120 20 30 35 + + Generates four total pieces, which can be assembled to form a row of + cubbies: one side piece (180mm depth, 120mm height) and three bottom pieces, + with widths of 20mm, 30mm, and 35mm (and each sharing the same depth of + 180mm). The side piece will need to be printed 4 times, and each bottom + piece printed once. + + thickness=3 strut_thickness=4 ./cubbies.sh 200 140 20 + + You can override any of the other variables from your scad file by passing + them into ./cubbies.sh in this manner. +``` + +### shopenscad.sh ### + +``` +$ ./shopenscad.sh -h +./shopenscad.sh: parse and execute OpenSCAD files, allowing default variables from input scad + to be overridden with values supplied from the shell. (The parsing of scad + files is done from the shell mostly with sed, and is very primitive) + +Usage: ./shopenscad.sh [-h] [-n] [-p] [-i] [-o OUTPUT-FILENAME] INPUT.scad + + -h, --help display this help + -p, --print-vars print variables parsed from INPUT.scad + -i, --interactive show output command and query for execution + -n, --no-act show output command, do not execute + -s, --shell-skel output a skeleton shell script for generating models + -o, --output-filename Specify an output filename. The following variables are + available to you: + + $VALUES - all values from INPUT.scad. This is the + default (-o '${VALUES}.stl'): + cube-15-10-5.stl + + $ALL - all variable names and values from INPUT.scad + (-o '${ALL}.stl'): + shape=cube,width=15,height=10,depth=5.stl + + Additionally, all variables parsed from INPUT.scad + are available in the associatve array , e.g.: + -o '${SCAD[part]}.stl' + +Usage examples: + + 1) Run openscad with default values from input scad file: + + $ ./shopenscad.sh input.scad + + 2) You can override variables in the input file with variables passed in + through the shell; this will override "part" and "cubby_width" + paramaters from input scad file. (Note that if the part is a string, + you need to include quotes.): + + $ cubby_width=200 part='"bottom"' ./shopenscad.sh input.scad + + + 3) Generate multiple models: + + $ for w in 250 300 350 400; do + cubby_width="" part="bottom" ./shopenscad.sh cubbies.scad + done +``` + +### test_fits.sh ### + +``` +$ ./test_fits.sh -h +Usage: +./test_fits.sh +./test_fits.sh [depth] [tolerance1 tolerance2 .. toleranceN] + +By default five test fit pieces are generated with a length of 120mm at the +tolerances 0.1mm, 0.2mm, 0.3mm, 0.4mm, and 0.5mm. + +Over ride the defaults by supplying paramaters; the first parameter the depth and the rest are +taken as tolerances. +``` + +## GPLv3 License ## + +Copyright (C) Steven Vasilogianis + +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 3 of the License, or (at your option) any later +version. + +[^1]: OpenSCAD already allows you to generate models with varying paramaters +through, e.g., `OpenSCAD -D "param1=100" -D "param2=10" scadfile.scad -o +output.stl`. Now you're going to want to change the output.stl filename to avoid +overwriting a previously generated model; generally, the information going into +your variables is useful to have in the output filename. diff --git a/cubbies.scad b/cubbies.scad new file mode 100644 index 0000000..9497c5e --- /dev/null +++ b/cubbies.scad @@ -0,0 +1,209 @@ +include ; // https://github.com/revarbat/BOSL2 +include ; +include ; +include ; + +$fn=20; + +part="both"; // [bottom::Bottom Only,side:Side Only,female_tabs:Holes only from side,male_tabs:Tabs only from bottom,view_fit:Debugging view to confirm tab fit,both:Bottom + Side] + +/* [Cubby dimensions] */ +cubby_width=40; +cubby_depth=120; +cubby_height=90; + +// thickness of bottom and side +thickness=5; + +// thickness of struts (used in both bottom and side) +strut_thickness=5; + +// If set (to non zero), this is used in the max_bridge paramater for the +// sparse_struct BOSL 2 function (which generates the wireframe rectangles). By +// default, it is calculated as max_bridge=(cubby_height + cubby_width)/5. +max_bridge=0; + +/* [Tab Dimensions] */ +// width of each tab +tab_width=10; +// material between tabs +tab_padding=5; +// this value will be added to the mortises for additional tolerance +tab_tolerance=0.5; + +/* [Screw Tabs] */ +screw_tabs=true; + +/* [Hidden] */ +tab_depth=thickness; +tab_height=thickness; +default_max_bridge=(cubby_height + cubby_width) / 5; + +if ( part == "both" ) { + both(); +} else if ( part =="bottom") { + cubby_bottom(cubby_width, cubby_depth); +} else if ( part == "side" ) { + cubby_side(cubby_depth, cubby_height, screw_tabs); +} else if ( part == "view_fit") { + view_tab_fit(cubby_width, cubby_depth, cubby_height); +} else if ( part == "male_tabs" ) { + male_tabs(cubby_width, cubby_depth); +} else if ( part == "female_tabs" ) { + female_tabs(cubby_depth, cubby_height); +} + +function assert_cubby_width() = + let (min_width = strut_thickness + 1) // I'm not sure why I need the +1 + assert(cubby_width >= min_width, + str("Cubby width must be atleast ", min_width, "mm")); + +function assert_cubby_depth() = + let (min_depth = tab_width * 2) + assert(cubby_depth >= min_depth, + str("Cubby width must be atleast ", min_depth, "mm")); + +function assert_cubby_height() = + let (min_height = cubby_side_bracket_height()) + assert(cubby_height >= min_height, + str("Cubby width must be atleast ", min_height, "mm")); + +function double (x) = x * 2; +function half (x) = x / 2; + +module both () { + gap=5; + cubby_bottom(cubby_width, cubby_depth); + left(((cubby_width + cubby_height) / 2) + (tab_height * 2) + gap) + cubby_side(cubby_depth, cubby_height, screw_tabs); +} + +module cubby_bottom (width, depth) { + let (_=assert_cubby_width()); + let (_=assert_cubby_depth()); + + rot(90) yrot(90) color("blue") bottom_wall() yrot(90) down(tab_height / 2) { + move_dist=(width / 2) + (tab_depth / 2); + fwd(move_dist) + alternating_tabs(false, "male", depth, tab_width, + tab_depth, tab_height, tab_padding, tab_tolerance); + back(move_dist) + alternating_tabs(true, "male", depth, tab_width, + tab_depth, tab_height, tab_padding, tab_tolerance); + } + children(); + module bottom_wall () { + strut_wall(width, depth) children(); + } +} + +// this calculation needs to be referenced by the view_tab_fit function +function cubby_side_bracket_height() = double(tab_height) + tab_tolerance; +module cubby_side (depth, height, screw_tabs=true) { + let (_=assert_cubby_depth()); + let (_=assert_cubby_height()); + + screw_tab_width=15; + screw_tab_height=20; + screw_tab_thickness=thickness; + + yrot(-90) color("red") side_wall() { + if ( screw_tabs ) { + position(TOP+FRONT+LEFT) screw_tab(); + up(screw_tab_height) position(BOTTOM+FRONT+LEFT) screw_tab(); + } + attach(BOTTOM, TOP) zrot(90) tab_bracket(); + attach(TOP, BOTTOM) zrot(90) tab_bracket(); + } + children(); + + module side_wall () { + strut_wall(depth, height - cubby_side_bracket_height() * 2) children(); + } + + module tab_bracket () { + difference() { + cuboid([depth,thickness,cubby_side_bracket_height()]); + alternating_tabs(true, "female", depth, tab_width, tab_depth, + tab_height, tab_padding, tab_tolerance); + alternating_tabs(false, "female", depth, tab_width, tab_depth, + tab_height, tab_padding, tab_tolerance); + } + children(); + } + + module screw_tab () { + difference() { + cuboid([screw_tab_width, screw_tab_thickness,screw_tab_height], + anchor=LEFT+TOP); + right(screw_tab_width * (2 / 3)) zrot(90) yrot(90) { + right(screw_tab_height * (1 / 3)) cyl(l=15, r=2, anchor=RIGHT); + right(screw_tab_height * (2 / 3)) cyl(l=15, r=2, anchor=LEFT); + } + } + children(); + } +} + +module strut_wall(w, h) { + bridge=max_bridge ? max_bridge : default_max_bridge; + sparse_strut( + l=w, + h=h, + thick=thickness, + maxang=45, + strut=5, + max_bridge=bridge + ) children(); +} + +module male_tabs (w, d) { + keep=(w / 2) - thickness; + left_half(x=-keep) cubby_bottom(cubby_width, cubby_depth); +} + +module female_tabs (d, h) { + // text inscribed (when printing tolerances) + text_inscription_depth=0.6; + text_size=thickness * 0.9; + text=str(tab_tolerance); + text_pos=[-h/2 + (thickness * 0.95), + -d / 2 + (thickness / 2), + (thickness / 2)]; + + xrot(180) left_half(x=(h / -2 + (thickness * 3))) + cubby_side(d, h, screw_tabs=false); + translate(text_pos) zrot(90) linear_extrude(text_inscription_depth) + color("green") text(size=text_size, text, font="Bitstream Vera Sans:style=Bold"); +} + +module view_tab_fit (w, d, h) { + // position for testing tab fit + module position_fit_test (left=true) { + cubby_bottom(w, d); + dist=(w / 2) + (thickness / -2) + tab_depth - 2; + down=half(h) - half(thickness) - + tab_height - half(tab_tolerance); + yflip() yrot(90) down(dist * (left ? 1 : -1)) left(down) + cubby_side(d, h, screw_tabs); + } + + position_fit_test(left=true); + yrot(180) up(h) position_fit_test(left=false); +} + +module alternating_tabs (first, gender, length, tab_width, tab_depth, + tab_height, tab_padding, tolerance) { + dist=length - (tab_padding * 2); + total_tabs=dist / (tab_width + tab_padding + tolerance); + half_tabs=total_tabs / 2; + tab_qty = total_tabs % 2 == 0 ? half_tabs : first ? + ceil(half_tabs) : floor(half_tabs); + start = first ? tab_padding : (tab_padding * 2) + tab_width; + spacing = (tab_width + tab_padding) * 2; + xcopies(spacing, tab_qty, dist, sp=((dist / -2) + start)) + dovetail(gender, angle=0, slide=add_tol(tab_depth), + width=add_tol(tab_width), height=add_tol(tab_height)); + + function add_tol (x) = gender == "male" ? x : x + tolerance; +} diff --git a/cubbies.sh b/cubbies.sh new file mode 100755 index 0000000..43a2416 --- /dev/null +++ b/cubbies.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# this script started off generated using "shopenscad.sh -s cubbies.scad" + +usage() { + cat <&2; exit 1; } +warn() { printf 'Warning: %s\n' "$*" >&2; } + +strip_multiline_comments () { + # https://stackoverflow.com/questions/13061785/remove-multi-line-comments + # https://stackoverflow.com/users/751863/steve + sed -r ':a; s%(.*)/\*.*\*/%\1%; ta; /\/\*/ !b; N; ba' +} + +strip_trailing_semicolon () { + sed -r 's/;$//' +} + +strip_leading_spaces () { + sed -r 's/^ +//' +} + +strip_trailing_spaces () { + sed -r 's/ +$//' +} + +strip_nonassignments () { + grep -e '^[A-Za-z0-9_]\+\s*=' +} + +chomp () { + strip_leading_spaces |strip_trailing_spaces +} + +parse_variable () { + #sed -r 's/^([a-zA-Z0-9_]+).*/\1/' + sed -r 's/^([^=]+)=.*/\1/' +} + +parse_value () { + #sed -r 's/^.*?=\s*([^;]+);.*$/\1/' + #sed -r 's/^[^=]+\s*=\s*([^;]+);.*$/\1/' + sed -r 's/.*=\s*([^;]+?).*/\1/' +} + +val_to_filename () { + echo "$1" |sed -r 's/"//g' |sed -r 's/ /_/g' +} + +strip_after_hidden () { + sed -n '/\/\* \[Hidden\] \*\//q;p' "$1" +} + +strip_trailing () { + trailing="$1" + input="$2" + echo ${input%${trailing}} +} + +main() +{ + parse_options "$@" + + output_params="" + default_output_filename="" + ALL="" + VALUES="" + val_sep="-" + var_sep="," + declare -A SCAD + declare -a scad_var_order + + while IFS= read -r line; do + [ "$line" ] || continue; + clean="$(echo -n "$line" |chomp)" + var=$(echo -n "$clean" |parse_variable |chomp) + + # use value provided on command line preferentially to any values in the .scad file + val="" + if [[ -n "${!var}" ]]; then + val=${!var} + else + val=$(echo -n "$clean" |parse_value |chomp) + fi + + if [ "$var" ] && [ "$val" ]; then + scad_var_order+=($var); + SCAD[$var]="$val" + output_params+="-D '$var=$val' " + VALUES+="$(val_to_filename "$val")${val_sep}" + ALL+="${var}=$(val_to_filename "$val")${var_sep}" + fi; + done < <(strip_after_hidden $SCAD_FILE | \ + strip_multiline_comments |strip_nonassignments |strip_trailing_semicolon) + + if [ "$PRINT_VARS" ]; then + for k in "${scad_var_order[@]}"; do + # Make sure quotes surround openscad strings + case "${SCAD[$k]:0:1}" in + \" ) line="${k}='${SCAD[$k]}'";; + \' ) line=${k}="\"${SCAD[$k]}\"";; + * ) line="${k}=${SCAD[$k]}";; + esac + echo $line + done + exit + fi + + if [ "$SHELL_SKEL" ]; then + all_vars=$($0 $SCAD_FILE -p |sed -e 's/^/# /') + cat <