fitment 0.1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []