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 +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: []
|