Skip to content

Instantly share code, notes, and snippets.

@Sh-ui
Last active March 25, 2019 16:31
Show Gist options
  • Save Sh-ui/63c3901bc7ab09124163b706f1199ea7 to your computer and use it in GitHub Desktop.
Save Sh-ui/63c3901bc7ab09124163b706f1199ea7 to your computer and use it in GitHub Desktop.
a tool to solve and scale right triangles
3 versions of my triangle scalar tool
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
Copyright (c) 2018, Ian Schuepbach
License, MIT
Version 1.1.2
contact author at, shuey298@gmail.com
triangle_tool.py:
This is a cmd tool that allows users to scale and/or solve for all values
in a right triangle, including sides a, b, and c -- and angles A, B, and C
The tool also allows control over what/how values are output to console,
and specificity in control over rounding, scaling by a reference side, etc.
"""
# all of these modules are required to run the program
from collections import OrderedDict as oDct # keeps dictionaries from scrambling
from trianglesolver import solve, degree # contains sin and cosine functions
from termcolor import colored, cprint # allows colored outputs
import numpy as np # contains math array functions
import cmd # allows command line functionality
def printDict(dictionary):
# prints dictionaries with key and value on each line
for k, v in dictionary.items(): print(k + ' =', v)
def noNones(*checks):
# will return False if any of the input arguments contain "None"
return False if any(check is None for check in checks) else True
def argify(string_in, ref_dict):
# ref_dict is copied to not alter it
fin = ref_dict.copy()
# string spaces are replaced with commas
# to prep for evaluation
string = string_in.replace(' ', ', ')
# evaluate the string as actual code, and parse as dict
temp = eval('dict({})'.format(string))
# find matching dictionary keys that are in the ref_dict
# and in the newly parsed dictionary
matched = {k: temp[k] for k in temp if k in fin}
# update the dict we copied earlier using any parsed values
# that match the ref_dict
fin.update(matched)
return fin
def solvify(items):
# used to return all solved angles and sides
a,b,c,A,B,C = items
# trianglesolver module uses rads so the degree measures inputed
# by the user must be converted using the "degree" constant
if A is not None: A = A * degree
if B is not None: B = B * degree
C = C * degree
try:
# try solving using only one missing side
if a is None: outs = solve(c=c, b=b, C=C)
elif b is None: outs = solve(a=a, c=c, C=C)
elif c is None: outs = solve(a=a, b=b, C=C)
# no else statement needed, if the var is NOT None
# the program will simply make no changes
except:
# try solving using 2 missing sides (side and angle)
if noNones(a, A): outs = solve(a=a, A=A,C=C)
elif noNones(a, B): outs = solve(a=a, B=B, C=C)
elif noNones(b, A): outs = solve(b=b, A=A, C=C)
elif noNones(b, B): outs = solve(b=b, B=B, C=C)
elif noNones(c, A): outs = solve(c=c, A=A, C=C)
elif noNones(c, B): outs = solve(c=c, B=B, C=C)
finally:
# no matter what happens always attempt to
# output the set of sides and angles
a,b,c,A,B,C = outs
# the angle values must be converted back to degrees
# using the degree constant once again (dividing)
return ([a, b, c, A / degree, B / degree, C / degree])
def triangify(items, output_format, prec, scale=None, side='c'):
# items will be a list containing the values for the angles and sides
# the newly created solvify function is used to make sure all the variables
# used in this function actually have a numeric value
a,b,c,A,B,C = solvify(items)
# side defines which side to use as a reference when scaling the number
# though the re-ordering process below isn't needed unless the user wants
# to scale their triangle, it is still necessary to to create lists for
# the proper keys and variables.
# ---
# check which side | side vals | side keys | name val location
if side is 'c': order = [a, b, c] + ['a', 'b', 'c']; set = [0,1]
if side is 'b': order = [c, a, b] + ['c', 'a', 'b']; set = [1,2]
if side is 'a': order = [c, b, a] + ['c', 'b', 'a']; set = [2,1]
# scale will be None when no argument for it is called
# this part will be skipped if scale is not called
if scale is not None:
# using np.array operation on a per element basis
sides_arrange = np.array(order[:3]) # order from side pick
# divide each value by the 3rd value in the set
# by re-ordering the set to put the reference side at the end of "order"
# it allows division freely where the last value in order will always be
# one this makes sure the reference side is first scaled down and the
# other sides act accordingly: this is done below
sides_scaling = sides_arrange / order[2]
# reference side is now equal to 1, and the other sides are decimal values
# that are equal to sine and cosine, multiplying the desired new length by
# these decimals essentially applies a ratio to the given (scale) number
sVals = (sides_scaling * scale).tolist()
else: sVals = order[:3] # just use the first 3 values of order w/o scaling
# the last three string in "order"
sKeys = order[3:]
# sum the values to get the perimeter
P = np.sum(sVals)
# use the name val location declared when order was defined to make
# sure the calculation for area use both legs of the triangle
S = (sVals[set[0]] * sVals[set[1]]) / 2
# list with labels for the sides (sKeys) and the angles, perimeter and area
fKeys = sKeys + ['A','B','C','P','S']
# list with values for sides (sVals) and angle, perimeter and area values
toVals = sVals + [A, B, C, P, S]
# if the prec argument is a number round all of the
# values to that number of digits after the decimal
if type(prec) is int: fVals = np.around(np.array(toVals),prec)
# if prec is specified but not a number, round vals to the nearest integer
else: fVals = (np.around(np.array(toVals), 0)).astype(int)
# zip the labels and values together to put all the values in one dict
triangle = oDct(zip(fKeys, fVals))
# allows user to type all to easily get every value (this is the default)
if output_format is 'all': output_format = 'abcABCPS'
# the final output will only contain keys that are named in the output_format
# and it will output them in the order specified by the output_format
fin = {k: triangle[k] for k in list(output_format) if k in triangle}
return fin
def helper(lines: list, varify=False, name='options', color=False):
# ending line of helper
constant = [f'\n type "{name}" for more info']
# combine the inputed list with the ending line
outs = [''] + lines + constant + ['']
# create blank list to append to
fin = []
# make sure each list element is colored
for i in outs:
i = ' ' + i
if color: fin.append(colored(i, color))
else: fin.append(i)
# if the argument "varify" is true helper will return a list
if varify: return fin
# unless varify is True, print all list items
else: print(*fin, sep='\n')
defaults = oDct([
('P', None), # perimeter
('S', None), # area
('a', None), # leg a
('b', None), # leg b
('c', None), # hypotenuse c
('A', None), # angle A (opposite leg a)
('B', None), # angle B (opposite leg b)
('C', 90), # angle C (will always be a right angle)
('prec', 2), # amount of digits after the decimal
('scale', 1), # number to scale a reference side to
('side', 'c'), # reference side to scale based on
('output', 'all')]) # how to format the output
defaultsInfo = [
'P = None (perimeter)',
'S = None (area)',
'a = None (leg/side a)',
'b = None (leg/side b)',
'c = None (hypotenuse c)',
'A = None (angle A)',
'B = None (angle B)',
'C = 90 (the right angle)',
'prec = 2 (number of digits after decimal)',
'scale = 1 (number to scale to)',
'side = "c" (side to scale by)',
'output = "all" (default outputs all above values)']
solveInfo = 'solve a right triangle with 2 sides or side and angle'
scaleInfo = 'scale a right triangle with 2 sides or side and angle, along' \
+ '\n ' + 'with a reference side length and a new side length to scale to'
argOptions = [
'possible outputs include:',
' - sides a, b, and c',
' - angles A, B, and C',
' - P for perimeter',
' - S for area', '',
'sides: a=LEN, b=LEN, c=LEN type 2 or (1 with angle)',
'angles: A=DEG, B=DEG, C=DEG type 1 with a side',
'output="string of letters" order and specification of outputs (letters)',
' type "abc" for sides or "all" for all, etc.',
'prec=INT number of digits to include after the decimal,',
' type "int" (in quotes) to round to nearest integer',
'side="side" the reference side on the triangle to use ',
' when scaling (type a letter in quotes)',
'scale=LEN the new length to scale the reference side to']
class ScalarCmd(cmd.Cmd):
def default(self, arg):
# this command is run if no other command is passed
print('invalid command, try typing "help"')
def do_quit(self, arg):
''' exits the program'''
cprint('\nexiting...\n', 'red') # prints "exiting..." in red
exit()
def do_credits(self,arg):
''' shows the info and credits docstring'''
cprint(__doc__, 'yellow')
def do_defaults(self,arg):
''' shows all default values for argument options'''
helper(defaultsInfo, color='green')
def help_solve(self):
helper([solveInfo]) # print each line of info using helper function
def help_scale(self):
helper([scaleInfo]) # print each line of info using helper function
def do_options(self,arg):
''' show all available argument options'''
helper(argOptions, name='help', color='green')
def do_solve(self, arg):
# command for solving a triangle with 2 values (no scaling)
try:
# if anything in this block is an error the exception block will trigger
# turn the input arguments into a dictionary
args = argify(arg, defaults)
# get just the sides and angle values from that dictionary
sides_angles = list(args.values())[2:8]
# solve the triangle using the sides and angles from args
# with the output specified by the user (or from defaults)
# and the prec (decimal count) specified by the user (or defaults)
outs = triangify(
sides_angles,
args['output'],
args['prec'])
# print the solved triangle values
print()
printDict(outs)
print() # <- easy blank lines
except Exception:
# this block is called if an error occurs above
# add an additional line to the helper command to specify error
error = 'invalid command' if len(arg) > 2 else 'empty command'
helper([error, solveInfo],'solve','print')
return # return to exit the loop and get ready for another input
def do_scale(self, arg):
# command for solving and scaling a triangle
try:
# if anything in this block is an error the exception block will trigger
# turn the input arguments into a dictionary
args = argify(arg, defaults)
# get just the sides and angle values from that dictionary
sides_angles = list(args.values())[2:8]
# solve the triangle using the sides and angles from args
outs = triangify(
sides_angles,
args['output'], # user or default output format
args['prec'], # user or default decimal count
args['scale'], # user or default scale amount
args['side']) # user or default side to scale based on
# print the solved triangle values
print()
printDict(outs)
print() # <- easy blank lines
except Exception:
# this block is called if an error occurs above
# add an additional line to the helper command to specify error
error = 'invalid command' if len(arg) > 2 else 'empty command'
helper([error, scaleInfo])
return # return to exit the loop and get ready for another input
def main(): # function to format the introduction and run the program
scalar = ScalarCmd()
scalar.prompt = colored('\n>', 'green')
scalar.ruler = colored('-', 'cyan')
scalar.intro = colored(
'\n' * 20 \
+ 'Triangle Scalar Tool' + '\n' \
+ '====================' + '\n' \
+ 'type help/? for commands\n', 'cyan')
scalar.cmdloop()
if __name__ == '__main__': main()
""" Copyright (c) 2018, Ian Schuepbach\n License, MIT\n Version 1.0.1\n contact author at, shuey298@gmail.com\n \n triangle_tool.py:\n This is a cmd tool that allows users to scale and/or solve for all values\n in a right triangle, including sides a, b, and c -- and angles A, B, and C.\n The tool also allows control over what/how values are output to console,\n and specificity in control over rounding, scaling by a reference side, etc."""
from collections import OrderedDict as oDct
from trianglesolver import solve,degree
from termcolor import colored,cprint
import numpy as np
import cmd
def printDict(dictionary):
for k,v in dictionary.items():print(k+' =',v)
def noNones(*checks):
return False if any(check is None for check in checks)else True
def argify(string_in,ref_dict):
fin=ref_dict.copy()
string=string_in.replace(' ',', ')
temp=eval('dict({})'.format(string))
matched={k:temp[k]for k in temp if k in fin}
fin.update(matched)
return fin
def solvify(items):
a,b,c,A,B,C=items
if A is not None:A=A*degree
if B is not None:B=B*degree
C=C*degree
try:
if a is None:outs=solve(c=c,b=b,C=C)
elif b is None:outs=solve(a=a,c=c,C=C)
elif c is None:outs=solve(a=a,b=b,C=C)
except:
if noNones(a,A):outs=solve(a=a,A=A,C=C)
elif noNones(a,B):outs=solve(a=a,B=B,C=C)
elif noNones(b,A):outs=solve(b=b,A=A,C=C)
elif noNones(b,B):outs=solve(b=b,B=B,C=C)
elif noNones(c,A):outs=solve(c=c,A=A,C=C)
elif noNones(c,B):outs=solve(c=c,B=B,C=C)
finally:
a,b,c,A,B,C=outs
return([a,b,c,A/degree,B/degree,C/degree])
def triangify(items,output_format,prec,scale=None,side='c'):
a,b,c,A,B,C=solvify(items)
if side is 'c':order=[a,b,c]+['a','b','c'];set=[0,1]
if side is 'b':order=[c,a,b]+['c','a','b'];set=[1,2]
if side is 'a':order=[c,b,a]+['c','b','a'];set=[2,1]
if scale is not None:
sides_arrange=np.array(order[:3])
sides_scaling=sides_arrange/order[2]
sVals =(sides_scaling*scale).tolist()
else:sVals=order[:3]
sKeys=order[3:]
P=np.sum(sVals)
S=(sVals[set[0]]*sVals[set[1]])/2
fKeys=sKeys+['A','B','C','P','S']
toVals=sVals+[A,B,C,P,S]
if type(prec)is int:fVals=np.around(np.array(toVals),prec)
else:fVals=(np.around(np.array(toVals),0)).astype(int)
triangle=oDct(zip(fKeys,fVals))
if output_format is 'all':output_format='abcABCPS'
fin={k:triangle[k]for k in list(output_format)if k in triangle}
return fin
def helper(lines:list,varify=False,name='options',color=False):
constant=[f'\n type "{name}" for more info']
outs=['']+lines+constant+['']
fin=[]
for i in outs:
i=' '+i
if color:fin.append(colored(i,color))
else:fin.append(i)
if varify:return fin
else:print(*fin,sep='\n')
defaults=oDct([('P',None),('S',None),('a',None),('b',None),('c',None),('A',None),('B',None),('C',90),('prec',2),('scale',1),('side','c'),('output','all')])
defaultsInfo=['P = None (perimeter)','S = None (area)','a = None (leg/side a)','b = None (leg/side b)','c = None (hypotenuse c)','A = None (angle A)','B = None (angle B)','C = 90 (the right angle)','prec = 2 (number of digits after decimal)','scale = 1 (number to scale to)','side = "c" (side to scale by)','output = "all" (default outputs all above values)']
solveInfo='solve a right triangle with 2 sides or side and angle'
scaleInfo='scale a right triangle with 2 sides or side and angle, along' +'\n '+'with a reference side length and a new side length to scale to'
argOptions=['possible outputs include:',' - sides a, b, and c',' - angles A, B, and C',' - P for perimeter',' - S for area','','sides: a=LEN, b=LEN, c=LEN type 2 or (1 with angle)','angles: A=DEG, B=DEG, C=DEG type 1 with a side','output="string of letters" order and specification of outputs (letters)',' type "abc" for sides or "all" for all, etc.','prec=INT number of digits to include after the decimal,',' type "int" (in quotes) to round to nearest integer','side="side" the reference side on the triangle to use ',' when scaling (type a letter in quotes)','scale=LEN the new length to scale the reference side to']
class ScalarCmd(cmd.Cmd):
def default(self,arg):
print('invalid command, try typing "help"')
def do_quit(self,arg):
cprint('\nexiting...\n','red')
exit()
def do_credits(self,arg):
cprint(__doc__,'yellow')
def do_defaults(self,arg):
helper(defaultsInfo,color='green')
def help_solve(self):
helper([solveInfo])
def help_scale(self):
helper([scaleInfo])
def do_options(self,arg):
helper(argOptions,name='help',color='green')
def do_solve(self,arg):
try:
args=argify(arg,defaults)
sides_angles=list(args.values())[2:8]
outs=triangify(sides_angles,args['output'],args['prec'])
print()
printDict(outs)
print()
except Exception:
error='invalid command' if len(arg)>2 else 'empty command'
helper([error,solveInfo],'solve','print')
return
def do_scale(self,arg):
try:
args=argify(arg,defaults)
sides_angles=list(args.values())[2:8]
outs=triangify(sides_angles,args['output'],args['prec'],args['scale'],args['side'])
print()
printDict(outs)
print()
except Exception:
error='invalid command' if len(arg)>2 else 'empty command'
helper([error,scaleInfo])
return
def main():
scalar=ScalarCmd()
scalar.prompt=colored('\n>','green')
scalar.ruler =colored('-','cyan')
scalar.intro =colored('\n'*20+'Triangle Scalar Tool'+'\n'+'===================='+'\n'+'type help/? for commands\n','cyan')
scalar.cmdloop()
if __name__=='__main__':main()
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
Copyright (c) 2018, Ian Schuepbach
License, MIT
Version 1.1.2
contact author at, shuey298@gmail.com
triangle_tool.py:
This is a cmd tool that allows users to scale and/or solve for all values
in a right triangle, including sides a, b, and c -- and angles A, B, and C
The tool also allows control over what/how values are output to console,
and specificity in control over rounding, scaling by a reference side, etc.
"""
from collections import OrderedDict as oDct
from trianglesolver import solve, degree
from termcolor import colored, cprint
import numpy as np
import os
import cmd
def printDict(dictionary):
for k, v in dictionary.items(): print(k + ' =', v)
def noNones(*checks):
return False if any(check is None for check in checks) else True
def argify(string_in, ref_dict):
fin = ref_dict.copy()
string = string_in.replace(' ', ', ')
temp = eval('dict({})'.format(string))
matched = {k: temp[k] for k in temp if k in fin}
fin.update(matched)
return fin
def solvify(items):
a,b,c,A,B,C = items
if A is not None: A = A * degree
if B is not None: B = B * degree
C = C * degree
try:
if a is None: outs = solve(c=c, b=b, C=C)
elif b is None: outs = solve(a=a, c=c, C=C)
elif c is None: outs = solve(a=a, b=b, C=C)
except:
if noNones(a, A): outs = solve(a=a, A=A,C=C)
elif noNones(a, B): outs = solve(a=a, B=B, C=C)
elif noNones(b, A): outs = solve(b=b, A=A, C=C)
elif noNones(b, B): outs = solve(b=b, B=B, C=C)
elif noNones(c, A): outs = solve(c=c, A=A, C=C)
elif noNones(c, B): outs = solve(c=c, B=B, C=C)
finally:
a,b,c,A,B,C = outs
return ([a, b, c, A / degree, B / degree, C / degree])
def triangify(items, output_format, prec, scale=None, side='c'):
a,b,c,A,B,C = solvify(items)
if side is 'c': order = [a, b, c] + ['a', 'b', 'c']; set = [0,1]
if side is 'b': order = [c, a, b] + ['c', 'a', 'b']; set = [1,2]
if side is 'a': order = [c, b, a] + ['c', 'b', 'a']; set = [2,1]
if scale is not None:
sides_arrange = np.array(order[:3])
sides_scaling = sides_arrange / order[2]
sVals = (sides_scaling * scale).tolist()
else: sVals = order[:3]
sKeys = order[3:]
P = np.sum(sVals)
S = (sVals[set[0]] * sVals[set[1]]) / 2
fKeys = sKeys + ['A','B','C','P','S']
toVals = sVals + [A, B, C, P, S]
if type(prec) is int: fVals = np.around(np.array(toVals),prec)
else: fVals = (np.around(np.array(toVals), 0)).astype(int)
triangle = oDct(zip(fKeys, fVals))
if output_format is 'all': output_format = 'abcABCPS'
fin = {k: triangle[k] for k in list(output_format) if k in triangle}
return fin
def helper(lines, varify=False, name='options', color=False):
constant = [f'\n type "{name}" for more info']
outs = [''] + lines + constant + ['']
fin = []
for i in outs:
i = ' ' + i
if color: fin.append(colored(i, color))
else: fin.append(i)
if varify: return fin
else: print(*fin, sep='\n')
defaults = oDct([
('P', None),
('S', None),
('a', None),
('b', None),
('c', None),
('A', None),
('B', None),
('C', 90),
('prec', 2),
('scale', 1),
('side', 'c'),
('output', 'all')])
defaultsInfo = [
'P = None (perimeter)',
'S = None (area)',
'a = None (leg/side a)',
'b = None (leg/side b)',
'c = None (hypotenuse c)',
'A = None (angle A)',
'B = None (angle B)',
'C = 90 (the right angle)',
'prec = 2 (number of digits after decimal)',
'scale = 1 (number to scale to)',
'side = "c" (side to scale by)',
'output = "all" (default outputs all above values)']
solveInfo = 'solve a right triangle with 2 sides or side and angle'
scaleInfo = 'scale a right triangle with 2 sides or side and angle, along' \
+ '\n ' + 'with a reference side length and a new side length to scale to'
argOptions = [
'possible outputs include:',
' - sides a, b, and c',
' - angles A, B, and C',
' - P for perimeter',
' - S for area', '',
'sides: a=LEN, b=LEN, c=LEN type 2 or (1 with angle)',
'angles: A=DEG, B=DEG, C=DEG type 1 with a side',
'output="string of letters" order and specification of outputs (letters)',
' type "abc" for sides or "all" for all, etc.',
'prec=INT number of digits to include after the decimal,',
' type "int" (in quotes) to round to nearest integer',
'side="side" the reference side on the triangle to use ',
' when scaling (type a letter in quotes)',
'scale=LEN the new length to scale the reference side to']
class ScalarCmd(cmd.Cmd):
def default(self, arg):
print('invalid command, try typing "help"')
def do_quit(self, arg):
''' exits the program'''
cprint('\nexiting...\n', 'red')
exit()
def do_credits(self,arg):
''' shows the info and credits docstring'''
cprint(__doc__, 'yellow')
def do_defaults(self,arg):
''' shows all default values for argument options'''
helper(defaultsInfo, color='green')
def help_solve(self):
helper([solveInfo])
def help_scale(self):
helper([scaleInfo])
def do_options(self,arg):
''' show all available argument options'''
helper(argOptions, name='help', color='green')
def do_solve(self, arg):
try:
args = argify(arg, defaults)
sides_angles = list(args.values())[2:8]
outs = triangify(
sides_angles,
args['output'],
args['prec'])
print()
printDict(outs)
print()
except Exception:
error = 'invalid command' if len(arg) > 2 else 'empty command'
helper([error, solveInfo])
return
def do_scale(self, arg):
try:
args = argify(arg, defaults)
sides_angles = list(args.values())[2:8]
outs = triangify(
sides_angles,
args['output'],
args['prec'],
args['scale'],
args['side'])
print()
printDict(outs)
print()
except Exception:
error = 'invalid command' if len(arg) > 2 else 'empty command'
helper([error, scaleInfo])
return
def main():
scalar = ScalarCmd()
scalar.prompt = colored('\n> ', 'green')
scalar.ruler = colored('-', 'cyan')
scalar.intro = colored(
'\n' * 20 \
+ 'Triangle Scalar Tool' + '\n' \
+ '====================' + '\n' \
+ 'type help/? for commands\n', 'cyan')
scalar.cmdloop()
if __name__ == '__main__': main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment