scad-btw 0.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +17 -0
  4. data/.ruby-version +1 -0
  5. data/CODE_OF_CONDUCT.md +84 -0
  6. data/Gemfile +13 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +31 -0
  9. data/Rakefile +12 -0
  10. data/exe/scad-btw +6 -0
  11. data/flake.lock +306 -0
  12. data/flake.nix +63 -0
  13. data/lib/extensions/jenncad.rb +91 -0
  14. data/lib/extensions/kernel.rb +17 -0
  15. data/lib/extensions/thor.rb +11 -0
  16. data/lib/scad/btw/base.rb +224 -0
  17. data/lib/scad/btw/cli/base.rb +31 -0
  18. data/lib/scad/btw/cli/concerns/export_formats.rb +43 -0
  19. data/lib/scad/btw/cli/concerns/export_options.rb +62 -0
  20. data/lib/scad/btw/cli/presets.rb +66 -0
  21. data/lib/scad/btw/cli/solid.rb +33 -0
  22. data/lib/scad/btw/cli/spoked.rb +33 -0
  23. data/lib/scad/btw/component/axle_hole.rb +53 -0
  24. data/lib/scad/btw/component/center.rb +58 -0
  25. data/lib/scad/btw/component/counterweight.rb +117 -0
  26. data/lib/scad/btw/component/flange.rb +96 -0
  27. data/lib/scad/btw/component/joint.rb +57 -0
  28. data/lib/scad/btw/component/joint_base.rb +34 -0
  29. data/lib/scad/btw/component/o_ring_groove.rb +95 -0
  30. data/lib/scad/btw/component/solid.rb +67 -0
  31. data/lib/scad/btw/component/spokes.rb +118 -0
  32. data/lib/scad/btw/component/tread.rb +109 -0
  33. data/lib/scad/btw/concerns/component_args.rb +26 -0
  34. data/lib/scad/btw/concerns/flange_heights.rb +24 -0
  35. data/lib/scad/btw/concerns/joint_args.rb +58 -0
  36. data/lib/scad/btw/concerns/part_commons.rb +29 -0
  37. data/lib/scad/btw/concerns/tread_width.rb +21 -0
  38. data/lib/scad/btw/converters/openscad_2_ascii_stl.rb +39 -0
  39. data/lib/scad/btw/presets/scale_1_38/cargo_wheel_thin_36_inch.rb +21 -0
  40. data/lib/scad/btw/presets/scale_1_38/v20.rb +83 -0
  41. data/lib/scad/btw/solid.rb +13 -0
  42. data/lib/scad/btw/spoked.rb +13 -0
  43. data/lib/scad/btw/version.rb +7 -0
  44. data/lib/scad/btw.rb +29 -0
  45. data/sig/scad/btw.rbs +6 -0
  46. metadata +150 -0
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'extensions/jenncad'
4
+
5
+ module Extensions
6
+ module Kernel
7
+ def require(path)
8
+ ret = super
9
+
10
+ if path == 'jenncad/profile_loader'
11
+ ::JennCad::ProfileLoader.prepend Extensions::JennCad::ProfileLoader
12
+ end
13
+
14
+ ret
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Extensions
4
+ module Thor
5
+ module Option
6
+ def show_default?
7
+ super || [TrueClass, FalseClass].any? { |c| default.is_a?(c) }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'scad/btw/concerns/component_args'
4
+ require 'scad/btw/concerns/part_commons'
5
+
6
+ require 'scad/btw/component/axle_hole'
7
+ require 'scad/btw/component/center'
8
+ require 'scad/btw/component/counterweight'
9
+ require 'scad/btw/component/flange'
10
+ require 'scad/btw/component/joint_base'
11
+ require 'scad/btw/component/joint'
12
+ require 'scad/btw/component/o_ring_groove'
13
+ require 'scad/btw/component/solid'
14
+ require 'scad/btw/component/spokes'
15
+ require 'scad/btw/component/tread'
16
+
17
+ module Scad
18
+ module Btw
19
+ class Base < JennCad::Part
20
+ include Scad::Btw::Concerns::PartCommons
21
+
22
+ # Wheel size mapping
23
+ # See eurobricks.com forum post https://bit.ly/3ZARZUZ for details
24
+ OLD_2_NEW_SIZE_MAP = {
25
+ 'XXL' => 13,
26
+ 'XLL' => 12,
27
+ 'XL' => 11,
28
+ 'LL' => 10,
29
+ 'L' => 9,
30
+ 'ML' => 8,
31
+ 'M' => 7,
32
+ 'MS' => 6,
33
+ 'S' => 5
34
+ }.freeze
35
+
36
+ DEFAULTS = {
37
+ COLOR: :gray,
38
+ HEIGHT: 8,
39
+ INNER_HEIGHT_DIFF: -0.5,
40
+ IS_BLIND: false,
41
+ IS_THIN: true,
42
+ THIN_HEIGHT: 5,
43
+ WITH_COUNTERWEIGHT: false,
44
+ WITH_JOINT: false,
45
+ WITH_O_RING_GROOVE: false
46
+ }.freeze
47
+
48
+ COMPONENT_ARGS_MAPPING = {
49
+ axle: { height: :height },
50
+ center: { height: :height },
51
+ counterweight: { height: :inner_height, diameter: :size_2_diameter },
52
+ flange: { diameter: :size_2_diameter },
53
+ joint: { height: :height },
54
+ o_ring_groove: { diameter: :size_2_diameter, thin: :thin? },
55
+ solid: { height: :inner_height, diameter: :size_2_diameter },
56
+ spokes: { height: :inner_height, diameter: :size_2_diameter },
57
+ tread: { diameter: :size_2_diameter, height: :height }
58
+ }.freeze
59
+
60
+ ARGS = {
61
+ prefix: :wheel,
62
+ args: {
63
+ size: {
64
+ description: 'Wheel size.', required: true, type: :string,
65
+ enum: OLD_2_NEW_SIZE_MAP.keys + OLD_2_NEW_SIZE_MAP.values.map(&:to_s)
66
+ },
67
+ is_thin: {
68
+ description: 'Whether wheel is thin (5 mm) or not (8 mm).', default: DEFAULTS[:IS_THIN], type: :boolean
69
+ },
70
+ inner_height: {
71
+ description: "Height of spokes or web. Defaults to: Wheel thickness #{DEFAULTS[:INNER_HEIGHT_DIFF]}",
72
+ type: :numeric
73
+ },
74
+ with_o_ring_groove: {
75
+ description: 'Whether wheel has o-ring groove.', default: DEFAULTS[:WITH_O_RING_GROOVE], type: :boolean
76
+ },
77
+ with_joint: {
78
+ description: 'Whether wheel has joint.', default: DEFAULTS[:WITH_JOINT], type: :boolean
79
+ },
80
+ with_counterweight: {
81
+ description: 'Whether wheel has counterweight.', default: DEFAULTS[:WITH_COUNTERWEIGHT], type: :boolean
82
+ }
83
+ }
84
+ }.freeze
85
+
86
+ def initialize(args = {})
87
+ args = {}.merge(args.transform_keys(&:to_sym))
88
+
89
+ super(args)
90
+
91
+ @args = args
92
+ end
93
+
94
+ def part
95
+ (parts_additions - parts_substractions).color(color)
96
+ end
97
+
98
+ def self.component_classes
99
+ Scad::Btw::Component.constants.map(&Scad::Btw::Component.method(:const_get)).select { |c| c.is_a? Class }
100
+ end
101
+
102
+ def self.skip_components
103
+ []
104
+ end
105
+
106
+ private
107
+
108
+ attr_reader :args
109
+
110
+ def skip_components
111
+ self.class.skip_components
112
+ end
113
+
114
+ def parts_additions
115
+ res = nil
116
+
117
+ component_classes.each do |c|
118
+ next if skip_component?(c, :addition)
119
+
120
+ res ? res += component_part(c) : res = component_part(c)
121
+ end
122
+
123
+ res
124
+ end
125
+
126
+ def parts_substractions
127
+ res = nil
128
+
129
+ component_classes.each do |c|
130
+ next if skip_component?(c, :substraction)
131
+
132
+ res ? res += component_part(c) : res = component_part(c)
133
+ end
134
+
135
+ res
136
+ end
137
+
138
+ # components
139
+
140
+ def component_part(component_class)
141
+ component_class.new(component_args(component_class)).part
142
+ end
143
+
144
+ def component_args(component_class)
145
+ component_global_args(component_class).merge(component_class.component_args(args)).merge(misc_args)
146
+ end
147
+
148
+ def component_global_args(component_class)
149
+ prefix = component_class::ARGS[:prefix]
150
+
151
+ COMPONENT_ARGS_MAPPING[prefix].transform_values { |v| send(v) }
152
+ end
153
+
154
+ def skip_component?(component_class, type)
155
+ return true unless type == component_class::TYPE
156
+ return true if skip_components.include?(component_class::ARGS[:prefix])
157
+ return true if not_with?(component_class::ARGS[:prefix])
158
+
159
+ false
160
+ end
161
+
162
+ def not_with?(component)
163
+ return false unless %i[counterweight joint o_ring_groove].include?(component)
164
+
165
+ !send("with_#{component}?".to_sym)
166
+ end
167
+
168
+ def component_classes
169
+ self.class.component_classes
170
+ end
171
+
172
+ # functions
173
+
174
+ def misc_args
175
+ args.except do |key, _|
176
+ component_classes.join(self.class).map { |c| c.component_args_key_map.values }.flatten.include?(key)
177
+ end
178
+ end
179
+
180
+ def height
181
+ thin? ? DEFAULTS[:THIN_HEIGHT] : DEFAULTS[:HEIGHT]
182
+ end
183
+
184
+ def thin?
185
+ args.fetch(:wheel_is_thin, DEFAULTS[:IS_THIN])
186
+ end
187
+
188
+ def inner_height
189
+ args[:wheel_inner_height] || (height + DEFAULTS[:INNER_HEIGHT_DIFF])
190
+ end
191
+
192
+ # See eurobricks.com forum post https://bit.ly/3ZARZUZ for details
193
+ def size_2_diameter
194
+ (size + 0.5) * 3.2
195
+ end
196
+
197
+ def size
198
+ provided_size = args.fetch(:wheel_size)
199
+
200
+ mapped_size = OLD_2_NEW_SIZE_MAP[provided_size] || provided_size
201
+
202
+ raise "Size #{provided_size} not supportet!" unless OLD_2_NEW_SIZE_MAP.values.include?(mapped_size.to_i)
203
+
204
+ mapped_size.to_f
205
+ end
206
+
207
+ def color
208
+ args[:wheel_color] || DEFAULTS[:COLOR]
209
+ end
210
+
211
+ def with_counterweight?
212
+ args[:wheel_with_counterweight] || with_joint? || DEFAULTS[:WITH_COUNTERWEIGHT]
213
+ end
214
+
215
+ def with_o_ring_groove?
216
+ args[:wheel_with_o_ring_groove] || DEFAULTS[:WITH_O_RING_GROOVE]
217
+ end
218
+
219
+ def with_joint?
220
+ args[:wheel_with_joint] || DEFAULTS[:WITH_JOINT]
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'extensions/thor'
5
+ Thor::Option.prepend Extensions::Thor::Option
6
+
7
+ require 'scad/btw'
8
+ require_relative 'presets'
9
+ require_relative 'solid'
10
+ require_relative 'spoked'
11
+
12
+ module Scad
13
+ module Btw
14
+ module Cli
15
+ class Base < Thor
16
+ desc 'preset SUBCOMMAND ...ARGS', 'Generate from presets'
17
+ subcommand 'preset', Scad::Btw::Cli::Presets
18
+
19
+ desc 'solid SUBCOMMAND ...ARGS', 'Generate solid wheel'
20
+ subcommand 'solid', Scad::Btw::Cli::Solid
21
+
22
+ desc 'spoked SUBCOMMAND ...ARGS', 'Generate spoked wheel'
23
+ subcommand 'spoked', Scad::Btw::Cli::Spoked
24
+
25
+ def self.exit_on_failure?
26
+ true
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tempfile'
4
+
5
+ module Scad
6
+ module Btw
7
+ module Cli
8
+ module Concerns
9
+ module ExportFormats
10
+ extend ActiveSupport::Concern
11
+
12
+ private
13
+
14
+ def export_part(part, file, format = 'scad')
15
+ case format
16
+ when 'scad'
17
+ export_part_to_scad(part, file)
18
+ when 'stl'
19
+ export_stl(part, file)
20
+ else
21
+ puts "Unknown export format `#{format}`. Choose one of `scad` and `stl`."
22
+ end
23
+ end
24
+
25
+ def export_part_to_scad(part, file)
26
+ JennCad::Exporters::OpenScad.new(part).save(file)
27
+ end
28
+
29
+ def export_stl(part, file)
30
+ tempfile = Tempfile.new('scad')
31
+
32
+ export_part_to_scad(part, tempfile.path)
33
+
34
+ Scad::Btw::Converters::Openscad2AsciiStl.convert(in_path: tempfile.path, out_path: file)
35
+ ensure
36
+ tempfile.close
37
+ tempfile.unlink
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scad
4
+ module Btw
5
+ module Cli
6
+ module Concerns
7
+ module ExportOptions
8
+ extend ActiveSupport::Concern
9
+
10
+ class_methods do
11
+ def _gen_options
12
+ _wheel_options + _component_options
13
+ end
14
+
15
+ def _wheel_options
16
+ _part_options(klass: Object.const_get(self::WHEEL_CLASS_NAME))
17
+ end
18
+
19
+ def _part_options(klass:)
20
+ klass::ARGS[:args].keys.map do |key|
21
+ _option_args(
22
+ prefix: klass::ARGS[:prefix],
23
+ name: key,
24
+ arg: klass::ARGS[:args][key]
25
+ )
26
+ end
27
+ end
28
+
29
+ def _component_options
30
+ Object.const_get(self::WHEEL_CLASS_NAME).component_classes
31
+ .map do |cc|
32
+ _component_option(klass: cc)
33
+ end.flatten
34
+ end
35
+
36
+ def _component_option(klass:)
37
+ if Object.const_get(self::WHEEL_CLASS_NAME).skip_components
38
+ .include?(klass::ARGS[:prefix])
39
+ []
40
+ else
41
+ _part_options(klass: klass)
42
+ end
43
+ end
44
+
45
+ def _option_args(prefix:, name:, arg:)
46
+ args = {
47
+ name: "#{prefix}_#{name}".to_sym,
48
+ kwargs: { desc: arg[:description] }
49
+ }
50
+
51
+ %i[type default required enum hide].each do |option|
52
+ args[:kwargs][option] = arg[option] if arg.key?(option)
53
+ end
54
+
55
+ args
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'scad/btw/cli/concerns/export_formats'
5
+
6
+ module Scad
7
+ module Btw
8
+ module Cli
9
+ class Presets < Thor
10
+ include ActiveSupport::Inflector
11
+ include Scad::Btw::Cli::Concerns::ExportFormats
12
+
13
+ PRESETS_NAMESPACE = 'Scad::Btw::Presets'
14
+
15
+ desc 'list', 'List available presets'
16
+ def list
17
+ print list_output
18
+ end
19
+
20
+ option :preset, required: true, type: :string
21
+ desc 'export FILE [FORMAT]', 'Export preset to file in either `scad` (default) or `stl` format'
22
+ def export(file, format = 'scad')
23
+ export_part(part, file, format)
24
+ end
25
+
26
+ private
27
+
28
+ def part
29
+ preset_class_name = "#{PRESETS_NAMESPACE}::#{camelize(underscore(options[:preset]))}"
30
+
31
+ constantize(preset_class_name).to_openscad
32
+ end
33
+
34
+ def list_output
35
+ <<~LIST_OUTPUT
36
+ Available presets:
37
+
38
+ #{presets.map { |p| " - #{p}" }.join("\n")}
39
+
40
+ Use `scad-btw preset export --preset <preset> <file>` to export a
41
+ preset to openscad.
42
+ LIST_OUTPUT
43
+ end
44
+
45
+ def presets
46
+ get_classes(constantize(PRESETS_NAMESPACE)).map do |c|
47
+ dasherize(underscore(c.name.delete_prefix("#{PRESETS_NAMESPACE}::")))
48
+ end
49
+ end
50
+
51
+ def get_classes(mod)
52
+ mod.constants.map do |c|
53
+ const = mod.const_get(c)
54
+ if const.is_a? Class
55
+ const
56
+ elsif const.is_a? Module
57
+ get_classes(const)
58
+ else
59
+ next
60
+ end
61
+ end.flatten
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'scad/btw/cli/concerns/export_options'
5
+ require 'scad/btw/cli/concerns/export_formats'
6
+
7
+ module Scad
8
+ module Btw
9
+ module Cli
10
+ class Solid < Thor
11
+ include Scad::Btw::Cli::Concerns::ExportOptions
12
+ include Scad::Btw::Cli::Concerns::ExportFormats
13
+ include ActiveSupport::Inflector
14
+
15
+ WHEEL_CLASS_NAME = 'Scad::Btw::Solid'
16
+
17
+ _gen_options.each do |conf|
18
+ option(conf[:name], conf[:kwargs])
19
+ end
20
+ desc 'export FILE [FORMAT]', 'Export solid wheel to file in either `scad` (default) or `stl` format'
21
+ def export(file, format = 'scad')
22
+ export_part(part, file, format)
23
+ end
24
+
25
+ private
26
+
27
+ def part
28
+ constantize(WHEEL_CLASS_NAME).to_openscad(options)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'scad/btw/cli/concerns/export_options'
5
+ require 'scad/btw/cli/concerns/export_formats'
6
+
7
+ module Scad
8
+ module Btw
9
+ module Cli
10
+ class Spoked < Thor
11
+ include Scad::Btw::Cli::Concerns::ExportOptions
12
+ include Scad::Btw::Cli::Concerns::ExportFormats
13
+ include ActiveSupport::Inflector
14
+
15
+ WHEEL_CLASS_NAME = 'Scad::Btw::Spoked'
16
+
17
+ _gen_options.each do |conf|
18
+ option(conf[:name], conf[:kwargs])
19
+ end
20
+ desc 'export FILE [FORMAT]', 'Export solid wheel to file in either `scad` (default) or `stl` format'
21
+ def export(file, format = 'scad')
22
+ export_part(part, file, format)
23
+ end
24
+
25
+ private
26
+
27
+ def part
28
+ constantize(WHEEL_CLASS_NAME).to_openscad(options)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'scad/btw/concerns/component_args'
4
+ require 'scad/btw/concerns/part_commons'
5
+
6
+ module Scad
7
+ module Btw
8
+ module Component
9
+ class AxleHole < JennCad::Part
10
+ include Scad::Btw::Concerns::PartCommons
11
+ include Scad::Btw::Concerns::ComponentArgs
12
+
13
+ TYPE = :substraction
14
+ SPLINE_WIDTH = 2
15
+ DIAMETER = 5
16
+
17
+ ARGS = {
18
+ prefix: :axle,
19
+ args: {
20
+ height: {
21
+ description: 'Wheel height. Defaults to: Wheel height defined ' \
22
+ 'by wheel thickness (`wheel_is_thin` flag).',
23
+ type: :numeric,
24
+ hide: true
25
+ }
26
+ }
27
+ }.freeze
28
+
29
+ def initialize(args = {})
30
+ super
31
+
32
+ @height = args.fetch(:height)
33
+ end
34
+
35
+ def part
36
+ res = spline
37
+
38
+ res + spline.rz(90)
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :height
44
+
45
+ # components
46
+
47
+ def spline
48
+ cube(x: DIAMETER, y: SPLINE_WIDTH, z: height + 1).mz(-0.5)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'scad/btw/concerns/component_args'
4
+ require 'scad/btw/concerns/part_commons'
5
+
6
+ module Scad
7
+ module Btw
8
+ module Component
9
+ class Center < JennCad::Part
10
+ include Scad::Btw::Concerns::PartCommons
11
+ include Scad::Btw::Concerns::ComponentArgs
12
+
13
+ TYPE = :addition
14
+ MIN_DIAMETER = 7.4
15
+
16
+ ARGS = {
17
+ prefix: :center,
18
+ args: {
19
+ height: {
20
+ description: 'Center height. Defaults to: Wheel height defined ' \
21
+ 'by wheel thickness (`wheel_is_thin` flag).',
22
+ type: :numeric,
23
+ hide: true
24
+ },
25
+ diameter: {
26
+ description: 'Center diameter. Minimum diameter of ' \
27
+ "#{MIN_DIAMETER} will be used if provided value " \
28
+ 'is smaller.',
29
+ default: MIN_DIAMETER,
30
+ type: :numeric
31
+ }
32
+ }
33
+ }.freeze
34
+
35
+ def initialize(args = {})
36
+ super
37
+
38
+ @diameter = args[:diameter] || 0
39
+ @height = args.fetch(:height)
40
+ end
41
+
42
+ def part
43
+ cylinder(d: calculated_diameter, h: height)
44
+ end
45
+
46
+ private
47
+
48
+ attr_reader :diameter, :height
49
+
50
+ # functions
51
+
52
+ def calculated_diameter
53
+ [diameter, MIN_DIAMETER].max
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end