engineering 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,6 +5,9 @@ require 'model'
5
5
  require 'sketch'
6
6
  require 'units'
7
7
 
8
+ require_relative 'builder/extrusion'
9
+ require_relative 'builder/model'
10
+ require_relative 'builder/sketch'
8
11
  require_relative 'sketchup'
9
12
 
10
13
  =begin
@@ -18,76 +21,42 @@ module Engineering
18
21
 
19
22
  # Create a new {Extrusion} subclass and initialize it with the given block
20
23
  # @param name [Symbol] The name of the resulting subclass
21
- def extrusion(symbol, &block)
22
- builder = Model::Extrusion::Builder.new
23
- builder.evaluate(&block) if block_given?
24
- initial_arguments = {sketch: builder.extrusion.sketch, length: builder.extrusion.length}.select {|k,v| v }
25
-
26
- klass = Class.new(Model::Extrusion)
27
-
28
- if initial_arguments.has_key?(:length)
29
- klass.instance_variable_set :@length, initial_arguments[:length]
30
- klass.class.send(:define_method, :length) { @length }
31
- end
32
-
33
- klass.instance_variable_set :@sketch, initial_arguments[:sketch]
34
- klass.class.send(:define_method, :sketch) { @sketch }
35
-
36
- klass.send :define_method, :initialize do |options={}, &block|
37
- raise ArgumentError, "Can't initialize with a length when #{self} already has a length attribute" if initial_arguments.has_key?(:length) and options.has_key?(:length)
38
- super initial_arguments.merge(options), &block
24
+ def extrusion(name=nil, &block)
25
+ klass = Engineering::Builder::Extrusion.build(&block)
26
+ if name
27
+ parent = (Module == self.class) ? self : Object
28
+ parent.const_set(name, klass)
39
29
  end
40
-
41
- Object.const_set(symbol, klass)
30
+ klass
42
31
  end
43
32
 
44
33
  # Create a new {Model} subclass and initialize it with the given block
45
34
  # @param name [Symbol] The name of the new {Model} subclass
46
- def model(name, &block)
47
- klass = Class.new(Model)
48
- builder = Model::Builder.new
49
- builder.evaluate(&block) if block_given?
50
- initial_elements = builder.elements
51
-
52
- # The defaults are hidden in an instance variable so that the passed block can't accidentally corrupt them
53
- attribute_defaults = builder.instance_variable_get(:@attribute_defaults) || {}
54
-
55
- # Bind any attribute getters and setters to the new subclass
56
- attribute_getters = builder.instance_variable_get(:@attribute_getters) || {}
57
- attribute_getters.each {|k, m| klass.send :define_method, k, m }
58
-
59
- attribute_setters = builder.instance_variable_get(:@attribute_setters) || {}
60
- attribute_setters.each {|k, m| klass.send :define_method, k, m }
61
-
62
- klass.send :define_method, :initialize do |*args, &block|
63
- super *(attribute_defaults.map {|k,v| { k => (v.respond_to?(:call) ? v.call : v) } }), *args, &block
64
- initial_elements.each {|a| push a }
35
+ def model(name=nil, &block)
36
+ klass = Builder::Model.build(&block)
37
+ if name
38
+ parent = (Module == self.class) ? self : Object
39
+ parent.const_set(name, klass)
65
40
  end
66
-
67
- Object.const_set(name, klass)
41
+ klass
68
42
  end
69
43
 
70
44
  # Create a new {Sketch} subclass and initialize it with the given block
71
45
  # @param name [Symbol] The name of the {Sketch} subclass
72
- def sketch(symbol, &block)
73
- builder = Sketch::Builder.new
74
- builder.evaluate(&block) if block_given?
75
- initial_elements = builder.elements
76
-
77
- klass = Class.new(Sketch)
78
- klass.send :define_method, :initialize do |*args, &block|
79
- super *args, &block
80
- initial_elements.each {|a| push a }
46
+ def sketch(name=nil, &block)
47
+ klass = Builder::Sketch.build(&block)
48
+ if name
49
+ parent = (Module == self.class) ? self : Object
50
+ parent.const_set(name, klass)
81
51
  end
82
-
83
- Object.const_set(symbol, klass)
52
+ klass
84
53
  end
85
54
 
86
55
  class Geometry::Polygon
87
56
  # Build a {Polygon} instance using the {Sketch} DSL
88
57
  # @return [Polygon]
89
58
  def self.build(&block)
90
- Sketch::PolygonBuilder.new.evaluate(&block)
59
+ Sketch::Builder::Polygon.new.evaluate(&block)
91
60
  end
92
61
  end
93
62
 
@@ -95,7 +64,7 @@ module Engineering
95
64
  # Build a {Polyline} instance using the {Sketch} DSL
96
65
  # @return [Polyline]
97
66
  def self.build(&block)
98
- Sketch::PolylineBuilder.new.evaluate(&block)
67
+ Sketch::Builder::Polyline.new.evaluate(&block)
99
68
  end
100
69
  end
101
70
  end
@@ -103,3 +72,8 @@ end
103
72
 
104
73
  self.extend Engineering::DSL
105
74
  include Geometry # Make Geometry types more readily available
75
+
76
+ # Allow the DSL to be called from within Module definitions
77
+ class Module
78
+ include Engineering::DSL
79
+ end
@@ -0,0 +1,26 @@
1
+ class Model
2
+ # Extensions to Model::DSL
3
+ module DSL
4
+ # Define a new read-write {Model} attribute. An optional default value can be supplied as either an argument, or as a block. The block will be evaluated the first time the attribute is accessed.
5
+ # @param name [String,Symbol] The new attribute's name
6
+ # @param value A default value for the new attribute
7
+ def attr_accessor(name, value=nil, &block)
8
+ define_attribute_reader name, value, &block
9
+ define_attribute_writer name
10
+ end
11
+ alias :attribute :attr_accessor
12
+
13
+ # Define a new read-only {Model} attribute. An optional default value can be supplied as either an argument, or as a block. The block will be evaluated the first time the attribute is accessed.
14
+ # @param name [String,Symbol] The new attribute's name
15
+ # @param value A default value for the new attribute
16
+ def attr_reader(name, value=nil, &block)
17
+ define_attribute_reader(name, value, &block)
18
+ end
19
+
20
+ # Define a new write-only {Model} attribute
21
+ # @param name [String,Symbol] The new attribute's name
22
+ def attr_writer(name)
23
+ define_attribute_writer(name)
24
+ end
25
+ end
26
+ end
@@ -81,6 +81,7 @@ module SketchUp
81
81
  end
82
82
 
83
83
  # Convert the given container to an array of strings that SketchUp can read
84
+ # @return [Array]
84
85
  def to_array(container, parent='model.entities', transformation=nil)
85
86
  case container
86
87
  when Model::Extrusion
@@ -90,11 +91,11 @@ module SketchUp
90
91
  to_array(container.sketch, parent, container.transformation).map {|l| "#{l}.pushpull(#{to_sketchup(-container.length)})"}
91
92
  end
92
93
  when Model::Group
94
+ elements = container.elements.map {|element| to_array(element, 'group.entities') }
93
95
  if container.transformation and not container.transformation.identity?
94
- add_instance(parent, container)
95
- else
96
- container.elements.map {|element| to_array(element, parent) }.flatten
96
+ elements << "group.transformation = #{to_sketchup(container.transformation)}"
97
97
  end
98
+ ["#{parent}.add_group.tap {|group|", elements.flatten.map {|line| line.prepend("\t") }, '}']
98
99
  when Model # !!! Must be after all subclasses of Model
99
100
  if container.transformation and not container.transformation.identity?
100
101
  add_instance(parent, container)
@@ -102,7 +103,14 @@ module SketchUp
102
103
  container.elements.map {|element| to_array(element, parent) }.flatten
103
104
  end
104
105
  when Sketch::Group
105
- container.geometry.map {|element| to_sketchup(element, parent, container.transformation) }.flatten
106
+ container.geometry.map do |element|
107
+ combined_transformation = transformation ? (transformation + container.transformation) : container.transformation
108
+ case element
109
+ when Sketch::Group then to_array(element, parent, combined_transformation)
110
+ else
111
+ to_sketchup(element, parent, combined_transformation)
112
+ end
113
+ end.flatten
106
114
  when Sketch # !!! Must be after all subclasses of Sketch
107
115
  container.geometry.map do |element|
108
116
  case element
@@ -120,6 +128,11 @@ module SketchUp
120
128
  case entity
121
129
  when Array
122
130
  entity.map {|v| to_sketchup(v, parent, transformation) }.join(', ')
131
+ when Geometry::Annulus
132
+ "->{ inner_face = #{parent}.add_circle(#{to_sketchup(entity.center, parent, transformation)}, [0,0,1], #{to_sketchup(entity.inner_radius)}).tap {|points| points[0].find_faces}.first.faces[0]\n \
133
+ outer_face = #{parent}.add_circle(#{to_sketchup(entity.center, parent, transformation)}, [0,0,1], #{to_sketchup(entity.outer_radius)}).tap {|points| points[0].find_faces}.first.faces[0]\n \
134
+ #{parent}.erase_entities(inner_face)\n \
135
+ outer_face }.call"
123
136
  when Geometry::Arc
124
137
  "#{parent}.add_arc(#{to_sketchup(entity.center)}, [1,0,0], [0,0,1], #{to_sketchup(entity.radius)}, #{to_sketchup(entity.start_angle)}, #{to_sketchup(entity.end_angle)})"
125
138
  when Geometry::Circle
@@ -145,7 +158,7 @@ module SketchUp
145
158
  end
146
159
  when Geometry::Polygon
147
160
  "#{parent}.add_face(#{to_sketchup(entity.points, parent, transformation)})"
148
- when Geometry::Rectangle
161
+ when Geometry::Rectangle, Geometry::Square
149
162
  "#{parent}.add_face(#{to_sketchup(entity.points, parent, transformation)})"
150
163
  when Geometry::Transformation
151
164
  pt = '[' + (entity.translation ? to_sketchup(entity.translation.to_a) : '0,0,0') + ']'
@@ -161,6 +174,13 @@ module SketchUp
161
174
  when Units
162
175
  s = entity.to_s
163
176
  SKETCHUP_UNITS[s] or raise "SketchUp won't recognize '#{s}'"
177
+ when Units::Operator
178
+ operator = case entity
179
+ when Units::Addition then ' + '
180
+ when Units::Division then ' / '
181
+ when Units::Subtraction then ' - '
182
+ end
183
+ '(' + entity.operands.map {|a| to_sketchup(a)}.join(operator) + ')'
164
184
  when Units::Numeric
165
185
  [entity.value, entity.units].compact.map {|a| to_sketchup(a)}.join '.'
166
186
  else
@@ -0,0 +1,143 @@
1
+ require 'minitest/autorun'
2
+ require 'builder/extrusion'
3
+
4
+ describe Engineering::Builder::Extrusion do
5
+ subject { Engineering::Builder::Extrusion }
6
+
7
+ it 'must build a subclass' do
8
+ subject.build.ancestors.must_include(Model::Extrusion)
9
+ Class.wont_be :respond_to?, :elements
10
+ end
11
+
12
+ it 'must build a subclass with an empty block' do
13
+ klass = subject.build {}
14
+ klass.ancestors.must_include(Model::Extrusion)
15
+ klass.sketch.ancestors.must_include(Sketch)
16
+ klass.sketch.elements.must_be_empty
17
+ end
18
+
19
+ it 'must build a subclass with a length property' do
20
+ subject.build.must_be :respond_to?, :length
21
+ Class.wont_be :respond_to?, :length
22
+ end
23
+
24
+ it 'must build a subclass with a sketch property' do
25
+ subject.build.must_be :respond_to?, :sketch
26
+ Class.wont_be :respond_to?, :sketch
27
+ end
28
+
29
+ it 'must have a length property that is availabe to the DSL' do
30
+ subject.build() { length 5; length.must_equal 5 }
31
+ end
32
+
33
+ describe 'when creating a subclass with attributes' do
34
+ let :klass do
35
+ subject.build do
36
+ attribute :attribute0
37
+ end
38
+ end
39
+
40
+ it 'must define the attributes' do
41
+ klass.new.must_be :respond_to?, :attribute0
42
+ klass.new.must_be :respond_to?, :attribute0=
43
+ end
44
+
45
+ it 'must define the attribute on the Sketch instance' do
46
+ klass.new.sketch.must_be :respond_to?, :attribute0
47
+ end
48
+
49
+ it 'must have working accessors' do
50
+ test_model = klass.new
51
+ test_model.attribute0 = 42
52
+ test_model.attribute0.must_equal 42
53
+ end
54
+
55
+ it 'must be able to initialize the attribute during construction' do
56
+ klass.new(attribute0: 37).attribute0.must_equal 37
57
+ end
58
+ end
59
+
60
+ describe 'when creating a subclass with attributes that have default values' do
61
+ let :klass do
62
+ subject.build do
63
+ attribute :attribute0, 42
64
+ end
65
+ end
66
+
67
+ it 'must define the attribute on the Sketch subclass' do
68
+ klass.sketch.must_be :respond_to?, :attribute0
69
+ end
70
+
71
+ it 'must have the default value' do
72
+ klass.new.attribute0.must_equal 42
73
+ end
74
+
75
+ it 'must allow the default value to be overriden' do
76
+ klass.new(attribute0: 24).attribute0.must_equal 24
77
+ end
78
+
79
+ it 'must not pollute Class' do
80
+ Class.wont_be :respond_to?, :attribute0
81
+ end
82
+
83
+ it 'must have class attributes' do
84
+ klass.attribute0.must_equal 42
85
+ end
86
+
87
+ it 'must not have a class setter' do
88
+ -> { klass.attribute0 = 5 }.must_raise NoMethodError
89
+ end
90
+ end
91
+
92
+ describe 'when creating a subclass with read-only attributes that have default values' do
93
+ let :klass do
94
+ subject.build do
95
+ attr_reader :attributeO, 42
96
+ end
97
+ end
98
+
99
+ it 'must have the default value' do
100
+ klass.new.attributeO.must_equal 42
101
+ end
102
+
103
+ it 'must not allow the default value to be overriden' do
104
+ -> { klass.new(attributeO: 24) }.must_raise NoMethodError
105
+ end
106
+
107
+ it 'must not pollute Class' do
108
+ Class.wont_be :respond_to?, :attribute0
109
+ end
110
+
111
+ it 'must have class attributes' do
112
+ klass.attributeO.must_equal 42
113
+ end
114
+
115
+ it 'must not have a class setter' do
116
+ -> { klass.attributeO = 5 }.must_raise NoMethodError
117
+ end
118
+
119
+ it 'must not have an instance setter' do
120
+ -> { klass.new.attributeO = 6 }.must_raise NoMethodError
121
+ end
122
+ end
123
+
124
+ describe 'when creating a subclass with a group' do
125
+ let :klass do
126
+ subject.build do
127
+ group origin:[1,2] do
128
+ end
129
+ end
130
+ end
131
+
132
+ it 'must create a Group subclass' do
133
+ klass.sketch.elements.count.must_equal 1
134
+ klass.sketch.elements.first.first.ancestors.must_include Sketch::Group
135
+ klass.sketch.elements.first.last.must_equal [origin:[1,2]]
136
+ end
137
+
138
+ it 'must pass the initializer arguments when instantiating the subclass' do
139
+ klass.new.sketch.first.must_be_kind_of Sketch::Group
140
+ klass.new.sketch.first.transformation.translation.must_equal Geometry::Point[1,2]
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,191 @@
1
+ require 'minitest/autorun'
2
+ require 'builder/model'
3
+
4
+ describe Engineering::Builder::Model do
5
+ subject { Engineering::Builder::Model }
6
+
7
+ it 'must build a Model subclass' do
8
+ subject.build.ancestors.must_include(Model)
9
+ Class.wont_be :respond_to?, :elements
10
+ end
11
+
12
+ it 'must build a Model subclass with an empty block' do
13
+ klass = subject.build {}
14
+ klass.ancestors.must_include(Model)
15
+ klass.elements.must_be_empty
16
+ Class.wont_be :respond_to?, :elements
17
+ end
18
+
19
+ describe 'when creating a Model subclass with attributes' do
20
+ let :klass do
21
+ subject.build do
22
+ attribute :attribute0
23
+ end
24
+ end
25
+
26
+ it 'must define the attributes for instances' do
27
+ klass.new.must_be :respond_to?, :attribute0
28
+ klass.new.must_be :respond_to?, :attribute0=
29
+ end
30
+
31
+ it 'must have working instance accessors' do
32
+ test_model = klass.new
33
+ test_model.attribute0 = 42
34
+ test_model.attribute0.must_equal 42
35
+ end
36
+
37
+ it 'must be able to initialize the attribute during construction' do
38
+ klass.new(attribute0: 37).attribute0.must_equal 37
39
+ end
40
+ end
41
+
42
+ describe 'when creating a Model subclass with attributes that have default values' do
43
+ let :klass do
44
+ subject.build do
45
+ attribute :attribute0, 42
46
+ end
47
+ end
48
+
49
+ it 'must have the default value' do
50
+ klass.new.attribute0.must_equal 42
51
+ end
52
+
53
+ it 'must allow the default value to be overriden' do
54
+ klass.new(attribute0: 24).attribute0.must_equal 24
55
+ end
56
+
57
+ it 'must have class attributes' do
58
+ klass.attribute0.must_equal 42
59
+ end
60
+
61
+ it 'must not pollute Class' do
62
+ Class.wont_be :respond_to?, :attribute0
63
+ end
64
+
65
+ it 'must not have a class setter' do
66
+ -> { klass.attribute0 = 5 }.must_raise NoMethodError
67
+ end
68
+ end
69
+
70
+ describe 'when creating a Model subclass with read-only attributes that have default values' do
71
+ let :klass do
72
+ subject.build do
73
+ attr_reader :attributeO, 42
74
+ end
75
+ end
76
+
77
+ it 'must have the default value' do
78
+ klass.new.attributeO.must_equal 42
79
+ end
80
+
81
+ it 'must not allow the default value to be overriden' do
82
+ -> { klass.new(attributeO: 24) }.must_raise NoMethodError
83
+ end
84
+
85
+ it 'must not pollute Class' do
86
+ Class.wont_be :respond_to?, :attribute0
87
+ end
88
+
89
+ it 'must have class attributes' do
90
+ klass.attributeO.must_equal 42
91
+ end
92
+
93
+ it 'must not have a class setter' do
94
+ -> { klass.attributeO = 5 }.must_raise NoMethodError
95
+ end
96
+
97
+ it 'must not have an instance setter' do
98
+ -> { klass.new.attributeO = 6 }.must_raise NoMethodError
99
+ end
100
+ end
101
+
102
+ describe 'when pushing subclasses with initializer arguments' do
103
+ let :klass do
104
+ subject.build do
105
+ push Model, origin:[1,2,3]
106
+ end
107
+ end
108
+
109
+ it 'must store the subclass in initializer arguments' do
110
+ klass.elements.first.must_be_kind_of Array
111
+ klass.elements.first.must_equal [Model, [origin:[1,2,3]]]
112
+ end
113
+
114
+ it 'must pass the initializer arguments when instantiating the subclass' do
115
+ klass.new.elements.first.must_be_kind_of Model
116
+ end
117
+ end
118
+
119
+ describe 'when creating a subclass with an extrusion' do
120
+ let :klass do
121
+ subject.build do
122
+ extrude length:10, origin:[1,2,3] do
123
+ end
124
+ end
125
+ end
126
+
127
+ it 'must create an Extrusion subclass' do
128
+ klass.elements.count.must_equal 1
129
+ klass.elements.first.first.ancestors.must_include Model::Extrusion
130
+ klass.elements.first.last.must_equal [length:10, origin:[1,2,3]]
131
+ end
132
+
133
+ it 'must pass the initializer arguments when instantiating the subclass' do
134
+ klass.new.first.must_be_kind_of Model::Extrusion
135
+ klass.new.first.transformation.translation.must_equal Geometry::Point[1,2,3]
136
+ end
137
+ end
138
+
139
+ describe 'when creating a subclass with a group' do
140
+ let :klass do
141
+ subject.build do
142
+ group origin:[1,2,3] do
143
+ end
144
+ end
145
+ end
146
+
147
+ it 'must create a Group subclass' do
148
+ klass.elements.count.must_equal 1
149
+ klass.elements.first.first.ancestors.must_include Model::Group
150
+ klass.elements.first.last.must_equal [origin:[1,2,3]]
151
+ end
152
+
153
+ it 'must pass the initializer arguments when instantiating the subclass' do
154
+ klass.new.first.must_be_kind_of Model::Group
155
+ klass.new.first.transformation.translation.must_equal Geometry::Point[1,2,3]
156
+ end
157
+ end
158
+
159
+ describe 'when creating a subclass that contains another Model subclass' do
160
+ let :klass do
161
+ subject.build do
162
+ push Model
163
+ end
164
+ end
165
+
166
+ it 'must add the subclass' do
167
+ klass.elements.length.must_equal 1
168
+ klass.elements.first.first.ancestors.must_include Model
169
+ klass.elements.first.last.must_equal []
170
+ end
171
+ end
172
+
173
+ describe 'when creating a subclass that contains another Model subclass that has argument' do
174
+ let :klass do
175
+ subject.build do
176
+ push Model, origin:[1,2,3]
177
+ end
178
+ end
179
+
180
+ it 'must add the subclass' do
181
+ klass.elements.length.must_equal 1
182
+ klass.elements.first.first.ancestors.must_include Model
183
+ klass.elements.first.last.must_equal [origin:[1,2,3]]
184
+ end
185
+
186
+ it 'must pass the initializer arguments when instantiating the subclass' do
187
+ klass.new.first.must_be_kind_of Model
188
+ klass.new.first.transformation.translation.must_equal Geometry::Point[1,2,3]
189
+ end
190
+ end
191
+ end