#!/bin/bash ################################################################ function usagemsg_vinyl_plank_calc_zbksh { stderr_zbksh " Function: vinyl_plank_calc_zbksh Description: This script is used to calculate the starting offset to use when laying Vinyl Plank flooring. The offset is the location on the floor to begin your first row so that you do not end up with narrow pieces around doors and other openings. A list of measurements to each door, corner, or other opening which will require a cut-out around the opening. The measurements should be made from a common wall and should be as acurate as possible in order for this script to provide accurate information back to the user. The list of measurements is read from a text file, one measurement per line. The order in which the measurment occur in the file is irrelevant, but it makes sense to list them in ascending order. The measurments should be in inches and entered in decimal format (not fractions). So for example, a measurement of 24 and 5/8 inches would be entered into the measurements file as: 24.625 The user can specify a "minimum desired remainder" which is the minimum width of the cut-out around a door, corner or other opening. The default value is 2.0 inches, but can be changed to any positive value. The point of this program is to provide the user with enough information to make this minimum value approach 1/2 of the plank width, if possible. FYI: That is not usually possible. The increment is used in the calculations to gradually increase the starting offset in an attempt to find a starting offset where NONE of the cut-outs result in a piece with a width that is less than the minimum desired remainder. The default value of the increment value is 1/8 inch specified as a decimal value of \"0.125\". If you specify a different increment, use a decimal point number, NOT A FRACTION. The Plank Width value is the EXACT width of one vinyl plank, which is used to calculate the number of full planks that will fit between the starting wall and any obstructions requiring a flooring cut-out. This value must be specified as a decimal point number, not a fraction. For example, the default value of 7 and 5/8 inches is specified as \"7.625\" The expansion gap is the manufacturers recommended value to leave around the edge of the floor so the floor can move, contract, and expand. The default value for this is 1/4 inch, but since the manufacturer usually recommends 1/4 inch all the way around the floor, it may be acceptable to increase this value to 1/2 inch. Default values are assigned to each of the variables, so the use does not need to specify any command line parameters if the default values match his/her requirements. Usage: ${1##*/} [-f datafile] [-d Description] Where: -d description = Text Description of the data set - Default: -s #.### = Starting Offset in decimal inches - Default:0.000 (0 Inches) -g #.### = Minimum desired remainder gap in decimal inches - Default:2.000 (2 Inches) -i #.### = Calculation Increment - Default:0.125 (1/8 Inch) -w #.### = Width of one flooring Plank in decimal inches - Default:7.625 (7 and 5/8 Inches ) -e #.### = Flooring Manufacturers recommended expansion gap - Default:0.250 (1/4 Inch) -c configFile = Configuration File - Default: .vinyl_plank_config -f dataFile = File that contains room measurments to each object requiring plank cut-out -a = Do NOT display all calculation sets, only display those above minimum desired remainder gap -h = Do NOT display any headers or separators -D delim = Field Delimiter character (single character) -z = Preview Mode Example Usage: vinyl_plank_calc -v -f ~/datafile.txt -d \"My Kitchen\" Author: Dana French (dfrench@mtxia.com) Date: 20210319 \"AutoContent\" enabled " } ################################################################ function stdout_zbksh { ${CMD_ECHO:-echo} "${@}" return 0 } ################################################################ function stderr_zbksh { stdout_zbksh "${@}" >&2 return 0 } ################################################################ function int_round_up { INT_RND_UP=$(( ( ( ( ${1:?# ERROR: value not specified for round up function} + 5 ) * 10 ) / 100 ) * 10 )) stdout_zbksh "${INT_RND_UP}" } ################################################################ ################################################################ ################################################################ #### #### Main body of script starts here #### ################################################################ #### #### Initialize Variables #### typeset TRUE="0" typeset FALSE="1" typeset PLK_TRUE="${TRUE:-0}" typeset PLK_FALSE="${FALSE:-1}" typeset VERBOSE="${VERBOSE:-${PLK_FALSE}}" typeset VERYVERB="${VERYVERB:-${PLK_FALSE}}" typeset PLK_VERBOSE="${VERBOSE:-${PLK_FALSE}}" typeset PLK_VERYVERB="${VERYVERB:-${PLK_FALSE}}" typeset PLK_INDEX="0" typeset PLK_TOG_DESMIN="${PLK_FALSE}" typeset PLK_DSP_DESMIN="${PLK_TRUE}" typeset PLK_TOG_HEADER="${PLK_TRUE}" typeset PLK_PROGNAME="vinyl_plank_calc_zbksh" typeset PLK_VERSION="1.7" typeset PLK_CONFIG_FILE=".vinyl_plank_config" typeset PLK_CONFFILE="" typeset PLK_DESCRIP="" typeset PLK_DELIM=" "$'\t' typeset PLK_STARTOFFSET_IN="0.000" typeset PLK_MINROWWID_IN="2.000" typeset PLK_INCREMENT_IN="0.125" typeset PLK_PLANKWID_IN="7.625" typeset PLK_EXPGAP_IN="0.250" typeset PLK_PLANKLEN_IN="48.000" typeset PLK_MINROWWID="2000" typeset PLK_INCREMENT="125" typeset PLK_PLANKWID="7625" typeset PLK_EXPGAP="250" typeset PLK_PLANKLEN="48000" typeset PLK_STOFFCNT="0" typeset PLK_STARTOFFSET="${PLK_EXPGAP}" ################################################################ #### #### Include the script configuration file ".vinyl_plank_config" from the #### users home directory or from the current directory where the script is #### being executed. #### PLK_CONFFILE="" [[ -f ~/${PLK_CONFIG_FILE} ]] && PLK_CONFFILE="~/${PLK_CONFIG_FILE}" [[ -f ./${PLK_CONFIG_FILE} ]] && PLK_CONFFILE="./${PLK_CONFIG_FILE}" [[ "_${PLK_CONFFILE}" != "_" ]] && (( PLK_VERBOSE == PLK_TRUE )) && stderr_zbksh "# Config file found: ${PLK_CONFFILE}" [[ "_${PLK_CONFFILE}" != "_" ]] && . ${PLK_CONFFILE} ################################################################ #### #### Process the command line options and arguments, saving the values #### as appropriate. #### typeset OPTIND="1" while getopts ":vVzc:s:g:i:w:e:c:f:d:D:ah" OPTION do case "${OPTION}" in 'c') PLK_CONFIG_FILE="${OPTARG}";; 'f') PLK_DATA_FILE="${OPTARG}";; 'd') PLK_DESCRIP="${OPTARG}";; 'D') PLK_DELIM="${OPTARG}";; 's') PLK_STARTOFFSET_IN="${OPTARG}";; 'g') PLK_MINROWWID_IN="${OPTARG}";; 'i') PLK_INCREMENT_IN="${OPTARG}";; 'w') PLK_PLANKWID_IN="${OPTARG}";; 'e') PLK_EXPGAP_IN="${OPTARG}" PLK_STARTOFFSET_IN="${OPTARG}";; 'a') PLK_DSP_DESMIN="${PLK_FALSE}";; 'h') PLK_TOG_HEADER="${PLK_FALSE}";; 'v') PLK_VERBOSE="${PLK_TRUE}";; 'V') PLK_VERYVERB="${PLK_TRUE}";; [?:#]) usagemsg_vinyl_plank_calc_zbksh "${PLK_PROGNAME}" && exit 1 ;; esac done shift $(( ${OPTIND} - 1 )) ################################################################ #### #### Check each required command line argument to verify it has a valid value. #### trap "usagemsg_vinyl_plank_calc_zbksh ${PLK_PROGNAME}" EXIT if [[ "_${PLK_DATA_FILE}" == "_" ]] then stderr_zbksh "# ERROR: A Data File containing your room measurements is required." exit 11 fi if [[ ! -f "${PLK_DATA_FILE}" ]] then stderr_zbksh "# ERROR: The specified Data File \"${PLK_DATA_FILE}\" does not exist or is not readable." exit 12 fi if [[ "_${PLK_DESCRIP}" == "_" ]] then PLK_DESCRIP="${PLK_DATA_FILE##*/}" fi trap "-" EXIT (( PLK_VERYVERB == TRUE )) && set -x ################################################################ #### #### Display values if PLK_VERBOSE mode is TRUE if (( PLK_VERBOSE == TRUE )) && (( PLK_TOG_HEADER == TRUE )) then stderr_zbksh "################################################################" stderr_zbksh "# Program Name...............: ${PLK_PROGNAME:=vinyl_plank_calc_zbksh}" stderr_zbksh "# Program Version............: ${PLK_VERSION}" stderr_zbksh "# Config File................: ${PLK_CONFIG_FILE}" stderr_zbksh "# Data File..................: ${PLK_DATA_FILE}" stderr_zbksh "# Description of Data File...: ${PLK_DESCRIP}" stderr_zbksh "# Starting Offset............: ${PLK_STARTOFFSET_IN}" stderr_zbksh "# Minimum Desired Remainder..: ${PLK_MINROWWID_IN}" stderr_zbksh "# Starting Offset Increment..: ${PLK_INCREMENT_IN}" stderr_zbksh "# Width of one Flooring Plank: ${PLK_PLANKWID_IN}" stderr_zbksh "# Recommended Expansion Gap..: ${PLK_EXPGAP_IN}" fi ################################################################ #### Read Data File typeset -a PLK_OBSTR_CUTOUT PLK_USER_INDEX="0" while read PLK_USER_CUTOUT PLK_USER_DESCRIP do if (( PLK_TOG_HEADER == TRUE )) then (( PLK_VERBOSE == PLK_TRUE )) && stderr_zbksh "# Reading Data File Records..: ${PLK_USER_CUTOUT} ${PLK_USER_DESCRIP}" fi PLK_USER_CUTOUT_IN="$( stdout_zbksh "scale=4; ${PLK_USER_CUTOUT} * 10000" | bc ) " PLK_OBSTR_CUTOUT[${PLK_USER_INDEX}]="${PLK_USER_CUTOUT_IN%%.*}" PLK_OBSTR_DESCRP[${PLK_USER_INDEX}]="${PLK_USER_DESCRIP}" (( ++PLK_USER_INDEX )) done < "${PLK_DATA_FILE}" ################################################################ #### The starting offset from the reference wall in 10thousands of an inch PLK_STARTOFFSET=$( stdout_zbksh "scale=3; ${PLK_STARTOFFSET_IN} * 10000" | bc ) PLK_STARTOFFSET="${PLK_STARTOFFSET%%.*}" #### The minimum desired row width between a full planks and a cut-out; in 10thousands of an inch PLK_MINROWWID=$( stdout_zbksh "scale=3; ${PLK_MINROWWID_IN} * 10000" | bc ) PLK_MINROWWID="${PLK_MINROWWID%%.*}" #### The increment to use for testing cut-outs; in 10thousands of an inch PLK_INCREMENT=$( stdout_zbksh "scale=3; ${PLK_INCREMENT_IN} * 10000" | bc ) PLK_INCREMENT="${PLK_INCREMENT%%.*}" #### Width of one flooring plank; in 10thousands of an inch PLK_PLANKWID=$( stdout_zbksh "scale=3; ${PLK_PLANKWID_IN} * 10000" | bc ) PLK_PLANKWID="${PLK_PLANKWID%%.*}" #### Acceptable expansion Gap in decimal point; in 10thousands of an inch PLK_EXPGAP=$( stdout_zbksh "scale=3; ${PLK_EXPGAP_IN} * 10000" | bc ) PLK_EXPGAP="${PLK_EXPGAP%%.*}" #### The length of one flooring plank; in 10thousands of an inch PLK_PLANKLEN=$( stdout_zbksh "scale=3; ${PLK_PLANKLEN_IN} * 10000" | bc ) PLK_PLANKLEN="${PLK_PLANKLEN%%.*}" typeset PLK_ADJROWWID=$(( ${PLK_MINROWWID} - ${PLK_INCREMENT} )) typeset PLK_ADJROWWID=$(( ${PLK_STARTOFFSET} - ${PLK_INCREMENT} )) typeset ADJ_PLANK=$(( ${PLK_PLANKWID} - ${PLK_INCREMENT} )) typeset -a PLK_START_OFFSET ################################################################ #### #### Build array of start Offsets using the user specified values or default values #### PLK_STOFFCNT="0" while (( ( ${PLK_ADJROWWID} + ${PLK_INCREMENT} ) < ${ADJ_PLANK} )) do PLK_ADJROWWID=$(( ${PLK_ADJROWWID} + ${PLK_INCREMENT} )) PLK_START_OFFSET[${PLK_STOFFCNT}]="${PLK_ADJROWWID}" (( ++PLK_STOFFCNT )) done #### #### Start the calculations using the dynamically generated starting offsets #### PLK_CNT="1" for PLK_OFFSET in "${PLK_START_OFFSET[@]}" do if (( PLK_TOG_HEADER == TRUE )) then PLK_FLOAT_OF="${PLK_OFFSET%????}.${PLK_OFFSET:${#PLK_OFFSET}-4:3}" stdout_zbksh "################################################################" stdout_zbksh "#### Starting Offset:${PLK_FLOAT_OF} #### Plank Width:${PLK_PLANKWID_IN} ####" fi PLK_TOG_DESMIN="${PLK_FALSE}" PLK_WASTETOTAL="0" for PLK_INDEX in "${!PLK_OBSTR_CUTOUT[@]}" do #### Assign the array value for the obstruction gap to the variable PLK_CUTOUTAT PLK_CUTOUTAT=$(( ${PLK_OBSTR_CUTOUT[${PLK_INDEX}]} )) (( ${PLK_OFFSET} > ${PLK_CUTOUTAT} )) && break #### Subtract the starting offset from the obstruction gap to determine the number of planks and the remainder PLK_QUOTIENT=$(( ( ( ${PLK_CUTOUTAT} - ${PLK_OFFSET} ) * 10000 ) / ${PLK_PLANKWID} )) #### Extract the number of full planks from the PLK_QUOTIENT integer value PLK_FULLPLANKS="$(( ${PLK_QUOTIENT} / 10000 ))" [[ "_${PLK_FULLPLANKS}" == "_" ]] && PLK_FULLPLANKS="0" #### Extract the remainder value from the Quotient value. #### This is the percentage of a full plank required as the last row of the current obstruction cut-out PLK_MODULO="( ${PLK_QUOTIENT} % 10000 )" #### Convert the last row percentage to the number of inches of a full plank eval PLK_CUTOUTROWWID="\"\$(( ( \${PLK_PLANKWID} * ${PLK_MODULO} ) / 10000 ))\"" #### Calculate the amount of wast associated with the last row of the current obstruction gap, #### to use as a way to put a rating on the set of calculations for the starting offset. (( ${PLK_OFFSET} < ${PLK_CUTOUTAT} )) && PLK_WASTEPERCENT=$(( 10000 - ${PLK_MODULO} )) || PLK_WASTEPERCENT="0" (( ${PLK_OFFSET} < ${PLK_CUTOUTAT} )) && eval PLK_WASTECUTOFF="\"\$(( ( \${PLK_PLANKWID} * ( 10000 - ${PLK_MODULO} ) ) / 10000 ))\"" || PLK_WASTECUTOFF="00000" (( ${PLK_OFFSET} < ${PLK_CUTOUTAT} )) && PLK_WASTETOTAL=$(( ${PLK_WASTETOTAL} + ( 10000 - ${PLK_MODULO} ) )) #### Create Floating Point values from the calculation Integers, for display to the User. PLK_FLOAT_SG="${PLK_OFFSET%????}.${PLK_OFFSET:${#PLK_OFFSET}-4:3}" PLK_FLOAT_GW="${PLK_CUTOUTAT%????}.${PLK_CUTOUTAT:${#PLK_CUTOUTAT}-4:3}" PLK_FLOAT_MO="${PLK_MODULO%????}.${PLK_MODULO:${#PLK_MODULO}-4:3}" PLK_FLOAT_WI="${PLK_WASTECUTOFF%????}.${PLK_WASTECUTOFF:${#PLK_WASTECUTOFF}-4:3}" PLK_FLOAT_EG="${PLK_EXPGAP%????}.${PLK_EXPGAP:${#PLK_EXPGAP}-4:3}" PLK_FLOAT_MG="${PLK_MINROWWID%????}.${PLK_MINROWWID:${#PLK_MINROWWID}-4:3}" PLK_CUTOUTROWWID=$( int_round_up ${PLK_CUTOUTROWWID} ) PLK_FLOAT_LI="${PLK_CUTOUTROWWID%????}.${PLK_CUTOUTROWWID:${#PLK_CUTOUTROWWID}-4:3}" #### Assemble a header line of column titles in array position 0, which will be displayed to the user. PLK_RESULT[0]="StartOffset${PLK_DELIM:${#PLK_DELIM}-1}" PLK_RESULT[0]="${PLK_RESULT[0]} CutOutRow${PLK_DELIM:${#PLK_DELIM}-1}" PLK_RESULT[0]="${PLK_RESULT[0]} FullPlanks${PLK_DELIM:${#PLK_DELIM}-1}" PLK_RESULT[0]="${PLK_RESULT[0]} CutOffRowWid${PLK_DELIM:${#PLK_DELIM}-1}" PLK_RESULT[0]="${PLK_RESULT[0]} CutOutRowPlankWid${PLK_DELIM:${#PLK_DELIM}-1}" PLK_RESULT[0]="${PLK_RESULT[0]} Comments${PLK_DELIM:${#PLK_DELIM}-1}" PLK_RESULT[0]="${PLK_RESULT[0]} DataDescription" #### Assemble a line of output to display to the user. Each line is specific to the Starting Offset value and #### the gap width distance to the obstruction requiring flooring cuts. PLK_RESULT[${PLK_CNT}]="${PLK_FLOAT_SG/#./0.}${PLK_DELIM}" PLK_RESULT[${PLK_CNT}]="${PLK_RESULT[${PLK_CNT}]} ${PLK_FLOAT_GW/#./0.}${PLK_DELIM}" PLK_RESULT[${PLK_CNT}]="${PLK_RESULT[${PLK_CNT}]} ${PLK_FULLPLANKS}${PLK_DELIM}" PLK_RESULT[${PLK_CNT}]="${PLK_RESULT[${PLK_CNT}]} ${PLK_FLOAT_WI/#./0.}${PLK_DELIM}" PLK_RESULT[${PLK_CNT}]="${PLK_RESULT[${PLK_CNT}]} ${PLK_FLOAT_LI/#./0.}" #### Test the value of the cutout remainder to determine if it is within the desired/specifed parameters. #### If not, provide the user with notification of that fact. if [[ "_${PLK_CUTOUTROWWID}" == _0 ]] #### test to see if it is equal to 0 then PLK_RESULT[${PLK_CNT}]="${PLK_RESULT[${PLK_CNT}]}000${PLK_DELIM} EXACT FIT" elif (( ${PLK_CUTOUTROWWID} < ${PLK_EXPGAP} )) #### Test to see if it is less than the expansion gap then PLK_RESULT[${PLK_CNT}]="${PLK_RESULT[${PLK_CNT}]}${PLK_DELIM} WITHIN ${PLK_FLOAT_EG} ExpGap" elif (( ${PLK_CUTOUTROWWID} < ( 2 * ${PLK_EXPGAP} ) )) #### Test to see if it is less than the 2 x expansion gap then PLK_RESULT[${PLK_CNT}]="${PLK_RESULT[${PLK_CNT}]}${PLK_DELIM} WITHIN 2x${PLK_FLOAT_EG} ExpGap" elif (( ${PLK_CUTOUTROWWID} < ${PLK_MINROWWID} )) #### Test to see if it is less than the minimum row width then PLK_RESULT[${PLK_CNT}]="${PLK_RESULT[${PLK_CNT}]}${PLK_DELIM} BELOW ${PLK_FLOAT_MG} Min" PLK_TOG_DESMIN="${PLK_TRUE}" else PLK_RESULT[${PLK_CNT}]="${PLK_RESULT[${PLK_CNT}]}${PLK_DELIM}" fi # DLF PLK_RESULT[${PLK_CNT}]="${PLK_RESULT[${PLK_CNT}]}${PLK_DELIM}"" ${PLK_OBSTR_DESCRP[PLK_INDEX]}" PLK_RESULT[${PLK_CNT}]="${PLK_RESULT[${PLK_CNT}]}${PLK_DELIM} ${PLK_OBSTR_DESCRP[PLK_INDEX]}" #### #### Display the plk_results of the calculations to the user for each increasing increment. #### (( ++PLK_CNT )) if ( (( ${PLK_DSP_DESMIN} == ${PLK_TRUE} )) && (( ${PLK_CNT} > ${#PLK_OBSTR_CUTOUT[@]} )) ) || ( (( ${PLK_DSP_DESMIN} == ${PLK_FALSE} )) && (( ${PLK_TOG_DESMIN} == ${PLK_FALSE} )) && (( ${PLK_CNT} > ${#PLK_OBSTR_CUTOUT[@]} )) ) then PLK_CNT="0" for PLK_IDX in "${!PLK_RESULT[@]}" do (( PLK_TOG_HEADER == FALSE )) && (( PLK_IDX == 0 )) && continue stdout_zbksh "${PLK_RESULT[${PLK_IDX}]}" unset PLK_RESULT[${PLK_IDX}] done if (( PLK_TOG_HEADER == TRUE )) then PLK_WASTETOTAVG=$(( ${PLK_WASTETOTAL} / ${#PLK_OBSTR_CUTOUT[@]} )) PLK_FLOAT_WTA="${PLK_WASTETOTAVG%????}.${PLK_WASTETOTAVG:${#PLK_WASTETOTAVG}-4:2}" stdout_zbksh "Averaged Percentage Waste Cut-Off from Sum of all Gap Row Cut-Off = ${PLK_FLOAT_WTA#.}%" PLK_WASTETOTAL="0" fi fi done unset PLK_RESULT PLK_CNT="1" done