Presumably, I “just” have to plug this function into a class and I should be good to go. We’ll see. (Hmm, seems to have gone smoothly. What am I missing?)

First, we’ll review the API of my current ConRod class (which is known not to work correctly, that’s why we’ve been working on this function for the past few days). Check that we’re green and committed. Check.

Here’s the existing class. More to it than one might have hoped:

class ConRod:
    def __init__(self, constraint: Wheel, radius, length, tilt_angle):
        self._constraint = constraint
        self._radius = radius
        self._length = length
        self._tilt_radians = tilt_angle * math.pi / 180
        self._lead_radians = 0 # to be provided in due time
        self.line = None

    def constrained(self, number):
        crank = self.crank_offset()
        x_proj = math.sqrt(self._length * self._length - crank.y * crank.y)
        constrained_x = crank.x + x_proj
        un_rotated = vector(constrained_x, 0, 0)
        rotated = un_rotated.rotate_xy(self._tilt_radians)
        center = self._constraint.constrained(0) # constrained?
        return rotated + center

    def init_draw(self, canvas):
        target = self.constrained(0)
        crank = self.crank_position()
        self.line = canvas.create_line(
            crank.x, crank.y,
            target.x, target.y,
            width=3, fill="red")
        wc = self._constraint.constrained(0)
        tg = vector(2*self._length, 0, 0).rotate_xy(self._tilt_radians)
        self.guide = canvas.create_line(wc.x, wc.y, wc.x+tg.x, wc.y+tg.y, fill="blue")
        one = vector(1,0).rotate_xy(self._tilt_radians)
        half = wc + one + tg * 0.5
        s = 0.1
        canvas.create_oval(half.x-s, half.y-s, half.x+s, half.y+s, fill='red')


    def crank_offset(self):
        return self._constraint.crank_offset(self._radius, self._tilt_radians)

    def crank_position(self):
        return self._constraint.crank_position(self._radius, self._tilt_radians)

    def draw(self, canvas):
        target = self.constrained(0)
        crank = self.crank_position()
        dist = crank.distance(target)
        canvas.coords(self.line,
                      crank.x, crank.y,
                      target.x, target.y)

    def update(self):
        pass

It computes its results live, rather than when update is called. We’ll fix that. It has drawing built in, as do my other very few linkage objects. Drawing should probably be deferred to some other object: we’ll not do that now.

We have methods to deliver the key values that we need, so we should be able to cache what we need or compute it on the fly as needed. I think we should cache: that’s the idea of the update method, so that a linkage component can be used as a parent multiple times without excess calculation.

The ConRod as written doesn’t handle lead_angle but our function does, so we’ll accept that value.

Let’s move the functions over to the conrod.py file. PyCharm obliges, and we’re still green. Commit.

Change signature to accept lead_angle:

class ConRod:
    def __init__(self, constraint: Wheel, radius, length, lead_angle, tilt_angle):
        self._constraint = constraint
        self._radius = radius
        self._length = length
        self._tilt_radians = tilt_angle * math.pi / 180
        self._lead_radians = lead_angle * math.pi / 180
        self.line = None

I see that there are a few trivial tests for the ConRod. Those should suffice, given the many tests we have for the functions, but we’ll decide later. With any luck at all (danger, Will Robinson, warning warning) those few will tell us whether we’ve got the function properly in place.

Commit again. I will want lots of save points to revert to.

For my first attempt at plugging this in, let’s try putting it into update and testing it there. We can borrow one of the existing function tests and put it in the conrod tests.

I fetch a test and modify it:

    def test_at_45_offset(self):
        origin = vector(2,3)
        wheel_radians = 0
        piston_angle = 45
        lead_angle = 0
        radius = 1
        length = 5
        wheel = Wheel(constraint=origin, angle_degrees=wheel_radians)
        conrod = ConRod(
            constraint=wheel, radius=radius,
            length=length, lead_angle=lead_angle, piston_angle=piston_angle)
        conrod.update()
        start = conrod.crank_position()
        finish = conrod.constrained(0)
        self.verify_constraints(
            radius, start, finish, origin, length, piston_angle)

That should be close enough to fail. It does. Now modify the class:

This got to be a bit much, and as it stands, it’s not right:

class ConRod:
    def __init__(self, constraint: Wheel, radius, length, lead_angle, piston_angle):
        self._constraint = constraint
        self._radius = radius
        self._length = length
        self._piston_radians = piston_angle * math.pi / 180
        self._lead_radians = lead_angle * math.pi / 180
        self.line = None

    def update(self):
        origin = self.origin()
        radians = self.wheel_radians()
        self._start, self._finish = con_rod_ends_complete(
            piston_radians = self._piston_radians,
            origin=origin,
            radius=self._radius,
            lead_radians=self._lead_radians,
            wheel_radians=radians,
            length=self._length
        )

    def origin(self):
        return self._constraint.constraint()
    def wheel_radians(self):
        return self._constraint.angle_radians()

    def constrained(self, number):
        return self._finish
    def crank_position(self):
        return self._start

Tests are failing, let’s see what we have wrong. Ah. My tests don’t call update. Let’s do that. They still fail. No worries, yet. Probably just typos, missing stuff, trivial. If not, we may have to revert and try something simpler.

The constraint is a property, not a method. Fix that. Same for wheel_radians. Two tests begin to run. The error message on that one is evocative:

0.7853981633974482 != 45 ± 4.5e-05

Degrees vs radians issue. Easy! Fix the test. We are green.

I could commit. I guess I will, though I don’t feel done. I commit, and run the graphical test. The conrod tracks correctly!

I think we’re good, but I’m tired. Why? I think I was a bit tense about this rather large one-step change. Whatever the reason, I’ll push this article and have a break.

I am about 90% sure it’s working. Definitely needs refactoring, including deciding whether we’re going to use properties or not.

See you next time!