#!/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