triangular 0.0.2 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/build.yml +30 -0
  3. data/.gitignore +3 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +31 -0
  6. data/Gemfile +3 -1
  7. data/Gemfile.lock +51 -12
  8. data/MIT-LICENSE +1 -1
  9. data/README.md +98 -0
  10. data/examples/slice_example.rb +6 -4
  11. data/lib/triangular/facet.rb +32 -42
  12. data/lib/triangular/line.rb +42 -16
  13. data/lib/triangular/point.rb +15 -15
  14. data/lib/triangular/polyline.rb +13 -12
  15. data/lib/triangular/ray.rb +41 -0
  16. data/lib/triangular/solid.rb +34 -36
  17. data/lib/triangular/units.rb +18 -14
  18. data/lib/triangular/vector.rb +8 -1
  19. data/lib/triangular/version.rb +3 -1
  20. data/lib/triangular/vertex.rb +17 -16
  21. data/lib/triangular.rb +4 -1
  22. data/spec/benchmark/benchmark_spec.rb +23 -0
  23. data/spec/profile/profile_spec.rb +20 -0
  24. data/spec/spec_helper.rb +17 -3
  25. data/spec/triangular/facet_spec.rb +235 -0
  26. data/spec/triangular/line_spec.rb +285 -0
  27. data/spec/triangular/point_spec.rb +108 -0
  28. data/spec/triangular/polyline_spec.rb +22 -0
  29. data/spec/triangular/ray_spec.rb +63 -0
  30. data/spec/{solid_spec.rb → triangular/solid_spec.rb} +71 -70
  31. data/spec/triangular/triangular_spec.rb +24 -0
  32. data/spec/triangular/units_spec.rb +77 -0
  33. data/spec/triangular/vector_spec.rb +23 -0
  34. data/spec/triangular/vertex_spec.rb +46 -0
  35. data/triangular.gemspec +22 -18
  36. metadata +114 -65
  37. data/README.rdoc +0 -64
  38. data/Rakefile +0 -11
  39. data/spec/facet_spec.rb +0 -233
  40. data/spec/line_spec.rb +0 -108
  41. data/spec/point_spec.rb +0 -88
  42. data/spec/polyline_spec.rb +0 -20
  43. data/spec/triangular_spec.rb +0 -22
  44. data/spec/units_spec.rb +0 -75
  45. data/spec/vertex_spec.rb +0 -44
@@ -1,90 +1,88 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Triangular
2
4
  class Solid
3
-
4
5
  attr_accessor :name, :facets, :units
5
-
6
+
6
7
  def initialize(name, *args)
7
8
  @name = name
8
9
  @facets = args
9
10
  @units = units
10
11
  end
11
-
12
+
12
13
  def to_s
13
- output = "solid #{@name || ""}\n"
14
+ output = "solid #{@name || ''}\n"
14
15
  @facets.each do |facet|
15
- output << "facet normal #{facet.normal.x.to_f} #{facet.normal.y.to_f} #{facet.normal.z.to_f}\n"
16
- output << "outer loop\n"
16
+ output += "facet normal #{facet.normal.x.to_f} #{facet.normal.y.to_f} #{facet.normal.z.to_f}\n"
17
+ output += "outer loop\n"
17
18
  facet.vertices.each do |vertex|
18
- output <<"vertex #{vertex.x.to_f} #{vertex.y.to_f} #{vertex.z.to_f}\n"
19
+ output += "vertex #{vertex.x.to_f} #{vertex.y.to_f} #{vertex.z.to_f}\n"
19
20
  end
20
- output << "endloop\n"
21
- output << "endfacet\n"
21
+ output += "endloop\n"
22
+ output += "endfacet\n"
22
23
  end
23
- output << "endsolid #{@name || ""}\n"
24
-
24
+ output += "endsolid #{@name || ''}\n"
25
+
25
26
  output
26
27
  end
27
-
28
- def get_bounds
28
+
29
+ def bounds
29
30
  largest_x = @facets[0].vertices[0].x
30
31
  largest_y = @facets[0].vertices[0].y
31
32
  largest_z = @facets[0].vertices[0].z
32
-
33
+
33
34
  smallest_x = @facets[0].vertices[0].x
34
35
  smallest_y = @facets[0].vertices[0].y
35
36
  smallest_z = @facets[0].vertices[0].z
36
-
37
+
37
38
  @facets.each do |facet|
38
39
  facet.vertices.each do |vertex|
39
40
  largest_x = vertex.x if vertex.x > largest_x
40
41
  largest_y = vertex.y if vertex.y > largest_y
41
42
  largest_z = vertex.z if vertex.z > largest_z
42
-
43
+
43
44
  smallest_x = vertex.x if vertex.x < smallest_x
44
45
  smallest_y = vertex.y if vertex.y < smallest_y
45
46
  smallest_z = vertex.z if vertex.z < smallest_z
46
47
  end
47
48
  end
48
-
49
+
49
50
  [Point.new(smallest_x, smallest_y, smallest_z), Point.new(largest_x, largest_y, largest_z)]
50
51
  end
51
-
52
+
52
53
  def align_to_origin!
53
- bounds = self.get_bounds
54
- self.translate!(-bounds[0].x, -bounds[0].y, -bounds[0].z)
54
+ translate!(-bounds[0].x, -bounds[0].y, -bounds[0].z)
55
55
  end
56
-
56
+
57
57
  def center!
58
- bounds = self.get_bounds
59
-
60
58
  x_translation = ((bounds[1].x - bounds[0].x).abs / 2) + -bounds[1].x
61
59
  y_translation = ((bounds[1].y - bounds[0].y).abs / 2) + -bounds[1].y
62
60
  z_translation = ((bounds[1].z - bounds[0].z).abs / 2) + -bounds[1].z
63
-
64
- self.translate!(x_translation, y_translation, z_translation)
61
+
62
+ translate!(x_translation, y_translation, z_translation)
65
63
  end
66
-
64
+
67
65
  def slice_at_z(z_plane)
68
- lines = @facets.map {|facet| facet.intersection_at_z(z_plane) }
66
+ lines = @facets.map { |facet| facet.intersection_at_z(z_plane) }
69
67
  lines.compact!
70
-
68
+
71
69
  Polyline.new(lines)
72
70
  end
73
-
71
+
74
72
  def translate!(x, y, z)
75
73
  @facets.each do |facet|
76
74
  facet.translate!(x, y, z)
77
75
  end
78
76
  end
79
-
77
+
80
78
  def self.parse(string)
81
- partial_pattern = /\s* solid\s+ (?<name> [a-zA-Z0-9\-\_\.]+)?/x
79
+ partial_pattern = /\s* solid\s+ (?<name> [a-zA-Z0-9\-_.]+)?/x
82
80
  match_data = string.match(partial_pattern)
83
-
84
- solid = self.new(match_data[:name])
85
-
86
- solid.facets = Facet.parse(string.gsub(partial_pattern, ""))
87
-
81
+
82
+ solid = new(match_data[:name])
83
+
84
+ solid.facets = Facet.parse(string.gsub(partial_pattern, ''))
85
+
88
86
  solid
89
87
  end
90
88
  end
@@ -1,30 +1,34 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
 
3
5
  module Triangular
6
+ class UnknownUnitError < RuntimeError; end
7
+
4
8
  class Units
5
-
6
9
  UNITS = {
7
- :inches => {:name => 'inches', :svg_name => 'in', :stroke_width => 0.005},
8
- :centimeters => {:name => 'centimeters', :svg_name => 'cm', :stroke_width => 0.01},
9
- :millimeters => {:name => 'millimeters', :svg_name => 'mm', :stroke_width => 0.1},
10
- :none => {:name => 'none', :svg_name => '', :stroke_width => 0.1}
11
- }
12
-
10
+ inches: { name: 'inches', svg_name: 'in', stroke_width: 0.005 },
11
+ centimeters: { name: 'centimeters', svg_name: 'cm', stroke_width: 0.01 },
12
+ millimeters: { name: 'millimeters', svg_name: 'mm', stroke_width: 0.1 },
13
+ none: { name: 'none', svg_name: '', stroke_width: 0.1 }
14
+ }.freeze
15
+
13
16
  def self.get_property(unit, name)
14
- raise "Unknown unit: #{unit}" unless UNITS.has_key?(unit)
17
+ raise UnknownUnitError, "Unknown unit: #{unit}" unless UNITS.key?(unit)
18
+
15
19
  UNITS[unit][name]
16
20
  end
17
-
21
+
18
22
  def self.name(unit)
19
- self.get_property(unit, :name)
23
+ get_property(unit, :name)
20
24
  end
21
-
25
+
22
26
  def self.svg_name(unit)
23
- self.get_property(unit, :svg_name)
27
+ get_property(unit, :svg_name)
24
28
  end
25
-
29
+
26
30
  def self.stroke_width(unit)
27
- self.get_property(unit, :stroke_width)
31
+ get_property(unit, :stroke_width)
28
32
  end
29
33
  end
30
34
  end
@@ -1,4 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Triangular
2
- class Vector < Point
4
+ class Vector < Point
5
+ def angle_to(other_vector)
6
+ dot_product = @x * other_vector.x + @y * other_vector.y + @z * other_vector.z
7
+ radians = Math.acos(dot_product)
8
+ radians * (180.0 / Math::PI)
9
+ end
3
10
  end
4
11
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Triangular
2
- VERSION = "0.0.2"
4
+ VERSION = '0.1.1'
3
5
  end
@@ -1,45 +1,46 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
 
3
5
  module Triangular
4
6
  class Vertex
5
7
  extend Forwardable
6
-
8
+
7
9
  def_delegator :@point, :x, :x
8
10
  def_delegator :@point, :y, :y
9
11
  def_delegator :@point, :z, :z
10
12
  def_delegator :@point, :translate!, :translate!
11
-
13
+
12
14
  attr_accessor :point
13
-
15
+
14
16
  def initialize(*args)
15
17
  if args.length == 1 && args.first.is_a?(Point)
16
18
  @point = args.first
17
19
  elsif args.length == 3
18
- @point = Point.new(args[0], args[1], args[2])
20
+ @point = Point.new(args[0], args[1], args[2])
19
21
  else
20
- raise "You must either supply the XYZ coordinates or a Point object to create a Vertex"
22
+ raise 'You must either supply the XYZ coordinates or a Point object to create a Vertex'
21
23
  end
22
24
  end
23
-
25
+
24
26
  def to_s
25
- "vertex #{@point.to_s}"
27
+ "vertex #{@point}"
26
28
  end
27
-
29
+
28
30
  def ==(other)
29
31
  return false unless other.is_a?(Vertex)
30
- self.x == other.x && self.y == other.y && self.z == other.z
32
+
33
+ x == other.x && y == other.y && z == other.z
31
34
  end
32
-
35
+
33
36
  def self.parse(string)
34
- string.strip!
35
- match_data = string.match(self.pattern)
36
-
37
- self.new(Point.parse(match_data[:point]))
37
+ match_data = string.strip.match(pattern)
38
+
39
+ new(Point.parse(match_data[:point]))
38
40
  end
39
-
41
+
40
42
  def self.pattern
41
43
  /vertex\s+ (?<point>#{Point.pattern})/x
42
44
  end
43
-
44
45
  end
45
46
  end
data/lib/triangular.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'triangular/point'
2
4
  require 'triangular/vertex'
3
5
  require 'triangular/vector'
@@ -6,12 +8,13 @@ require 'triangular/polyline'
6
8
  require 'triangular/facet'
7
9
  require 'triangular/units'
8
10
  require 'triangular/solid'
11
+ require 'triangular/ray'
9
12
 
10
13
  module Triangular
11
14
  def self.parse(string)
12
15
  Solid.parse(string)
13
16
  end
14
-
17
+
15
18
  def self.parse_file(path)
16
19
  File.open(path) do |file|
17
20
  Solid.parse(file.read)
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.configure do |config|
6
+ config.after(:suite) do
7
+ benchmarks = {}
8
+
9
+ benchmarks['Triangular.parse_file'] = Benchmark.measure do
10
+ Triangular.parse_file(File.expand_path("#{File.dirname(__FILE__)}/../fixtures/y-axis-spacer.stl"))
11
+ end
12
+
13
+ puts "\n\n\n"
14
+ puts '--------------------------------------------------------------------------------'
15
+ puts '- Benchmark Results: -'
16
+ puts '--------------------------------------------------------------------------------'
17
+ puts "\n"
18
+
19
+ benchmarks.each do |test_name, result|
20
+ puts test_name.ljust(34) + ": #{result}"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.configure do |config|
6
+ config.after(:suite) do
7
+ profile = RubyProf.profile do
8
+ Triangular.parse_file(File.expand_path("#{File.dirname(__FILE__)}/../fixtures/y-axis-spacer.stl"))
9
+ end
10
+
11
+ puts "\n\n\n"
12
+ puts '--------------------------------------------------------------------------------'
13
+ puts '- Profile Results: -'
14
+ puts '--------------------------------------------------------------------------------'
15
+ puts "\n"
16
+
17
+ printer = RubyProf::FlatPrinter.new(profile)
18
+ printer.print($stdout, min_percent: 2)
19
+ end
20
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,18 @@
1
- require "bundler/setup"
2
- require "triangular"
1
+ # frozen_string_literal: true
3
2
 
4
- include Triangular
3
+ require 'simplecov'
4
+ SimpleCov.start
5
+
6
+ require 'bundler/setup'
7
+ require 'triangular'
8
+ require 'benchmark'
9
+ require 'ruby-prof'
10
+
11
+ RSpec.configure do |config|
12
+ # Enable flags like --only-failures and --next-failure
13
+ config.example_status_persistence_file_path = '.rspec_status'
14
+
15
+ config.expect_with :rspec do |c|
16
+ c.syntax = :expect
17
+ end
18
+ end
@@ -0,0 +1,235 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Triangular::Facet do
6
+ describe '.parse' do
7
+ context 'with a correctly formatted facet' do
8
+ before :each do
9
+ @result = Triangular::Facet.parse(<<-FACET)
10
+ facet normal 0.0 0.0 -1.0
11
+ outer loop
12
+ vertex 16.5 0.0 -0.75
13
+ vertex 0.0 -9.5 -0.75
14
+ vertex 0.0 0.0 -0.75
15
+ endloop
16
+ endfacet
17
+ FACET
18
+ end
19
+
20
+ it 'should return a facet object' do
21
+ expect(@result).to be_a Triangular::Facet
22
+ end
23
+
24
+ it 'should return a facet with 3 vertices' do
25
+ expect(@result.vertices.length).to eq(3)
26
+ end
27
+
28
+ it 'should return a facet with vertices of type Vertex' do
29
+ @result.vertices.each do |vertex|
30
+ expect(vertex).to be_a Triangular::Vertex
31
+ end
32
+ end
33
+
34
+ it 'should return a facet with a normal of type Vector' do
35
+ expect(@result.normal).to be_a Triangular::Vector
36
+ end
37
+
38
+ it 'should correctly set the normal values' do
39
+ expect(@result.normal.x).to eq(0)
40
+ expect(@result.normal.y).to eq(0)
41
+ expect(@result.normal.z).to eq(-1)
42
+ end
43
+
44
+ it 'should correctly set the values for the first vertex' do
45
+ expect(@result.vertices[0].x).to eq(16.5)
46
+ expect(@result.vertices[0].y).to eq(0)
47
+ expect(@result.vertices[0].z).to eq(-0.75)
48
+ end
49
+
50
+ it 'should correctly set the values for the second vertex' do
51
+ expect(@result.vertices[1].x).to eq(0)
52
+ expect(@result.vertices[1].y).to eq(-9.5)
53
+ expect(@result.vertices[1].z).to eq(-0.75)
54
+ end
55
+
56
+ it 'should correctly set the values for the third vertex' do
57
+ expect(@result.vertices[2].x).to eq(0)
58
+ expect(@result.vertices[2].y).to eq(0)
59
+ expect(@result.vertices[2].z).to eq(-0.75)
60
+ end
61
+ end
62
+
63
+ context 'when passed multiple facets' do
64
+ before do
65
+ @result = Triangular::Facet.parse(<<-FACET)
66
+ facet normal 0.0 0.0 -1.0
67
+ outer loop
68
+ vertex 16.5 0.0 -0.75
69
+ vertex 0.0 -9.5 -0.75
70
+ vertex 0.0 0.0 -0.75
71
+ endloop
72
+ endfacet
73
+ facet normal 0.0 0.0 -1.0
74
+ outer loop
75
+ vertex 16.5 0.0 -0.75
76
+ vertex 0.0 -9.5 -0.75
77
+ vertex 0.0 0.0 -0.75
78
+ endloop
79
+ endfacet
80
+ FACET
81
+ end
82
+
83
+ it 'should return multiple facet objects' do
84
+ expect(@result).to be_a Array
85
+ @result.each do |item|
86
+ expect(item).to be_a Triangular::Facet
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ describe '#to_s' do
93
+ it 'should return the string representation for a facet' do
94
+ facet = Triangular::Facet.new
95
+ facet.normal = Triangular::Vector.new(0, 0, 1)
96
+ facet.vertices << Triangular::Point.new(1, 2, 3)
97
+ facet.vertices << Triangular::Point.new(1, 2, 3)
98
+ facet.vertices << Triangular::Point.new(1, 2, 3)
99
+
100
+ expected_string = "facet normal 0.0 0.0 1.0\n"
101
+ expected_string += "outer loop\n"
102
+ expected_string += "#{facet.vertices[0]}\n"
103
+ expected_string += "#{facet.vertices[1]}\n"
104
+ expected_string += "#{facet.vertices[2]}\n"
105
+ expected_string += "endloop\n"
106
+ expected_string += "endfacet\n"
107
+
108
+ expect(facet.to_s).to eq(expected_string)
109
+ end
110
+ end
111
+
112
+ describe '#intersection_at_z' do
113
+ context 'for a facet that intersects the target Z plane' do
114
+ before do
115
+ vertex1 = Triangular::Vertex.new(0.0, 0.0, 0.0)
116
+ vertex2 = Triangular::Vertex.new(0.0, 0.0, 6.0)
117
+ vertex3 = Triangular::Vertex.new(6.0, 0.0, 6.0)
118
+
119
+ @facet = Triangular::Facet.new(nil, vertex1, vertex2, vertex3)
120
+ end
121
+
122
+ context 'when the target Z plane is 3.0' do
123
+ it 'should return a line object' do
124
+ expect(@facet.intersection_at_z(3.0)).to be_a Triangular::Line
125
+ end
126
+
127
+ it 'should return a line with the correct start value' do
128
+ expect(@facet.intersection_at_z(3.0).start.x).to eq(0.0)
129
+ expect(@facet.intersection_at_z(3.0).start.y).to eq(0.0)
130
+ expect(@facet.intersection_at_z(3.0).start.z).to eq(3.0)
131
+ end
132
+
133
+ it 'should return a line with the correct end value' do
134
+ expect(@facet.intersection_at_z(3.0).end.x).to eq(3.0)
135
+ expect(@facet.intersection_at_z(3.0).end.y).to eq(0.0)
136
+ expect(@facet.intersection_at_z(3.0).end.z).to eq(3.0)
137
+ end
138
+ end
139
+
140
+ context 'when the target Z plane is 6.0' do
141
+ it 'should return a line object' do
142
+ expect(@facet.intersection_at_z(6.0)).to be_a Triangular::Line
143
+ end
144
+
145
+ it 'should return a line with the correct start value' do
146
+ expect(@facet.intersection_at_z(6.0).start.x).to eq(0.0)
147
+ expect(@facet.intersection_at_z(6.0).start.y).to eq(0.0)
148
+ expect(@facet.intersection_at_z(6.0).start.z).to eq(6.0)
149
+ end
150
+
151
+ it 'should return a line with the correct end value' do
152
+ expect(@facet.intersection_at_z(6.0).end.x).to eq(6.0)
153
+ expect(@facet.intersection_at_z(6.0).end.y).to eq(0.0)
154
+ expect(@facet.intersection_at_z(6.0).end.z).to eq(6.0)
155
+ end
156
+ end
157
+ end
158
+
159
+ context 'with vertices in both positive and negative space' do
160
+ before do
161
+ @facet = Triangular::Facet.parse(<<-FACET)
162
+ facet normal -0.0 1.0 -0.0
163
+ outer loop
164
+ vertex -1.0 1.0 1.0
165
+ vertex 1.0 1.0 -1.0
166
+ vertex -1.0 1.0 -1.0
167
+ endloop
168
+ endfacet
169
+ FACET
170
+ end
171
+
172
+ it 'should return a line with the correct start value' do
173
+ expect(@facet.intersection_at_z(0.0).start.x).to eq(0.0)
174
+ expect(@facet.intersection_at_z(0.0).start.y).to eq(1.0)
175
+ expect(@facet.intersection_at_z(0.0).start.z).to eq(0.0)
176
+ end
177
+
178
+ it 'should return a line with the correct end value' do
179
+ expect(@facet.intersection_at_z(0.0).end.x).to eq(-1.0)
180
+ expect(@facet.intersection_at_z(0.0).end.y).to eq(1.0)
181
+ expect(@facet.intersection_at_z(0.0).end.z).to eq(0.0)
182
+ end
183
+ end
184
+
185
+ context 'for a facet that lies on the target Z plane' do
186
+ before do
187
+ vertex1 = Triangular::Vertex.new(0.0, 0.0, 1.0)
188
+ vertex2 = Triangular::Vertex.new(2.0, 0.0, 1.0)
189
+ vertex3 = Triangular::Vertex.new(2.0, 2.0, 1.0)
190
+
191
+ @facet = Triangular::Facet.new(nil, vertex1, vertex2, vertex3)
192
+ end
193
+
194
+ it 'should return nil' do
195
+ expect(@facet.intersection_at_z(1.0)).to eq(nil)
196
+ end
197
+ end
198
+
199
+ context 'for a facet that does not intersect the target Z plane' do
200
+ before do
201
+ vertex1 = Triangular::Vertex.new(0.0, 0.0, 0.0)
202
+ vertex2 = Triangular::Vertex.new(2.0, 0.0, 0.0)
203
+ vertex3 = Triangular::Vertex.new(2.0, 2.0, 0.0)
204
+
205
+ @facet = Triangular::Facet.new(nil, vertex1, vertex2, vertex3)
206
+ end
207
+
208
+ it 'should return nil' do
209
+ expect(@facet.intersection_at_z(1.0)).to eq(nil)
210
+ end
211
+ end
212
+ end
213
+
214
+ describe '#translate!' do
215
+ before do
216
+ @facet = Triangular::Facet.parse(<<-FACET)
217
+ facet normal 0.0 0.0 -1.0
218
+ outer loop
219
+ vertex -16.5 0.0 -0.75
220
+ vertex 0.0 -9.5 -0.75
221
+ vertex 0.0 0.0 -0.75
222
+ endloop
223
+ endfacet
224
+ FACET
225
+ end
226
+
227
+ it "should call translate on each of it's Vertices" do
228
+ expect(@facet.vertices[0]).to receive(:translate!).with(16.5, 9.5, 0.75)
229
+ expect(@facet.vertices[1]).to receive(:translate!).with(16.5, 9.5, 0.75)
230
+ expect(@facet.vertices[2]).to receive(:translate!).with(16.5, 9.5, 0.75)
231
+
232
+ @facet.translate!(16.5, 9.5, 0.75)
233
+ end
234
+ end
235
+ end