floorplanner-fml 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ module Floorplanner
2
+ class Document
3
+
4
+ def to_rib(design_id,out_path)
5
+ @design = Design.new(@xml,design_id)
6
+ @design.build_geometries
7
+ rib = File.new(out_path,'w')
8
+ rib.write @design.to_rib
9
+ rib.close
10
+ end
11
+
12
+ end
13
+
14
+ module RibExport
15
+ def to_rib
16
+ raise "No geometries to export. Call build_geometries first" unless @areas && @walls
17
+
18
+ template = ERB.new(
19
+ File.read(
20
+ File.join(Floorplanner.config['views_path'],'design.rib.erb')))
21
+ template.result(binding)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module Floorplanner
2
+ module SvgExport
3
+ def to_svg
4
+ # translate to x:0,y:0
5
+ bbox = @walls.bounding_box
6
+ dx = bbox[:min].distance_x(bbox[:max])
7
+ dy = bbox[:min].distance_y(bbox[:max])
8
+ min_x = -bbox[:min].x
9
+ min_y = -bbox[:min].y
10
+ # fit into document dimensions
11
+ width , height , padding = Floorplanner.config['svg']['width'],
12
+ Floorplanner.config['svg']['height'],
13
+ Floorplanner.config['svg']['padding']
14
+ ratio = ( width < height ? width : height ) * padding / ( dx > dy ? dx : dy )
15
+ # center on stage
16
+ mod_x = min_x + (width /ratio)/2 - dx/2
17
+ mod_y = min_y + (height/ratio)/2 - dy/2
18
+
19
+ template = ERB.new(
20
+ File.read(
21
+ File.join(Floorplanner.config['views_path'],'design.svg.erb')))
22
+ template.result(binding)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,97 @@
1
+ module Floorplanner
2
+ class Wall3D < Geom::TriangleMesh
3
+ UP = Geom::Number3D.new(0,0,1)
4
+ attr_accessor(:baseline,:outline,:inner,:outer,:name)
5
+ def initialize(baseline,thickness,height,name)
6
+ super()
7
+ @baseline = baseline
8
+ @thickness = thickness
9
+ @height = height
10
+ @name = name
11
+
12
+ # create inner and outer Edges of wall
13
+ @inner = @baseline.offset(@thickness/2.0,UP)
14
+ @outer = @baseline.offset(-@thickness/2.0,UP)
15
+ @openings = Array.new
16
+ end
17
+
18
+ def opening(position,size,type)
19
+ @openings << {:position => position, :size => size, :type => type}
20
+ end
21
+
22
+ def windows
23
+ @openings.collect{|o| o.window}.compact
24
+ end
25
+
26
+ # create base 'outline' polygon of wall
27
+ def prepare(num_start_connections,num_end_connections)
28
+ @outline = Geom::Polygon.new
29
+ if num_start_connections == 1 || num_start_connections == 2
30
+ @outline.vertices.push(
31
+ @outer.start_point,
32
+ @inner.start_point)
33
+ else
34
+ @outline.vertices.push(
35
+ @outer.start_point,
36
+ @baseline.start_point,
37
+ @inner.start_point)
38
+ end
39
+
40
+ if num_end_connections == 1 || num_end_connections == 2
41
+ @outline.vertices.push(
42
+ @inner.end_point,
43
+ @outer.end_point)
44
+ else
45
+ @outline.vertices.push(
46
+ @inner.end_point,
47
+ @baseline.end_point,
48
+ @outer.end_point)
49
+ end
50
+ @outline.vertices.reverse!
51
+ @outline.data[:color] = "#ff9999"
52
+ end
53
+
54
+ def update
55
+ @openings.each_with_index do |opening,i|
56
+ op = Opening3D.new(@baseline,@thickness,opening)
57
+ op.update
58
+ @meshes << op
59
+ @openings[i] = op
60
+ end
61
+ @openings = @openings.sort_by{|o| o.position.distance(@baseline.start_point.position)}
62
+ @outline.update
63
+ @meshes << @outline
64
+
65
+ # create top cap for wall
66
+ top_cap = @outline.clone
67
+ top_cap.transform_vertices(Geom::Matrix3D.translation(0,0,@height))
68
+ @meshes << top_cap
69
+ # flip bottom cap
70
+ @outline.reverse
71
+
72
+ # create walls side polygons
73
+ num = @outline.vertices.length
74
+ starts = [@outer.start_point,@inner.start_point,@baseline.start_point]
75
+ ends = [@outer.end_point, @inner.end_point, @baseline.end_point]
76
+ outs = [@outer.start_point,@outer.end_point]
77
+ @outline.vertices.each_with_index do |v,i|
78
+ j = @outline.vertices[(i+1) % num]
79
+ # omit starting and ending polygons
80
+ next if ( starts.include?(v) && starts.include?(j) ) ||
81
+ ( ends.include?(v) && ends.include?(j) )
82
+
83
+ edge = Geom::Edge.new(v,j)
84
+ poly = edge.extrude(@height,UP)
85
+ mesh = Geom::TriangleMesh.new
86
+ mesh << poly
87
+
88
+ # drill holes to side
89
+ @openings.each do |opening|
90
+ opening.drill(mesh,outs.include?(v))
91
+ end
92
+ mesh.update
93
+ @meshes << mesh
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,165 @@
1
+ module Floorplanner
2
+ class WallBuilder < Geom::TriangleMesh
3
+
4
+ def initialize(&block)
5
+ super()
6
+ @connections = Hash.new
7
+ @base_vertices = Array.new
8
+ @walls = Array.new
9
+ block.call(self)
10
+ end
11
+
12
+ def each(&block)
13
+ @walls.each{|w| block.call(w)}
14
+ end
15
+
16
+ def collect(&block)
17
+ @walls.collect{|w| block.call(w)}
18
+ end
19
+
20
+ def vertex(vertex)
21
+ if existing = find_vertex(@base_vertices,vertex,Floorplanner.config['geom_snap'])
22
+ existing
23
+ else
24
+ @base_vertices << vertex
25
+ vertex
26
+ end
27
+ end
28
+
29
+ def wall(sp,ep,thickness,height)
30
+ @connections[sp] = Array.new unless @connections.include?(sp)
31
+ @connections[ep] = Array.new unless @connections.include?(ep)
32
+ cs = Geom::Connection.new(ep, 0.0)
33
+ ce = Geom::Connection.new(sp, 0.0)
34
+ @connections[sp] << cs unless @connections[sp].include?(cs)
35
+ @connections[ep] << ce unless @connections[ep].include?(ce)
36
+ @walls << Wall3D.new(Geom::Edge.new(sp,ep), thickness, height, "wall_#{@walls.length}")
37
+ end
38
+
39
+ # call after adding walls
40
+ def opening(position,size,type)
41
+ @walls.each do |wall|
42
+ if wall.outline.point_inside(position)
43
+ wall.opening(position,size,type)
44
+ end
45
+ end
46
+ end
47
+
48
+ def prepare
49
+ @base_vertices.each do |v|
50
+ connections = @connections[v]
51
+ next if connections.length.zero?
52
+
53
+ connections.each do |c|
54
+ x = c.point.x - v.x
55
+ y = c.point.y - v.y
56
+ c.angle = Math.atan2(y,x)
57
+ end
58
+ connections.sort! {|a,b| a.angle <=> b.angle}
59
+ connections.each_index do |i|
60
+ j = (i+1) % connections.length
61
+
62
+ w0 , w1 = find_wall(v,connections[i].point),
63
+ find_wall(v,connections[j].point)
64
+
65
+ flipped0 , flipped1 = (w0.baseline.end_point == v),
66
+ (w1.baseline.end_point == v)
67
+
68
+ e0 , e1 = flipped0 ? w0.outer : w0.inner,
69
+ flipped1 ? w1.inner : w1.outer
70
+
71
+ isect = Geom::Intersection.line_line(e0.start_point.position,e0.end_point.position,e1.start_point.position,e1.end_point.position,true)
72
+
73
+ if isect.status == Geom::Intersection::INTERSECTION
74
+ # the two edges intersect!
75
+ # adjust the edges so they touch at the intersection.
76
+ if isect.alpha[0].abs < 2
77
+ if flipped0
78
+ e0.end_point.x = isect.points[0].x
79
+ e0.end_point.y = isect.points[0].y
80
+ else
81
+ e0.start_point.x = isect.points[0].x
82
+ e0.start_point.y = isect.points[0].y
83
+ end
84
+
85
+ if flipped1
86
+ e1.end_point.x = isect.points[0].x
87
+ e1.end_point.y = isect.points[0].y
88
+ else
89
+ e1.start_point.x = isect.points[0].x
90
+ e1.start_point.y = isect.points[0].y
91
+ end
92
+ else
93
+ # parallel
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ @walls.each do |wall|
100
+ num_start = @connections[wall.baseline.start_point].length
101
+ num_end = @connections[wall.baseline.end_point].length
102
+ wall.prepare(num_start,num_end)
103
+ end
104
+ end
105
+
106
+ def update
107
+ @walls.each do |wall|
108
+ # here comes the cache
109
+ wall.update
110
+ @vertices.concat(wall.vertices)
111
+ @faces.concat(wall.faces)
112
+ end
113
+
114
+ $stderr.puts "Walls Vertices before: #{@vertices.length.to_s}"
115
+ # remove same instances
116
+ @vertices.uniq!
117
+ # remove same vertices
118
+ old = @vertices.dup
119
+ @vertices = Array.new
120
+ old.each do |v|
121
+ @vertices.push(v) unless @vertices.include?(v) # find_vertex(@vertices,v) #
122
+ end
123
+ $stderr.puts "Walls Vertices: #{@vertices.length.to_s}"
124
+ $stderr.puts "Walls Faces : #{@faces.length.to_s}"
125
+ end
126
+
127
+ # make use of cache
128
+ def vertices
129
+ @vertices
130
+ end
131
+
132
+ def faces
133
+ @faces
134
+ end
135
+
136
+ def windows
137
+ @walls.collect{|w| w.windows}.flatten
138
+ end
139
+
140
+ private
141
+
142
+ def find_wall(sp,ep)
143
+ @walls.each do |wall|
144
+ if wall.baseline.start_point.equal?(sp,Floorplanner.config['geom_snap']) &&
145
+ wall.baseline.end_point.equal?(ep,Floorplanner.config['geom_snap'])
146
+ return wall
147
+ elsif wall.baseline.end_point.equal?(sp,Floorplanner.config['geom_snap']) &&
148
+ wall.baseline.start_point.equal?(ep,Floorplanner.config['geom_snap'])
149
+ return wall
150
+ end
151
+ end
152
+ nil
153
+ end
154
+
155
+ def find_vertex(arr,v,snap=Floorplanner.config['uniq_snap'])
156
+ arr.each do |vertex|
157
+ if v.equal?(vertex,snap)
158
+ return vertex
159
+ end
160
+ end
161
+ nil
162
+ end
163
+
164
+ end
165
+ end
data/lib/geom.rb ADDED
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.push(File.dirname(__FILE__))
2
+ require 'matrix'
3
+ require 'geom/number'
4
+ require 'geom/vertex'
5
+ require 'geom/triangle_mesh'
6
+ require 'geom/polygon'
7
+ require 'geom/triangle'
8
+ require 'geom/plane'
9
+ require 'geom/matrix3d'
10
+ require 'geom/intersection'
11
+ require 'geom/connection'
12
+ require 'geom/edge'
13
+ require 'geom/ear_trim'
@@ -0,0 +1,14 @@
1
+ module Geom
2
+ class Connection
3
+ alias_method(:==, :equal?)
4
+ attr_accessor(:point,:angle)
5
+ def initialize(point,angle)
6
+ @point = point
7
+ @angle = angle
8
+ end
9
+
10
+ def equal?(other)
11
+ other.point == @point && other.angle == @angle
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,52 @@
1
+ #
2
+ # Implements "Ear trimming" triangulation algorithm
3
+ #
4
+ module Geom
5
+ class EarTrim
6
+ def self.triangulate(poly)
7
+ result = Array.new
8
+ points = poly.vertices.dup
9
+ num = points.length
10
+ count = num*2
11
+ indices = Hash.new
12
+
13
+ return [ [0,1,2] ] if num == 3
14
+ points.reverse! if poly.winding == Polygon::WINDING_CW
15
+ points.each_with_index do |p,i|
16
+ indices[p] = i
17
+ end
18
+
19
+ while num > 2
20
+ return nil if count > num*2 # overflow
21
+ count += 1
22
+
23
+ i = 0
24
+ while i < num
25
+ j = (i+num-1) % num
26
+ k = (i+1) % num
27
+
28
+ if is_ear(points,j,i,k)
29
+ # save triangle
30
+ result.push([indices[points[j]],indices[points[i]],indices[points[k]]])
31
+ # remove vertex
32
+ points.delete_at(i)
33
+ num = points.length
34
+ count = 0
35
+ end
36
+ i += 1
37
+ end
38
+ end
39
+ result
40
+ end
41
+
42
+ def self.is_ear(points,u,v,w)
43
+ poly = Polygon.new([points[u],points[v],points[w]])
44
+ return false if poly.area < 0
45
+ points.length.times do |i|
46
+ next if i == u || i == v || i == w
47
+ return false if poly.point_inside(points[i])
48
+ end
49
+ true
50
+ end
51
+ end
52
+ end
data/lib/geom/edge.rb ADDED
@@ -0,0 +1,89 @@
1
+ module Geom
2
+ class Edge
3
+ attr_accessor(:start_point,:end_point)
4
+ def initialize(sp,ep)
5
+ @start_point = sp
6
+ @end_point = ep
7
+ end
8
+
9
+ def direction
10
+ Number3D.sub(@start_point.position,@end_point.position)
11
+ end
12
+
13
+ def length
14
+ x = @end_point.x - @start_point.x;
15
+ y = @end_point.y - @start_point.y;
16
+ z = @end_point.z - @start_point.z;
17
+ Math.sqrt(x*x + y*y + z*z);
18
+ end
19
+
20
+ def offset(distance,up)
21
+ up.normalize
22
+ edge = clone
23
+ dir = direction
24
+
25
+ Matrix3D.multiply_vector_3x3(Matrix3D.rotation_matrix(up.x,up.y,up.z, -Math::PI/2),dir)
26
+ dir.normalize
27
+
28
+ dir.x *= distance
29
+ dir.y *= distance
30
+ dir.z *= distance
31
+
32
+ edge.start_point.x += dir.x
33
+ edge.start_point.y += dir.y
34
+ edge.start_point.z += dir.z
35
+ edge.end_point.x += dir.x
36
+ edge.end_point.y += dir.y
37
+ edge.end_point.z += dir.z
38
+
39
+ edge
40
+ end
41
+
42
+ def extrude(distance,direction)
43
+ edge = clone
44
+ [edge.start_point,edge.end_point].each do |v|
45
+ v.x += distance*direction.x
46
+ v.y += distance*direction.y
47
+ v.z += distance*direction.z
48
+ end
49
+
50
+ poly = Polygon.new
51
+ poly.vertices.push(
52
+ edge.end_point , edge.start_point,
53
+ @start_point , @end_point)
54
+ poly
55
+ end
56
+
57
+ def snap(point)
58
+ x1 = @start_point.x
59
+ y1 = @start_point.y
60
+ z1 = @start_point.z
61
+ x2 = @end_point.x
62
+ y2 = @end_point.y
63
+ z2 = @end_point.z
64
+ x3 = point.x
65
+ y3 = point.y
66
+ z3 = point.z
67
+ dx = x2-x1
68
+ dy = y2-y1
69
+ dz = z2-z1
70
+ if dx == 0 && dy == 0 && dz == 0
71
+ return @start_point
72
+ else
73
+ t = ((x3 - x1) * dx + (y3 - y1) * dy + (z3 - z1) * dz) / (dx**2 + dy**2 + dz**2)
74
+ x0 = x1 + t * dx
75
+ y0 = y1 + t * dy
76
+ z0 = z1 + t * dz
77
+ return Vertex.new(x0,y0,z0)
78
+ end
79
+ end
80
+
81
+ def clone
82
+ Edge.new(@start_point.clone,@end_point.clone)
83
+ end
84
+
85
+ def to_s
86
+ "#<Geom::Edge:#{@start_point.to_s},#{@end_point.to_s}>"
87
+ end
88
+ end
89
+ end