fitment 0.1.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 21354fe7c73dd378577343e9bd1b41597e1bd9974fec3cecd9b40b5c57474e51
4
+ data.tar.gz: 562137377c7d88d67d5b9e8ce8084a9e20f2a93e75521b0cd16e28073bf8db71
5
+ SHA512:
6
+ metadata.gz: e0b44ed21aa425d258493f1849602f3f4c131127e79674efcfc914b96cb8c51d1dc99c540bed33f5c3b572093ac02770a355d1b20743ce1d709cc9228d51da9b
7
+ data.tar.gz: 3a09c6b0fefdd10cbdc0f3b5c2011fe7082a8db62a568359c39baa0abe5c74422a58437549def0a6cc87a1a92a72caf7ef48d76fa70e9e9a3eba7610e4a3ab52
data/README.md ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,87 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new :test do |t|
4
+ t.pattern = "test/*.rb"
5
+ t.warning = true
6
+ end
7
+
8
+ task default: :test
9
+
10
+ desc "Run example scripts"
11
+ task :examples do
12
+ Dir['examples/**/*.rb'].each { |filepath|
13
+ puts
14
+ sh "ruby -Ilib #{filepath}"
15
+ puts
16
+ }
17
+ end
18
+
19
+ #
20
+ # METRICS
21
+ #
22
+
23
+ begin
24
+ require 'flog_task'
25
+ FlogTask.new do |t|
26
+ t.threshold = 400
27
+ t.dirs = ['lib']
28
+ t.verbose = true
29
+ end
30
+ rescue LoadError
31
+ warn 'flog_task unavailable'
32
+ end
33
+
34
+ begin
35
+ require 'flay_task'
36
+
37
+ # Monkey patch here because flay doesn't respect dirs anymore
38
+ # created mostly by adam12 in #ruby on Libera.Chat
39
+
40
+ module FlayTaskExt
41
+ def define
42
+ desc "Analyze for code duplication in: #{dirs.join(", ")}"
43
+ task name do
44
+ require "flay"
45
+ flay = Flay.run(dirs)
46
+ flay.report if verbose
47
+
48
+ raise "Flay total too high! #{flay.total} > #{threshold}" if
49
+ flay.total > threshold
50
+ end
51
+ self
52
+ end
53
+ end
54
+
55
+ FlayTask.prepend(FlayTaskExt)
56
+
57
+ FlayTask.new do |t|
58
+ t.threshold = 100
59
+ t.dirs = ['lib']
60
+ t.verbose = true
61
+ end
62
+ rescue LoadError
63
+ warn 'flay_task unavailable'
64
+ end
65
+
66
+ begin
67
+ require 'roodi_task'
68
+ RoodiTask.new config: '.roodi.yml', patterns: ['lib/**/*.rb']
69
+ rescue LoadError
70
+ warn "roodi_task unavailable"
71
+ end
72
+
73
+ #
74
+ # GEM BUILD / PUBLISH
75
+ #
76
+
77
+ begin
78
+ require 'buildar'
79
+
80
+ Buildar.new do |b|
81
+ b.gemspec_file = 'fitment.gemspec'
82
+ b.version_file = 'VERSION'
83
+ b.use_git = true
84
+ end
85
+ rescue LoadError
86
+ warn "buildar tasks unavailable"
87
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0.1
@@ -0,0 +1,22 @@
1
+ require 'fitment/combo'
2
+
3
+ FC = Fitment::Combo
4
+
5
+ stock18 = FC.new_with_params(225, 40, 18, 8, 46)
6
+ stock19 = FC.new_with_params(235, 35, 19, 8, 49)
7
+ after18 = FC.new_with_params(245, 40, 18, 8, 45)
8
+ after19 = FC.new_with_params(245, 35, 19, 8, 49)
9
+
10
+ puts 'Tire Wheel Increase'
11
+ puts '----------------------------'
12
+ puts [stock19, 'Baseline, Factory 19'].join(' ')
13
+ puts [after18, stock19.increase(after18).inspect].join(' ')
14
+ puts [after19, stock19.increase(after19).inspect].join(' ')
15
+ puts
16
+
17
+ puts 'Tire Wheel Increase'
18
+ puts '----------------------------'
19
+ puts [stock18, 'Baseline, Factory 18'].join(' ')
20
+ puts [after18, stock18.increase(after18).inspect].join(' ')
21
+ puts [stock19, stock18.increase(stock19).inspect].join(' ')
22
+ puts [after19, stock18.increase(after19).inspect].join(' ')
data/fitment.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'fitment'
3
+ s.summary = "Simple, fast, and comprehensive wheel and tire fitment analysis"
4
+ s.description = "Use this tool to fill out your fenders and get flush"
5
+ s.authors = ["Rick Hull"]
6
+ s.homepage = "https://github.com/rickhull/fitment"
7
+ s.license = "LGPL-3.0"
8
+
9
+ s.required_ruby_version = "~> 2"
10
+
11
+ s.version = File.read(File.join(__dir__, 'VERSION')).chomp
12
+
13
+ s.files = %w[fitment.gemspec VERSION README.md Rakefile]
14
+ s.files += Dir['lib/**/*.rb']
15
+ s.files += Dir['test/**/*.rb']
16
+ s.files += Dir['examples/**/*.rb']
17
+ end
@@ -0,0 +1,227 @@
1
+ require 'fitment'
2
+
3
+ Fitment.autoload :Tire, 'fitment/tire'
4
+ Fitment.autoload :Wheel, 'fitment/wheel'
5
+ Fitment.autoload :OffsetWheel, 'fitment/wheel'
6
+
7
+ module Fitment
8
+ class Combo
9
+ class ValidationError < RuntimeError; end
10
+ class DiameterMismatch < ValidationError; end
11
+ class WidthMismatch < ValidationError; end
12
+ class MinError < WidthMismatch; end
13
+ class Max45Error < WidthMismatch; end
14
+ class MaxError < WidthMismatch; end
15
+
16
+ # we'll go from 6" to 10" rim width, with emphasis on 7" to 9"
17
+ # a set of 3 tire widths for each rim width: min, max45, max
18
+ # max45 is the maximum width listed at 45% aspect ratio (no higher)
19
+ # data below is from https://www.tyresizecalculator.com
20
+ # path: /charts/tire-width-for-a-wheel-rim-size-chart
21
+ BY_RIM_WIDTH = [[6.0, 175, 185, 225],
22
+ [6.5, 195, 195, 235],
23
+ [7.0, 195, 215, 255],
24
+ [7.5, 205, 225, 265],
25
+ [8.0, 225, 245, 275],
26
+ [8.5, 235, 255, 295],
27
+ [9.0, 255, 275, 305],
28
+ [9.5, 255, 285, 315],
29
+ [10.0, 275, 295, 295]]
30
+
31
+ BELOW_6 = [[3.5, 125, nil, 135],
32
+ [4.0, 135, nil, 145],
33
+ [4.5, 145, nil, 165],
34
+ [5.0, 155, nil, 185],
35
+ [5.5, 165, 165, 205]]
36
+
37
+ ABOVE_10 = [[10.5, 285, 315, 315],
38
+ [11.0, 305, 315, 345],
39
+ [11.5, 315, 335, 345],
40
+ [12.0, 325, 345, 345],
41
+ [12.5, 345, 345, 345],
42
+ [13.0, 355, 355, 355]]
43
+
44
+ # smoothness / continuity corrections for likely data anomalies
45
+ BY_RIM_WIDTH[1][1] = 185 # not 195
46
+ BY_RIM_WIDTH[7][1] = 265 # not 255
47
+ BY_RIM_WIDTH[8][3] = 325 # not 295
48
+ ABOVE_10[0][3] = 335 # not 315
49
+
50
+ #
51
+ # Now, let's model the above table with equations
52
+ # For every x (rim width inches), we have 3 ys (tire width millimeters)
53
+ # min, max45, max
54
+ #
55
+
56
+ MODELS = {
57
+ # Linear: y = a + bx (or: mx + b)
58
+ simple: [20, 40, 75], # assume 25.4 slope
59
+ basic: [20.689, 38.467, 74.022], # assume 25.4 slope
60
+ linear: [[13.222, 26.333], # r2 = 0.9917
61
+ [12.333, 28.667], # r2 = 0.9941
62
+ [71.889, 25.667]], # r2 = 0.9926
63
+ }
64
+ # Determined via CompSci gem: CompSci::Fit.best(xs, ys)
65
+ MODELS[:best] = [[:linear, *MODELS[:linear][0]], # r2 = 0.9917; y = a + bx
66
+ [:power, 33.389, 0.95213], # r2 = 0.9943; y = ax^b
67
+ [:power, 59.621, 0.74025]] # r2 = 0.9939; y = ax^b
68
+ # uses BELOW_6 and ABOVE_10
69
+ MODELS[:extended] = [[:linear, 30.165, 24.647], # r2 = 0.9954
70
+ [:logarithmic, -229.62, 228.82], # r2 = 0.9951
71
+ [:logarithmic, -103.60, 183.05]] # r2 = 0.9908
72
+
73
+ # rounds to e.g. 235, 245, 255, etc. (per industry convention)
74
+ def self.snap(flt)
75
+ ((flt - 5) / 10.0).round * 10 + 5
76
+ end
77
+
78
+ # linear model, given a rim width in inches, determine:
79
+ # * minimum tire width in mm
80
+ # * maximum tire width limited to 45% aspect ratio
81
+ # * maximum tire width above 45% aspect ratio (big truck tires)
82
+ # * models: :actual, :simple, :basic, :linear, :best, :extended
83
+ def self.tire_widths(rim_width_in, model = :actual)
84
+ if model == :actual
85
+ (BY_RIM_WIDTH + BELOW_6 + ABOVE_10).each { |row|
86
+ return row[1..3] if rim_width_in == row[0]
87
+ }
88
+ raise("no match for model :actual, width #{rim_width_in}")
89
+ end
90
+ params = MODELS.fetch(model)
91
+ case model
92
+ when :simple, :basic
93
+ b = MM_PER_INCH
94
+ params.map { |a| snap(a + b * rim_width_in) }
95
+ when :linear
96
+ params.map { |(a, b)| snap(a + b * rim_width_in) }
97
+ when :best, :extended
98
+ params.map { |(model, a, b)|
99
+ snap(case model
100
+ when :logarithmic
101
+ a + b * Math.log(rim_width_in)
102
+ when :linear
103
+ a + b * rim_width_in
104
+ when :power
105
+ a * rim_width_in ** b
106
+ else
107
+ raise("unknown model: #{model}")
108
+ end)
109
+ }
110
+ else
111
+ raise("unknown model: #{model}")
112
+ end
113
+ end
114
+
115
+ def self.new_with_params(tread_with, ratio, diameter, width, offset,
116
+ et: true)
117
+ tire = Tire.new(tread_with, ratio, diameter)
118
+ wheel = et ?
119
+ Wheel.new(diameter, width, offset) :
120
+ OffsetWheel.new(diameter, width, offset)
121
+ Combo.new(wheel: wheel, tire: tire)
122
+ end
123
+
124
+ def self.new_with_offset(t, r, d, w, o)
125
+ self.new_with_params(t, r, d, w, o, et: false)
126
+ end
127
+
128
+ TIRE_EXCESS = 15 # extra rubber material near the bead relevant to fitment
129
+
130
+ attr_accessor :tire, :wheel, :model
131
+
132
+ def initialize(tire:, wheel:, model: :actual)
133
+ @tire = tire
134
+ @wheel = wheel
135
+ @model = model
136
+ end
137
+
138
+ def to_s
139
+ [@tire, @wheel].join(' ')
140
+ end
141
+
142
+ def validate_diameter!
143
+ wd, twd = @wheel.diameter, @tire.wheel_diameter
144
+ raise(DiameterMismatch, "wheel: %i; tire: %i" % [wd, twd]) if wd != twd
145
+ true
146
+ end
147
+
148
+ def validate_width!
149
+ msg = "tire width %i %s %s %i for wheel width %i"
150
+
151
+ min, max45, max = self.class.tire_widths(@wheel.width, @model)
152
+ raise(MinError, "no min available for width %i" % @wheel.width) if !min
153
+ if @tire.width < min
154
+ raise(MinError, msg % [@tire.width, '<', 'min', min, @wheel.width])
155
+ end
156
+ if max45 and @tire.ratio <= 45 and @tire.width > max45
157
+ raise(Max45Error, msg % [@tire.width, '>', 'max45', max45, @wheel.width])
158
+ end
159
+ raise(MaxError, "no max available for width %i" % @wheel.width) if !max
160
+ if @tire.width > max
161
+ raise(MaxError, msg % [@tire.width, '>', 'max', max, @wheel.width])
162
+ end
163
+ true
164
+ end
165
+
166
+ def validate!
167
+ validate_diameter!
168
+ validate_width!
169
+ end
170
+
171
+ def short_width
172
+ Fitment.mm(@wheel.width) + TIRE_EXCESS * 2
173
+ end
174
+
175
+ def short_height
176
+ Fitment.mm(@wheel.diameter) + TIRE_EXCESS * 2
177
+ end
178
+
179
+ def short_box
180
+ [short_width.round(2), short_height.round(2)]
181
+ end
182
+
183
+ def tall_width
184
+ @tire.width
185
+ end
186
+
187
+ def tall_height
188
+ @tire.od_mm
189
+ end
190
+
191
+ def tall_box
192
+ [tall_width.round(2), tall_height.round(2)]
193
+ end
194
+
195
+ def bounding_box
196
+ [[short_width, tall_width].max,
197
+ [short_height, tall_height].max,]
198
+ end
199
+
200
+ # returns [inside increase, outside increase, diameter increase] for each
201
+ # of :wheel and :tire
202
+ #
203
+ def increase(other)
204
+ short_width_increase = other.short_width - self.short_width
205
+ short_height_increase = other.short_height - self.short_height
206
+ tall_width_increase = other.tall_width - self.tall_width
207
+ tall_height_increase = other.tall_height - self.tall_height
208
+
209
+ # smaller et on other wheel moves it outside; more inside clearance
210
+ et_decrease = self.wheel.et - other.wheel.et
211
+
212
+ halfwidth = short_width_increase * 0.5
213
+ wheel = [halfwidth - et_decrease, # inner expansion
214
+ halfwidth + et_decrease, # outer expansion
215
+ short_height_increase] # height increase
216
+
217
+ halfwidth = tall_width_increase * 0.5
218
+ tire = [halfwidth - et_decrease, # inside expansion
219
+ halfwidth + et_decrease, # outside expansion
220
+ tall_height_increase] # height increase
221
+
222
+ { wheel: wheel.map { |flt| flt.round(2) },
223
+ tire: tire.map { |flt| flt.round(2) }, }
224
+ end
225
+ alias_method(:clearance, :increase)
226
+ end
227
+ end
@@ -0,0 +1,70 @@
1
+ require 'fitment'
2
+
3
+ module Fitment
4
+ class Tire
5
+ def self.ratio_int(ratio_flt)
6
+ (ratio_flt * 100.0 / 5).round * 5
7
+ end
8
+
9
+ def self.ratio_flt(ratio_int)
10
+ ratio_int / 100.0
11
+ end
12
+
13
+ # mm
14
+ def self.sidewall_height(width_mm, ratio_flt)
15
+ width_mm * ratio_flt
16
+ end
17
+
18
+ # inches
19
+ def self.overall_diameter(width_mm, ratio_flt, wheel_in)
20
+ 2 * Fitment.in(sidewall_height(width_mm, ratio_flt)) + wheel_in
21
+ end
22
+
23
+ # the aspect ratio is stored as an integer for easy assignment and
24
+ # comparison, and converted to a float for calculations
25
+ attr_reader :width, :ratio, :wheel_diameter
26
+
27
+ def initialize(width_mm, ratio, wheel_in)
28
+ @width = width_mm
29
+ if ratio < 0.99 and ratio > 0.1
30
+ @ratio = self.class.ratio_int(ratio)
31
+ elsif ratio.is_a? Integer and ratio%5 == 0 and ratio > 10 and ratio < 99
32
+ @ratio = ratio
33
+ else
34
+ raise("unexpected ratio: #{ratio}")
35
+ end
36
+ @wheel_diameter = wheel_in
37
+ end
38
+
39
+ def to_s
40
+ [[@width, @ratio].join('/'), @wheel_diameter].join('R')
41
+ end
42
+
43
+ def ratio_flt
44
+ self.class.ratio_flt(@ratio)
45
+ end
46
+
47
+ # sidewall height, (mm) and in
48
+ def sh_mm
49
+ self.class.sidewall_height(@width, ratio_flt).round(1)
50
+ end
51
+ alias_method :sidewall_height, :sh_mm
52
+
53
+ def sh_in
54
+ Fitment.in(sh_mm).round(2)
55
+ end
56
+
57
+ # overall diameter, mm and (in)
58
+ def od_in
59
+ self.class.overall_diameter(@width, ratio_flt, @wheel_diameter).round(2)
60
+ end
61
+ alias_method :overall_diameter, :od_in
62
+
63
+ alias_method(:series, :ratio)
64
+ alias_method(:aspect_ratio, :ratio)
65
+
66
+ def od_mm
67
+ Fitment.mm(od_in).round(1)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,50 @@
1
+ require 'fitment'
2
+
3
+ module Fitment
4
+ class Wheel
5
+ def self.et(offset_in, width_in)
6
+ (-1 * Fitment.mm(offset_in - width_in / 2)).round
7
+ end
8
+
9
+ def self.offset(et_mm, width_in)
10
+ (width_in / 2 - Fitment.in(et_mm)).round(2)
11
+ end
12
+
13
+ attr_reader :diameter, :width, :et
14
+ attr_accessor :bolt_pattern
15
+
16
+ def initialize(diameter_in, width_in, et = 0, bolt_pattern: "")
17
+ @diameter = diameter_in
18
+ @width = width_in
19
+ @et = et
20
+ @bolt_pattern = bolt_pattern
21
+ end
22
+
23
+ def to_s
24
+ "#{@diameter}x#{@width} ET#{et} #{@bolt_pattern}".strip
25
+ end
26
+
27
+ def offset
28
+ self.class.offset(@et, @width)
29
+ end
30
+ end
31
+
32
+ class OffsetWheel < Wheel
33
+ attr_reader :offset
34
+
35
+ def initialize(diameter_in, width_in, offset_in, bolt_pattern: "")
36
+ @diameter = diameter_in
37
+ @width = width_in
38
+ @offset = offset_in
39
+ @bolt_pattern = bolt_pattern
40
+ end
41
+
42
+ def to_s
43
+ "#{@diameter}x#{@width} #{@offset}\" offset #{@bolt_pattern}".strip
44
+ end
45
+
46
+ def et
47
+ self.class.et(@offset, @width)
48
+ end
49
+ end
50
+ end
data/lib/fitment.rb ADDED
@@ -0,0 +1,19 @@
1
+ module Fitment
2
+ MM_PER_INCH = 25.4
3
+
4
+ def self.in(mm)
5
+ mm.to_f / MM_PER_INCH
6
+ end
7
+
8
+ def self.mm(inches)
9
+ inches.to_f * MM_PER_INCH
10
+ end
11
+
12
+ # https://www.tiresandco.ca/tire-equivalence-advice.html
13
+ # [min, ideal, ideal, max]
14
+ # this is a simple linear model: y = a + bx; a = 55; b = 20
15
+ # [min, min+10, min+20, min+30]
16
+ def self.tire_widths(rim_width_in)
17
+ Array.new(4) { |i| (55 + 20 * rim_width_in + i * 10).round }
18
+ end
19
+ end
data/test/combo.rb ADDED
@@ -0,0 +1,175 @@
1
+ require 'fitment/combo'
2
+ require 'minitest/autorun'
3
+
4
+ include Fitment
5
+
6
+ describe Combo do
7
+ describe "validating tire width against wheel width" do
8
+ # below should match Combo::BY_RIM_WIDTH
9
+ ACTUAL = {
10
+ 5.0 => [155, nil, 185],
11
+ 8.0 => [225, 245, 275],
12
+ 9.5 => [265, 285, 315],
13
+ 12.0 => [325, 345, 345],
14
+ }
15
+
16
+ it "snaps to industry standard mm widths" do
17
+ expect(Combo.snap(254.3)).must_equal 255
18
+ expect(Combo.snap(250)).must_equal 255
19
+ expect(Combo.snap(249.9)).must_equal 245
20
+ expect(Combo.snap(240)).must_equal 245
21
+ expect(Combo.snap(239.9)).must_equal 235
22
+ end
23
+
24
+ it "uses an :actual tire model" do
25
+ expect(Combo.tire_widths(5.0, :actual)).must_equal ACTUAL[5.0]
26
+ expect(Combo.tire_widths(8.0, :actual)).must_equal ACTUAL[8.0]
27
+ expect(Combo.tire_widths(9.5, :actual)).must_equal ACTUAL[9.5]
28
+ expect(Combo.tire_widths(12.0, :actual)).must_equal ACTUAL[12.0]
29
+ end
30
+
31
+ it "uses an :extended tire model" do
32
+ expect(Combo.tire_widths(5.0, :extended)).must_equal [155, 135, 195]
33
+ expect(Combo.tire_widths(8.0, :extended)).must_equal ACTUAL[8.0]
34
+ expect(Combo.tire_widths(9.5, :extended)).must_equal [265, 285, 305]
35
+ expect(Combo.tire_widths(12.0, :extended)).must_equal [325, 335, 355]
36
+ end
37
+
38
+ it "uses a :best tire model" do
39
+ expect(Combo.tire_widths(5.0, :best)).must_equal [145, 155, 195]
40
+ expect(Combo.tire_widths(8.0, :best)).must_equal ACTUAL[8.0]
41
+ expect(Combo.tire_widths(9.5, :best)).must_equal ACTUAL[9.5]
42
+ expect(Combo.tire_widths(12.0, :best)).must_equal [325, 355, 375]
43
+ end
44
+
45
+ it "uses a :linear tire model" do
46
+ expect(Combo.tire_widths(5.0, :linear)).must_equal [145, 155, 205]
47
+ expect(Combo.tire_widths(8.0, :linear)).must_equal ACTUAL[8.0]
48
+ expect(Combo.tire_widths(9.5, :linear)).must_equal ACTUAL[9.5]
49
+ expect(Combo.tire_widths(12.0, :linear)).must_equal [325, 355, 375]
50
+ end
51
+
52
+ it "uses a :basic tire model" do
53
+ expect(Combo.tire_widths(5.0, :basic)).must_equal [145, 165, 205]
54
+ expect(Combo.tire_widths(8.0, :basic)).must_equal ACTUAL[8.0]
55
+ expect(Combo.tire_widths(9.5, :basic)).must_equal [265, 275, 315]
56
+ expect(Combo.tire_widths(12.0, :basic)).must_equal [325, 345, 375]
57
+ end
58
+
59
+ it "uses a :simple tire model" do
60
+ expect(Combo.tire_widths(5.0, :simple)).must_equal [145, 165, 205]
61
+ expect(Combo.tire_widths(8.0, :simple)).must_equal ACTUAL[8.0]
62
+ expect(Combo.tire_widths(9.5, :simple)).must_equal ACTUAL[9.5]
63
+ expect(Combo.tire_widths(12.0, :simple)).must_equal [325, 345, 375]
64
+ end
65
+ end
66
+
67
+ describe "real world combo examples" do
68
+ before do
69
+ @stock18 = Combo.new_with_params(225, 40, 18, 8, 46)
70
+ @stock19 = Combo.new_with_params(235, 35, 19, 8, 49)
71
+
72
+ @after18 = Combo.new_with_params(245, 40, 18, 8, 45)
73
+ @after19 = Combo.new_with_params(245, 35, 19, 8, 49)
74
+
75
+ @audi = [@stock18, @stock19, @after18, @after19]
76
+ end
77
+
78
+ it "validates the examples" do
79
+ @audi.each { |combo| expect(combo.validate!).must_equal(true) }
80
+ end
81
+
82
+ it "has a short box" do
83
+ @audi.each { |combo|
84
+ expect(combo.short_width).must_be(:>, Fitment.mm(combo.wheel.width))
85
+ expect(combo.short_height).must_be(:>,
86
+ Fitment.mm(combo.wheel.diameter))
87
+ }
88
+ end
89
+
90
+ it "has a tall box" do
91
+ @audi.each { |combo|
92
+ expect(combo.tall_width).must_equal(combo.tire.width)
93
+ expect(combo.tall_height).must_equal(combo.tire.od_mm)
94
+ }
95
+ end
96
+
97
+ it "calculates dimensional increase for a new combo" do
98
+ new_18 = @stock19.increase(@after18)
99
+
100
+ new_18_wheel = new_18.fetch(:wheel)
101
+ expect(new_18_wheel).must_be_kind_of Array
102
+ expect(new_18_wheel.length).must_equal 3
103
+ expect(new_18_wheel[0]).must_be(:<, 0) # smaller et, more inner clearance
104
+ expect(new_18_wheel[1]).must_be(:>, 0) # smaller et, less outer clearance
105
+ expect(new_18_wheel[2]).must_be(:<, 0) # smaller diameter wheel
106
+
107
+ new_18_tire = new_18.fetch(:tire)
108
+ expect(new_18_tire).must_be_kind_of Array
109
+ expect(new_18_tire.length).must_equal 3
110
+ expect(new_18_tire[0]).must_be(:>, 0) # wider tire outweighs smaller et
111
+ expect(new_18_tire[1]).must_be(:>, 0) # wider tire and smaller et
112
+ expect(new_18_tire[1]).must_be(:>, new_18_tire[0]) # smaller et
113
+ expect(new_18_tire[2]).must_be(:>, 0) # big aspect ratio
114
+
115
+ new_19 = @stock19.increase(@after19)
116
+
117
+ new_19_wheel = new_19.fetch(:wheel)
118
+ expect(new_19_wheel).must_be_kind_of Array
119
+ expect(new_19_wheel.length).must_equal 3
120
+ expect(new_19_wheel[0]).must_be_within_epsilon(0.0) # same wheel
121
+ expect(new_19_wheel[1]).must_be_within_epsilon(0.0) # same wheel
122
+ expect(new_19_wheel[2]).must_be_within_epsilon(0.0) # same wheel
123
+
124
+ new_19_tire = new_19.fetch(:tire)
125
+ expect(new_19_tire).must_be_kind_of Array
126
+ expect(new_19_tire.length).must_equal 3
127
+ expect(new_19_tire[0]).must_be(:>, 0) # wider tire, same et
128
+ expect(new_19_tire[1]).must_be(:>, 0) # wider tire, same et
129
+ expect(new_19_tire[2]).must_be(:>, 0) # same aspect ratio on wider tire
130
+ end
131
+ end
132
+
133
+ describe "unlikely combos" do
134
+ it "validates a truck tire" do
135
+ truck = Combo.new_with_offset(275, 55, 16, 8, 4)
136
+ expect(truck.validate!).must_equal true
137
+
138
+ too_wide = Combo.new_with_offset(325, 55, 16, 8, 4)
139
+ expect { too_wide.validate! }.must_raise Combo::MaxError
140
+ end
141
+
142
+ it "rejects wheel size mismatch" do
143
+ @tire19 = Tire.new(255, 35, 19)
144
+ @wheel18 = Wheel.new(18, 8, 45)
145
+ @tire17 = Tire.new(205, 40, 17)
146
+
147
+
148
+ loose = Combo.new(wheel: @wheel18, tire: @tire19)
149
+ expect { loose.validate_diameter! }.must_raise Combo::DiameterMismatch
150
+
151
+ tight = Combo.new(wheel: @wheel18, tire: @tire17)
152
+ expect { tight.validate_diameter! }.must_raise Combo::DiameterMismatch
153
+
154
+ okay = Combo.new(wheel: @wheel18, tire: Tire.new(225, 40, 18))
155
+ expect(okay.validate_diameter!).must_equal true
156
+ end
157
+
158
+ it "rejects width mismatch" do
159
+ @wide_tire = Tire.new(315, 35, 18)
160
+ @narrow_wheel = Wheel.new(18, 6)
161
+ @narrow_tire = Tire.new(185, 45, 18)
162
+ @wide_wheel = Wheel.new(18, 11)
163
+
164
+ loose = Combo.new(wheel: @narrow_wheel, tire: @wide_tire)
165
+ narrow = Combo.new(wheel: @narrow_wheel, tire: @narrow_tire)
166
+ tight = Combo.new(wheel: @wide_wheel, tire: @narrow_tire)
167
+ wide = Combo.new(wheel: @wide_wheel, tire: @wide_tire)
168
+
169
+ expect { loose.validate_width! }.must_raise Combo::WidthMismatch
170
+ expect(narrow.validate_width!).must_equal true
171
+ expect { tight.validate_width! }.must_raise Combo::WidthMismatch
172
+ expect(wide.validate_width!).must_equal true
173
+ end
174
+ end
175
+ end
data/test/fitment.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'fitment'
2
+ require 'minitest/autorun'
3
+
4
+ describe Fitment do
5
+ it "has a conversion constant" do
6
+ expect(Fitment::MM_PER_INCH).must_equal 25.4
7
+ end
8
+
9
+ it "converts mm to inches and vice versa" do
10
+ expect(Fitment.in 25.4).must_be_within_epsilon 1.0
11
+ expect(Fitment.mm 1).must_equal 25.4
12
+ expect(Fitment.mm 12).must_equal 25.4 * 12
13
+ end
14
+
15
+ it "has a simple linear tire to wheel fitment model" do
16
+ # from https://www.tiresandco.ca/tire-equivalence-advice.html
17
+ src_data = {
18
+ 6.0 => [175, 185, 195, 205],
19
+ 8.5 => [225, 235, 245, 255],
20
+ 12.5 => [305, 315, 325, 335],
21
+ }
22
+
23
+ src_data.each { |rim_width, tire_widths|
24
+ expect(Fitment.tire_widths(rim_width)).must_equal tire_widths
25
+ }
26
+ end
27
+ end
data/test/tire.rb ADDED
@@ -0,0 +1,82 @@
1
+ require 'fitment/tire'
2
+ require 'minitest/autorun'
3
+
4
+ T = Fitment::Tire
5
+
6
+ describe T do
7
+ before do
8
+ @w = 245
9
+ @r = 35
10
+ @d = 18
11
+ @t = T.new(@w, @r, @d)
12
+ @sh_mm = 85.8
13
+ @od_in = 24.75
14
+ end
15
+
16
+ it "calculates sidewall height" do
17
+ expect(T.sidewall_height(255, 0.45)).must_be_within_epsilon 255 * 0.45
18
+ expect(T.sidewall_height(195, 0.55)).must_be_within_epsilon 195 * 0.55
19
+ end
20
+
21
+ it "calculates overall diameter" do
22
+ expect(T.overall_diameter(255, 0.45, 17)
23
+ ).must_be_within_epsilon 26.03543307086614
24
+ expect(T.overall_diameter(255, 0.45, 18)
25
+ ).must_be_within_epsilon 27.03543307086614
26
+ end
27
+
28
+ it "initializes with width, ratio, and wheel diameter" do
29
+ expect(@t).must_be_kind_of(T)
30
+ expect(@t.width).must_equal @w
31
+ expect(@t.ratio).must_equal @r
32
+ expect(@t.wheel_diameter).must_equal @d
33
+ end
34
+
35
+ it "intializes with ratio between 0 and 1" do
36
+ t = T.new(225, 0.35, 18)
37
+ expect(t).must_be_kind_of(T)
38
+ expect(t.ratio).must_equal 35
39
+ end
40
+
41
+ it "has a sidewall height in mm and inches" do
42
+ expect(@t.sidewall_height).must_equal @sh_mm
43
+ expect(@t.sh_mm).must_equal @sh_mm
44
+ expect(@t.sh_in).must_be(:<, @sh_mm)
45
+ end
46
+
47
+ it "has an overall diameter in mm and inches" do
48
+ expect(@t.overall_diameter).must_equal @od_in
49
+ expect(@t.od_in).must_equal @od_in
50
+ expect(@t.od_mm).must_be(:>, @od_in)
51
+ end
52
+
53
+ describe "2018 Audi S3" do
54
+ before do
55
+ @stock18 = T.new(225, 40, 18)
56
+ @stock19 = T.new(235, 35, 19)
57
+
58
+ @after18 = T.new(245, 40, 18)
59
+ @after19 = T.new(245, 35, 19)
60
+ end
61
+
62
+ it "compares differences" do
63
+ expect(@stock18.width).must_be(:<, @stock19.width)
64
+ expect(@stock18.width).must_be(:<, @after18.width)
65
+ expect(@stock18.overall_diameter).must_be(:<, @stock19.overall_diameter)
66
+ expect(@stock18.overall_diameter).must_be(:<, @after18.overall_diameter)
67
+ expect(@stock18.sidewall_height).must_be(:>, @stock19.sidewall_height)
68
+ expect(@stock18.sidewall_height).must_be(:<, @after18.sidewall_height)
69
+
70
+ expect(@stock19.width).must_be(:<, @after18.width)
71
+ expect(@stock19.width).must_be(:<, @after19.width)
72
+ expect(@stock19.overall_diameter).must_be(:<, @after18.overall_diameter)
73
+ expect(@stock19.overall_diameter).must_be(:<, @after19.overall_diameter)
74
+ expect(@stock19.sidewall_height).must_be(:<, @after18.sidewall_height)
75
+ expect(@stock19.sidewall_height).must_be(:<, @after19.sidewall_height)
76
+
77
+ expect(@after18.width).must_equal(@after19.width)
78
+ expect(@after18.overall_diameter).must_be(:<, @after19.overall_diameter)
79
+ expect(@after18.sidewall_height).must_be(:>, @after19.sidewall_height)
80
+ end
81
+ end
82
+ end
data/test/wheel.rb ADDED
@@ -0,0 +1,58 @@
1
+ require 'fitment/wheel'
2
+ require 'minitest/autorun'
3
+
4
+ include Fitment
5
+
6
+ describe Wheel do
7
+ it "calculates ET given offset" do
8
+ expect(Wheel.et(3.5, 8)).must_be_kind_of(Numeric)
9
+ end
10
+
11
+ it "calculates offset given ET" do
12
+ expect(Wheel.offset(45, 8)).must_be_kind_of(Numeric)
13
+ end
14
+
15
+ it "initializes with diameter and width" do
16
+ d,w = 18,8
17
+ wheel = Wheel.new(d, w)
18
+ expect(wheel).must_be_kind_of Wheel
19
+ expect(wheel.diameter).must_equal d
20
+ expect(wheel.width).must_equal w
21
+ expect(wheel.et).must_equal 0
22
+ expect(wheel.bolt_pattern).must_equal ""
23
+ end
24
+
25
+ it "initializes with optional ET" do
26
+ d,w,et = 18,8,35
27
+ wet = Wheel.new(d, w, et)
28
+ expect(wet).must_be_kind_of Wheel
29
+ expect(wet.et).must_equal et
30
+ end
31
+
32
+ it "initializes with optional bolt_pattern" do
33
+ d,w,bp = 18,8,"5x112"
34
+ wbp = Wheel.new(d, w, bolt_pattern: bp)
35
+ expect(wbp).must_be_kind_of Wheel
36
+ expect(wbp.bolt_pattern).must_equal bp
37
+ end
38
+
39
+ it "allows updates to bolt_pattern" do
40
+ d,w,bp = 18,8,"5x112"
41
+ wbp = Wheel.new(d, w)
42
+ expect(wbp).must_be_kind_of Wheel
43
+ expect(wbp.bolt_pattern).must_equal ""
44
+ wbp.bolt_pattern = bp
45
+ expect(wbp.bolt_pattern).must_equal bp
46
+ end
47
+
48
+ describe OffsetWheel do
49
+ it "initializes with required offset" do
50
+ d,w,offset = 20,9,6
51
+ expect { OffsetWheel.new(d, w) }.must_raise ArgumentError
52
+ offset = 4
53
+ wof = OffsetWheel.new(d, w, offset)
54
+ expect(wof).must_be_kind_of OffsetWheel
55
+ expect(wof.offset).must_equal offset
56
+ end
57
+ end
58
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fitment
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Rick Hull
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 1980-01-01 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Use this tool to fill out your fenders and get flush
14
+ email:
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - README.md
20
+ - Rakefile
21
+ - VERSION
22
+ - examples/audi_s3.rb
23
+ - fitment.gemspec
24
+ - lib/fitment.rb
25
+ - lib/fitment/combo.rb
26
+ - lib/fitment/tire.rb
27
+ - lib/fitment/wheel.rb
28
+ - test/combo.rb
29
+ - test/fitment.rb
30
+ - test/tire.rb
31
+ - test/wheel.rb
32
+ homepage: https://github.com/rickhull/fitment
33
+ licenses:
34
+ - LGPL-3.0
35
+ metadata: {}
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - "~>"
43
+ - !ruby/object:Gem::Version
44
+ version: '2'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.1.2
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Simple, fast, and comprehensive wheel and tire fitment analysis
55
+ test_files: []