jenncad 1.0.0.pre.alpha15 → 1.0.0.pre.alpha16

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3eebd115b8e6783b61fff33f1b002f935f3d2c2023df70df9ff3b9850f12245f
4
- data.tar.gz: 806a99e1195f98ee1e8450030f9e2809c11217af7bf19e8cac88ad9408146a40
3
+ metadata.gz: ef20af7d3fc9a11140dacb23030a9ba671a80cbd39491caed609d6755f8bc85c
4
+ data.tar.gz: 4e515e6fbedcfa16b315b8ea02cafe8a6d41207fbdd9aece996165edee7f9214
5
5
  SHA512:
6
- metadata.gz: a120ac12bfe18f3532560c10452ae9460d21bd8ba2e4d287669b9b23cdf903476541fed341b81ce7feb8e906ccc8087a7a0f46f5f49e63d66b6421cd8e4d0984
7
- data.tar.gz: 06e239cea5a153ecb3b8ab1873574bc8d9a03e51d1c0d75e5fe72f393e1584971bbc2ba388d9da82ce58cc79c2f949e206962be9052da29b0a24e351c176a53d
6
+ metadata.gz: 17111c38b461ecdf683d2813284279b4bced3296d97dc9aebdf17797955affea2ad275bd21f5935680c09a8f08234ebac9c5bbb67ce197fdfe6c3e0a6489d0cf
7
+ data.tar.gz: 9022e918187212aa81f4f02b28c9a9ac11d7b8d36c6afb36835346298309ec2204430478032c406177315ee9807d0513bb52f0d8581dfec9c3df13e91b42f5f8
@@ -118,15 +118,15 @@ module JennCad
118
118
  File.open(filename, "w") do |f|
119
119
  f.puts "class #{classname} < Part"
120
120
  f.puts " def initialize(opts={})"
121
- f.puts " @opts = {"
122
- f.puts " x: 10,"
123
- f.puts " y: 10,"
124
- f.puts " z: 5,"
125
- f.puts " }.merge(opts)"
121
+ f.puts " @x = 10"
122
+ f.puts " @y = 10"
123
+ f.puts " @z = 5"
126
124
  f.puts " end"
127
125
  f.puts ""
128
126
  f.puts " def part"
129
- f.puts " cube(@opts)"
127
+ f.puts " base = cube(x: @x, y: @y, z: @z)"
128
+ f.puts " res = base.fix"
129
+ f.puts " res"
130
130
  f.puts " end"
131
131
  f.puts "end"
132
132
  end
@@ -178,6 +178,8 @@ module JennCad::Exporters
178
178
  new_obj(part, :projection, collect_params(part), parse(part.parts))
179
179
  when JennCad::Primitives::Polygon
180
180
  new_obj(part, :polygon, collect_params(part))
181
+ when JennCad::Primitives::Polyhedron
182
+ new_obj(part, :polyhedron, collect_params(part))
181
183
  when JennCad::StlImport
182
184
  new_obj(part, :import, collect_params(part))
183
185
  when JennCad::Part
@@ -212,7 +214,7 @@ module JennCad::Exporters
212
214
  return part.openscad_params
213
215
  end
214
216
  res = {}
215
- [:d, :h, :d1, :d2, :size, :fn, :points, :file].each do |var|
217
+ [:d, :h, :d1, :d2, :size, :fn, :points, :paths, :faces, :convexity, :file].each do |var|
216
218
  if part.respond_to? var
217
219
  res[var] = part.send var
218
220
  end
@@ -6,6 +6,7 @@ module JennCad::Features
6
6
  super({})
7
7
  @name = name.gsub(".","_")
8
8
  @parts = [part]
9
+ @zref = part
9
10
  end
10
11
 
11
12
  def z
data/lib/jenncad/part.rb CHANGED
@@ -1,9 +1,54 @@
1
+ module AutoName
2
+ def initialize(args={})
3
+ unless args.empty?
4
+ @auto_name = "#{self.class}_#{args.map{|key, val| "#{key}_#{val}"}.join('_')}"
5
+ end
6
+ super(args)
7
+ end
8
+ end
9
+
1
10
  module JennCad
2
11
  # Part should be inherited from the user when making parts
3
12
  class Part < Thing
13
+ def self.inherited(subclass)
14
+ subclass.prepend(AutoName) if subclass.superclass == Part
15
+ end
16
+
17
+ # this function both gets and defines hardware
18
+ def hardware(hw_type=nil, args={})
19
+ @_hw ||= {}
20
+ if hw_type == nil
21
+ return @_hw
22
+ end
23
+
24
+ anchors = args[:anchor] || args[:anchors]
25
+ unless anchors.kind_of? Array
26
+ anchors = [anchors]
27
+ end
28
+
29
+ anchors.each do |a|
30
+ @_hw[a] = {
31
+ hw_type: hw_type,
32
+ size: args[:size],
33
+ d: args[:d],
34
+ len: args[:len],
35
+ pos: anchor(a, args[:from]),
36
+ }
37
+ end
38
+ self
39
+ end
40
+ alias :hw :hardware
41
+
42
+ def fix_name_for_openscad(name)
43
+ [":", ",", ".", "[", "]","-", " "].each do |key|
44
+ name.gsub!(key, "_")
45
+ end
46
+ name
47
+ end
4
48
 
5
49
  def to_openscad
6
- a = Aggregation.new(self.class.to_s, self.get_contents)
50
+ name = @name || @auto_name || self.class.to_s
51
+ a = Aggregation.new(fix_name_for_openscad(name), self.get_contents)
7
52
  a.transformations = @transformations
8
53
  if self.has_explicit_color?
9
54
  a.color(self.color)
@@ -101,6 +101,11 @@ module JennCad::Primitives
101
101
  end
102
102
 
103
103
  def inner_anchors(dist, prefix=:inner_, recreate=false)
104
+ if dist.nil?
105
+ $log.error "Distance of nil passed to inner anchors. Please check the variable name you passed along"
106
+ return self
107
+ end
108
+
104
109
  @inner_anchor_defs ||= []
105
110
  @inner_anchor_defs << { "dist": dist, "prefix": prefix } unless recreate
106
111
 
@@ -139,6 +139,14 @@ module JennCad::Primitives
139
139
  end
140
140
  end
141
141
 
142
+ def z=(val)
143
+ @z = val
144
+ @h = val
145
+ opts[:h] = val
146
+ opts[:z] = val
147
+ set_anchors
148
+ end
149
+
142
150
  def h
143
151
  z + z_margin
144
152
  end
@@ -4,7 +4,7 @@ module JennCad::Primitives
4
4
  def initialize(part, args={})
5
5
  @transformations = []
6
6
  @parts = [part]
7
- @z = args[:h] || args[:height]
7
+ @z = args[:h] || args[:height] || args[:z]
8
8
  @center_bool = args[:center]
9
9
  @convexity = args[:convexity]
10
10
  @twist = args[:twist]
@@ -1,9 +1,11 @@
1
1
  module JennCad::Primitives
2
2
  class Polygon < Primitive
3
- attr_accessor :points
3
+ attr_accessor :points, :paths
4
4
  def initialize(args)
5
- super
6
5
  @points = args[:points]
6
+ @paths = args[:paths]
7
+ @convexity = args[:convexity] || 10
8
+ super
7
9
  end
8
10
  end
9
11
  end
@@ -0,0 +1,35 @@
1
+ module JennCad::Primitives
2
+ class Polyhedron < Primitive
3
+ attr_accessor :points, :faces, :convexity
4
+ def initialize(args)
5
+ @opts = args
6
+ @points = args[:points]
7
+ @faces = args[:faces]
8
+ @convexity = args[:convexity] || 10
9
+
10
+ super
11
+ end
12
+
13
+ def face(i)
14
+ unless @faces[i]
15
+ $log.error "polyhedron: Cannot find face #{i}"
16
+ return self
17
+ end
18
+ face = 0
19
+ poly_faces = []
20
+ poly_points = []
21
+ @faces[i].each do |f|
22
+ point = @points[f]
23
+ if point.nil?
24
+ $log.error "polyhedron: Cannot find point #{f} for face #{i}"
25
+ end
26
+ poly_points << point
27
+ poly_faces << face
28
+ face += 1
29
+ #poly_points << [point[0], point[1]]
30
+ end
31
+ #polygon(points: poly_points)
32
+ polyhedron(points: poly_points, faces: [poly_faces, poly_faces.reverse])
33
+ end
34
+ end
35
+ end
@@ -64,18 +64,20 @@ module JennCad::Primitives
64
64
  res
65
65
  end
66
66
 
67
- def flat(edge)
67
+ def flat(*edges)
68
68
  @opts[:flat_edges] ||= []
69
- @opts[:flat_edges] << edge
69
+ edges.each do |edge|
70
+ @opts[:flat_edges] << edge
71
+ end
70
72
  self
71
73
  end
72
74
 
73
75
  private
74
76
  def apply_flat_edge(edge)
75
77
  case edge
76
- when :up
78
+ when :up, :top
77
79
  cube(x: @x, y: @y/2.0, z: @z).nc.moveh(y:@y)
78
- when :down
80
+ when :down, :bottom
79
81
  cube(x: @x, y: @y/2.0, z: @z).nc
80
82
  when :right
81
83
  cube(x: @x/2.0, y: @y, z: @z).nc.moveh(x:@x)
@@ -14,16 +14,21 @@ module JennCad::Primitives
14
14
  args = [:d, :z].zip(args.flatten).to_h
15
15
  args.deep_merge!(m)
16
16
  end
17
-
18
- args[:z] ||= args[:h]
17
+ args = parse_xyz_shortcuts(args)
18
+ if args[:z].to_d > 0
19
+ args[:h] = args[:z]
20
+ else
21
+ args[:z] = nil
22
+ end
19
23
 
20
24
  @opts = {
21
25
  d: 0,
22
26
  a: 0,
23
- z: nil,
24
27
  r: nil,
25
28
  x: 0,
26
29
  y: 0,
30
+ z: nil,
31
+ cz: false,
27
32
  margins: {
28
33
  r: 0,
29
34
  d: 0,
@@ -33,9 +38,13 @@ module JennCad::Primitives
33
38
 
34
39
  super(opts)
35
40
 
36
- @d = @opts[:d]
37
- @a = @opts[:a]
38
- @h = @opts[:h]
41
+ @d = @opts[:d].to_d
42
+ @a = @opts[:a].to_d
43
+ @h = @opts[:h].to_d
44
+ @z = @h
45
+ @x = @opts[:x].to_d
46
+ @y = @opts[:y].to_d
47
+
39
48
  @r = @opts[:r] || nil
40
49
  if @r
41
50
  @d = @r * 2
@@ -54,7 +63,61 @@ module JennCad::Primitives
54
63
 
55
64
  # TODO: this needs anchors like cube
56
65
  # TODO: color on this needs to apply to hull, not on the cylinders.
66
+ set_anchors
67
+ end
68
+
69
+ def cz
70
+ @opts[:cz] = true
71
+ @transformations << Move.new(z: -@z / 2.0)
72
+ set_anchors
73
+ self
74
+ end
75
+
76
+
77
+ def set_anchors
78
+ @anchors = {} # reset anchors
79
+ if @opts[:d]
80
+ rad = @opts[:d] / 2.0
81
+ else
82
+ rad = @opts[:r]
83
+ end
57
84
 
85
+ if @x > 0
86
+ set_anchor :left, x: - rad
87
+ set_anchor :right, x: @x + rad
88
+ elsif @x < 0
89
+ set_anchor :left, x: @x - rad
90
+ set_anchor :right, x: rad
91
+ else
92
+ set_anchor :left, x: -rad
93
+ set_anchor :right, x: rad
94
+ end
95
+ if @y > 0
96
+ set_anchor :bottom, y: - rad
97
+ set_anchor :top, y: @y + rad
98
+ elsif @y < 0
99
+ set_anchor :bottom, y: @y - rad
100
+ set_anchor :top, y: rad
101
+ else
102
+ set_anchor :bottom, y: -rad
103
+ set_anchor :top, y: rad
104
+ end
105
+
106
+ set_anchor :center1, xy: 0
107
+ set_anchor :center2, x: @x, y: @y
108
+
109
+ # TODO: figure out if we also want to have "corners"
110
+ # - possibly move it like a cube
111
+ # - points at 45 ° angles might not be that useful unless you can get the point on the circle at a given angle
112
+ # - inner/outer points could be useful for small $fn values
113
+
114
+ if @opts[:cz]
115
+ set_anchor :bottom_face, z: -@z/2.0
116
+ set_anchor :top_face, z: @z/2.0
117
+ else
118
+ set_anchor :bottom_face, z: 0
119
+ set_anchor :top_face, z: @z
120
+ end
58
121
  end
59
122
 
60
123
  def to_openscad
@@ -57,6 +57,7 @@ module JennCad::Primitives
57
57
  others.each do |part|
58
58
  #puts part.inspect
59
59
  #puts "#{part.calc_z+part.calc_h} ; #{compare_h}"
60
+ add_z = nil
60
61
  if part.respond_to? :z
61
62
  part.opts[:margins] ||= {}
62
63
  if part.referenced_z && part.z != 0.0
@@ -64,28 +65,40 @@ module JennCad::Primitives
64
65
  when JennCad::Circle
65
66
  when JennCad::BooleanObject
66
67
  else
67
- $log.debug part if part.opts[:debug]
68
+ # $log.debug part if part.opts[:debug]
68
69
  part.opts[:margins][:z] ||= 0.0
69
70
  unless part.opts[:margins][:z] == 0.2
71
+ $log.debug "fixing possible z fighting for referenced object: #{part.class} #{part.z} 0.1 down" if part.opts[:debug]
70
72
  part.opts[:margins][:z] = 0.2
71
73
  part.mz(-0.1)
72
74
  end
73
75
  end
74
76
  elsif part.z == compare_h
75
77
  $log.debug "fixing possible z fighting: #{part.class} #{part.z}" if part.opts[:debug]
76
- part.opts[:margins][:z] += 0.008
77
- part.mz(-0.004)
78
+ add_z = 0.008
79
+ move_z = -0.004
78
80
  elsif part.calc_z == compare_z
79
81
  # puts "z fighting at bottom: #{part.calc_z}"
80
- part.opts[:margins][:z] += 0.004
82
+ add_z = 0.004
81
83
  # part.z+=0.004
82
- part.mz(-0.002)
84
+ move_z = -0.002
83
85
  elsif part.calc_z.to_d+part.calc_h.to_d == compare_h
84
86
  # puts "z fighting at top: #{compare_h}"
85
87
  #part.z+=0.004
86
- part.opts[:margins][:z] += 0.004
87
- part.mz(0.002)
88
+ add_z = 0.004
89
+ move_z = 0.002
88
90
  end
91
+
92
+ if add_z
93
+ if part.kind_of? Part
94
+ part.modify_values(part, {z: add_z}, {mode: :add})
95
+ end
96
+ part.opts[:margins][:z] += add_z
97
+ part.mz(move_z)
98
+ end
99
+
100
+
101
+
89
102
  end
90
103
  end
91
104
  end
@@ -5,6 +5,7 @@ require "jenncad/primitives/sphere"
5
5
  require "jenncad/primitives/cube"
6
6
  require "jenncad/primitives/rounded_cube"
7
7
  require "jenncad/primitives/polygon"
8
+ require "jenncad/primitives/polyhedron"
8
9
  require "jenncad/primitives/slot"
9
10
  require "jenncad/primitives/boolean_object"
10
11
  require "jenncad/primitives/union_object"
@@ -18,6 +18,10 @@ module JennCad
18
18
  Polygon.new(args).set_parent(self)
19
19
  end
20
20
 
21
+ def polyhedron(args)
22
+ Polyhedron.new(args).set_parent(self)
23
+ end
24
+
21
25
  def slot(*args)
22
26
  Slot.new(args).set_parent(self)
23
27
  end
data/lib/jenncad/thing.rb CHANGED
@@ -35,12 +35,70 @@ module JennCad
35
35
 
36
36
  def set_flag(key)
37
37
  set_option(key, true)
38
+ self
38
39
  end
39
40
 
40
41
  def unset_flag(key)
41
42
  set_option(key, false)
43
+ self
44
+ end
45
+
46
+
47
+ def cut_to(face, part=nil, args={})
48
+ an = anchor(face, part)
49
+ unless an
50
+ $log.error "Cannot find anchor to cut_to"
51
+ return self
52
+ end
53
+ if an[:z].to_d == 0.0
54
+ $log.error "cut_to only supports cuts to an anchor with Z set. This anchor: #{an}"
55
+ return self
56
+ end
57
+ modify_values(self, z: an[:z].to_d)
58
+ self.name="#{self.class}_cut_to_#{an[:z].to_f}"
59
+ self
60
+ end
61
+
62
+ def modify_values(parts, value, opts = {})
63
+ case parts
64
+ when Array
65
+ parts.each do |pa|
66
+ modify_values(pa, value, opts)
67
+ end
68
+ else
69
+ if parts.kind_of?(BooleanObject)
70
+ modify_values(parts.only_additives_of(parts), value, opts)
71
+ elsif parts.kind_of?(Part)
72
+ modify_values(parts.part, value, opts)
73
+ modify_values(parts.get_contents, value, opts)
74
+ parts.modify_values!(value, opts)
75
+ elsif parts.kind_of?(Primitive)
76
+ parts.modify_values!(value, opts)
77
+ end
78
+ end
79
+ end
80
+
81
+ def modify_values!(values, opts)
82
+ $log.info "Modify value! #{self.class} #{values}" if self.debug?
83
+ values.each do |key, val|
84
+ if @opts
85
+ case opts[:mode]
86
+ when :add
87
+ @opts[key] = @opts[key].to_d + val.to_d
88
+ when :sub
89
+ @opts[key] = @opts[key].to_d - val.to_d
90
+ else
91
+ @opts[key] = val
92
+ end
93
+ end
94
+ if self.respond_to? key
95
+ self.send("#{key}=", @opts[key])
96
+ end
97
+ end
98
+ $log.info "Modified value now: #{self.inspect}" if self.debug?
42
99
  end
43
100
 
101
+
44
102
  def debug?
45
103
  option(:debug) || false
46
104
  end
@@ -55,20 +113,22 @@ module JennCad
55
113
  self
56
114
  end
57
115
 
58
- def anchor(name, thing=nil)
116
+ def anchor(name, thing=nil, args={})
59
117
  if thing
60
- res = thing.anchor(name)
118
+ res = thing.anchor(name, nil, args)
61
119
  return res unless res.nil?
62
120
  end
63
121
  @anchors ||= {}
64
122
  if anch = @anchors[name]
65
123
  return anch
124
+ elsif args[:fail_quick] && args[:fail_quick] == true
125
+ return
66
126
  elsif @parent
67
127
  return @parent.anchor(name)
68
128
  elsif self.respond_to? :get_contents
69
129
  con = get_contents
70
130
  if con.respond_to? :anchor
71
- con.anchor(name)
131
+ con.anchor(name, nil, fail_quick: true)
72
132
  end
73
133
  end
74
134
  end
@@ -82,9 +142,19 @@ module JennCad
82
142
  alias :sa :set_anchor
83
143
 
84
144
  def set_anchor_from(name, new_name, args={})
85
- a = anchor(name).dup
145
+ unless name.kind_of? Symbol or name.kind_of? String
146
+ $log.error "set_anchor_from: name must be a string or symbol. Supplied: #{name}"
147
+ return
148
+ end
149
+ unless new_name.kind_of? Symbol or new_name.kind_of? String
150
+ $log.error "set_anchor_from: new_name must be a string or symbol. Supplied: #{new_name}"
151
+ return
152
+ end
153
+
154
+
155
+ a = anchor(name, args[:from]).dup
86
156
  if !a
87
- log.error "set_anchor_from couldn't find anchor #{name}"
157
+ $log.error "set_anchor_from couldn't find anchor #{name}"
88
158
  return
89
159
  end
90
160
 
@@ -213,6 +283,27 @@ module JennCad
213
283
  return args
214
284
  end
215
285
 
286
+ # reset last move
287
+ def reset_last_move
288
+ lt = @transformations.last
289
+ unless lt.class == Move
290
+ $log.error "Tried to call rst_move but last object is a #{lt.class}"
291
+ return self
292
+ end
293
+ @transformations.delete_at(-1)
294
+
295
+ self
296
+ end
297
+ alias :rstlm :reset_last_move
298
+
299
+ # resets all transformations
300
+ def reset
301
+ @transformations = []
302
+ self
303
+ end
304
+ alias :rst :reset
305
+
306
+
216
307
  def move(args={})
217
308
  return self if args.nil? or args.empty?
218
309
 
@@ -275,7 +366,7 @@ module JennCad
275
366
  thing = nil
276
367
  end
277
368
 
278
- an = anchor(key, thing)
369
+ an = anchor(key, thing, args)
279
370
 
280
371
  unless an
281
372
  $log.error "Error: Anchor #{key} not found"
@@ -293,6 +384,7 @@ module JennCad
293
384
  end
294
385
  end
295
386
  end
387
+ alias :ma :movea
296
388
 
297
389
  # move to anchor - inverted
298
390
  def moveai(key, thing=nil, args={})
@@ -303,7 +395,7 @@ module JennCad
303
395
  args[:inverted] = true
304
396
  movea(key, thing, args)
305
397
  end
306
-
398
+ alias :mai :moveai
307
399
 
308
400
  # move half
309
401
  def moveh(args={})
@@ -549,7 +641,7 @@ module JennCad
549
641
  return @parts unless @parts.nil?
550
642
 
551
643
  if @cache
552
- return @cache
644
+ return @cache unless option(:no_cache) == true
553
645
  end
554
646
 
555
647
  if self.respond_to? :part
@@ -661,5 +753,12 @@ module JennCad
661
753
  end
662
754
  end
663
755
 
756
+
757
+ def to_mod(name)
758
+ a = Aggregation.new(name, self)
759
+ a.transformations = @transformations
760
+ a
761
+ end
762
+
664
763
  end
665
764
  end
@@ -1,3 +1,3 @@
1
1
  module JennCad
2
- VERSION = "1.0.0-alpha15"
2
+ VERSION = "1.0.0-alpha16"
3
3
  end
data/lib/jenncad.rb CHANGED
@@ -6,7 +6,6 @@ require "geo3d"
6
6
  require "deep_merge"
7
7
  require "fileutils"
8
8
  require "observr"
9
- require "hanami/cli"
10
9
  require "active_support"
11
10
 
12
11
  include Math
data/todo.txt CHANGED
@@ -11,16 +11,6 @@
11
11
  Features wanted
12
12
  ===================
13
13
  28.4.22:
14
- - Parts often come with 4 holes around a center, and I often do something like:
15
-
16
- [-1, 1].each do |i|
17
- [-1, 1].each do |j|
18
- res += @bolt.cut.move(x: @hole_center*i, y: @hole_center*j)
19
- end
20
- end
21
-
22
- there should be a shorter way to do this.
23
-
24
14
 
25
15
  - instead of moving Bolts output, make it the default to make their positions an anchor
26
16
 
@@ -34,6 +24,12 @@ there should be a shorter way to do this.
34
24
  - but also, when you have a part that can be mounted in any top/bottom direction, you always end up having the bolts facing the wrong direction..
35
25
  - Bolts should be able to also make thread inserts
36
26
 
27
+ 8.5.22:
28
+ - need an easy way to switch between faces on an object to work efficiently in a 2.5D way
29
+ i.e. I worked on a part stacking a few things, added a cube along and now wanted to use inner_anchors for the y face for screws. it was easier to make anchors manually for the prototype
30
+
31
+
32
+
37
33
 
38
34
 
39
35
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jenncad
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.alpha15
4
+ version: 1.0.0.pre.alpha16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jennifer Glauche
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-03 00:00:00.000000000 Z
11
+ date: 2022-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: geo3d
@@ -137,6 +137,7 @@ files:
137
137
  - lib/jenncad/primitives/intersection_object.rb
138
138
  - lib/jenncad/primitives/linear_extrude.rb
139
139
  - lib/jenncad/primitives/polygon.rb
140
+ - lib/jenncad/primitives/polyhedron.rb
140
141
  - lib/jenncad/primitives/primitive.rb
141
142
  - lib/jenncad/primitives/projection.rb
142
143
  - lib/jenncad/primitives/rotate_extrude.rb