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.
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.
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))
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:
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.
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
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.
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.
awesome!!! thanks
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.
I have never written perl. I definitely don’t like reading it . 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.
For anyone whos never seen perl here is the game frogger in 2048bytes of code:
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.
There’s more than one way to do it
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.
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!
That looks like a high quality finish. Looks great.
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.
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.