jenncad 1.0.0.pre.alpha1 → 1.0.0.pre.alpha4

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: 81f68c07dc7fd50719d6ce024f56eb25712ef97c9b31568dcfa6658d9fa01596
4
- data.tar.gz: 3c877488f386a7d7c6f8dd3b1543ca84641316ae2dbd5d6bcbb4577fbf94ae7c
3
+ metadata.gz: ea1fa04592f59d248c5801c3b645f16429bd7ae9f0b754aa467c8a401c89ec4b
4
+ data.tar.gz: 4df185f63b22b5945784cf820b4af937e4fbc660875f36093bbd5ebe9baa2aa9
5
5
  SHA512:
6
- metadata.gz: 9a95e7f398d275b81bfb4c9bbe21d3bd0e306708f0ef514e8391f50837eab066ec7aaca3ee46e4bbb08cd4a0c3fbee009b899b87aea2b5fe86ea5a74f6294ee3
7
- data.tar.gz: 5425a1f502e12c8b8300995f921b2e9ce1d10f214728f5f2b6508610aa783f76907f3ffcc9bb40ed88e997302a12a896dc7afa9c544d6e3f0216750e4914e37f
6
+ metadata.gz: '0809987ec1b33518b17402acb27b659da2dc7b97ee304d1549abd857ae4448e0342d6ee4523e8d2277d7ce765e1278f996af9882b5da91b71c93acec8bd9ffd9'
7
+ data.tar.gz: e412b05272163c37096d36a44d83ac3d3c5ae07089d7d8d7236c1c53a263c24742566b691efbb5a03c9148960cb7734eb2859bc279e450e34c131fda2ae85af5
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+
2
+ # jenncad
3
+ Create physical objects in Ruby, OpenScad export
4
+
5
+ This is a successor to my older project CrystalScad.
6
+
7
+ # Installation
8
+
9
+ A packaged release is not yet available, please build your own package in the meantime:
10
+
11
+ $ git clone git@github.com:jglauche/jenncad.git
12
+ $ cd jenncad
13
+ $ rake install
14
+
15
+ This will create a gem and a binary jenncad.
16
+
17
+ # Using Jenncad
18
+
19
+ **To create a new project directory, run:**
20
+
21
+ $ jenncad create meow
22
+
23
+
24
+
25
+
26
+ This will generate a directory meow/ and an executable ruby file meow.rb in its directory
27
+
28
+ $ cd meow
29
+ $ ./meow
30
+
31
+ This will generate a dummy project which generates a dummy cube as OpenSCAD output:
32
+
33
+ $ cat output/meow.scad
34
+ > $fn=64;
35
+ > translate([-5, -5, 0])cube([10, 10, 10.0]);
36
+
37
+ **Automatically refresh OpenSCAD output while developing**
38
+
39
+ Jenncad bundles the observr gem which will check if files were changed while developing. In the project directory run:
40
+
41
+ $ jenncad
42
+ This should display something like:
43
+ > refreshing...
44
+ > ok
45
+ > JennCad running, refreshing on file changes. Press ctrl+c to exit
46
+
47
+ **Note:** This does not check for new files in the project. You will have to restart it when you create a new part in a new file
48
+
49
+ **Create new part**
50
+
51
+ In your project directory, run:
52
+
53
+ $ jenncad new cat
54
+ part parts/cat.rb created. In your meow.rb add to class Meow:
55
+ def cat
56
+ Cat.new(config)
57
+ end
58
+
59
+
60
+ You will have to link the part to the project manually into the meow.rb file in your project directory. When you add it, your meow.rb should look like this:
61
+
62
+ #!/usr/bin/env ruby
63
+ require "jenncad"
64
+ include JennCad
65
+
66
+ class Meow < Project
67
+ def config
68
+ {}
69
+ end
70
+
71
+ def meow
72
+ cube(10,10,10)
73
+ end
74
+
75
+ def cat
76
+ Cat.new(config)
77
+ end
78
+
79
+ end
80
+ Meow.new.run
81
+
82
+
83
+
data/jenncad.gemspec CHANGED
@@ -19,9 +19,10 @@ Gem::Specification.new do |gem|
19
19
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
20
  gem.require_paths = ["lib"]
21
21
 
22
- gem.required_ruby_version = ">= 2.5.0"
22
+ gem.required_ruby_version = ">= 2.6.0"
23
23
  gem.add_runtime_dependency "geo3d"
24
24
  gem.add_runtime_dependency "deep_merge"
25
25
  gem.add_runtime_dependency "hanami-cli"
26
26
  gem.add_runtime_dependency "activesupport"
27
+ gem.add_runtime_dependency "observr"
27
28
  end
@@ -17,8 +17,8 @@ module JennCad
17
17
  end
18
18
 
19
19
  def colors
20
- @colors ||= []
21
- if @colors.empty?
20
+ case @colors
21
+ when nil, []
22
22
  @colors = auto_colors
23
23
  end
24
24
  @colors
@@ -0,0 +1,260 @@
1
+ module JennCad::Exporters
2
+ class OpenScadObject
3
+ def initialize(cmd, args, children=[])
4
+ @command = cmd
5
+ @args = args
6
+ case children
7
+ when Array
8
+ @children = children
9
+ else
10
+ @children = [children]
11
+ end
12
+ end
13
+
14
+ def nl
15
+ "\n"
16
+ end
17
+
18
+ def to_s
19
+ case @command
20
+ when nil
21
+ ""
22
+ when :head
23
+ res = "$fn=64;"+nl
24
+ @children.each do |c|
25
+ res += c.to_s+nl
26
+ end
27
+ res
28
+ when :module
29
+ handle_module
30
+ when String, Symbol
31
+ handle_command
32
+ else
33
+ end
34
+ end
35
+
36
+ def handle_module
37
+ res = "module #{@args}(){"+nl
38
+ res += tabs(1, @children.map{|c| c.handle_command(2) })
39
+ res += "}"
40
+ res
41
+ end
42
+
43
+ def handle_command(i=1)
44
+ case @children.size
45
+ when 0
46
+ "#{@command}(#{handle_args});"
47
+ when 1
48
+ "#{@command}(#{handle_args})#{@children.first.handle_command(i+1)}"
49
+ when (1..)
50
+ res = "#{@command}(#{handle_args}){"
51
+ res += nl
52
+ inner = @children.map do |c|
53
+ next if c == nil
54
+ c.handle_command(i+1)
55
+ end
56
+ res += tabs(i, inner.compact)
57
+ res += nl
58
+ res += tabs(i-1,["}"])+nl
59
+ res
60
+ end
61
+ end
62
+
63
+ def tabs(i,a)
64
+ a.map{ |l|
65
+ " " * i + l
66
+ }.join(nl)
67
+ end
68
+
69
+ def handle_args
70
+ case @args
71
+ when String, Symbol
72
+ return "\"#{@args}\""
73
+ when Array
74
+ return @args.map do |l|
75
+ if l == nil
76
+ 0
77
+ elsif l.kind_of? Array
78
+ l # skipping check of 2-dmin Arrays for now (used in multmatrix)
79
+ elsif l.to_i == l.to_f
80
+ l.to_i
81
+ else
82
+ l.to_f
83
+ end
84
+ end
85
+ when Hash
86
+ res = []
87
+ @args.each do |k,v|
88
+ if k.to_s == "fn"
89
+ k = "$fn"
90
+ end
91
+ if v == nil
92
+ next
93
+ end
94
+ if !v.kind_of?(Array) && !v.kind_of?(TrueClass) && !v.kind_of?(FalseClass) && v == v.to_i
95
+ v = v.to_i
96
+ end
97
+ if v.kind_of? String
98
+ q = "\""
99
+ else
100
+ q = ""
101
+ end
102
+ res << "#{k}=#{q}#{v}#{q}"
103
+ end
104
+ res.join(",").gsub("size=","")
105
+ else
106
+ ""
107
+ end
108
+ end
109
+ end
110
+
111
+ class OpenScad
112
+ include ActiveSupport::Inflector
113
+ def initialize(part)
114
+ @imports = []
115
+ @modules = {}
116
+ @global_fn = 64
117
+ @object_tree = OpenScadObject.new(:head, nil, parse(part))
118
+ end
119
+
120
+ def save(file)
121
+ File.open(file,"w") do |f|
122
+ @imports.uniq.each do |val|
123
+ f.puts "use <#{val}.scad>\n"
124
+ end
125
+
126
+ @modules.each do |key, val|
127
+ f.puts val.to_s
128
+ end
129
+ f.puts @object_tree.to_s
130
+ end
131
+ end
132
+
133
+ def parse(part)
134
+ if part.respond_to? :to_openscad
135
+ part = part.to_openscad
136
+ end
137
+
138
+ if part.respond_to? :analyze_z_fighting
139
+ part = part.analyze_z_fighting
140
+ end
141
+
142
+ case part
143
+ when Array
144
+ part.map{ |p| parse(p) }
145
+ when JennCad::OpenScadImport
146
+ handle_import(part)
147
+ when JennCad::Aggregation
148
+ handle_aggregation(part)
149
+ when JennCad::UnionObject
150
+ bool('union', part)
151
+ when JennCad::SubtractObject
152
+ bool('difference', part)
153
+ when JennCad::IntersectionObject
154
+ bool('intersection', part)
155
+ when JennCad::HullObject
156
+ bool('hull', part)
157
+ when JennCad::Primitives::Circle
158
+ prim('circle', part)
159
+ when JennCad::Primitives::Cylinder
160
+ prim('cylinder', part)
161
+ when JennCad::Primitives::Sphere
162
+ prim('sphere', part)
163
+ when JennCad::Primitives::Cube
164
+ prim('cube', part)
165
+ when JennCad::Primitives::LinearExtrude
166
+ new_obj(part, :linear_extrude, part.openscad_params, parse(part.parts))
167
+ when JennCad::Primitives::RotateExtrude
168
+ new_obj(part, :rotate_extrude, part.openscad_params, parse(part.parts))
169
+ when JennCad::Primitives::Projection
170
+ new_obj(part, :projection, collect_params(part), parse(part.parts))
171
+ when JennCad::Primitives::Polygon
172
+ new_obj(part, :polygon, collect_params(part))
173
+ when JennCad::StlImport
174
+ new_obj(part, :import, collect_params(part))
175
+ when JennCad::Part
176
+ parse(part.part)
177
+ when nil
178
+ new_obj(part, nil)
179
+ else
180
+ puts "unknown part #{part.class}"
181
+ OpenScadObject.new(nil,nil)
182
+ end
183
+ end
184
+
185
+ def new_obj(part, cmd, args=nil, children=[])
186
+ transform(part) do
187
+ apply_color(part) do
188
+ OpenScadObject.new(cmd, args, children)
189
+ end
190
+ end
191
+ end
192
+
193
+ def bool(type, part)
194
+ new_obj(part, type, nil, parse(part.parts))
195
+ end
196
+
197
+ def prim(type, part)
198
+ new_obj(part, type, collect_params(part))
199
+ end
200
+
201
+ def collect_params(part)
202
+ if part.respond_to? :openscad_params
203
+ return part.openscad_params
204
+ end
205
+ res = {}
206
+ [:d, :h, :d1, :d2, :size, :fn, :points, :file].each do |var|
207
+ if part.respond_to? var
208
+ res[var] = part.send var
209
+ end
210
+ end
211
+ case res[:fn]
212
+ when @global_fn
213
+ res[:fn] = nil
214
+ else
215
+ end
216
+ res
217
+ end
218
+
219
+ def apply_color(part, &block)
220
+ return block.yield if part.nil? or part.color_or_fallback.nil?
221
+ OpenScadObject.new("color", part.color_or_fallback, block.yield)
222
+ end
223
+
224
+ def transform(part, &block)
225
+ return block.yield if part.transformations.nil?
226
+
227
+ case t = part.transformations.pop
228
+ when nil, []
229
+ block.yield
230
+ when JennCad::Move
231
+ OpenScadObject.new(:translate, t.coordinates, transform(part, &block))
232
+ when JennCad::Rotate, JennCad::Mirror
233
+ OpenScadObject.new(demodulize(t.class).downcase, t.coordinates, transform(part, &block))
234
+ when JennCad::Scale
235
+ OpenScadObject.new(:scale, t.scale, transform(part, &block))
236
+ when JennCad::Multmatrix
237
+ OpenScadObject.new(:multmatrix, t.m, transform(part, &block))
238
+ else
239
+ puts "unknown transformation #{t}"
240
+ end
241
+ end
242
+
243
+ def handle_aggregation(part, tabindex=0)
244
+ register_module(part) unless @modules[part.name]
245
+ transform(part) do
246
+ new_obj(part, part.name, nil)
247
+ end
248
+ end
249
+
250
+ # accept aggregation
251
+ def register_module(part)
252
+ @modules[part.name] = OpenScadObject.new(:module, part.name, parse(part.part))
253
+ end
254
+
255
+ def handle_import(part)
256
+ @imports << part.import
257
+ new_obj(part, part.name, part.args)
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,16 @@
1
+ module JennCad::Features
2
+ class Aggregation < Feature
3
+ attr_accessor :parts
4
+
5
+ def initialize(name=nil, part=nil)
6
+ super({})
7
+ @name = name
8
+ @parts = [part] # NOTE: single length arrayto make checking children easier
9
+ end
10
+
11
+ def part
12
+ @parts.first
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,95 @@
1
+ module JennCad::Features
2
+ class Climb < Feature
3
+ def initialize(opts, block)
4
+ @opts = {
5
+ offset: :auto,
6
+ step: nil,
7
+ steps: nil,
8
+ bottom: nil,
9
+ top: nil,
10
+ z: nil,
11
+ }.deep_merge!(opts)
12
+ if @opts[:step].nil? && @opts[:steps].nil?
13
+ raise "please define at least one of :step or :steps for climb"
14
+ end
15
+ super(@opts)
16
+ @block = block
17
+ end
18
+
19
+ def z_or_referenced
20
+ case z = @opts[:z]
21
+ when nil
22
+ referenced_z.z
23
+ else
24
+ z
25
+ end
26
+ end
27
+
28
+ def get_step(z)
29
+ case step = @opts[:step]
30
+ when nil, :auto, 0, 0.0
31
+ steps = @opts[:steps]
32
+ (z / steps).floor
33
+ else
34
+ step.to_f
35
+ end
36
+ end
37
+
38
+ def get_offset(z)
39
+ case offset = @opts[:offset]
40
+ when :auto
41
+ step = get_step(z)
42
+ ((z % step) + step) / 2.0
43
+ when nil, 0, 0.0
44
+ 0.0
45
+ else
46
+ offset
47
+ end
48
+ end
49
+
50
+ def climb_from_bottom(offset, step, n)
51
+ n.times.map{ |i| @block.yield.mz(offset+step*i) }.union
52
+ end
53
+
54
+ def climb_from_top(z, offset, step, n)
55
+ n.times.map{ |i| @block.yield.mz(z-offset-step*i) }.union
56
+ end
57
+
58
+ def to_openscad
59
+ ref_z = z_or_referenced
60
+ step = get_step(ref_z)
61
+ steps, top, bottom = @opts.values_at(:steps, :top, :bottom)
62
+
63
+ offset = get_offset(ref_z)
64
+
65
+ lo = (ref_z-offset*2).to_f % step.to_f
66
+ unless lo.to_f == 0.0
67
+ puts "[Warning]: climb has leftover offset #{lo}"
68
+ end
69
+
70
+ if steps
71
+ top = steps
72
+ bottom = steps
73
+ end
74
+
75
+ unless top or bottom
76
+ climb_from_bottom(offset, step, ((ref_z-offset*2) / step).floor + 1 )
77
+ else
78
+ res = nil
79
+ if top
80
+ res += climb_from_top(ref_z, offset, step, top)
81
+ end
82
+ if bottom
83
+ res += climb_from_bottom(offset, step, bottom)
84
+ end
85
+ res
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ def climb(args, &block)
92
+ Climb.new(args, block)
93
+ end
94
+
95
+ end
@@ -0,0 +1,35 @@
1
+ module JennCad::Features
2
+ module Cuttable
3
+ def cut(args, &block)
4
+ if args[:x]
5
+ l = args[:x].min * @opts[:y] / 2.0
6
+ r = args[:x].max * @opts[:y] / 2.0
7
+ prepare_cut(l, r, &block).flip_x
8
+ elsif args[:y]
9
+ l = args[:y].min * @opts[:y] / 2.0
10
+ r = args[:y].max * @opts[:y] / 2.0
11
+ prepare_cut(l, r, &block).flip_y
12
+ elsif args[:z]
13
+ raise "cut for Z is not implemented yet"
14
+ end
15
+ end
16
+
17
+ def prepare_cut(l, r, &block)
18
+ part = block.call
19
+ if part.z.to_f > 0.0
20
+ part.opts[:margins][:z] = 0.2
21
+ if l == 0.0
22
+ part.mz(r+0.1)
23
+ else
24
+ part.mz(l+part.z.to_f-0.2)
25
+ end
26
+ else
27
+ part.opts[:margins][:z] = 0.2
28
+ part.z = l.abs + r.abs + 0.2
29
+ part.mz(-0.1)
30
+ end
31
+ end
32
+
33
+
34
+ end
35
+ end
@@ -0,0 +1,4 @@
1
+ module JennCad::Features
2
+ class Feature < JennCad::Thing
3
+ end
4
+ end
@@ -1,4 +1,4 @@
1
- module JennCad::Primitives
1
+ module JennCad::Features
2
2
  class OpenScadImport < Aggregation
3
3
  attr_accessor :import, :args
4
4
 
@@ -0,0 +1,10 @@
1
+ module JennCad::Features
2
+ class StlImport < Feature
3
+ attr_accessor :file, :args
4
+
5
+ def initialize(file, args={})
6
+ @file = file
7
+ @args = args
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ require "jenncad/features/feature"
2
+ require "jenncad/features/aggregation"
3
+ require "jenncad/features/cuttable"
4
+ require "jenncad/features/openscad_include"
5
+ require "jenncad/features/climb"
6
+ require "jenncad/features/stl_import"
7
+
8
+
9
+ module JennCad
10
+ include Features
11
+ end
data/lib/jenncad/part.rb CHANGED
@@ -2,13 +2,16 @@ module JennCad
2
2
  # Part should be inherited from the user when making parts
3
3
  class Part < Thing
4
4
 
5
- def make_openscad_compatible
6
- auto_color
7
- a = Aggregation.new(self.class.to_s, make_openscad_compatible!(self.part))
5
+ def to_openscad #make_openscad_compatible
6
+ # auto_color
7
+ a = Aggregation.new(self.class.to_s, self.part) #make_openscad_compatible!(self.part))
8
8
  a.transformations = @transformations
9
- a.color(color)
9
+ a.color(:auto)
10
10
  a
11
11
  end
12
12
 
13
+ def part
14
+ end
15
+
13
16
  end
14
17
  end
@@ -12,18 +12,38 @@ class Array
12
12
  else
13
13
  res = part
14
14
  end
15
- # FIXME: I added 0.01 to all for now to fix z-fighting issues; this should not be hardcoded like this
16
- res, z = res.move(z:z), z + res.z.to_f + 0.01 unless skip_z
15
+ res, z = res.mz(z), z + res.z.to_f unless skip_z
17
16
  res
18
17
  end
19
18
  .union
20
19
  end
21
20
 
22
- def union(&block)
23
- if block
24
- UnionObject.new(block.yield)
25
- else
26
- UnionObject.new(self)
27
- end
21
+ def union
22
+ UnionObject.new(self)
23
+ end
24
+ alias u union
25
+
26
+ def subtraction
27
+ SubtractObject.new(self)
28
28
  end
29
+ alias subtract subtraction
30
+ alias sub subtraction
31
+ alias s subtraction
32
+
33
+ def intersection
34
+ IntersectionObject.new(self)
35
+ end
36
+ alias intersect intersection
37
+ alias i intersection
38
+
39
+ def hull
40
+ HullObject.new(self)
41
+ end
42
+ alias h hull
43
+
44
+
45
+ def random
46
+ self[Random.rand(size)]
47
+ end
48
+
29
49
  end
@@ -10,6 +10,16 @@ module JennCad::Primitives
10
10
  after_add
11
11
  end
12
12
 
13
+ def add_or_new(part)
14
+ case @transformations
15
+ when nil, []
16
+ add(part)
17
+ self
18
+ else
19
+ self.class.new(self, part)
20
+ end
21
+ end
22
+
13
23
  def add(part)
14
24
  @parts << part
15
25
  after_add
@@ -39,7 +49,7 @@ module JennCad::Primitives
39
49
 
40
50
  def inherit_zref
41
51
  return if @parts.first == nil
42
- return if @parts.first.z.to_f == 0.0
52
+ #return if @parts.first.z.to_f == 0.0
43
53
  get_primitives(@parts[1..-1]).flatten.each do |part|
44
54
  if part.z.to_f == 0.0
45
55
  part.set_option :zref, @parts.first