laser-cutter 0.5.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/README.md +20 -16
- data/bin/laser-cutter +4 -158
- data/lib/laser-cutter.rb +2 -0
- data/lib/laser-cutter/aggregator.rb +57 -0
- data/lib/laser-cutter/box.rb +37 -24
- data/lib/laser-cutter/cli/opt_parser.rb +131 -0
- data/lib/laser-cutter/cli/serializer.rb +51 -0
- data/lib/laser-cutter/configuration.rb +3 -2
- data/lib/laser-cutter/geometry.rb +0 -3
- data/lib/laser-cutter/geometry/dimensions.rb +3 -3
- data/lib/laser-cutter/geometry/point.rb +0 -46
- data/lib/laser-cutter/geometry/shape/line.rb +40 -1
- data/lib/laser-cutter/geometry/shape/rect.rb +2 -2
- data/lib/laser-cutter/geometry/tuple.rb +73 -27
- data/lib/laser-cutter/notching.rb +10 -0
- data/lib/laser-cutter/notching/base.rb +13 -0
- data/lib/laser-cutter/notching/edge.rb +87 -0
- data/lib/laser-cutter/notching/path_generator.rb +249 -0
- data/lib/laser-cutter/renderer/base.rb +1 -1
- data/lib/laser-cutter/renderer/box_renderer.rb +2 -2
- data/lib/laser-cutter/renderer/layout_renderer.rb +19 -8
- data/lib/laser-cutter/renderer/meta_renderer.rb +8 -9
- data/lib/laser-cutter/version.rb +1 -1
- data/spec/aggregator_spec.rb +65 -0
- data/spec/box_spec.rb +5 -1
- data/spec/dimensions_spec.rb +0 -1
- data/spec/edge_spec.rb +43 -0
- data/spec/line_spec.rb +42 -19
- data/spec/path_generator_spec.rb +30 -36
- data/spec/point_spec.rb +2 -2
- data/spec/rect_spec.rb +1 -1
- data/spec/renderer_spec.rb +14 -5
- metadata +13 -5
- data/lib/laser-cutter/geometry/edge.rb +0 -33
- data/lib/laser-cutter/geometry/notched_path.rb +0 -46
- data/lib/laser-cutter/geometry/path_generator.rb +0 -129
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'colored'
|
3
|
+
require 'json'
|
4
|
+
require 'hashie/mash'
|
5
|
+
require 'laser-cutter'
|
6
|
+
require_relative 'serializer'
|
7
|
+
|
8
|
+
module Laser
|
9
|
+
module Cutter
|
10
|
+
module CLI
|
11
|
+
class OptParser
|
12
|
+
def self.puts_error(e)
|
13
|
+
STDERR.puts "Whoops, #{e}".red
|
14
|
+
STDERR.puts "Try --help or --examples for more info...".yellow
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.parse(args)
|
18
|
+
banner_text = <<-EOF
|
19
|
+
#{('Laser-Cutter v'+ Laser::Cutter::VERSION).bold}
|
20
|
+
|
21
|
+
Usage: laser-cutter [options] -o filename.pdf
|
22
|
+
eg: laser-cutter -z 1x1.5x2/0.125 -O -o box.pdf
|
23
|
+
EOF
|
24
|
+
|
25
|
+
examples = <<-EOF
|
26
|
+
|
27
|
+
Examples:
|
28
|
+
1. Create a box defined in inches, set kerf to 0.008" and open PDF in preview right after:
|
29
|
+
|
30
|
+
laser-cutter -z 3x2x2/0.125 -k 0.008 -O -o box.pdf
|
31
|
+
|
32
|
+
2. Create a box defined in millimeters, print verbose info, and set
|
33
|
+
page size to A3, and layout to landscape, and stroke width to 1/2mm:
|
34
|
+
|
35
|
+
laser-cutter -u mm -w70 -h20 -d50 -t4.3 -n5 -iA3 -l landscape -s0.5 -v -O -o box.pdf
|
36
|
+
|
37
|
+
3. List all possible page sizes in metric systems:
|
38
|
+
|
39
|
+
laser-cutter -L -u mm
|
40
|
+
|
41
|
+
4. Create a box with provided dimensions, and save the config to a file
|
42
|
+
for later use:
|
43
|
+
|
44
|
+
laser-cutter -z 1.1x2.5x1.5/0.125/0.125 -p 0.1 -O -o box.pdf -W box-settings.json
|
45
|
+
|
46
|
+
5. Read settings from a previously saved file:
|
47
|
+
|
48
|
+
laser-cutter -O -o box.pdf -R box-settings.json
|
49
|
+
cat box-settings.json | laser-cutter -O -o box.pdf -R -
|
50
|
+
|
51
|
+
EOF
|
52
|
+
options = Hashie::Mash.new
|
53
|
+
options.verbose = false
|
54
|
+
options.units = 'in'
|
55
|
+
|
56
|
+
opt_parser = OptionParser.new do |opts|
|
57
|
+
opts.banner = banner_text.blue
|
58
|
+
opts.separator "Specific Options:"
|
59
|
+
opts.on("-w", "--width WIDTH", "Internal width of the box") { |value| options.width = value }
|
60
|
+
opts.on("-h", "--height HEIGHT", "Internal height of the box") { |value| options.height = value }
|
61
|
+
opts.on("-d", "--depth DEPTH", "Internal depth of the box") { |value| options.depth= value }
|
62
|
+
opts.on("-t", "--thickness THICKNESS", "Thickness of the box material") { |value| options.thickness = value }
|
63
|
+
opts.on("-n", "--notch NOTCH", "Optional notch length (aka \"tab width\"), guide only") { |value| options.notch = value }
|
64
|
+
opts.on("-k", "--kerf KERF", "Kerf - cut width (default is 0.007in)") { |value| options.kerf = value }
|
65
|
+
opts.separator ""
|
66
|
+
opts.on("-m", "--margin MARGIN", "Margins from the edge of the document") { |value| options.margin = value }
|
67
|
+
opts.on("-p", "--padding PADDING", "Space between the boxes on the page") { |value| options.padding = value }
|
68
|
+
opts.on("-s", "--stroke WIDTH", "Numeric stroke width of the line") { |value| options.stroke = value }
|
69
|
+
opts.on("-i", "--page_size LETTER", "Document page size, default is autofit the box.") { |value| options.page_size = value }
|
70
|
+
opts.on("-l", "--page_layout portrait", "Page layout, other option is 'landscape' ") { |value| options.page_layout = value }
|
71
|
+
opts.separator ""
|
72
|
+
opts.on("-O", "--open", "Open generated file with system viewer before exiting") { |v| options.open = v }
|
73
|
+
opts.on("-W", "--write CONFIG_FILE", "Save provided configuration to a file, use '-' for STDOUT") { |v| options.write_file = v }
|
74
|
+
opts.on("-R", "--read CONFIG_FILE", "Read configuration from a file, or use '-' for STDIN") { |v| options.read_file = v }
|
75
|
+
opts.separator ""
|
76
|
+
opts.on("-L", "--list-all-page-sizes", "Print all available page sizes with dimensions and exit") { |v| options.list_all_page_sizes = true }
|
77
|
+
opts.on("-M", "--no-metadata", "Do not print box metadata on the PDF") { |value| options.metadata = value }
|
78
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely") { |v| options.verbose = v }
|
79
|
+
opts.on("-B", "--inside-box", "Draw the inside boxes (helpful to verify kerfing)") { |v| options.inside_box = v }
|
80
|
+
opts.on("-D", "--debug", "Show full exception stack trace on error") { |v| options.debug = v }
|
81
|
+
opts.separator ""
|
82
|
+
opts.on("--examples", "Show detailed usage examples") { puts opts; puts examples.yellow; exit }
|
83
|
+
opts.on("--help", "Show this message") { puts opts; exit }
|
84
|
+
opts.on("--version", "Show version") { puts Laser::Cutter::VERSION; exit }
|
85
|
+
opts.separator ""
|
86
|
+
opts.separator "Common Options:"
|
87
|
+
opts.on_tail("-o", "--file FILE", "Required output filename of the PDF") { |value| options.file = value }
|
88
|
+
opts.on_tail("-z", "--size WxHxD/T[/N]",
|
89
|
+
"Combined internal dimensions: W = width, H = height,\n#{" " * 37}D = depth, T = thickness, and optional N = notch length\n\n") do |size|
|
90
|
+
options.size = size
|
91
|
+
end
|
92
|
+
opts.on_tail("-u", "--units UNITS", "Either 'in' for inches (default) or 'mm'") { |value| options.units = value }
|
93
|
+
end
|
94
|
+
|
95
|
+
opt_parser.parse!(args)
|
96
|
+
|
97
|
+
if options.read_file
|
98
|
+
# these options are kept from the command line
|
99
|
+
override_with = %w(debug verbose read_file)
|
100
|
+
keep = options.reject{ |k,v| !override_with.include?(k)}
|
101
|
+
Serializer.new(options).deserialize
|
102
|
+
options.merge!(keep)
|
103
|
+
end
|
104
|
+
|
105
|
+
config = Laser::Cutter::Configuration.new(options.to_hash)
|
106
|
+
if config.list_all_page_sizes
|
107
|
+
puts PageManager.new(config.units).all_page_sizes
|
108
|
+
exit 0
|
109
|
+
end
|
110
|
+
|
111
|
+
if options.verbose
|
112
|
+
puts "Starting with the following configuration:"
|
113
|
+
puts JSON.pretty_generate(config.to_hash).green
|
114
|
+
end
|
115
|
+
|
116
|
+
config.validate!
|
117
|
+
|
118
|
+
if config.write_file
|
119
|
+
Serializer.new(config).serialize
|
120
|
+
end
|
121
|
+
|
122
|
+
config
|
123
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument, Laser::Cutter::MissingOption => e
|
124
|
+
puts opt_parser.banner.blue
|
125
|
+
puts_error(e)
|
126
|
+
exit 1
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'laser-cutter'
|
3
|
+
|
4
|
+
module Laser
|
5
|
+
module Cutter
|
6
|
+
module CLI
|
7
|
+
class Serializer
|
8
|
+
attr_accessor :options
|
9
|
+
def initialize(options = {})
|
10
|
+
self.options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def deserialize
|
14
|
+
string = if options.read_file.eql?('-')
|
15
|
+
$stdin.read
|
16
|
+
elsif File.exist?(options.read_file)
|
17
|
+
File.read(options.read_file)
|
18
|
+
end
|
19
|
+
if string
|
20
|
+
options.replace(JSON.load(string))
|
21
|
+
end
|
22
|
+
rescue Exception => e
|
23
|
+
STDERR.puts "Error reading options from file #{options.read_file}, #{e.message}".red
|
24
|
+
if options.verbose
|
25
|
+
STDERR.puts e.backtrace.join("\n").red
|
26
|
+
end
|
27
|
+
exit 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def serialize
|
31
|
+
output = if options.write_file.eql?('-')
|
32
|
+
$stdout
|
33
|
+
elsif options.write_file
|
34
|
+
File.open(options.write_file, 'w')
|
35
|
+
else
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
output.puts(JSON.pretty_generate(options))
|
39
|
+
output.close if output != $stdout
|
40
|
+
rescue Exception => e
|
41
|
+
STDERR.puts "Error writing options to file #{options.write_file}, #{e.message}".red
|
42
|
+
if options.verbose
|
43
|
+
STDERR.puts e.backtrace.join("\n").red
|
44
|
+
end
|
45
|
+
exit 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -13,6 +13,7 @@ module Laser
|
|
13
13
|
units: 'in',
|
14
14
|
page_layout: 'portrait',
|
15
15
|
metadata: true
|
16
|
+
#kerf: 0.007 # smallest kerf for thin material, usually it's more than that.
|
16
17
|
}
|
17
18
|
|
18
19
|
UNIT_SPECIFIC_DEFAULTS = {
|
@@ -30,7 +31,7 @@ module Laser
|
|
30
31
|
|
31
32
|
SIZE_REGEXP = /[\d\.]+x[\d\.]+x[\d\.]+\/[\d\.]+(\/[\d\.]+)?/
|
32
33
|
|
33
|
-
FLOATS = %w(width height depth thickness notch margin padding stroke)
|
34
|
+
FLOATS = %w(width height depth thickness notch margin padding stroke kerf)
|
34
35
|
NON_ZERO = %w(width height depth thickness stroke)
|
35
36
|
REQUIRED = %w(width height depth thickness notch file)
|
36
37
|
|
@@ -50,7 +51,7 @@ module Laser
|
|
50
51
|
self[k] = self[k].to_f if (self[k] && self[k].is_a?(String))
|
51
52
|
end
|
52
53
|
self.merge!(UNIT_SPECIFIC_DEFAULTS[self['units']].merge(self))
|
53
|
-
self['notch'] = self['thickness'] * 3.0 if self['thickness'] && self['notch'].nil?
|
54
|
+
self['notch'] = (self['thickness'] * 3.0).round(5) if self['thickness'] && self['notch'].nil?
|
54
55
|
end
|
55
56
|
|
56
57
|
def validate!
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module Laser
|
2
2
|
module Cutter
|
3
3
|
module Geometry
|
4
|
-
MINIMUM_NOTCHES_PER_SIDE = 3
|
5
4
|
end
|
6
5
|
end
|
7
6
|
end
|
@@ -10,5 +9,3 @@ require 'laser-cutter/geometry/tuple'
|
|
10
9
|
require 'laser-cutter/geometry/dimensions'
|
11
10
|
require 'laser-cutter/geometry/point'
|
12
11
|
require 'laser-cutter/geometry/shape'
|
13
|
-
require 'laser-cutter/geometry/edge'
|
14
|
-
require 'laser-cutter/geometry/path_generator'
|
@@ -5,52 +5,6 @@ module Laser
|
|
5
5
|
def self.[] *array
|
6
6
|
Point.new *array
|
7
7
|
end
|
8
|
-
|
9
|
-
def customize_args(args)
|
10
|
-
if args.first.is_a?(Point)
|
11
|
-
return args.first.to_a
|
12
|
-
end
|
13
|
-
args
|
14
|
-
end
|
15
|
-
|
16
|
-
def x= value
|
17
|
-
coords[0] = value
|
18
|
-
end
|
19
|
-
|
20
|
-
def x
|
21
|
-
coords[0]
|
22
|
-
end
|
23
|
-
|
24
|
-
def y= value
|
25
|
-
coords[1] = value
|
26
|
-
end
|
27
|
-
|
28
|
-
def y
|
29
|
-
coords[1]
|
30
|
-
end
|
31
|
-
|
32
|
-
def separator
|
33
|
-
','
|
34
|
-
end
|
35
|
-
|
36
|
-
def hash_keys
|
37
|
-
[:x, :y]
|
38
|
-
end
|
39
|
-
|
40
|
-
def move_by w, h
|
41
|
-
Point.new(x + w, y + h)
|
42
|
-
end
|
43
|
-
|
44
|
-
def <=>(other)
|
45
|
-
self.x == other.x ? self.y <=> other.y : self.x <=> other.x
|
46
|
-
end
|
47
|
-
|
48
|
-
def < (other)
|
49
|
-
self.x == other.x ? self.y < other.y : self.x < other.x
|
50
|
-
end
|
51
|
-
def > (other)
|
52
|
-
self.x == other.x ? self.y > other.y : self.x > other.x
|
53
|
-
end
|
54
8
|
end
|
55
9
|
end
|
56
10
|
end
|
@@ -2,6 +2,11 @@ module Laser
|
|
2
2
|
module Cutter
|
3
3
|
module Geometry
|
4
4
|
class Line < Shape
|
5
|
+
|
6
|
+
def self.[] *array
|
7
|
+
self.new *array
|
8
|
+
end
|
9
|
+
|
5
10
|
attr_accessor :p1, :p2
|
6
11
|
|
7
12
|
def initialize(point1, point2 = nil)
|
@@ -17,6 +22,30 @@ module Laser
|
|
17
22
|
raise 'Both points are required for line definition' unless (p1 && p2)
|
18
23
|
end
|
19
24
|
|
25
|
+
def overlaps?(another)
|
26
|
+
xs, ys = sorted_coords(another)
|
27
|
+
return false unless xs.all?{|x| x == xs[0] } || ys.all?{|y| y == ys[0] }
|
28
|
+
return false if xs.first[1] < xs.last[0] || xs.first[0] > xs.last[1]
|
29
|
+
return false if ys.first[1] < ys.last[0] || ys.first[0] > ys.last[1]
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def sorted_coords(another)
|
34
|
+
xs = [[p1.x, p2.x].sort, [another.p1.x, another.p2.x].sort]
|
35
|
+
ys = [[p1.y, p2.y].sort, [another.p1.y, another.p2.y].sort]
|
36
|
+
return xs, ys
|
37
|
+
end
|
38
|
+
|
39
|
+
def xor(another)
|
40
|
+
return nil unless overlaps?(another)
|
41
|
+
xs, ys = sorted_coords(another)
|
42
|
+
xs.flatten!.sort!
|
43
|
+
ys.flatten!.sort!
|
44
|
+
|
45
|
+
[ Line.new(Point[xs[0], ys[0]], Point[xs[1], ys[1]]),
|
46
|
+
Line.new(Point[xs[2], ys[2]], Point[xs[3], ys[3]])]
|
47
|
+
end
|
48
|
+
|
20
49
|
def relocate!
|
21
50
|
dx = p2.x - p1.x
|
22
51
|
dy = p2.y - p1.y
|
@@ -48,7 +77,17 @@ module Laser
|
|
48
77
|
end
|
49
78
|
|
50
79
|
def <=>(other)
|
51
|
-
|
80
|
+
n1 = self.normalized
|
81
|
+
n2 = other.normalized
|
82
|
+
n1.p1.eql?(n2.p1) ? n1.p2 <=> n2.p2 : n1.p1 <=> n2.p1
|
83
|
+
end
|
84
|
+
|
85
|
+
def < (other)
|
86
|
+
self.p1 == other.p1 ? self.p2 < other.p2 : self.p1 < other.p1
|
87
|
+
end
|
88
|
+
|
89
|
+
def > (other)
|
90
|
+
self.p1 == other.p1 ? self.p2 > other.p2 : self.p1 > other.p1
|
52
91
|
end
|
53
92
|
|
54
93
|
def hash
|
@@ -28,9 +28,9 @@ module Laser
|
|
28
28
|
super
|
29
29
|
self.vertices = []
|
30
30
|
vertices << p1
|
31
|
-
vertices << p1.
|
31
|
+
vertices << p1.plus(w, 0)
|
32
32
|
vertices << p2
|
33
|
-
vertices << p1.
|
33
|
+
vertices << p1.plus(0, h)
|
34
34
|
self.sides = []
|
35
35
|
vertices.each_with_index do |v, index|
|
36
36
|
sides << Line.new(v, vertices[(index + 1) % vertices.size])
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'matrix'
|
1
2
|
module Laser
|
2
3
|
module Cutter
|
3
4
|
module Geometry
|
@@ -6,60 +7,107 @@ module Laser
|
|
6
7
|
PRECISION = 0.000001
|
7
8
|
|
8
9
|
def initialize(*args)
|
9
|
-
args = customize_args(args)
|
10
10
|
x = args.first
|
11
|
-
if x.is_a?(String)
|
11
|
+
coordinates = if x.is_a?(String)
|
12
12
|
parse_string(x)
|
13
13
|
elsif x.is_a?(Hash)
|
14
14
|
parse_hash(x)
|
15
15
|
elsif x.is_a?(Array)
|
16
|
-
|
16
|
+
x.clone
|
17
|
+
elsif x.is_a?(Tuple) or x.is_a?(Vector)
|
18
|
+
x.to_a
|
17
19
|
else
|
18
|
-
|
20
|
+
args.clone
|
19
21
|
end
|
20
|
-
|
21
|
-
self.coords.
|
22
|
+
coordinates.map!(&:to_f)
|
23
|
+
self.coords = Vector.[](*coordinates)
|
22
24
|
end
|
23
25
|
|
24
|
-
def
|
25
|
-
|
26
|
+
def + x, y = nil
|
27
|
+
shift = if x.is_a?(Vector)
|
28
|
+
x
|
29
|
+
elsif x.is_a?(Tuple)
|
30
|
+
x.coords
|
31
|
+
elsif y
|
32
|
+
Vector.[](x,y)
|
33
|
+
end
|
34
|
+
self.class.new(self.coords + shift)
|
26
35
|
end
|
27
36
|
|
28
|
-
def separator
|
29
|
-
raise NotImplementedError
|
30
|
-
# 'x'
|
31
|
-
end
|
32
37
|
|
33
|
-
|
34
|
-
raise NotImplementedError
|
35
|
-
# [:x, :y, :z] or [:h, :w, :d]
|
36
|
-
end
|
38
|
+
alias_method :plus, :+
|
37
39
|
|
38
40
|
def to_a
|
39
|
-
self.coords.
|
41
|
+
self.coords.to_a
|
40
42
|
end
|
41
43
|
|
42
44
|
def to_s
|
43
|
-
"{#{coords.map { |a| sprintf("%.5f", a) }.join(separator)}}"
|
45
|
+
"{#{coords.to_a.map { |a| sprintf("%.5f", a) }.join(separator)}}"
|
44
46
|
end
|
45
47
|
|
46
48
|
def valid?
|
47
|
-
raise "Have nil value: #{self.inspect}" if coords.any? { |c| c.nil? }
|
49
|
+
raise "Have nil value: #{self.inspect}" if coords.to_a.any? { |c| c.nil? }
|
48
50
|
true
|
49
51
|
end
|
50
52
|
|
53
|
+
def x= value
|
54
|
+
self.coords = Vector.[](value, coords.[](1))
|
55
|
+
end
|
56
|
+
|
57
|
+
def y= value
|
58
|
+
self.coords = Vector.[](coords.[](0), value)
|
59
|
+
end
|
60
|
+
|
61
|
+
def x
|
62
|
+
coords.[](0)
|
63
|
+
end
|
64
|
+
|
65
|
+
def y
|
66
|
+
coords.[](1)
|
67
|
+
end
|
68
|
+
|
69
|
+
def separator
|
70
|
+
','
|
71
|
+
end
|
72
|
+
|
73
|
+
def [] value
|
74
|
+
coords.[](value)
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# Override in subclasses, eg:
|
79
|
+
# def separator
|
80
|
+
# ';'
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# def hash_keys
|
84
|
+
# [:x, :y, :z] or [:h, :w, :d]
|
85
|
+
# end
|
86
|
+
def hash_keys
|
87
|
+
[:x, :y]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Identity, cloning and sorting/ordering
|
51
91
|
def eql?(other)
|
52
92
|
return false unless other.respond_to?(:coords)
|
53
93
|
equal = true
|
54
94
|
self.coords.each_with_index do |c, i|
|
55
|
-
if (c - other.coords[i])**2 > PRECISION
|
95
|
+
if (c - other.coords.to_a[i])**2 > PRECISION
|
56
96
|
equal = false
|
57
97
|
break
|
58
98
|
end
|
59
99
|
end
|
60
100
|
equal
|
61
101
|
end
|
62
|
-
|
102
|
+
def <=>(other)
|
103
|
+
self.x == other.x ? self.y <=> other.y : self.x <=> other.x
|
104
|
+
end
|
105
|
+
def < (other)
|
106
|
+
self.x == other.x ? self.y < other.y : self.x < other.x
|
107
|
+
end
|
108
|
+
def > (other)
|
109
|
+
self.x == other.x ? self.y > other.y : self.x > other.x
|
110
|
+
end
|
63
111
|
def clone
|
64
112
|
clone = super
|
65
113
|
clone.coords = self.coords.clone
|
@@ -69,16 +117,14 @@ module Laser
|
|
69
117
|
private
|
70
118
|
|
71
119
|
#
|
72
|
-
#
|
73
|
-
# and then to a new instance.
|
74
|
-
#
|
120
|
+
# Convert from, eg "100,50" to [100.0, 50.0],
|
75
121
|
def parse_string string
|
76
|
-
|
122
|
+
string.split(separator).map(&:to_f)
|
77
123
|
end
|
78
124
|
|
125
|
+
# Return array of coordinates
|
79
126
|
def parse_hash hash
|
80
|
-
|
81
|
-
hash_keys.each { |k| self.coords << hash[k] }
|
127
|
+
hash_keys.map{ |k,v| hash[k] }
|
82
128
|
end
|
83
129
|
|
84
130
|
end
|