Piston Rod
Let’s do a simple piston this morning. I am hopeful that it’ll be easy.
The ConRod, which I should probably have named ConnectingRod, generally connects to a piston, or a piston rod, which travels in a straight line back and forth, pushed by the ConRod. We’ll represent it graphically by a simple t-shaped graphic, or perhaps just a thick line. In actual application, I believe that the piston will typically be the end of a chain of components, and while it will have some physical extent, it will not serve as a parent for any other object. Nonetheless, I think we’ll give it a length and some kind of output point. Since the piston can be angled, it will need an angle as well.
All of these details are subject to change, as we learn, and possibly again as we try to apply this program somewhere, if we ever do. So we’ll hang loose—but then we always do.
As is our fashion, we’ll drive the thing with some tests. Rather than work out specific geometry, we’ll assume a useful verify method and then populate it.
class Piston:
def __init__(self, *, parent):
self._parent = parent
class TestPiston:
def verify(self, piston):
assert False
def test_exists(self):
piston = Piston(parent=None)
self.verify(piston)
That’s enough to fail, and it does. PyCharm let me down a bit. I had forgotten the self. on the call to verify, and without a word, PyCharm imported a verify from enum. That confused me for a moment. There’s probably a configuration setting for that, but PyCharm settings are a jungle that I don’t care to enter just now.
Now I need to make this thing actually work. I want it to have a length, and an angle. We’ll need a parent, and I’d prefer not to have to rig up a complete linkage story test to drive out the rather simple code that we need. A test double of some kind may be needed.
First the constructor. I’ll just add in the new parameters in __init__ and PyCharm will demand that I fill them in.
This got a bit much but didn’t take long:
class TestPiston:
def verify(self, piston):
piston.update()
assert piston._start == piston._parent._finish
assert piston._start.distance(piston._finish) == piston._length
delta = piston._finish - piston._start
assert math.atan2(delta.y, delta.x) == piston._radians
def test_basic(self):
class Parent:
def __init__(self, finish):
self._finish = finish
fin = vector(3,7)
piston = Piston(parent=Parent(fin), length=2, angle=0)
self.verify(piston)
class Piston:
def __init__(self, *, parent, length, angle):
self._start = None
self._finish = None
self._parent = parent
self._length = length
self._radians = angle*math.pi/180
def update(self):
self._start = self._parent._finish
finish_offset = vector(self._length,0).rotate_xy(self._radians)
self._finish = self._start + finish_offset
I just made a local class Parent to hold the info needed for the test. Let’s try a harder test, and if you’ll permit, I think I’ll print some values, despite my confidence in the test. I’ll move the class outside the method and test class, so that I can reach it.
def test_45(self):
fin = vector(3,7)
piston = Piston(parent=Parent(fin), length=2, angle=45)
self.verify(piston)
I had to change the test to use pytest.approx, no surprise there, I almost did it at the beginning. Values were within 10 to the minus lots of digits. I didn’t print anything, but now let’s add a Piston to the drawing example and see what we get.
def init_draw(self, canvas):
s = self._start
f = self._finish
self.line = canvas.create_line(s.x, s.y, f.x, f.y, width=6, fill='green')
def draw(self, canvas):
s = self._start
f = self._finish
canvas.coords(self.line, s.x, s.y, f.x, f.y)
And in the drawing creation, of course, we add:
piston = Piston(parent=con_rod, length=piston_length, angle=tilt_angle)
components.append(piston)
And the result, ah my foes and oh my friends, it makes a lovely line:
Summary
Let’s sum up and get to having a Saturday. This code works, but it isn’t “right”. It’s accessing private members of its parent. We still need some sensible and consistent way to do that. I have not yet moved the code to the ‘src’ tree: it’s still in tst'. And probably other things as well.
But I can show it to my customer as progress, and the hard part, such as it was, is done. That there remains about as much time to finish it as we’ve spent so far, well, that’s OK.
I am pleased, and satisfied with the morning’s effort. See you soon!