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

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