New polar design. Formerly "Look what I found"

A few more pics … it’s running pretty good now just need to balance the bed a bit and get the lights working with a dimmer.

Thanks @jeffeb3 for the thr to gcode converter this wouldn’t have happened without you.


7 Likes

I added a bit to @jeffeb3 's code. This will take all files from a “thr” subfolder, process them and save them to a “gcode folder” I’m not much of a python programmer so use at your own risk.

import math
import os
import pathlib
import shutil


# Config
THETA_UNITS_PER_ROTATION = 6.0
# Folders
INPUT_DIR = "thr"
OUTPUT_DIR = "gcode"
PROCESSED_DIR = "archive"
# Pre Post Code
START_GCODE = "G1F60"
END_GCODE = ""


# Set Up folders
if not os.path.exists(INPUT_DIR):
    print("You didn't have a thr folder so we are setting on up for you. Place your thr files there that you want processed and run this script again.")
    os.makedirs(INPUT_DIR)

if not os.path.exists(OUTPUT_DIR):
    print("You didn't have a gcode folder so we are setting on up for you. You will find your processed files there.")
    os.makedirs(OUTPUT_DIR)

if not os.path.exists(PROCESSED_DIR):
    print("You didn't have a archive folder so we are setting on up for you. You will find your thr files that have already been converted moved there.")
    os.makedirs(PROCESSED_DIR)


for filename in os.scandir(INPUT_DIR):
    if filename.is_file():
        if pathlib.Path(filename.name).suffix == ".thr":
            print(filename.path)
            # Open the files.
            with open(filename.path, 'r') as infile:
                with open(os.path.join(OUTPUT_DIR, pathlib.Path(filename.name).stem + ".gcode"), 'w') as outfile:
                    #Start GCODE
                    outfile.write(START_GCODE + "\n")
                    for line in infile:

                        # First, separate by content and comments
                        parts = line.strip().split('#')

                        if len(parts[0].strip()) == 0:
                            # This line doesn't have anything that isn't a comment
                            outfile.write(line.replace('#',';'))
                            continue

                        else:
                            (theta, rho) = parts[0].split()

                            # Here, we're actually doing the math.
                            x = THETA_UNITS_PER_ROTATION * float(theta) / (2*math.pi)
                            y = float(rho)

                            outfile.write("G1 X{:0.003f} Y{:0.003f}\n".format(x, y))          

                        # We just moved each motor a bunch. A whole lot.
                        # When we start the next file, we don't want it to unroll
                        # So we will make the current coordinate something that is small, but the same.
                        xmod = x % THETA_UNITS_PER_ROTATION

                    outfile.write("G92 X{:0.003f} ; Reset the coordinates\n".format(xmod))
                    outfile.write("\n" +  END_GCODE + "\n")
                

        #Move Processed Files to separate folder. This is Useful if using this as a watchdog app.
        shutil.move(filename.path,  os.path.join(PROCESSED_DIR , filename.name))

1 Like

sort of?

You can do this:

                            (theta, rho) = parts[0].split()

                            # Here, we're actually doing the math.
                            x = THETA_UNITS_PER_ROTATION * float(theta) / (2*math.pi)
                            y = float(rho)
                            speed = calcSpeed(rho)

                            outfile.write("G1 X{:0.003f} Y{:0.003f} F{:0.003f}\n".format(x, y, speed))

Then you can experiment to make the calcSpeed function do whatever you want. For example:

def calcSpeed(rho):
    MAX_SPEED = 600 # "Units" per minute, not second
    # Compute a speed that is at least 0.1 * the max,
    # but smoothly transitions to full speed at the outside edge.
    return (0.1 + rho * 0.9) * MAX_SPEED

That probably won’t do what you’re expecting. There is a geometry problem here. Let me dump some thoughts on it:

  1. These coordinates (x, y, theta, rho) look like points, but they aren’t. They are lines. The line always goes from the previous point, to the x, y you are working on. To compute the speed needed to reach 0.9,0.5, you need to really know where that line starts. The thr output in sandify does a pretty decent job of making a bunch of line segments, instead of any long lines. So the segments will be short, but you may need to look at the angle, or the change in theta compared to the change in rho.
  2. What is your ideal? Or what specific problem are you trying to solve? Do you want the ball to move at a constant speed? Do you want the machine to move faster at the edges? Or slower at the edge? Figure out what you want first.
  3. The units are a bit goofy. 36 “units per minute” on a purely rotational move, would rotate the gantry in 10 seconds. 60 “units per minute” on a purely rho move would go from the inside to the outside in 1 second. The firmware is going to treat those things as equal units. You may need to reverse that logic to get it to work well.
  4. There’s no reason you can’t experiment with it, and just fudge it until it is pleasant. There is no reason to be exact. Even something like a look up table is a totally valid solution here.

If you really wanted it to be a constant linear speed, I think we could get close by keeping the previous theta, rho, and then computing the arc length traveled, and the radial distance, then combining them with the 6:1 ratio to create the right feedrate for that line segment.

It is an interesting problem. It should be fun to figure it out. Go enjoy it.

1 Like

I guess the ideal is for the ball to appear to stay at a constant velocity with an acceleration curve. The issue that I’m facing is that a design with a lot of traffic near the center (low rho) is painfully slow while on the edge (high rho) the ball is throwing up a rooster tail of sand behind it.

Ill definitely play with it it doesn’t need to be perfect but I think I’ll at least need to track previous t,r and compare to current t,r to get something accurate. I’ll post what I come up with thx.

Here’s the quick and dirty function . Seems to work great with the exception of extremely long x moves like spiral wipes. I’m happy with it as is.

def calcSpeed(OLD_Y, y):

    speed =(1/(OLD_Y + y + .001)/2) * BASELINE_SPEED
    if speed > MAX_SPEED:
        speed = MAX_SPEED
    if speed < MIN_SPEED:
        speed = MIN_SPEED
    return speed
1 Like

Looks really good. That should definitely do the trick.

I’m going to give you some tips. Your code is good, but I think it could be more readable (but function the same):

averageY = (OLD_Y + y) / 2.0
averageY = max(averageY, 0.001) # keep it from reaching zero
speed = BASELINE_SPEED / averageY

It is a little different, near zero. But it shows your intent better.

Nothing wrong with your code though. Thanks for sharing.

1 Like

Hi. I don’t understand how the movement is trasmitted from the stepper to the rotational axis. Can you explain or is possible to see a picture/drawing of the movement? Thanks

It’s just using a wheel hooked to a stepper rotating around an axle using friction:

The hub of the wheel is printed in PLA then I glued a piece of GT2 belt teeth facing inward as the “tire”

To further increase friction the purple ring is made from a piece of 220grit sandpaper (very high tech)

For a sand table its accurate enough and doesn’t miss enough steps to matter. The hard part is getting steps/mm as perfect as possible as its unique to the build based on the wheels distance from the center point. You can get close with simple math then just keep running test patterns until its right. For my machine its 30,360 steps for 1 rotation.

3 Likes

awesome!!! thanks

1 Like

I find it weird that anytime I have to write something in Python I revert to Perl habits of trying to keep everything on a single line even though I havnt touched Perl in 10 years. I think its because I never have adapted to pythons use of indents.

Your interpretation is superior in readability.

2 Likes

I have never written perl. I definitely don’t like reading it :slight_smile:. That may be the difference.

My day job is C++ for Linux. Unlike embedded C/C++, we prefer readability to optimization. It isn’t everyone’s style, but I am pretty experienced with it. I trust the compiler (gcc) to optimize clear code into fast code for me.

Python is similar to me. But it will punish you (w.r.t. speed and memory usage) for being too lazy. It won’t matter in this case. I get burned when dealing with large data structures like images. I remember writing one that just copied the image to a new format and it was holding onto 50x the memory that the C++ equivalent was. I still don’t know what it did with all that memory.

1 Like

For anyone whos never seen perl here is the game frogger in 2048bytes of code:

image

evalevalq.q>trd!Uj:%L<061:%C<csnvo:%f<fsddo0:%c<cmtd:%x<xdmmnv:%I<011:%u<251:%bs<bsd`udSdbu`ofmd:%w<lnwd:%U<2:%t<L`hoVhoenv,?
odv),idhfiu<?314-,vheui<?254(:%b<%t,?B`ow`r:%b,?bnoghftsd),vheui<?%u-,idhfiu<?311(:%b,?q`bj)(:s)3-3-%u-001-%c(:s)3-081-%u-311
-%f(:s)3-001-%u-031-%f(:s)3-1-%u-34-%f(:gns)%{<1:%{=%u:%{*<71(zs)%{-01-%{*51-54-%f-%f(:|s)3-1-%u-04-cm`bj(:%b,?%bs)3-1-%u-311
(:%G<,041:v)1-%L-31-C-%x(:v)%G-%L-,021-C-%x(:%B<,91:v),31-041-,4-B-%c(:v),91-041-,74-B-%c(:%E<,%I:v)1-021-31-E-%x(:v),%I-021-,
 91-E-%x(:%K<,231:v),71-81-,31-@-%C(:v),301-81-,%L-@-%C(:v),%u-81-,211-@-%C(:%M<,%u:v),51-61-1-F-%C(:v),%L-61-,021-F-%C(:v),%u
-61-,211-F-%C(:%J<%u:v)751-41-791-[-%C(:v)401-41-441-[-%C(:v)%u-41-291-[-%C(:%b,?bsd`udNw`m)063-080-091-088-,u`fr<?G-,ghmm<?f
sddo5(:S)1(:%b,?sdqd`u)%I-]'t(:%t,?choe)&=Envo?&<?rtczS),0(:'V:%b,?%w)G-1-31(hg)%x=081(:|(:%t,?choe)&=Tq?&<?rtczS)0(:%b,?%w)G
-1-,31(:|(:%t,?choe)&=Mdgu?&<?rtcz'V:%b,?%w)G-,31-1(hg)%y?31(:|(:%t,?choe)&=Shfiu?&<?rtcz'V:%b,?%w)G-31-1(hg)%Y=%u,31(:|(:L`h
oMnnq)(:dyhu:rtc!vz%b,?%bs)%^Z1\-%^Z0\-%^Z3\-%^Z0\*8-,u`fr<?%^Z2\-,ghmm<?%^Z5\(:|rtc!tzhg)%G?%u(z%G*<%L:%d<,%G:%G<,%L:|dmrdz%
G*<01:%d<01:|%b,?%w)C-%d-1(:hg)%B?%u(z%B*<%I:%d<,%B:%B<,%I:|dmrdz%B*<01:%d<01:|%b,?%w)B-%d-1(:hg)%E?%u(z%E*<031:%d<,%E:%E<,03
1:|dmrdz%E*<01:%d<01:|%b,?%w)E-%d-1(:hg)%K?%u(z%K*<229:%d<,%K:%K<,251:|dmrdz%K*<7:%d<7:|%b,?%w)@-%d-1(:hg)%M?%u(z%M*<271:%d<,
%M:%M<,271:|dmrdz%M*<9:%d<9:|%b,?%w)F-%d-1(:hg)%J=,%u(z%J,<%u:%d<,%J:%J<%u:|dmrdz%J,<7:%d<,7:|%b,?%w)[-%d-1(:'V:hg)%x=081(zhg
))%x?031(}})%x=001((zAn<%b,?ghoe)nwdsm`qqhof-%y-%x-%Y-%X(:hg)%x?031(zhg)%"n(z'R:||dmrdzhg)%x?58(zhg)%"n?0(z%n<7:%n*<3hg)%x=81
(:%n<,7hg)%x=61(:%b,?%w)G-%n-1(:|dmrdz'R:||dmrdzhg)%"n?0(z'R:|dmrdzS)00(:%U**:%O**:'R:v)%y-%x-%Y-Q-%f(:%b,?edmdud)&Q&(hg))%O$
4((:||||rmddq)4(''Uj;;dyhu)1(hg)%U=0(:||rtc!Rz%U,,:qshou#]`#:%b,?%w)G-063,%y-081,%x(:|rtc!SzP)cm`bj(:%R*<%^Z1\:P)sde(: |rtc!P
z%b,?bsd`udUdyu)%L-9,udyu<?%R/1-,ghmm<?%^Z1\(:|rtc!sz%b,?%bs)%^Z1\-%^Z0\-%^Z3\-%^Z2\-,ghmm<?%^Z5\-,ntumhod<?%^Z4\(:|rtc!Vz)%y
-%x-%Y-%X(<%b,?bnnser)G(:|>^chr($$/$$)x2016.

http://www.foo.be/docs/tpj/issues/vol5_3/tpj0503-0014.html

3 Likes

There’s more than one way to do it :wink:

@jeffeb3 is there an existing function for conversion between polar gcode and Cartesian gcode?

Converting a point is easy:

x = rho * cos(theta)
y = rho * sin(theta)

or

rho = sqrt(x*x + y*y)
theta = atan2(y,x)

But the devil is in the details. Gcode doesn’t define a point, it defines a line (from wherever you are, to the point in the next G1 command). A line in polar is an arc in cartesian. The solution in sandify is to break up any lines into small enough lines that the difference doesn’t matter.

The atan is also tricky, because going from -6.27 to 0.01 in radian is trivial, but in gcode, that would make it travel most of the way around the circle, instead of just crossing the 0.0 radian line. So there is going to be some work to keep track of that angle, and find the smallest angle to connect them. If you have a previous angle, find the direction that is smallest to the next angle, and then add that difference to the previous angle to get the new angle.

These tricks are built into the code that generates thr from sandify. That code is here and here.

The easiest way to convert a cartesian gcode file to your polar gcode is to import it into sandify, export it as thr and then run it through your script.

2 Likes

Well the table is done for now… still need a cleaner way to control it than fluid webui but it looks more like a piece of furniture than a science project so I’ll call that a win.

Thanks all that provided help making it happen!

16 Likes

That looks like a high quality finish. Looks great.

1 Like

Hi, after few months, can you say something about the mechanism you realized? Is it reliable? The wheel is working well? Loosing steps after continuously working for hours?

It actually works far better than I expected. No problems at all from the wheel system though at some point I may have to replace the sandpaper with something better. It’s within a couple of mm by the end of most jobs which is fine. So far the machine probably has around 100 run hours on it.

I wouldn’t use the 1/4 ply again if given a choice. It’s noisy and kind of acts like a tensioned drum. I’ve been thinking about doing a thin epoxy pour over the bed to try and deaden it a bit.

I’d also add an endstop as my homing method right now is to just slam the machine to the center stops and reset 0,0.

1 Like

could you share your 3d files? I want to build a new table with this geometry and I’m happy if I can start from yours stl files.