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

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: 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