jes notes Index Gallery . 4th axis Anniversary Clocks Blog ideas Micro-machining CNC milling machine Paint colours CNC Router Shaft passers Snap issues Software ideas Stepper motor clock Toy ideas Watchmaking Wildflowers

2024-01-23

Last modified: 2024-01-23 20:59:00

< 2024-01-22 2024-01-25 >

G-code sender

Plan for today:

Real-time commands

I've done most of the real-time stuff. There is still some syncing issue where sometimes it will think Grbl has 3 bytes in the buffer when there are none. This is almost certainly a $G\n that didn't get a reply, but I'm not sure why. I've made it not send $G\n until after the first status report, which should mean it doesn't send $G\n until Grbl is ready to respond to it. So not sure what needs fixing there, leaving it for now.

Looking at the toolpath view jumping around when changing work offset... it looks like the Grbl MPos is changing by more than I expect. If my current WPos is at X0 and I change it to X-1, I think that should only affect WPos and WCO, I don't think MPos should change. But in fact MPos changes by 2mm! What is going on there? Am I computing MPos wrong, or is Grbl?

OK, I had MPos = WPos - WCO, that is wrong, it should be MPos = WPos + WCO. Fixing usage of the WCO, plus making it compute velocity based on MPos instead of WPos, seems to have sorted everything out.

Jogging

Now the jogging thing. The issue is that when you do a series of taps for an incremental jog, we send them as G91 relative movements. If you then do a continuous jog in another axis, then that aborts the relative movements.

Why? Because at the start of a continuous jog we send a jog cancel, so that if you start a continuous jog of a second axis it takes effect immediately instead of waiting for the planner buffer to empty.

But maybe that's not necessary any more now that the jog movements are so short? Let's just try not sending a jog cancel when you start a continuous jog and see if that solves it.

OK, no, that's no good. The problem is if you send a series of incremental jogs, and then start a continuous jog, we want the continuous jog to start executing immediately, but it actually doesn't start until the incremental jogs are complete. So we definitely need a jog cancel at the start of a continuous jog. And we also need to do incremental jogs in absolute coordinates.

If incremental jogging was all we had to do, we could initialise the jog target to WPos, and whenever you do an incremental jog we just add the jog increment to the relevant axis in the jog target, and send a jog cancel and a G90 absolute movement to the new jog target.

The problem now is that we still do want to support continuous jog, and we also want to support movements entered via the MDI.

When you press a key for an incremental jog, there are several possible states:

If the axis is at rest, then we can take the current WPos as the start point. If the axis is moving due to queued incremental jogs then we must not change the start point. If the axis is moving due to a recently-aborted continuous jog or an MDI command then the end point is indeterminate, and I'd say it's OK to take the current WPos as the start point.

Maybe in the jog control loop, we check periodically whether the velocity is 0, and if so, we update the jog start point to be the current WPos. And whenever we hit an incremental jog key, we add the increment to the target and send a jog cancel, G90 absolute jog to new target, and restart continuous.

So now the issue is that if the velocity never reaches 0 and we try to do an incremental jog, we'll send that axis off to an uncontrolled distance in an uncontrolled direction.

Another issue is that if we do an incremental jog while a continuous jog is running, the continuous jog stops while the incremental movement takes place, because of how the planner works. So do we need continuous jogs to use absolute coordinates as well?

Another issue is that when we send the first incremental jog command, the velocity is still zero, and at the next tick we reset the jog target back to the start, undoing the first incremental jog. That jog does execute if we now do nothing, so it does apply, but multiple keypresses means the first increment is lost and every series of incremental jogs is one increment short of what it should be.

Oh well, this is actually really hard. I have made it always use absolute coordinates, and both incremental and continuous jogging by updating the target coordinates, and resetting Target to Wpos whenever the axis is stationary for too long. It has the issue that if the "stationary for too long" timer is too short, then it fires when a jog is just about to start, and if it's too long then it doesn't fire and out-of-date targets are applied.

I'm leaving it for now. It's not obvious that the new failure modes are better than the old ones though.

Toolpath rendering

Next up: splitting up toolpath rendering into separate layers.

Straightforward enough for now, there is obviously more that could be done though:

This will do for now though. A good compromise between efficiency and simplicity. And it will be nice and easy to insert an extra layer showing the loaded G-code.

DRO editing

I want to be able to click on a DRO element, or type its letter ("X") and enter a mode where I am editing the DRO values.

I want to be able to type:

and press enter to commit.

While typing I want to see:

I don't want it to bugger up the flow of the GUI, I want it to appear in a floating layer next to the thing I'm editing. And I probably want to be able to use the same type of editor for other things (jog increment and jog feed rate, for starters).

So maybe, in implementation order:

And then test it:

Well I've got most of it done, but only with a hard-coded location. I can't actually figure out how to find out where any given component is being drawn on the screen, it doesn't seem to be exposed. Everything returns dimensions saying how big it is, and the layout components deal with adding offsets to the gtx.Ops, but I don't see how to work out the currently active offset. Would I have to walk through gtx.Ops myself adding it up?? Seems crazy.

Maybe I should be drawing the popup from the same bit of code that draws the DRO, and then it will automatically get placed next to it? But how do I make sure it is drawn on top of everything else?

OK, next idea: at the point that the initial DRO is drawn, try to clone the ops list, save it for later, and reuse it when drawing the input popup.

Couldn't get that to work, but what I did manage to do is use a op.Macro() and op.Defer() to defer the drawing of the input popup until the end of the frame, and then I can attach it to the code that draws the DRO but it still gets drawn over the top of everything else. Great success.

Pretty pleased with this:

It is not perfectly aligned with the box above, and the alignment changes with zoom level, that's because it really wants to be offset to the position of the black box with "100.000" written in it, but at the place I implemented it, it only knows the offset to the "X" at the left, so I had to guess a further offset to line it up. Good enoguh for now.

When you press X, Y, or Z on the keyboard, it lets you type in a number or expression, shows you the new value inside the box, and retains the old value visible outside the box.

I tested most of the "And then test it" bullet points above. Just didn't do the ones about entering directly from MDI mode, because without being able to click to edit, there is no way to enter directly from MDI.

OK, update: I made it so when you click on it it lets you enter a new one, and it works perfectly fine, except when you submit it you end up in jog mode instead of MDI mode, I think because the initial click takes you out of MDI. I don't particularly care about this for now so I'm satisfied.

Interaction hints

I didn't get on to this, but I was thinking perhaps I could display them as text on the status bar. Several ideas:

  1. at relevant times, call a.SetHints([]string{ ... }) with a bunch of hints, and it cycles through them either in order or at random.

  2. at relevant tmies, call a.SetHint("...") with a single line of (potentially multiple) hints, and it is shown in the status bar.

  3. we could make it so that the hint is cleared at the start of each frame, and needs to be explicitly set by any component that thinks it is in control, so that it will appear in the status bar by the time that gets rendered

< 2024-01-22 2024-01-25 >