engineering 0.2 → 0.3

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.
@@ -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