JanetRossini.github.io

Lua, LSL, Blender, Python in Second Life


Project maintained by JanetRossini

Linkages II

Sep 11, 2025 • [designlinkagesluatesting]


It has been a while. I’ve been working in Python, where I could draw and export moving linkage diagrams. But I am motivated, I can’t imagine why, to return to Luau and SLua for this work. Weird twists and turns, including a brief detour into VSCode. Weird.

In the prior article, way back in September, I had just begun a new file, ‘linkages2’,so now I’ll need to review that versus ‘linkages’, and I’ve done a lot of Python work since then, so I’ll need to see how that squares with what we have here. We’ll start with what I remember and then let the code take part in our work.

In the Python version, and not in the Luau versions, I’ve built a semi-smart collection called Linkage, a collection of simple objects called Element, each of which holds a component (a wheel, bell crank, etc), and a parent, another component on which the current one depends. So you’d have a FixedPoint upon which a Wheel depends, upon which a SideRod depends, and so on.

By the nature of that format, the Linkage collection will be built with the parent of any given component in the collection before its dependents. So this means that we can calculate the details of the linkage by initializing any of the base components and then just go through the collection updating. Any child’s parents will be updated before the child, so we have no recursion and no tree searching to do.

For this to work, the Components all need to respond to the same messages, of which a key message is update, which takes two input values and returns two outputs.

The outputs from update are start and finish, the vector positions that define the position and rotation of the component. start, by convention, will probably be the root of the component as drawn, and finish will typically be the point on that component which the children of that component will use to compute their own start and finish. For example, in a connecting rod, its start will be where it connects to the crank, and its finish will be where it connects to the piston.

Typically, then, the piston will only be interested in the finish value, from which it will work out its own start and finish. This will be a bit more clear when we see some code, I hope.

Anyway, the Linkage object lets us apply any function to all the components, in the right order to allow their parents to be computed before they are, using a function called apply in Linkage class. No matter what function we’re applying, typically draw, the Linkage updates each component before calling that function, using this Python code:

    def apply(
            self,
            func: Callable[[Component, Any ,Any], Any]):
        for element in self._elements.values():
            self.update_element(element)
            element.dump()
            func(element.component, element.start, element.finish)

    def update_element(self, elt: Element):
        parent_s,parent_f = self.fetch_sf(elt.parent)
        elt.start, elt.finish = elt.component.update(parent_s, parent_f)

    def fetch_sf(self, parent:Component):
        element = self._elements.get(
            parent,
            self.Element(None, None))
        return element.start, element.finish

This sequence just means that on every draw cycle, or, in SL, every time we move the linkage, we update before we draw or move. As it should be.

The Point?

The point of all this is that for any linkage, we have useful Components like wheels and cranks and side rods and connecting rods and bell cranks, and we can assemble them from parent to child, put then into the Linkage, and their behavior will all be calculated automatically.

The above is what works in Python, and that may be of value in Blender if we choose to animate by collecting some number of animation points and export them to SL like Puppeteer. That might even work, although I am not clear what we do in between the exported values. Probably linear interpolation between end points will work.

My mission here, at least today, is to replicate the Python objects and their behavior in SLua, which will provide for direct animation with the same “easy” setup as in the Python, where you “just” connect Components in parent-child order and the code takes it from there. We’ll find out whether this makes sense by doing it, though I see no reason why it won’t work.

The Plan?

I have no plan as yet. I see these initial options:

  1. Refactor starting from ‘linkages’, implementing the Linkage class and using it;

  2. Start with an empty file, implement Linkage class, then bring over linkages objects one at a time and wire them in.

  3. Start from scratch and do all the objects anew.

I think #3 is right out. There’s enough good in the existing Luau objects. The ones we have are doing all the necessary math and there’s no reason to do that over. I would surely copy / paste it anyway, or use it as a visual template. I don’t see any value to doing the geometry over again.

Probably #2 would be slightly neater, but it doesn’t take into account all the useful tests we have for the components, which would also have to move or be redone.

I think we’ll start with #1 and see what happens. Well, a modified #1. We’ll save the current version off somewhere as an archive, not just in the repo, then start refactoring and adding capability (typically the update, which isn’t really refactoring).

However

What about linkages2? What’s up with it? Ah. It’s just a few lines, nothing really useful there. We’ll let that be and otherwise ignore it.

I have a single big repo for all my SL work, under git. That will probably be sufficient for what I’m doing.

To It

Let’s begin by creating the Linkage class via testing.

… work continues …

2025-12-08

In the interim, I have begun to work with VSCode, because there is or will be a link from Second Life’s scripts to a VSCode plugin that purports to be helpful. At this writing I am not clear on what it will do: I have not yet tried the plug-in, just getting used to VSCode.

I have a new folder on my desktop ‘vscode’, in which I presently have two folders for small projects from which to learn about VSCode’s works and pomps.

The ‘first’ folder just contains this small file:

function hello()
    print("i am function hello")
end

print('hello world')
for i,c in {'abc','def','ghi'} do
    print(i, c)
end

hello()

I can run that in the VSCode Terminal window:

first $ luau main.lua
hello world
1       abc
2       def
3       ghi
i am function hello

My second project is first_test. That folder includes three files, ‘class.lua’, ‘expectations.lua’, and ‘test_require.lua’. The original purpose of this little project was to try VSCode with a project using require, which we’ll want in SL. the plug-in that I mentioned earlier has something to do with that: at this time I am not clear just what, and whether it’s the only way to do things.

In setting up this little project, I learned that the luau language plug-in for VSCode is quite strict, and I’ve modified the expectations file to suppress the warnings, most of which were complaining about my use of _ as a legitimate variable, since it is conventionally used only to contain trash and therefore should never be read. At the moment, the ‘test_require’ file is this:

--!nocheck

local class = require('./class')
local Test = require('./expectations')

local TWO_PI = 2*math.pi
local DEG_TO_RAD = math.pi/180
local RAD_TO_DEG = 180/math.pi

function Test:featureMini()
    Test:describe('simple required test', function()
        Test:test('hookup', function()
            Test:expect(2+2).is(4)
        end)
    end)
end



function Test:featureLinkages()
    Test:describe('wheel and coupling rod', function()

        Test:test('unit wheel move 90 degrees_clockwise', function()
            local wheel = DriveWheel{x=1,y=1,radius=1}
            local circumference = 2*math.pi*1
            local distance = circumference / 4
            wheel:move(distance)
            Test:expect(wheel:angle()).is(270, 0.01)
        end)
    end)
end

-- DriveWheel

DriveWheel = class()
-- assumes right side, forward is +x
function DriveWheel:init(parms)
    self._x = parms.x or error('expected x')
    self._y = parms.y or error('expected y')
    self._r = parms.radius or error('expected radius')
    self._angle = (parms.angle or 0)*DEG_TO_RAD
end

function DriveWheel:adjusted_offset(v)
    return v:rotate_2d(self._angle)
end

function DriveWheel:drive_point(radius)
    return self:position() + self:adjusted_offset(vector(self._r, 0))
end

function DriveWheel:angle()
    return self._angle*RAD_TO_DEG
end

function DriveWheel:calculate()
    -- does nothing, supports the standard protocol
end

function DriveWheel:move(distance)
    self:rotate_by( - distance / self._r)
end

function DriveWheel:set_angle(degrees)
    self._angle = degrees*DEG_TO_RAD
end

function DriveWheel:position()
    return vector(self._x, self._y)
end

function DriveWheel:rotate_by(angle_change)
    self._angle = (self._angle + angle_change) % TWO_PI
end

Test:execute()

After making my initial simple test work, I started a test called featureLinkages. My plan was to replicate my Linkage class from Python, with tests. Then, as you can see, I derailed and began to test DriveWheel instead. I think my reasoning was that before I tried to build something new, it might make sense to bring over something that was known to work. Anyway, the tests run:

first_test $ luau test_require.lua 
Feature: simple required test
simple required test, 2 Tests: Pass 2, Fail 0, Ignored 0.

Feature: wheel and coupling rod
wheel and coupling rod, 2 Tests: Pass 2, Fail 0, Ignored 0.

Yes, the test count is still weird, since there is only one expect in each test, not two. Someday I’ll find and fix that.

Status So Far …

I can sort of work VSCode, well enough to write tests and code, although with less power than PyCharm and less facility than I can edit with Sublime. I believe that VSCode can emulate Sublime’s key map and perhaps I’ll rig it to do that.

And Now …

I need to do some actual work. Let’s see if we can actually do a Linkage class with tests. I’ve now opened this markdown file in VSCode, so that I can edit more easily as I go. I’m losing spell checking over here. I think there’s a plug-in for that. I’ll shave that yak later, I really want to get something done here.

I shave the yak immediately, because there were three typos in the paragraph above. There’s an extension called ‘code spell checker’ that seems to do pretty well.

Backing Away?

I have a useful version of linkage code in Python, with the ability to draw moving diagrams. I have an earlier version, pretty much complete, in a large-ish Lua file ‘test_linkages.lua’, over in my Sublime Text repo area.

I think that the learning required for VSCode isn’t likely to pay off now, and maybe never, compared to the nearly-as-good leverage I have using Sublime Text. I’m using the same luau compiler for both.

I’m going to abandon VSCode, at least for now, in aid of getting more work done. VSCode offers no real leverage that I can see, beyond what I can do now without it.

Weird result. We’ll see if this is a good decision but I think it is. At least today I think it is.