Generating Images from Stepper Files


#1

The Glowforge cloud software has an unusual way of setting the amount of laser power that hits the material. Rather than adjusting the high-voltage output to the tube, they set the voltage to its maximum, and then use a dithering algorithm to switch the laser on and off at a high speed - effectively a PWM process.

To see this in action, I ran a series of engraves, captured the stepper files, and used the Glowforge Utilities and some custom scripting to generate an image representing the laser power and firings over the engraving period.

I used the following image of a solid 100% black .500" square:
500_sq_blk
( Here is the actual SVG: 500_black.zip (8.2 KB))

I set the following in the GFUI:

That resulted in this stepper waveform file: MAN_VP_1000_50_0_340.zip (17.4 KB)

Next, I whipped up the this quick and dirty Python script:

from GF.MOTION import motion, steps, position, laser
from PIL import Image, ImageDraw

def new_position(cur_pos, dlt):
    """
    Calculates the XYZ position by adding the delta provided
    :param cur_pos: dict in the form: {'X': x, 'Y': y, 'Z': z}
    :param dlt: dict in the form: {'X': dx, 'Y': dy, 'Z': dz}
    :return: dict in the form: {'X': x+dx, 'Y': y+dy, 'Z': z+dz}
    """
    for axis in cur_pos:
        cur_pos[axis] = cur_pos[axis] + dlt[axis]
    return cur_pos

mot = motion.Motion('MAN_VP_1000_50_0_340')

# Find the first time we go in reverse on X axis.
# It's the easiest way to find out where our test engrave has begun.
# Of course, we lose the first line, but that's OK. It does other things well.
r_pulse = mot.raw_pulse[steps.find(mot.raw_pulse, 'X', False) - 1:]

# Now we just use the rest of it for our work.
r_pulse = r_pulse[steps.find(r_pulse, 'X', True) - 1:]

# Find out how big of an array we need for our image and initialize it with zeros
extents = position.extents(r_pulse)
img = Image.new('RGB', (extents['XP'] + 1, extents['YP'] + 1))
draw = ImageDraw.Draw(img)

# Reset our position counter
pos = {'X': 0, 'Y': 0, 'Z': 0}

# Start drawing out the picture
while True:
    # Find next step in the pulse file
    step_loc = steps.find(r_pulse)
    if not step_loc:
        # No more steps, we are done
        break
    # Grab the chunk of the pulse data between now and the last step
    chunk = r_pulse[0:step_loc]
    # Remove that space from the remaining pulse data
    r_pulse = r_pulse[step_loc:]
    # Calculate the new head position
    pos = new_position(pos, position.delta(chunk))
    # Calculate how much laser power we threw down
    power = int((float(laser.on_count(chunk)) / float(step_loc)) * 255)
    # Plot the laser power data on the image at the current location
    draw.point((pos['X'], extents['YP'] - pos['Y']), fill=(power, power, power))

# We're done. Save the image.
img.save(name + '.png', 'PNG', compress_level=0, optimze=False)

That (horribly inefficient, but effective) code spits out the following image:

You can see from the picture how a dot pattern artifact was introduced to our solid object engraving. This is where the ‘tweed’ pattern you see in solid engravings is coming from.

Here are results from several of my tests:
(the multiplier I use for each image may have varied in order to get a decent picture, so your mileage may vary)

Manual Engrave, 500 Speed, 10% Precision Power, 340 LPI: MAN_VP_500_10_0_340.zip (19.1 KB)

Manual Engrave, 500 Speed, 50% Precision Power, 340 LPI: MAN_VP_500_50_0_340.zip (16.4 KB)

Manual Engrave, 500 Speed, 100% Precision Power, 340 LPI: MAN_VP_500_100_0_340.zip (18.1 KB)

Manual Engrave, 1000 Speed, 10% Precision Power, 340 LPI: MAN_VP_1000_10_0_340.zip (17.0 KB)

Manual Engrave, 1000 Speed, 50% Precision Power, 340 LPI: MAN_VP_1000_50_0_340.zip (17.4 KB)

Manual Engrave, 1000 Speed, 100% Precision Power, 340 LPI: MAN_VP_1000_100_0_340.zip (17.2 KB)


Grabbing Motion Files & Bed Images - The Easy Way
Grabbing Motion Files & Bed Images - The Easy Way
#2

For fun, I also did some Lenna engravings using Proofgrade automatic settings.

This is the Lenna image source, which I resized in the GFUI to be 2" x 2":

Graphic Draft: LENNA_ENGRAVE_GRAPHIC_DRAFT.zip (170.1 KB)

Graphic HD: LENNA_ENGRAVE_GRAPHIC_HD.zip (519.6 KB)

Graphic SD: LENNA_ENGRAVE_GRAPHIC_SD.zip (239.1 KB)

Photo Draft: LENNA_ENGRAVE_PHOTO_DRAFT.zip (111.9 KB)

You can see from all of them that there are some significant pattern artifacts that appear evenly throughout the image, and are unlikely to be caused by the source file itself.


#3

During precision power it uses an 8 period waveform for PWM and that is not synchronised with the motor steps. So to get the power level you need to take blocks of 8 consecutive samples and calculate the percentage. If you average the samples between steps you will get a weird beating between the PWM period and motor step frequency.

There will be some patterning because they get more levels than 8 by dithering but I don’t think it is as extreme as your pictures.

Also they get more motor speeds by dithering the steps and that will be averaged by the inertia of the axes. So again the steps are not an accurate measure of position.


#4

This won’t be hard to correct for in the example code. However, as there are typically a large number of samples between steps, I don’t know that it will make much of an impact. I’ll re-run it with the power being average on 8 sample counts.

As this is an engraving, the speed of the x-axis is constant during the laser firing, so I am not sure I understand what you mean.

These same patterns do show up in actual engravings, and it does tend to be quite extreme in many cases.


#5

Is it an equal number of 100uS time slots between each step? With 10kHz sampling there are only a limited number of speeds you can have that are an exact subdivision. They get intermediate speeds by dithering the step delays. Exactly the same as they get more than 8 power levels by dithering the PWM.

I wonder if you pick a speed that is exactly a subdivision of 10kHz and hit on a power than is an exact division of 8 (will be machine dependent I think) then do you get a ripple free result?


#6

I hadn’t actually been tracking that. I’ll whip up some code that will determine the stepper timing.

Challenge accepted. :smiley:

Though, this might be more difficult in practice, as the speed numbers are arbitrary.

There is some truth to getting less ‘tweed’ in the resulting engraving by varying the speed and power levels. When trying to get decent looking solid object engraves in acrylic, it is very sensitive to speed and power.


#7

I haven’t had a chance to go back and experiment with the different speeds and capture the associated stepper files. However, I do have this result from an acrylic test which inspired me to do the tests in the first place:

The numbers above each section indicate the speed.

You can see that the “interference pattern” is consistent, regardless of the head speed.


#8

I think that means it is position dependent, so perhaps mechanical. If it was due to the laser modulation it would stretch with speed.

They did make the rookie error of running the toothed belt of a smooth pulley. That would give waves at 2mm or perhaps 1mm periods.

Or it could be stepper motor waveform distortion. That would be a 0.6mm period with 30 tooth pulleys and 400 step motors, I think.