fitment 0.1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +0 -0
- data/Rakefile +87 -0
- data/VERSION +1 -0
- data/examples/audi_s3.rb +22 -0
- data/fitment.gemspec +17 -0
- data/lib/fitment/combo.rb +227 -0
- data/lib/fitment/tire.rb +70 -0
- data/lib/fitment/wheel.rb +50 -0
- data/lib/fitment.rb +19 -0
- data/test/combo.rb +175 -0
- data/test/fitment.rb +27 -0
- data/test/tire.rb +82 -0
- data/test/wheel.rb +58 -0
- metadata +55 -0
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
|
data/examples/audi_s3.rb
ADDED
@@ -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
|
data/lib/fitment/tire.rb
ADDED
@@ -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: []
|