Skip to content

Commit bbaf1d5

Browse files
committed
add units converter
1 parent 48d6f10 commit bbaf1d5

File tree

3 files changed

+188
-4
lines changed

3 files changed

+188
-4
lines changed

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
## PyCalc, a collection of simple calculators in PyGame
22

3-
Three scientific calculators (using binary internally):
3+
### Overview
44

5-
pycalc_light, pycalc_dark, pycalc_lcars
5+
Four scientific calculators (using binary numbers internally):
66

7-
Four-function calculator (using decimal internally, based on Python's *decimal* module):
7+
* pycalc_light (light theme)
8+
* pycalc_dark (dark theme)
9+
* pycalc_lcars (LCARS theme)
10+
* pycalc_units (converts from US units to metric units, e.g. to l, m, m², and kg)
811

9-
pycalc_desk
12+
There is also pycalc_desk, a four-function calculator using decimal internally, based on Python's *decimal* module.
13+
14+
### Usage
1015

1116
The **decimal calculator** is useful to avoid the rounding errors of the binary calculators due to their decimal➜binary➜decimal conversions, which is better for e.g. financial calculations. It also has memory (M+ to add, MR to recall value, AC to clear) and the comma is used as a decimal separator in results.
1217

18+
For the **unit converter**, just attach the unit to the numerical value. E.g. to convert 1.5 quarts to liters, use "1.5_qt". If you would like to _divide_ by a value, put the number and unit in parentheses, e.g. "(1.5_qt)", otherwise the result will be wrong!
19+
1320
The **scientific calculators** work the same as the Python math module in interactive mode:
1421

1522
* They use binary numbers internally, not decimal, so it has the usual rounding errors when comparing its results to a decimal calculator, e.g. "0.1 + 0.2" does not evaluate to exactly 0.3.
@@ -43,6 +50,8 @@ The hex buttons (a to f) and "j" can also be used as these predefined variables
4350

4451
![screenshot3](pycalc3.png "PyCalc screenshot (decimal version)")
4552

53+
![screenshot3](pycalc4.png "PyCalc screenshot (units converter)")
54+
4655
### License
4756

4857
Public Domain / CC0

pycalc4.png

13.4 KB
Loading

pycalc_units.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#!/usr/bin/env python
2+
3+
# Python calculator for conversion to SI units
4+
5+
import pygame
6+
from math import *
7+
8+
BACKGROUND = 255, 255, 255
9+
BORDER = 2
10+
RES = 600,495 # initial window size
11+
HSIZE, VSIZE = 6, 9 # button grid dimension (top row is for display)
12+
MAXLEN = 100 # maximum digits
13+
14+
# font name
15+
fn = "fsex2.ttf"
16+
17+
us_units = (
18+
("_floz", "*0.0295735295625"), # l
19+
("_pt", "*0.473176473"), # l
20+
("_qt", "*0.946352946"), # l
21+
("_gal", "*3.785411784"), # l
22+
("_nm", "*1852"), # m
23+
("_in", "*0.0254"), # m
24+
("_ft", "*0.3048"), # m
25+
("_sqft", "*0.09290304"), # m²
26+
("_acre", "*4046.8564224"), # m²
27+
("_yd", "*0.9144"), # m
28+
("_mi", "*1609.344"), # m
29+
("_lb", "*0.45359237"), # kg
30+
)
31+
32+
# button layout
33+
bmap = """_in _ft _yd _mi ** C
34+
_gal exp( sin( cos( tan( log10(
35+
_pt pi abs( **2 sqrt( log(
36+
_qt deg( SCI ( ) /
37+
_floz rad( 7 8 9 *
38+
_sqft fah( 4 5 6 -
39+
_acre cel( 1 2 3 +
40+
_nm _lb 0 . j ="""
41+
42+
# color indices for buttons
43+
col_ind = """333312
44+
311111
45+
311111
46+
311114
47+
315554
48+
315554
49+
315554
50+
335554"""
51+
52+
col_ind = col_ind.splitlines()
53+
54+
# RGB colors for each color index
55+
cols = (
56+
(255, 255, 255),
57+
(255,96,117),
58+
(255,237,83),
59+
(142,148,255),
60+
(228,142,215),
61+
(159,229,110),
62+
)
63+
64+
# text colors and sizes
65+
button_rgb = 0, 0, 0
66+
display_rgb = 0, 0, 0
67+
size_button = 30
68+
size_disp = 55
69+
70+
but = []
71+
for l in bmap.splitlines():
72+
bb = l.split()
73+
but.append(bb)
74+
75+
def deg(x):
76+
return degrees(x)
77+
78+
def rad(x):
79+
return radians(x)
80+
81+
def fah(x):
82+
# Celsius -> Fahrenheit
83+
return (x * 9/5) + 32
84+
85+
def cel(x):
86+
# Fahrenheit -> Celsius
87+
return (x - 32) * 5/9
88+
89+
class PyCalc:
90+
def __init__(self):
91+
pygame.init()
92+
self.res = RES
93+
self.screen = pygame.display.set_mode(self.res, pygame.RESIZABLE)
94+
pygame.display.set_caption('PyCalc (units)')
95+
self.screen.fill(BACKGROUND)
96+
self.font = pygame.font.Font(fn, size_button)
97+
self.fontres = pygame.font.Font(fn, size_disp)
98+
self.clock = pygame.time.Clock()
99+
self.inp = ""
100+
101+
def events(self):
102+
global a, b
103+
104+
for event in pygame.event.get():
105+
if event.type == pygame.QUIT: self.running = False
106+
if event.type == pygame.VIDEORESIZE:
107+
self.res = event.w, event.h
108+
self.last = 0
109+
self.screen = pygame.display.set_mode(self.res, pygame.RESIZABLE)
110+
if event.type == pygame.MOUSEBUTTONDOWN:
111+
x, y = pygame.mouse.get_pos()
112+
boxx, boxy = self.res[0] // HSIZE, self.res[1] // VSIZE
113+
X, Y = x // boxx, y // boxy
114+
if Y == 0 or Y > VSIZE - 1 or X > HSIZE - 1:
115+
return
116+
ci = but[Y - 1][X]
117+
if ci == "=": # evaluate expression
118+
try:
119+
inp2 = self.inp
120+
for u1, u2 in us_units:
121+
inp2 = inp2.replace(u1, u2)
122+
self.inp = str(eval(inp2))
123+
a = eval(self.inp)
124+
except:
125+
self.inp += " *ERROR*"
126+
elif ci == "C": # clear screen
127+
self.inp = ""
128+
elif ci == "SCI": # convert to scientific notation
129+
try:
130+
self.inp = "%e" % float(self.inp)
131+
except:
132+
pass
133+
else: # or add button text to input
134+
self.inp += but[Y - 1][X]
135+
136+
def run(self):
137+
self.running = True
138+
while self.running:
139+
self.clock.tick(10)
140+
self.events()
141+
self.update()
142+
pygame.quit()
143+
144+
def update(self):
145+
self.screen.fill(BACKGROUND)
146+
boxx, boxy = self.res[0] // HSIZE, self.res[1] // VSIZE
147+
for y in range(1, VSIZE):
148+
for x in range(HSIZE):
149+
ci = int(col_ind[y - 1][x])
150+
pygame.draw.rect(self.screen, cols[ci],
151+
[x * boxx + BORDER, y * boxy + BORDER,
152+
boxx - 2 * BORDER, boxy - 2 * BORDER])
153+
t = but[y - 1][x]
154+
if t[-1] == "(" and t != "(":
155+
t = t[:-1]
156+
if t != "#":
157+
tr = self.font.render(t, True, button_rgb)
158+
ox = max(0, (boxx - tr.get_width()) // 2)
159+
oy = max(0, (boxy - tr.get_height()) // 2)
160+
self.screen.blit(tr, (x * boxx + BORDER + ox, y * boxy + BORDER + oy))
161+
if len(self.inp) < MAXLEN: # check if maximum digits exceeded
162+
t = self.inp
163+
else:
164+
try:
165+
self.inp = "%e" % float(self.inp)
166+
t = self.inp
167+
except:
168+
t = "*TOO LONG*"
169+
tr = self.fontres.render(t, True, display_rgb)
170+
self.screen.blit(tr, (0, 0))
171+
pygame.display.flip()
172+
173+
app = PyCalc()
174+
app.run()
175+

0 commit comments

Comments
 (0)