Class: Oozby::Environment

Inherits:
Object
  • Object
show all
Defined in:
lib/oozby/environment.rb

Constant Summary collapse

ResolutionDefaults =
{
  degrees_per_fragment: 12,
  fragments_per_turn: 30,
  minimum: 2,
  fragments: 0
}

Class Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ooz: nil) ⇒ Environment

Returns a new instance of Environment.



15
16
17
18
19
20
21
22
23
24
25
# File 'lib/oozby/environment.rb', line 15

def initialize(ooz: nil)
  @parent = ooz
  @ast = []
  @defaults = {center: false}
  @resolution = ResolutionDefaults.dup
  @modifier = nil
  @one_time_modifier = nil
  @preprocess = true
  @method_preprocessor = Oozby::Preprocessor.new(env: self, ooz: @parent)
  @scanned_scad_files = []
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, **hash, &proc) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/oozby/environment.rb', line 57

def method_missing method_name, *args, **hash, &proc
  # unless we know of this method in OpenSCAD or the preprocessor, abort!
  unless oozby_method_defined? method_name
    # grab a list of all known methods, suggest a guess to user
    known = @method_preprocessor.known
    known.push(*public_methods(false))
    known.push(*Oozby.constants)
    known.delete_if { |x| x.to_s.start_with? '_' }
    matcher = Amatch::Sellers.new(method_name.to_s)
    suggestion = known.min_by { |item| matcher.match(item.to_s) }
    
    warn "Called unknown method #{method_name}()"
    warn "Perhaps you meant #{suggestion}?" if suggestion
    
    return super # continue to raise the usual error and all that
  end
  
  oozby_send_method method_name, *args, **hash, &proc
end

Class Attribute Details

.activeObject

Returns the value of attribute active.



12
13
14
# File 'lib/oozby/environment.rb', line 12

def active
  @active
end

Instance Method Details

#_abstract_treeObject

returns the abstract tree



274
# File 'lib/oozby/environment.rb', line 274

def _abstract_tree; @ast; end

#_apply_modifier(new_modifier, &children) ⇒ Object



261
262
263
264
265
266
267
268
269
270
# File 'lib/oozby/environment.rb', line 261

def _apply_modifier new_modifier, &children
  if children
    previously = @modifier
    @modifier = new_modifier
      instance_eval &children
    @modifier = previously
  else
    @one_time_modifier = new_modifier
  end
end

#_execute_oozby(&code) ⇒ Object

run some oozby code contained inside a proc



233
234
235
236
237
238
239
# File 'lib/oozby/environment.rb', line 233

def _execute_oozby &code
  previously = self.class.active
  self.class.active = self
  result = instance_exec(&code)
  self.class.active = previously
  return result
end

#_fragments_for(diameter: nil, radius: nil) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/oozby/environment.rb', line 184

def _fragments_for diameter: nil, radius: nil
  radius = diameter.to_f / 2.0 if diameter
  if @resolution[:fragments] != 0
    @resolution[:fragments]
  else
    max = (radius.to_f * Math::PI * 2.0) / @resolution[:minimum].to_f
    if @resolution[:fragments_per_turn] > max
      max
    else
      @resolution[:fragments_per_turn]
    end
  end
end

#_require_scad_file(filename, execute: true) ⇒ Object



224
225
226
227
228
229
230
# File 'lib/oozby/environment.rb', line 224

def _require_scad_file filename, execute: true
  raise "OpenSCAD file #{filename} not found" unless File.exists? filename
  _scan_methods_from_scad_file filename
  
  # add include statement to resulting openscad code
  @ast.push(execute: !!execute, import: filename)
end

#_scan_methods_from_scad_file(filename) ⇒ Object



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/oozby/environment.rb', line 241

def _scan_methods_from_scad_file filename
  raise "OpenSCAD file #{filename} not found" unless File.exists? filename
  data = File.read(filename)
  @scanned_scad_files.push filename
  
  # parse out method definitions to add to our environment
  data.gsub!(/\/\/.+?\n/m, "\n") # filter off single line comments
  data.gsub!(/\/\*.+?\*\//m, '') # filter out multiline comments
  data.scan /module[ \t]([a-zA-Z_9-9]+)/ do |module_name|
    @method_preprocessor.openscad_methods.push module_name.first.to_sym
  end
  
  # find any references to more files and recurse in to those
  data.scan /(use|include)[ \t]\<(.+?)\>/ do |filename|
    unless @scanned_scad_files.include? filename
      _scan_methods_from_scad_file filename
    end
  end
end

#_subscope(*args, &proc) ⇒ Object

create a new scope that inherits from this one, and capture syntax tree created



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/oozby/environment.rb', line 28

def _subscope *args, &proc
  # capture instance variables
  capture = {}
  instance_variables.each { |key| capture[key] = self.instance_variable_get(key) }
  
  # reset for ast capture
  @ast = []
  @one_time_modifier = nil
  @defaults = @defaults.dup
  @resolution = @resolution.dup
  proc[*args]
  syntax_tree = @ast
  
  # restore instance variables
  capture.each { |key, value| self.instance_variable_set(key, value) }
  
  return syntax_tree
end

#defaults(settings = nil, &proc) ⇒ Object



122
123
124
125
126
127
128
129
130
131
# File 'lib/oozby/environment.rb', line 122

def defaults settings = nil, &proc
  return @defaults if settings == nil
  previous = @defaults
  @defaults = @defaults.merge(settings)
  if proc
    ret = instance_eval &proc
    @defaults = previous
  end
  ret
end

#ghost(&proc) ⇒ Object

the background modifier



199
200
201
# File 'lib/oozby/environment.rb', line 199

def ghost &proc
  _apply_modifier('%', &proc)
end

#highlight(&proc) ⇒ Object

the debug modifier



204
205
206
# File 'lib/oozby/environment.rb', line 204

def highlight &proc
  _apply_modifier('#', &proc)
end

#inject_abstract_tree(code) ⇒ Object



47
48
49
50
# File 'lib/oozby/environment.rb', line 47

def inject_abstract_tree code
  code = [code] unless code.is_a? Array
  @ast.push(*code)
end

#inspectObject

make backtraces clearer



277
# File 'lib/oozby/environment.rb', line 277

def inspect; "OozbyFile"; end

#lookup(key, table) ⇒ Object

implement the openscad lookup function Look up value in table, and linearly interpolate if there’s no exact match. The first argument is the value to look up. The second is the lookup table – a vector of key-value pairs. table can be an array of key/value subarray pairs, or a hash with numeric keys



138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/oozby/environment.rb', line 138

def lookup key, table
  table = table.to_a if table.is_a? Hash
  table.sort! { |x,y| x[0] <=> y[0] }
  b = table.bsearch { |x| x[0] >= key } || table.last
  index_b = table.index(b)
  a = if index_b > 0 then table[index_b - 1] else b end
  
  return a[1] if key <= a[0]
  return b[1] if key >= b[0]
  
  key_difference = b[0] - a[0]
  value_proportion = (key - a[0]).to_f / key_difference
  (a[1].to_f * value_proportion) + (b[1].to_f * (1.0 - value_proportion))
end

#oozby_method_defined?(name) ⇒ Boolean

do we know of this method

Returns:

  • (Boolean)


53
54
55
# File 'lib/oozby/environment.rb', line 53

def oozby_method_defined? name
  @method_preprocessor.known?(name.to_sym) or !@preprocess or respond_to?(name)
end

#oozby_send_method(method_name, *args, **hash, &proc) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/oozby/environment.rb', line 77

def oozby_send_method method_name, *args, **hash, &proc
  if proc
    children = _subscope(&proc)
  else
    children = []
  end
  
  element = Oozby::Element.new({
    method: method_name,
    args: args, named_args: hash,
    children: children, 
    modifier: @one_time_modifier || @modifier,
    call_address: @ast.length
  })
  
  @ast.push(comment: "oozby: #{element}") if @parent.debug
  element = @method_preprocessor.transform_call(element) if @preprocess
  element.abduct @ast
  @one_time_modifier = nil
  
  return element
end

#preprocessor(state, &proc) ⇒ Object



115
116
117
118
119
120
# File 'lib/oozby/environment.rb', line 115

def preprocessor state, &proc
  previous = @transform
  @preprocess = state
  instance_eval &proc
  @preprocess = previous
end

#require(*args) ⇒ Object



213
214
215
216
217
218
219
220
221
222
# File 'lib/oozby/environment.rb', line 213

def require *args
  file = args.first
  if file.end_with? '.scad'
    _require_scad_file(*args)
  elsif File.exists? "#{file}.scad"
    _require_scad_file("#{args.shift}.scad", *args)
  else
    Kernel.require(*args)
  end
end

#resolution(**settings, &proc) ⇒ Object

gets and sets resolution settings



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/oozby/environment.rb', line 164

def resolution **settings, &proc
  warn "Setting fragments_per_turn and degrees_per_fragment together makes no sense!" if settings[:fragments_per_turn] && settings[:degrees_per_fragment]
  settings[:fragments_per_turn] ||= settings.delete(:facets_per_turn)
  settings[:degrees_per_fragment] ||= settings.delete(:degrees_per_facet)
  settings[:fragments] ||= settings.delete(:facets)
  settings.delete_if { |key,value| value == nil }
  previous_settings = @resolution
  @resolution.merge! settings
  @resolution[:degrees_per_fragment] = 360.0 / settings[:fragments_per_turn].to_f if settings[:fragments_per_turn]
  @resolution[:fragments_per_turn] = 360.0 / settings[:degrees_per_fragment].to_f if settings[:degrees_per_fragment]
  
  if proc
    return_data = instance_eval(&proc)
    @resolution = previous_settings
    return_data
  else
    @resolution.dup
  end
end

#root(&proc) ⇒ Object

the root modifier



209
210
211
# File 'lib/oozby/environment.rb', line 209

def root &proc
  _apply_modifier('!', &proc)
end

#turn(iterations = nil, &proc) ⇒ Object

utility for rotating around in a full circle and doing stuff



154
155
156
157
158
159
160
161
# File 'lib/oozby/environment.rb', line 154

def turn iterations = nil, &proc
  if iterations
    raise "Iterations must be Numeric" unless iterations.is_a? Numeric
    (0.0...360.0).step(360.0 / iterations, &proc)
  else
    0.0...360.0 # return a range which can be iterated however
  end
end