floorplanner-fml 0.2

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