Script taken from here. It calculates CNC miter pipe trajectories. See this for more reference.
#!/usr/bin/env ruby
# unit conversion constants
# internally, we compute everything in points == 1/72 inch
CM_PER_IN = 2.54
IN_PER_CM = 1.0 / CM_PER_IN
IN_PER_FT = 12.0
PTS_PER_IN = 72.0
CENTI = 100.0
MILLI = 1000.0
LENGTH_CONVERSION = {'in' => PTS_PER_IN,
'ft' => IN_PER_FT * PTS_PER_IN,
'mm' => CENTI / MILLI * IN_PER_CM * PTS_PER_IN,
'cm' => IN_PER_CM * PTS_PER_IN,
'm' => CENTI / 1.0 * IN_PER_CM * PTS_PER_IN}
SUPPORTED = LENGTH_CONVERSION.keys.join ', '
EPSILON = 1.0e-3
TWOPI = 2.0 * Math::PI
PAPER_WIDTH = 8.5 * LENGTH_CONVERSION['in']
PAPER_HEIGHT = 11.0 * LENGTH_CONVERSION['in']
MARGIN = 1 * LENGTH_CONVERSION['in']
def usage!
$stderr.puts <<-Usage
Usage: pipemiter [d1] [d2] [angle] [units]
Where d1 is the larger outer diameter, d2 is smaller outer diameter,
angle is the join angle in degrees, and units is one of
{#{SUPPORTED}}. Default unit is inches.
Alternatively, use no arguments for interactive mode.
Output is written as an encapsulated postscript document
to standard out.
Usage
exit
end
if ARGV.include? '-h'
usage!
elsif ARGV.size == 3 or ARGV.size == 4
# command line mode
d1_input = ARGV[0].to_f
d2_input = ARGV[1].to_f
angle_input = ARGV[2].to_f
units_input = ARGV[3] || 'in'
else
$stderr.print "Preferred unit (one of #{SUPPORTED}): "
units_input = $stdin.readline
$stderr.print "Outer Diameter of larger pipe: "
d1_input = $stdin.readline.to_f
$stderr.print "Outer Diameter of smaller pipe: "
d2_input = $stdin.readline.to_f
$stderr.print "Angle of joint (degrees): "
angle_input = $stdin.readline.to_f
end
units = units_input.strip.downcase
unless LENGTH_CONVERSION.has_key? units
$stderr.puts "Error: unsupported unit #{units_input}"
usage!
end
d1_pts = LENGTH_CONVERSION[ units ] * d1_input
r1_pts = d1_pts / 2.0
d2_pts = LENGTH_CONVERSION[ units ] * d2_input
r2_pts = d2_pts / 2.0
unless d1_pts >= d2_pts
$stderr.puts "Error: d1 must be greater than or equal to d2"
usage!
end
# The circumference of the smaller pipe
c2_pts = TWOPI * r2_pts
if MARGIN + c2_pts + MARGIN > PAPER_HEIGHT
$stderr.puts "Error: curve cannot be rendered on standard paper"
usage!
end
angle_r = TWOPI * angle_input / 360.0
if angle_r <= 0.0 or angle_r >= 360.0
$stderr.puts "Error: invalid angle (no parallel pipes allowed)."
usage!
end
# Cool, all of the numbers look sane.
# We could do this the analytical way,
# but this is quick-n-dirty.
# The squared radius of the larger pipe
r1_sqr = r1_pts * r1_pts
r2_sqr = r2_pts * r2_pts
# The sine of the joint angle
sin_angle = Math::sin( angle_r )
cos_angle = Math::cos( angle_r )
xmin = Math::sqrt( r1_sqr - r2_sqr ) / cos_angle
ymin = 0
xmax = ( Math::sqrt( r1_sqr ) + r2_pts * sin_angle ) / cos_angle
ymax = c2_pts
width = xmax - xmin
height = ymax - ymin
puts <<-EPS_HEADER
%!PS-Adobe EPSF-3.0
%%BoundingBox: #{MARGIN} #{MARGIN} #{MARGIN+width} #{MARGIN+height}
%%EndComments
% www.cheaphack.net
newpath
1 setlinewidth
EPS_HEADER
cmd = "moveto"
# Make a loop around the smaller pipe
theta = 0.0
while theta < TWOPI
# distance along the circumference of the smaller pipe
d = c2_pts * theta / TWOPI
# Any point with this angle shares these y,z coordinates
y = r2_pts * Math::sin(theta)
z = r2_pts * Math::cos(theta)
x = ( Math::sqrt(r1_sqr - z*z) + y*sin_angle ) / cos_angle
# Emit a line segment
puts "#{MARGIN + x - xmin} #{MARGIN + d - ymin} #{cmd}"
# Get ready for next iteration
cmd = "lineto"
theta += EPSILON
end
puts <<-EPS_TAILER
stroke
newpath 1 setlinewidth #{MARGIN} #{MARGIN} moveto #{width} 0 rlineto stroke
newpath 1 setlinewidth #{MARGIN} #{MARGIN+height} moveto #{width} 0 rlineto stroke
/Times-roman findfont 12 scalefont setfont #{MARGIN + width - 12} #{MARGIN + height/2} moveto 90 rotate (Keep) show
showpage
%%EOF
EPS_TAILER