From 59ebbb95f0c3c89428c327d84d1e80f603fe5120 Mon Sep 17 00:00:00 2001 From: Edward Date: Mon, 16 Aug 2021 10:35:34 -0400 Subject: added script to build docker image and run containers --- build_docker.sh | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100755 build_docker.sh diff --git a/build_docker.sh b/build_docker.sh new file mode 100755 index 0000000..da10c3e --- /dev/null +++ b/build_docker.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +cd "${0%/*}" || exit 1 + +# set the default Docker image tag to dactyl-keyboard +IMAGE_TAG="dactyl-keyboard" + +# by default, don't rebuild the image +REBUILD=false; + +# check for command line flags +while getopts 'ri:' flag; do + case "${flag}" in + r) REBUILD=true ;; # if the -r flag is set, we should rebuild the image + i) IMAGE_TAG="${OPTARG}" + esac +done + +# get the image ID, and save the return code so we'll know if the image exists +IMAGE_ID=$(docker inspect --type=image --format={{.Id}} ${IMAGE_TAG}) +INSPECT_RETURN_CODE=$? + +# if we were specifically told to rebuild, or if the image doesn't exists, then build the docker image +if $REBUILD || [ $INSPECT_RETURN_CODE -ne 0 ]; then + docker build -t ${IMAGE_TAG} -f docker/Dockerfile . +fi + +# run each of the dactyl commands in temporary containers +docker run --name dm-run -d --rm -v "`pwd`/src:/app/src" -v "`pwd`/things:/app/things" ${IMAGE_TAG} python3 -i dactyl_manuform.py > /dev/null 2>&1 +docker run --name dm-config -d --rm -v "`pwd`/:/app/src" -v "`pwd`/things:/app/things" ${IMAGE_TAG} python3 -i generate_configuration.py > /dev/null 2>&1 +docker run --name dm-release-build -d --rm -v "`pwd`/:/app/src" -v "`pwd`/things:/app/things" ${IMAGE_TAG} python3 -i model_builder.py > /dev/null 2>&1 + +# show progress indicator while until dm-run container completes +while $(docker inspect --format={{.Id}} dm-run > /dev/null 2>&1); do + echo -n "." + sleep 1.5 +done + +echo $'\n\nDactyl-Manuform export is complete!\n' \ No newline at end of file -- cgit v1.2.3 From fe613b517237357575e9cb6eee565231d1833746 Mon Sep 17 00:00:00 2001 From: Edward Date: Mon, 16 Aug 2021 10:45:41 -0400 Subject: ignore macOS system files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 46f8691..6eb3c45 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ *.scad /target .idea/ +.DS_Store *.FCStd1 debug_* */__pycache__/* -- cgit v1.2.3 From 9da33c963556a1f84c6ed3e4375f78507ebe8bf6 Mon Sep 17 00:00:00 2001 From: Edward Date: Thu, 19 Aug 2021 09:32:27 -0400 Subject: bash script now allows choosing which script to run updated bash script name --- build_docker.sh | 39 ---------------------------- run.sh | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 39 deletions(-) delete mode 100755 build_docker.sh create mode 100755 run.sh diff --git a/build_docker.sh b/build_docker.sh deleted file mode 100755 index da10c3e..0000000 --- a/build_docker.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -cd "${0%/*}" || exit 1 - -# set the default Docker image tag to dactyl-keyboard -IMAGE_TAG="dactyl-keyboard" - -# by default, don't rebuild the image -REBUILD=false; - -# check for command line flags -while getopts 'ri:' flag; do - case "${flag}" in - r) REBUILD=true ;; # if the -r flag is set, we should rebuild the image - i) IMAGE_TAG="${OPTARG}" - esac -done - -# get the image ID, and save the return code so we'll know if the image exists -IMAGE_ID=$(docker inspect --type=image --format={{.Id}} ${IMAGE_TAG}) -INSPECT_RETURN_CODE=$? - -# if we were specifically told to rebuild, or if the image doesn't exists, then build the docker image -if $REBUILD || [ $INSPECT_RETURN_CODE -ne 0 ]; then - docker build -t ${IMAGE_TAG} -f docker/Dockerfile . -fi - -# run each of the dactyl commands in temporary containers -docker run --name dm-run -d --rm -v "`pwd`/src:/app/src" -v "`pwd`/things:/app/things" ${IMAGE_TAG} python3 -i dactyl_manuform.py > /dev/null 2>&1 -docker run --name dm-config -d --rm -v "`pwd`/:/app/src" -v "`pwd`/things:/app/things" ${IMAGE_TAG} python3 -i generate_configuration.py > /dev/null 2>&1 -docker run --name dm-release-build -d --rm -v "`pwd`/:/app/src" -v "`pwd`/things:/app/things" ${IMAGE_TAG} python3 -i model_builder.py > /dev/null 2>&1 - -# show progress indicator while until dm-run container completes -while $(docker inspect --format={{.Id}} dm-run > /dev/null 2>&1); do - echo -n "." - sleep 1.5 -done - -echo $'\n\nDactyl-Manuform export is complete!\n' \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..998e81d --- /dev/null +++ b/run.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +cd "${0%/*}" || exit 1 + + + +# set the default Docker image tag to dactyl-keyboard +IMAGE_TAG="dactyl-keyboard" + +# by default, don't rebuild the image +REBUILD=false; + +# get the command the user would like to run +COMMAND=${1:?A command is required. Try \'run help\'} + +case $COMMAND in + help) + echo "Usage:" + echo " run [command]" + echo "" + echo "Available Commands:" + echo " help show this help" + echo " generate output the keyboard files to the 'things' directory" + echo " configure " + echo " release " + echo "" + echo "Flags:" + echo " -r rebuild the docker image" + echo " -i the tag that should be applied to the docker image" + exit 0 + ;; + generate) + SCRIPT=dactyl_manuform.py + ;; + configure) + SCRIPT=generate_configuration.py + ;; + release) + SCRIPT=model_builder.py + ;; + *) + echo "Invalid command. Try 'run help'" + exit 1 +esac + + + +# check for command line flags +while getopts 'ri:' flag; do + case "${flag}" in + r) REBUILD=true ;; # if the -r flag is set, we should rebuild the image + i) IMAGE_TAG="${OPTARG}" + esac +done + +# get the image ID, and save the return code so we'll know if the image exists +IMAGE_ID=$(docker inspect --type=image --format={{.Id}} ${IMAGE_TAG}) +INSPECT_RETURN_CODE=$? + +# if we were specifically told to rebuild, or if the image doesn't exists, then build the docker image +if $REBUILD || [ $INSPECT_RETURN_CODE -ne 0 ]; then + docker build -t ${IMAGE_TAG} -f docker/Dockerfile . +fi + + + + +# run the command in a temporary container +docker run --name dm-run -d --rm -v "`pwd`/src:/app/src" -v "`pwd`/things:/app/things" ${IMAGE_TAG} python3 -i $SCRIPT > /dev/null 2>&1 + + + +# show progress indicator while until dm-run container completes +while $(docker inspect --format={{.Id}} dm-run > /dev/null 2>&1); do + echo -n "." + sleep 1.5 +done + +echo $'\n\nDactyl-Manuform export is complete!\n' \ No newline at end of file -- cgit v1.2.3 From 092c1ec1d91728c3292011f060c2fe5a163998df Mon Sep 17 00:00:00 2001 From: Edward Date: Thu, 19 Aug 2021 09:45:07 -0400 Subject: added a 'build' command also removed check for a command argument; just let an empty command fall through to the default case --- run.sh | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/run.sh b/run.sh index 998e81d..2e07595 100755 --- a/run.sh +++ b/run.sh @@ -11,7 +11,9 @@ IMAGE_TAG="dactyl-keyboard" REBUILD=false; # get the command the user would like to run -COMMAND=${1:?A command is required. Try \'run help\'} +COMMAND=${1} + + case $COMMAND in help) @@ -20,6 +22,7 @@ case $COMMAND in echo "" echo "Available Commands:" echo " help show this help" + echo " build rebuild the docker image" echo " generate output the keyboard files to the 'things' directory" echo " configure " echo " release " @@ -29,6 +32,10 @@ case $COMMAND in echo " -i the tag that should be applied to the docker image" exit 0 ;; + build) + docker build -t ${IMAGE_TAG} -f docker/Dockerfile . + exit 0 + ;; generate) SCRIPT=dactyl_manuform.py ;; @@ -43,8 +50,6 @@ case $COMMAND in exit 1 esac - - # check for command line flags while getopts 'ri:' flag; do case "${flag}" in @@ -53,6 +58,8 @@ while getopts 'ri:' flag; do esac done + + # get the image ID, and save the return code so we'll know if the image exists IMAGE_ID=$(docker inspect --type=image --format={{.Id}} ${IMAGE_TAG}) INSPECT_RETURN_CODE=$? @@ -64,12 +71,9 @@ fi - # run the command in a temporary container docker run --name dm-run -d --rm -v "`pwd`/src:/app/src" -v "`pwd`/things:/app/things" ${IMAGE_TAG} python3 -i $SCRIPT > /dev/null 2>&1 - - # show progress indicator while until dm-run container completes while $(docker inspect --format={{.Id}} dm-run > /dev/null 2>&1); do echo -n "." -- cgit v1.2.3 From 3a74bee2fcd1561f5cf23a4f57fe41062203313e Mon Sep 17 00:00:00 2001 From: Edward Date: Thu, 19 Aug 2021 15:40:34 -0400 Subject: allow multiple config files generate_configuration.py now only generates a configuration file; it no longer also runs dactyl_manuform.py. it will save the generated configuration file to the configs directory, and it will be named according to the config_name. passing a --config= argument to the script will set the config_name and save_dir. dactyl_manuform.py now also accepts a --config= argument. if non is passed, the default values from generate_configuration.py are used. otherwise, the defaults will be overridden by the values from the config specified. because of these changes, i removed run_config.json. it should have default values anyway, so it seemed reduandant and confusing. the run command has also been updated to allow for the config changes, and some bugs have also been fixed. --- .gitignore | 1 + configs/.gitkeep | 0 run.sh | 84 ++++++++++---- src/dactyl_manuform.py | 15 ++- src/generate_configuration.py | 30 +++-- src/run_config.json | 260 ------------------------------------------ 6 files changed, 88 insertions(+), 302 deletions(-) create mode 100644 configs/.gitkeep delete mode 100644 src/run_config.json diff --git a/.gitignore b/.gitignore index 6eb3c45..41ac558 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ debug_* */__pycache__/* *~$* things/ +configs/*.json \ No newline at end of file diff --git a/configs/.gitkeep b/configs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/run.sh b/run.sh index 2e07595..ad6a922 100755 --- a/run.sh +++ b/run.sh @@ -10,26 +10,71 @@ IMAGE_TAG="dactyl-keyboard" # by default, don't rebuild the image REBUILD=false; -# get the command the user would like to run -COMMAND=${1} +# leave config empty to use default values +CONFIG="" + + +# check for command line flags +while test $# -gt 0; do + case "$1" in + -r|--rebuild) + REBUILD=true + shift + ;; + -t|--tag) + if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then + IMAGE_TAG=$2 + shift 2 + else + echo "Error: Argument for $1 is missing" >&2 + exit 1 + fi + ;; + -c|--config) + CONFIG=$2 + shift 2 + ;; + -*|--*) + echo "Error: Unknown flag $1" >&2 + exit 1 + ;; + *) + COMMAND=$1 + shift; + ;; + esac +done case $COMMAND in help) + echo "Dactyl-Manuform Keyboard Generator" + echo "" + echo "Use this tool to configure and generate files for building a keyboard. All" + echo "commands will be run in a Docker contianer, which will be built if it does" + echo "not already exist." + echo "" + echo "" echo "Usage:" - echo " run [command]" + echo " run [-r] [-i ] [-c ] " echo "" echo "Available Commands:" - echo " help show this help" - echo " build rebuild the docker image" - echo " generate output the keyboard files to the 'things' directory" - echo " configure " - echo " release " + echo " help Show this help" + echo " build Rebuild the docker image" + echo " release Run model_builder.py" + echo " generate Output the keyboard files to the './things' directory" + echo " configure Generate a configuration file with default values. The config" + echo " file will be saved to configs/.json. If the" + echo " -c flag is not set, the defailt config_name will be used." echo "" echo "Flags:" - echo " -r rebuild the docker image" - echo " -i the tag that should be applied to the docker image" + echo " -c Set the configuration file to use. This should be the name of the file" + echo " only, without a file extension, and it is relative to the './configs'" + echo " directory. For example, '-c my-custom-dm' will refer to a file located" + echo " at './configs/my-custom-dm.json'" + echo " -r Rebuild the docker image" + echo " -t The tag that should be applied to the docker image" exit 0 ;; build) @@ -50,15 +95,6 @@ case $COMMAND in exit 1 esac -# check for command line flags -while getopts 'ri:' flag; do - case "${flag}" in - r) REBUILD=true ;; # if the -r flag is set, we should rebuild the image - i) IMAGE_TAG="${OPTARG}" - esac -done - - # get the image ID, and save the return code so we'll know if the image exists IMAGE_ID=$(docker inspect --type=image --format={{.Id}} ${IMAGE_TAG}) @@ -70,9 +106,13 @@ if $REBUILD || [ $INSPECT_RETURN_CODE -ne 0 ]; then fi +# if a config file was specified, set the command line argument for the python script +if [[ ! -z $CONFIG ]]; then + CONFIG_OPTION="--config=${CONFIG}" +fi # run the command in a temporary container -docker run --name dm-run -d --rm -v "`pwd`/src:/app/src" -v "`pwd`/things:/app/things" ${IMAGE_TAG} python3 -i $SCRIPT > /dev/null 2>&1 +docker run --name dm-run -d --rm -v "`pwd`/src:/app/src" -v "`pwd`/things:/app/things" -v "`pwd`/configs:/app/configs" ${IMAGE_TAG} python3 $SCRIPT $CONFIG_OPTION > /dev/null 2>&1 # show progress indicator while until dm-run container completes while $(docker inspect --format={{.Id}} dm-run > /dev/null 2>&1); do @@ -80,4 +120,6 @@ while $(docker inspect --format={{.Id}} dm-run > /dev/null 2>&1); do sleep 1.5 done -echo $'\n\nDactyl-Manuform export is complete!\n' \ No newline at end of file +echo "" +echo "Dactyl-Manuform '${COMMAND}' is complete!" +echo "" \ No newline at end of file diff --git a/src/dactyl_manuform.py b/src/dactyl_manuform.py index bb164b5..5677791 100644 --- a/src/dactyl_manuform.py +++ b/src/dactyl_manuform.py @@ -1,6 +1,7 @@ import numpy as np from numpy import pi import os.path as path +import getopt, sys import json import os @@ -23,11 +24,15 @@ import generate_configuration as cfg for item in cfg.shape_config: locals()[item] = cfg.shape_config[item] -## LOAD RUN CONFIGURATION FILE AND WRITE TO ANY VARIABLES IN FILE. -with open('run_config.json', mode='r') as fid: - data = json.load(fid) -for item in data: - locals()[item] = data[item] +## CHECK FOR CONFIG FILE AND WRITE TO ANY VARIABLES IN FILE. +opts, args = getopt.getopt(sys.argv[1:], "", ["config="]); +for opt, arg in opts: + if opt in ('--config'): + with open(os.path.join(r"..", "configs", arg + '.json'), mode='r') as fid: + data = json.load(fid) + for item in data: + locals()[item] = data[item] + # Really rough setup. Check for ENGINE, set it not present from configuration. try: diff --git a/src/generate_configuration.py b/src/generate_configuration.py index e2d71b0..08d02ff 100644 --- a/src/generate_configuration.py +++ b/src/generate_configuration.py @@ -1,3 +1,6 @@ +import sys +import getopt +import os import json @@ -301,23 +304,18 @@ shape_config = { #################################### def save_config(): - print("Saving Configuration") - with open('run_config.json', mode='w') as fid: - json.dump(shape_config, fid, indent=4) - -def update_config(fname, fname_out=None): - if fname_out is None: - fname_out == "updated_config.json" - # Open existing config, update with any new parameters, and save to updated_config.json - with open(fname, mode='r') as fid: - last_shape_config = json.load(fid) - shape_config.update(last_shape_config) - - with open(fname_out, mode='w') as fid: + # Check to see if the user has specified an alternate config + opts, args = getopt.getopt(sys.argv[1:], "", ["config="]); + for opt, arg in opts: + if opt in ('--config'): + # If a config file was specified, set the config_name and save_dir + shape_config['save_dir'] = arg + shape_config['config_name'] = arg + + # Write the config to ./configs/.json + with open(os.path.join(r"..", "configs", shape_config['config_name'] + '.json'), mode='w') as fid: json.dump(shape_config, fid, indent=4) if __name__ == '__main__': - save_config() - from dactyl_manuform import * - run() \ No newline at end of file + save_config() \ No newline at end of file diff --git a/src/run_config.json b/src/run_config.json deleted file mode 100644 index 627b536..0000000 --- a/src/run_config.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "ENGINE": "solid", - "save_dir": "DM_4x5_NOTCHPLT_DEFTMB_NOLED_EXTCTRL", - "config_name": "DM_4x5_NOTCHPLT_DEFTMB_NOLED_EXTCTRL", - "show_caps": false, - "nrows": 4, - "ncols": 5, - "alpha": 0.26179916666666664, - "beta": 0.08726638888888888, - "centercol": 3, - "centerrow_offset": 3, - "tenting_angle": 0.26179916666666664, - "symmetry": "symmetric", - "column_style_gt5": "orthographic", - "column_style": "standard", - "thumb_offsets": [ - 6, - -3, - 7 - ], - "keyboard_z_offset": 11, - "thumb_style": "DEFAULT", - "default_1U_cluster": true, - "minidox_Usize": 1.6, - "thumb_plate_tr_rotation": 0.0, - "thumb_plate_tl_rotation": 0.0, - "thumb_plate_mr_rotation": 0.0, - "thumb_plate_ml_rotation": 0.0, - "thumb_plate_br_rotation": 0.0, - "thumb_plate_bl_rotation": 0.0, - "pinky_1_5U": false, - "first_1_5U_row": 0, - "last_1_5U_row": 5, - "extra_width": 2.5, - "extra_height": 1.0, - "wall_z_offset": 15, - "wall_x_offset": 5, - "wall_y_offset": 6, - "left_wall_x_offset": 12, - "left_wall_z_offset": 3, - "left_wall_lower_y_offset": 0, - "left_wall_lower_z_offset": 0, - "wall_thickness": 4.5, - "wall_base_y_thickness": 4.5, - "wall_base_x_thickness": 4.5, - "wall_base_back_thickness": 4.5, - "fixed_angles": [ - 0.17453277777777776, - 0.17453277777777776, - 0, - 0, - 0, - -0.26179916666666664, - -0.26179916666666664 - ], - "fixed_x": [ - -41.5, - -22.5, - 0, - 20.3, - 41.4, - 65.5, - 89.6 - ], - "fixed_z": [ - 12.1, - 8.3, - 0, - 5, - 10.7, - 14.5, - 17.5 - ], - "fixed_tenting": 0.0, - "plate_style": "NOTCH", - "hole_keyswitch_height": 14.0, - "hole_keyswitch_width": 14.0, - "nub_keyswitch_height": 14.4, - "nub_keyswitch_width": 14.4, - "undercut_keyswitch_height": 14.0, - "undercut_keyswitch_width": 14.0, - "notch_width": 5.0, - "sa_profile_key_height": 12.7, - "sa_length": 18.5, - "sa_double_length": 37.5, - "plate_thickness": 5.1, - "plate_rim": 2.0, - "clip_thickness": 1.4, - "clip_undercut": 1.0, - "undercut_transition": 0.2, - "plate_file": null, - "plate_offset": 0.0, - "oled_mount_type": "NONE", - "oled_center_row": 1.25, - "oled_translation_offset": [ - 0, - 0, - 4 - ], - "oled_rotation_offset": [ - 0, - 0, - 0 - ], - "oled_configurations": { - "UNDERCUT": { - "oled_mount_width": 15.0, - "oled_mount_height": 35.0, - "oled_mount_rim": 3.0, - "oled_mount_depth": 6.0, - "oled_mount_cut_depth": 20.0, - "oled_mount_location_xyz": [ - -80.0, - 20.0, - 45.0 - ], - "oled_mount_rotation_xyz": [ - 13.0, - 0.0, - -6.0 - ], - "oled_left_wall_x_offset_override": 28.0, - "oled_left_wall_z_offset_override": 0.0, - "oled_left_wall_lower_y_offset": 12.0, - "oled_left_wall_lower_z_offset": 5.0, - "oled_mount_undercut": 1.0, - "oled_mount_undercut_thickness": 2.0 - }, - "SLIDING": { - "oled_mount_width": 12.5, - "oled_mount_height": 25.0, - "oled_mount_rim": 2.5, - "oled_mount_depth": 8.0, - "oled_mount_cut_depth": 20.0, - "oled_mount_location_xyz": [ - -78.0, - 10.0, - 41.0 - ], - "oled_mount_rotation_xyz": [ - 6.0, - 0.0, - -3.0 - ], - "oled_left_wall_x_offset_override": 24.0, - "oled_left_wall_z_offset_override": 0.0, - "oled_left_wall_lower_y_offset": 12.0, - "oled_left_wall_lower_z_offset": 5.0, - "oled_thickness": 4.2, - "oled_edge_overlap_end": 6.5, - "oled_edge_overlap_connector": 5.5, - "oled_edge_overlap_thickness": 2.5, - "oled_edge_overlap_clearance": 2.5, - "oled_edge_chamfer": 2.0 - }, - "CLIP": { - "oled_mount_width": 12.5, - "oled_mount_height": 39.0, - "oled_mount_rim": 2.0, - "oled_mount_depth": 7.0, - "oled_mount_cut_depth": 20.0, - "oled_mount_location_xyz": [ - -78.0, - 20.0, - 42.0 - ], - "oled_mount_rotation_xyz": [ - 12.0, - 0.0, - -6.0 - ], - "oled_left_wall_x_offset_override": 24.0, - "oled_left_wall_z_offset_override": 0.0, - "oled_left_wall_lower_y_offset": 12.0, - "oled_left_wall_lower_z_offset": 5.0, - "oled_thickness": 4.2, - "oled_mount_bezel_thickness": 3.5, - "oled_mount_bezel_chamfer": 2.0, - "oled_mount_connector_hole": 6.0, - "oled_screen_start_from_conn_end": 6.5, - "oled_screen_length": 24.5, - "oled_screen_width": 10.5, - "oled_clip_thickness": 1.5, - "oled_clip_width": 6.0, - "oled_clip_overhang": 1.0, - "oled_clip_extension": 5.0, - "oled_clip_width_clearance": 0.5, - "oled_clip_undercut": 0.5, - "oled_clip_undercut_thickness": 2.5, - "oled_clip_y_gap": 0.2, - "oled_clip_z_gap": 0.2 - } - }, - "web_thickness": 4.0, - "post_size": 0.1, - "post_adj": 0, - "screws_offset": "INSIDE", - "screw_insert_height": 3.8, - "screw_insert_bottom_radius": 2.655, - "screw_insert_top_radius": 2.55, - "wire_post_height": 7, - "wire_post_overhang": 3.5, - "wire_post_diameter": 2.6, - "controller_mount_type": "EXTERNAL", - "external_holder_height": 12.5, - "external_holder_width": 28.75, - "external_holder_xoffset": -5.0, - "screw_hole_diameter": 2, - "base_thickness": 3.0, - "base_offset": 3.0, - "base_rim_thickness": 5.0, - "screw_cbore_diameter": 4.0, - "screw_cbore_depth": 2.0, - "plate_holes": false, - "plate_holes_xy_offset": [ - 0.0, - 0.0 - ], - "plate_holes_width": 14.3, - "plate_holes_height": 14.3, - "plate_holes_diameter": 1.7, - "plate_holes_depth": 20.0, - "column_offsets": [ - [ - 0, - 0, - 0 - ], - [ - 0, - 0, - 0 - ], - [ - 0, - 2.82, - -4.5 - ], - [ - 0, - 0, - 0 - ], - [ - 0, - -6, - 5 - ], - [ - 0, - -6, - 5 - ], - [ - 0, - -6, - 5 - ] - ] -} \ No newline at end of file -- cgit v1.2.3