fiber_pattern 0.3.0 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97a576e2a84dd27f7a1690c4bddbe30adfa4eea6bd323aa19dd3389a1e10e5cb
4
- data.tar.gz: fa3d3634bab8fa1cdd9f64b2c2f9027d4afc1361a67a26284ed71df6ec8b353e
3
+ metadata.gz: d0ed0351785dcc4a977f481938aecf3b3d787b91752558bc307cf2b72ad962e8
4
+ data.tar.gz: c16dbb1de69fa26de99bc7dc12504db55c1707bf5a9e7388973fdf1954998e18
5
5
  SHA512:
6
- metadata.gz: bfc2843e21944d714cfee9270802e629ab1d08e340f1bfb7a7b59f2e5f8bd30469369b6108ad61f3429cfc306150f7af463d53dd43665d572ed6025a33df7c3d
7
- data.tar.gz: 6f065951604cbcf7612ea115b2aceeff0978f71b0620d61ddafe4e0013dfa2f400f9baa084e45d0da432c5ed999913f240e9c53f1c3a79c013537667f743f83a
6
+ metadata.gz: d9fc5ede7f7838208da2e8382414bebf621af83d557df70d5222531b8087b389dda3d213608149d4f478c146ef803b676c93925d4c0749e2cd889bd71132ba3e
7
+ data.tar.gz: '0090202feec59f5190da9296132831e0ffaa12389cf0fed5a66c4222662c5700aec07a42ef2b0b1fa0c748261a801eccf6bc63baf8130be985f7b98a4d882699'
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberPattern
4
+ # Value object representing key body dimensions.
5
+ #
6
+ # Accepts any set of named measurements as keyword arguments.
7
+ # Each value should be a FiberUnits::Length.
8
+ #
9
+ # @example
10
+ # body = FiberPattern::BodyMeasurements.new(
11
+ # bust: 36.inches,
12
+ # waist: 30.inches,
13
+ # hip: 38.inches,
14
+ # arm_length: 24.inches
15
+ # )
16
+ # body.bust # => 36.inches
17
+ # body[:waist] # => 30.inches
18
+ # body.measurements # => [:bust, :waist, :hip, :arm_length]
19
+ class BodyMeasurements
20
+ # @return [Hash<Symbol, FiberUnits::Length>] all measurements
21
+ attr_reader :data
22
+
23
+ # @param measurements [Hash<Symbol, FiberUnits::Length>] named body measurements
24
+ def initialize(**measurements)
25
+ raise ArgumentError, "at least one measurement is required" if measurements.empty?
26
+
27
+ @data = measurements.freeze
28
+ define_accessors!
29
+ end
30
+
31
+ # Returns the measurement names.
32
+ #
33
+ # @return [Array<Symbol>]
34
+ def measurements
35
+ data.keys
36
+ end
37
+
38
+ # Hash-style access to a measurement.
39
+ #
40
+ # @param name [Symbol]
41
+ # @return [FiberUnits::Length, nil]
42
+ def [](name)
43
+ data[name]
44
+ end
45
+
46
+ # Returns measurements as a plain hash.
47
+ #
48
+ # @return [Hash<Symbol, FiberUnits::Length>]
49
+ def to_h
50
+ data.dup
51
+ end
52
+
53
+ private
54
+
55
+ def define_accessors!
56
+ data.each_key do |name|
57
+ define_singleton_method(name) { data[name] }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberPattern
4
+ # Value object representing ease preferences per measurement.
5
+ #
6
+ # Ease is the difference between body measurements and finished garment
7
+ # measurements. Positive ease creates a looser fit; negative ease creates
8
+ # a tighter fit.
9
+ #
10
+ # @example
11
+ # ease = FiberPattern::Ease.new(
12
+ # bust: 4.inches,
13
+ # waist: 2.inches,
14
+ # hip: 2.inches
15
+ # )
16
+ # ease.bust # => 4.inches
17
+ # ease[:waist] # => 2.inches
18
+ # ease.for(:bust) # => 4.inches
19
+ class Ease
20
+ # @return [Hash<Symbol, FiberUnits::Length>] all ease values
21
+ attr_reader :data
22
+
23
+ # @param ease_values [Hash<Symbol, FiberUnits::Length>] named ease values
24
+ def initialize(**ease_values)
25
+ raise ArgumentError, "at least one ease value is required" if ease_values.empty?
26
+
27
+ @data = ease_values.freeze
28
+ define_accessors!
29
+ end
30
+
31
+ # Returns the ease value for a given measurement.
32
+ #
33
+ # @param name [Symbol]
34
+ # @return [FiberUnits::Length]
35
+ # @raise [ArgumentError] if no ease is defined for the measurement
36
+ def for(name)
37
+ raise ArgumentError, "no ease defined for #{name.inspect}" unless data.key?(name)
38
+
39
+ data[name]
40
+ end
41
+
42
+ # Hash-style access to an ease value.
43
+ #
44
+ # @param name [Symbol]
45
+ # @return [FiberUnits::Length, nil]
46
+ def [](name)
47
+ data[name]
48
+ end
49
+
50
+ # Returns all measurement names that have ease defined.
51
+ #
52
+ # @return [Array<Symbol>]
53
+ def measurements
54
+ data.keys
55
+ end
56
+
57
+ # Returns ease values as a plain hash.
58
+ #
59
+ # @return [Hash<Symbol, FiberUnits::Length>]
60
+ def to_h
61
+ data.dup
62
+ end
63
+
64
+ private
65
+
66
+ def define_accessors!
67
+ data.each_key do |name|
68
+ next if name == :for # avoid overriding #for
69
+
70
+ define_singleton_method(name) { data[name] }
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberPattern
4
+ # Combines body measurements with ease to produce finished garment dimensions.
5
+ #
6
+ # @example
7
+ # body = FiberPattern::BodyMeasurements.new(bust: 36.inches, waist: 30.inches)
8
+ # ease = FiberPattern::Ease.new(bust: 4.inches, waist: 2.inches)
9
+ # garment = FiberPattern::GarmentSizing.new(body: body, ease: ease)
10
+ # garment.bust # => 40.inches
11
+ # garment.waist # => 32.inches
12
+ class GarmentSizing
13
+ # @return [FiberPattern::BodyMeasurements]
14
+ attr_reader :body
15
+
16
+ # @return [FiberPattern::Ease]
17
+ attr_reader :ease
18
+
19
+ # @param body [FiberPattern::BodyMeasurements] body measurements
20
+ # @param ease [FiberPattern::Ease] ease preferences
21
+ def initialize(body:, ease:)
22
+ validate!(body, ease)
23
+ @body = body
24
+ @ease = ease
25
+ define_accessors!
26
+ end
27
+
28
+ # Returns the finished garment dimension for a given measurement.
29
+ #
30
+ # @param name [Symbol]
31
+ # @return [FiberUnits::Length] body measurement + ease
32
+ # @raise [ArgumentError] if the measurement is not defined
33
+ def dimension(name)
34
+ raise ArgumentError, "unknown measurement #{name.inspect}" unless body.data.key?(name)
35
+
36
+ body[name] + (ease[name] || zero_length(body[name]))
37
+ end
38
+
39
+ # Hash-style access to finished dimensions.
40
+ #
41
+ # @param name [Symbol]
42
+ # @return [FiberUnits::Length]
43
+ def [](name)
44
+ dimension(name)
45
+ end
46
+
47
+ # Returns all finished garment dimensions.
48
+ #
49
+ # @return [Hash<Symbol, FiberUnits::Length>]
50
+ def dimensions
51
+ body.measurements.each_with_object({}) do |name, result|
52
+ result[name] = dimension(name)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def validate!(body, ease)
59
+ ease.measurements.each do |name|
60
+ unless body.measurements.include?(name)
61
+ raise ArgumentError, "ease defines #{name.inspect} but body measurements does not"
62
+ end
63
+ end
64
+ end
65
+
66
+ def define_accessors!
67
+ body.measurements.each do |name|
68
+ define_singleton_method(name) { dimension(name) }
69
+ end
70
+ end
71
+
72
+ def zero_length(reference)
73
+ reference * 0
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FiberPattern
4
+ # Standard size chart based on Craft Yarn Council (CYC) guidelines.
5
+ #
6
+ # Provides lookup of standard body measurements by size, and a closest-size
7
+ # finder that matches body measurements to the nearest standard size.
8
+ #
9
+ # @example
10
+ # chart = FiberPattern::SizeChart.new
11
+ # chart.size(:m) # => { bust: 36.inches, waist: 28.inches, hip: 38.inches }
12
+ # chart.closest_size(bust: 37.inches, waist: 29.inches, hip: 39.inches) # => :m
13
+ class SizeChart
14
+ # CYC standard women's body measurements (inches).
15
+ # Source: Craft Yarn Council Standards & Guidelines
16
+ CYC_WOMEN = {
17
+ xs: {bust: 28, waist: 20, hip: 30},
18
+ s: {bust: 32, waist: 24, hip: 34},
19
+ m: {bust: 36, waist: 28, hip: 38},
20
+ l: {bust: 40, waist: 32, hip: 42},
21
+ xl: {bust: 44, waist: 36, hip: 46},
22
+ xxl: {bust: 48, waist: 40, hip: 50},
23
+ xxxl: {bust: 52, waist: 44, hip: 54},
24
+ xxxxl: {bust: 56, waist: 48, hip: 58},
25
+ xxxxxl: {bust: 60, waist: 52, hip: 62}
26
+ }.freeze
27
+
28
+ # @return [Hash<Symbol, Hash<Symbol, FiberUnits::Length>>] size chart data
29
+ attr_reader :chart
30
+
31
+ # @return [Array<Symbol>] ordered size names
32
+ attr_reader :sizes
33
+
34
+ # Creates a new size chart.
35
+ #
36
+ # @param chart [Hash<Symbol, Hash<Symbol, Numeric>>] raw chart data mapping size names
37
+ # to measurement hashes. Values are converted to inches. Defaults to CYC_WOMEN.
38
+ def initialize(chart: CYC_WOMEN)
39
+ @chart = chart.each_with_object({}) do |(size_name, measurements), result|
40
+ result[size_name] = measurements.transform_values { |v| v.is_a?(Numeric) ? v.inches : v }
41
+ end.freeze
42
+ @sizes = @chart.keys.freeze
43
+ end
44
+
45
+ # Returns body measurements for a standard size.
46
+ #
47
+ # @param name [Symbol] size name
48
+ # @return [Hash<Symbol, FiberUnits::Length>]
49
+ # @raise [ArgumentError] if the size is not in the chart
50
+ def size(name)
51
+ raise ArgumentError, "unknown size #{name.inspect}" unless chart.key?(name)
52
+
53
+ chart[name]
54
+ end
55
+
56
+ # Finds the closest standard size to the given body measurements.
57
+ #
58
+ # Compares using the sum of squared differences across all provided
59
+ # measurements. Only measurements present in both the query and the
60
+ # chart are compared.
61
+ #
62
+ # @param measurements [Hash<Symbol, FiberUnits::Length>] body measurements to match
63
+ # @return [Symbol] the closest size name
64
+ # @raise [ArgumentError] if no measurements overlap with the chart
65
+ def closest_size(**measurements)
66
+ comparable_keys = measurements.keys & chart.values.first.keys
67
+ raise ArgumentError, "no comparable measurements provided" if comparable_keys.empty?
68
+
69
+ sizes.min_by do |size_name|
70
+ size_data = chart[size_name]
71
+ comparable_keys.sum do |key|
72
+ diff = measurements[key].value - size_data[key].value
73
+ diff * diff
74
+ end
75
+ end
76
+ end
77
+
78
+ # Returns a BodyMeasurements object for a standard size.
79
+ #
80
+ # @param name [Symbol] size name
81
+ # @return [FiberPattern::BodyMeasurements]
82
+ def body_measurements_for(name)
83
+ BodyMeasurements.new(**size(name))
84
+ end
85
+ end
86
+ end
@@ -3,6 +3,6 @@
3
3
  # :nocov:
4
4
  module FiberPattern
5
5
  # Current gem version.
6
- VERSION = "0.3.0"
6
+ VERSION = "0.4.0"
7
7
  end
8
8
  # :nocov:
data/lib/fiber_pattern.rb CHANGED
@@ -9,6 +9,10 @@ require_relative "fiber_pattern/scaling"
9
9
  require_relative "fiber_pattern/shaping"
10
10
  require_relative "fiber_pattern/grade_rules"
11
11
  require_relative "fiber_pattern/grader"
12
+ require_relative "fiber_pattern/body_measurements"
13
+ require_relative "fiber_pattern/ease"
14
+ require_relative "fiber_pattern/garment_sizing"
15
+ require_relative "fiber_pattern/size_chart"
12
16
 
13
17
  # Utilities for generating fiber pattern measurements from gauge data.
14
18
  module FiberPattern
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fiber_pattern
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Meagan Waller
@@ -76,11 +76,15 @@ files:
76
76
  - LICENSE.txt
77
77
  - README.md
78
78
  - lib/fiber_pattern.rb
79
+ - lib/fiber_pattern/body_measurements.rb
80
+ - lib/fiber_pattern/ease.rb
81
+ - lib/fiber_pattern/garment_sizing.rb
79
82
  - lib/fiber_pattern/grade_rules.rb
80
83
  - lib/fiber_pattern/grader.rb
81
84
  - lib/fiber_pattern/repeat.rb
82
85
  - lib/fiber_pattern/scaling.rb
83
86
  - lib/fiber_pattern/shaping.rb
87
+ - lib/fiber_pattern/size_chart.rb
84
88
  - lib/fiber_pattern/sizing.rb
85
89
  - lib/fiber_pattern/version.rb
86
90
  - sig/fiber_pattern.rbs