fml 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.gitignore +8 -0
  2. data/README +1 -0
  3. data/Rakefile +3 -0
  4. data/bin/fml2dae.rb +15 -0
  5. data/bin/fml2obj.rb +10 -0
  6. data/fml.gemspec +36 -0
  7. data/lib/collada/document.rb +101 -0
  8. data/lib/collada/geometry.rb +110 -0
  9. data/lib/config.yml +17 -0
  10. data/lib/floorplanner/area_builder.rb +42 -0
  11. data/lib/floorplanner/asset.rb +185 -0
  12. data/lib/floorplanner/collada_export.rb +118 -0
  13. data/lib/floorplanner/design.rb +108 -0
  14. data/lib/floorplanner/document.rb +68 -0
  15. data/lib/floorplanner/obj_export.rb +24 -0
  16. data/lib/floorplanner/opening3d.rb +140 -0
  17. data/lib/floorplanner/rib_export.rb +24 -0
  18. data/lib/floorplanner/svg_export.rb +25 -0
  19. data/lib/floorplanner/wall3d.rb +97 -0
  20. data/lib/floorplanner/wall_builder.rb +165 -0
  21. data/lib/floorplanner.rb +52 -0
  22. data/lib/geom/connection.rb +14 -0
  23. data/lib/geom/ear_trim.rb +52 -0
  24. data/lib/geom/edge.rb +89 -0
  25. data/lib/geom/glu_tess.rb +34 -0
  26. data/lib/geom/intersection.rb +38 -0
  27. data/lib/geom/matrix3d.rb +141 -0
  28. data/lib/geom/number.rb +104 -0
  29. data/lib/geom/plane.rb +36 -0
  30. data/lib/geom/polygon.rb +264 -0
  31. data/lib/geom/triangle.rb +38 -0
  32. data/lib/geom/triangle_mesh.rb +94 -0
  33. data/lib/geom/vertex.rb +33 -0
  34. data/lib/geom.rb +13 -0
  35. data/lib/keyhole/archive.rb +36 -0
  36. data/tasks/github-gem.rake +315 -0
  37. data/views/design.dae.erb +439 -0
  38. data/views/design.obj.erb +17 -0
  39. data/views/design.rib.erb +17 -0
  40. data/views/design.svg.erb +42 -0
  41. data/xml/collada_schema_1_4.xsd +11046 -0
  42. data/xml/fml.rng +268 -0
  43. data/xml/fml2kml.xsl +59 -0
  44. metadata +117 -0
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.swp
2
+ *.fml
3
+ *~
4
+ assets/
5
+ nbproject/
6
+ cache
7
+ .autotest
8
+ *.gem
data/README ADDED
@@ -0,0 +1 @@
1
+ TODO: write some docs
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ Dir["tasks/**/*.rake"].each { |tasks_file| load(tasks_file) }
2
+
3
+ GithubGem::RakeTasks.new(:gem)
data/bin/fml2dae.rb ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.join(File.dirname(__FILE__), "/../lib" )
3
+ require 'floorplanner'
4
+
5
+ if ARGV.length < 2
6
+ puts "\n Usage: fml2dae.rb [-xrefs] design_id|design_name path/to/fml out.dae"
7
+ else
8
+ xrefs = false
9
+ if ARGV[0] == "-xrefs"
10
+ ARGV.shift
11
+ xrefs = true
12
+ end
13
+ doc = Floorplanner::Document.new(ARGV[0])
14
+ doc.to_dae(ARGV[1],xrefs)
15
+ end
data/bin/fml2obj.rb ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.join(File.dirname(__FILE__), "/../lib" )
3
+ require 'floorplanner'
4
+
5
+ if ARGV.length < 2
6
+ puts "\n Usage: fml2dae.rb design_id path/to/fml"
7
+ else
8
+ doc = Floorplanner::Document.new(ARGV[1])
9
+ doc.to_obj(ARGV[0],ARGV[2])
10
+ end
data/fml.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "fml"
5
+ s.version = "0.2.1"
6
+ s.date = "2009-09-23"
7
+
8
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.3.0") if s.respond_to? :required_rubygems_version=
9
+
10
+ s.authors = ["Dusan Maliarik"]
11
+ s.description = %q{Floor plan document toolkit}
12
+ s.email = %q{dusan.maliarik@gmail.com}
13
+ s.executables = ["fml2dae.rb","fml2obj.rb"]
14
+ s.files = %w(lib/keyhole/archive.rb lib/floorplanner/opening3d.rb bin/fml2dae.rb xml/fml2kml.xsl xml/fml.rng xml/collada_schema_1_4.xsd lib/floorplanner/design.rb lib/floorplanner/collada_export.rb views/design.dae.erb lib/geom/plane.rb lib/geom.rb lib/floorplanner/area_builder.rb lib/config.yml .gitignore lib/geom/intersection.rb lib/geom/ear_trim.rb lib/geom/connection.rb lib/floorplanner.rb fml.gemspec lib/geom/polygon.rb lib/geom/edge.rb lib/floorplanner/asset.rb views/design.rib.erb lib/geom/matrix3d.rb lib/floorplanner/rib_export.rb lib/floorplanner/obj_export.rb Rakefile README views/design.obj.erb lib/geom/triangle.rb lib/geom/triangle_mesh.rb lib/floorplanner/wall_builder.rb lib/floorplanner/svg_export.rb lib/collada/geometry.rb views/design.svg.erb tasks/github-gem.rake lib/geom/vertex.rb lib/geom/glu_tess.rb lib/floorplanner/wall3d.rb lib/floorplanner/document.rb bin/fml2obj.rb lib/geom/number.rb lib/collada/document.rb)
15
+ s.homepage = %q{http://floorplanner.com/}
16
+ s.require_paths = ["lib"]
17
+ s.rubygems_version = %q{1.3.1}
18
+ s.summary = %q{Floorplanner.com FML document toolkit}
19
+
20
+ if s.respond_to? :specification_version then
21
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
22
+ s.specification_version = 2
23
+
24
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
25
+ s.add_runtime_dependency(%q<libxml-ruby>)
26
+ s.add_runtime_dependency(%q<rubyzip>)
27
+ else
28
+ s.add_dependency(%q<libxml-ruby>)
29
+ s.add_dependency(%q<rubyzip>)
30
+ end
31
+ else
32
+ s.add_dependency(%q<libxml-ruby>)
33
+ s.add_dependency(%q<rubyzip>)
34
+ end
35
+ end
36
+
@@ -0,0 +1,101 @@
1
+ require 'cgi'
2
+
3
+ module Collada
4
+ class Document < XML::Document
5
+ XMLNS = "http://www.collada.org/2005/11/COLLADASchema"
6
+ VERSION = "1.4.1"
7
+
8
+ COLLADA = 'COLLADA'
9
+ ASSET = 'asset'
10
+ LIBRARY_MATERIALS = 'library_materials'
11
+ LIBRARY_EFFECTS = 'library_effects'
12
+ LIBRARY_GEOMETRIES = 'library_geometries'
13
+ LIBRARY_VISUAL_SCENES = 'library_visual_scenes'
14
+ SCENE = 'scene'
15
+ VISUAL_SCENE = 'visual_scene'
16
+ INSTANCE_VISUAL_SCENE = 'instance_visual_scene'
17
+
18
+ attr_accessor :assets_libraries
19
+
20
+ def initialize
21
+ super
22
+ self.root = XML::Node.new(COLLADA)
23
+ self.root.namespaces.namespace = XML::Namespace.new(self.root,nil,XMLNS)
24
+ self.root.attributes['version'] = VERSION
25
+
26
+ create_structure
27
+
28
+ self.assets_libraries = []
29
+ end
30
+
31
+ def << (node)
32
+ root << node
33
+ end
34
+
35
+ def place(doc,position,rotation,scale)
36
+ unless materials_and_effects_included?(doc.name)
37
+ doc.library_materials.each do |material|
38
+ library_materials_node << imported(material)
39
+ end
40
+ doc.library_effects.each do |effect|
41
+ library_effects_node << imported(effect)
42
+ end
43
+ materials_and_effects_added(doc.name)
44
+ end
45
+
46
+ doc.library_geometries.each do |geometry|
47
+ library_geometries_node << imported(geometry)
48
+ end
49
+ library_visual_scenes_node << imported(doc.library_visual_scenes)
50
+ end
51
+
52
+ private
53
+ def create_structure
54
+ self << XML::Node.new(ASSET)
55
+
56
+ self << XML::Node.new(LIBRARY_MATERIALS)
57
+ self << XML::Node.new(LIBRARY_EFFECTS)
58
+ self << XML::Node.new(LIBRARY_GEOMETRIES)
59
+ self << (visual_scenes = XML::Node.new(LIBRARY_VISUAL_SCENES))
60
+ visual_scenes << (visual_scene = XML::Node.new(VISUAL_SCENE))
61
+ visual_scene['id'] = 'MainScene'
62
+ visual_scene['name'] = 'MainScene'
63
+
64
+ self << (scene = XML::Node.new(SCENE))
65
+ scene << (scene_node = XML::Node.new(INSTANCE_VISUAL_SCENE))
66
+ scene_node['url'] = '#MainScene'
67
+ end
68
+
69
+ def library_visual_scenes_node
70
+ self.find("//#{VISUAL_SCENE}").first
71
+ end
72
+
73
+ def library_geometries_node
74
+ find_node(LIBRARY_GEOMETRIES)
75
+ end
76
+
77
+ def library_effects_node
78
+ find_node(LIBRARY_EFFECTS)
79
+ end
80
+
81
+ def library_materials_node
82
+ find_node(LIBRARY_MATERIALS)
83
+ end
84
+
85
+ def find_node(node_name)
86
+ self.root.find(node_name).first
87
+ end
88
+
89
+ def materials_and_effects_included?(namespace)
90
+ assets_libraries.include?(namespace)
91
+ end
92
+
93
+ def materials_and_effects_added(namespace)
94
+ assets_libraries << namespace
95
+ end
96
+
97
+ def imported(nodes)
98
+ self.import(nodes)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,110 @@
1
+ module Collada
2
+ class Geometry < Geom::TriangleMesh
3
+
4
+ SCENE_QUERY = '/COLLADA/scene/instance_visual_scene'
5
+ VISUAL_SCENE_QUERY = '/COLLADA/library_visual_scenes/visual_scene[@id="%s"]'
6
+ GEOMETRY_QUERY = '/COLLADA/library_geometries/geometry[@id="%s"]'
7
+ NODE_QUERY = '/COLLADA/library_nodes//node[@id="%s"]'
8
+
9
+ def self.doc(xml)
10
+ result = Geometry.new
11
+ result.instance_eval do
12
+ @xml = xml
13
+ @cache = {}
14
+
15
+ scene_id = xml.find(SCENE_QUERY).first.attributes['url'][1..-1]
16
+ scene = xml.find(VISUAL_SCENE_QUERY % scene_id).first
17
+ scene.children.each do |child|
18
+ eval_node(child) if child.name == "node"
19
+ end
20
+ end
21
+ result
22
+ end
23
+
24
+ private
25
+
26
+ def eval_node(node,parent=nil)
27
+ parent = node.parent unless parent
28
+ parent_matrix = Geom::Matrix3D.identity
29
+ if parent.name == "node"
30
+ if @cache.include?(parent)
31
+ parent_matrix = @cache[parent]
32
+ else
33
+ parent_matrix = get_node_matrix(parent)
34
+ @cache[parent] = parent_matrix
35
+ end
36
+ end
37
+
38
+ node_matrix = get_node_matrix(node,parent_matrix)
39
+ @cache[node] = node_matrix
40
+
41
+ node.children.each do |child|
42
+ case child.name
43
+ when "node"
44
+ eval_node(child,node)
45
+ when "instance_geometry"
46
+ load_geometry(child.attributes['url'][1..-1],node_matrix)
47
+ when "instance_node"
48
+ eval_node(expand_node(child),node)
49
+ end
50
+ end
51
+ end
52
+
53
+ def load_geometry(gid,matrix=nil)
54
+ geometry = @xml.find(GEOMETRY_QUERY % gid).first
55
+ mesh = Geom::TriangleMesh.new
56
+
57
+ floats = Array.new
58
+ geometry.find('mesh/triangles').each do |triangles|
59
+ vertices_id = triangles.find('input[@semantic="VERTEX"]').first.attributes['source'][1..-1]
60
+ source_id = geometry.find('mesh/vertices[@id="%s"]/input[@semantic="POSITION"]' % vertices_id).first.attributes['source'][1..-1]
61
+
62
+ floats.concat geometry.find('mesh/source[@id="%s"]/float_array' % source_id).first.get_floats
63
+ end
64
+
65
+ (floats.length/3).times do |i|
66
+ offset = i*3
67
+ mesh.vertices.push Geom::Vertex.new(floats[offset],floats[offset+1],floats[offset+2])
68
+ end
69
+
70
+ mesh.transform_vertices(matrix) if matrix
71
+ @meshes << mesh
72
+ end
73
+
74
+ def expand_node(instance)
75
+ node_id = instance.attributes['url'][1..-1]
76
+ @xml.find(NODE_QUERY % node_id).first
77
+ end
78
+
79
+ def get_node_matrix(node,source=nil)
80
+ result = source ? source : Geom::Matrix3D.identity
81
+ node.children.each do |child|
82
+ case child.name
83
+ when "translate"
84
+ f = child.get_floats
85
+ t = Geom::Matrix3D.translation(f[0],f[1],f[2])
86
+ result = result.multiply(t)
87
+ when "scale"
88
+ f = child.get_floats
89
+ t = Geom::Matrix3D.scale(f[0],f[1],f[2])
90
+ result = result.multiply(t)
91
+ when "rotate"
92
+ f = child.get_floats
93
+ t = Geom::Matrix3D.rotation_matrix(f[0],f[1],f[2],f[3])
94
+ result = result.multiply(t)
95
+ when "matrix"
96
+ f = child.get_floats
97
+ t = Geom::Matrix3D[
98
+ [ *f[0..3] ],
99
+ [ *f[4..7] ],
100
+ [ *f[8..11] ],
101
+ [ *f[12..15] ]
102
+ ]
103
+ result = result.multiply(t)
104
+ end
105
+ end
106
+ result
107
+ end
108
+
109
+ end
110
+ end
data/lib/config.yml ADDED
@@ -0,0 +1,17 @@
1
+ content_base_url: http://cdn.floorplanner.com/assets/
2
+ dae_cache_path: /home/skrat/workspace/floorplanner/fml2dae_cache/
3
+ area_textures_path: /home/skrat/workspace/floorplanner/fml2dae_cache/textures_2d/
4
+
5
+ wall_height: 2.2
6
+ wall_thickness: 0.25
7
+ geom_snap: 0.1
8
+ uniq_snap: 0.0000005
9
+ openings:
10
+ window_base: 0.65
11
+ window_height: 1.6
12
+ door_height: 2.2
13
+
14
+ svg:
15
+ width: 1024
16
+ height: 1024
17
+ padding: 0.8
@@ -0,0 +1,42 @@
1
+ module Floorplanner
2
+ class AreaBuilder < Geom::TriangleMesh
3
+
4
+ attr_reader :ceiling_z
5
+
6
+ def initialize(&block)
7
+ super()
8
+ @meshes = {}
9
+ block.call(self)
10
+ end
11
+
12
+ def each(&block)
13
+ @meshes.each_value{|a| block.call(a)}
14
+ end
15
+
16
+ def vertex(p)
17
+ if existing = @vertices.index(p)
18
+ @vertices[existing]
19
+ else
20
+ @vertices << p; p
21
+ end
22
+ end
23
+
24
+ HEX_RE = "(?i:[a-f\\d])"
25
+ def area(vertices,params=nil)
26
+ a_id = vertices.hash.abs.to_s + "_" +params[:type]
27
+ if params[:color] =~ /\A#((?:#{HEX_RE}{2,2}){3,4})\z/
28
+ params[:color] = [*$1.scan(/.{2,2}/).collect {|value| value.hex / 255.0}]
29
+ else
30
+ params[:color] = [1,1,1]
31
+ end
32
+ @meshes[a_id] = Geom::Polygon.new(vertices, nil, params.merge({:id => a_id}))
33
+ end
34
+
35
+ def update(ceiling_z)
36
+ @ceiling_z = ceiling_z
37
+ @meshes.each do |id,mesh|
38
+ mesh.update
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,185 @@
1
+ module Floorplanner
2
+ class Asset
3
+ LIBRARY_GEOMETRIES = '/COLLADA/library_geometries/geometry'
4
+ LIBRARY_EFFECTS = '/COLLADA/library_effects/effect'
5
+ LIBRARY_MATERIALS = '/COLLADA/library_materials/material'
6
+ LIBRARY_NODES = '/COLLADA/library_nodes/node'
7
+ LIBRARY_IMAGES = '/COLLADA/library_images/image'
8
+ VISUAL_SCENE_QUERY = '/COLLADA/library_visual_scenes/visual_scene/node'
9
+
10
+ NO_NS_NAME = %w{ param }
11
+
12
+ CACHE_PATH = File.join(Floorplanner.config['dae_cache_path'], 'kmz')
13
+
14
+ attr_reader :id, :name, :title, :dae_path
15
+
16
+ def self.get(asset_id,asset_title,asset_url3d)
17
+ FileUtils.mkdir_p(CACHE_PATH)
18
+ asset_url = Floorplanner.config['content_base_url'] + URI.escape(asset_url3d)
19
+
20
+ cached_path = File.join(CACHE_PATH,asset_id)
21
+ if File.exists?(cached_path)
22
+ Log.info "Cached asset: #{asset_id}"
23
+ @kmz = Keyhole::Archive.new(cached_path)
24
+ Asset.new(asset_id,asset_title,@kmz)
25
+ else
26
+ Log.info "Downloading asset: #{asset_url}"
27
+ cached = File.new(cached_path,'w')
28
+ remote = open(asset_url)
29
+ cached.write(remote.read)
30
+ cached.close
31
+
32
+ @kmz = Keyhole::Archive.new(cached_path)
33
+ asset = Asset.new(asset_id,asset_title,@kmz)
34
+ asset.adjust_paths!
35
+ asset
36
+ end
37
+ end
38
+
39
+ def initialize(id,title,kmz)
40
+ @dae_path = kmz.dae_path(id)
41
+ @kmz = kmz
42
+ @id = id
43
+ @title = title
44
+ @xml = XML::Document.string(File.read(@dae_path).gsub(/xmlns=".+"/, ''))
45
+ @name = File.basename(@dae_path.gsub(/\.|dae/,''))
46
+ @images_dict = {}
47
+ end
48
+
49
+ def measurement_unit
50
+ end
51
+
52
+ def library_materials
53
+ return @materials if @materials
54
+ materials = @xml.find(LIBRARY_MATERIALS)
55
+ materials.each {|mat| namespace!(mat)}
56
+ @materials = materials
57
+ end
58
+
59
+ def library_effects
60
+ return @effects if @effects
61
+ effects = @xml.find(LIBRARY_EFFECTS)
62
+ effects.each {|eff| namespace!(eff)}
63
+ @effects = effects
64
+ end
65
+
66
+ def library_geometries
67
+ return @geometries if @geometries
68
+ geometries = @xml.find(LIBRARY_GEOMETRIES)
69
+ geometries.each{|geo| namespace!(geo)}
70
+ @geometries = geometries
71
+ end
72
+
73
+ def library_nodes
74
+ return @nodes if @nodes
75
+ nodes = @xml.find(LIBRARY_NODES)
76
+ nodes.each{|nod| namespace!(nod)}
77
+ @nodes = nodes
78
+ end
79
+
80
+ def library_images
81
+ return @images if @images
82
+ images = @xml.find(LIBRARY_IMAGES)
83
+ images.each{|img| namespace!(img) && update_path!(img)}
84
+ @images = images
85
+ end
86
+
87
+ def visual_scene_node
88
+ return @scene_node if @scene_node
89
+ @scene_node = namespace!(@xml.find(VISUAL_SCENE_QUERY).first)
90
+ end
91
+
92
+ def bounding_box
93
+ mesh = Collada::Geometry.doc @xml
94
+ mesh.bounding_box
95
+ end
96
+
97
+ def bounding_box_size
98
+ box = bounding_box
99
+ Geom::Number3D.new(
100
+ box[:max].distance_x(box[:min]),
101
+ box[:max].distance_y(box[:min]),
102
+ box[:max].distance_z(box[:min])
103
+ )
104
+ end
105
+
106
+ def scale_ratio(target_size)
107
+ bbox_size = bounding_box_size
108
+ result = Geom::Number3D.new
109
+
110
+ result.x = target_size.x / bbox_size.x
111
+ result.y = target_size.y / bbox_size.y
112
+ result.z = 0.01 # (result.x + result.y) / 2.0 # TODO: correct Z size in FML
113
+
114
+ result
115
+ end
116
+
117
+ def save_textures(root_path)
118
+ images = @xml.find(LIBRARY_IMAGES)
119
+ FileUtils.mkdir_p(root_path) unless images.length.zero?
120
+
121
+ images.each do |image|
122
+ relative_to_dae = image.find('init_from').first.content
123
+ img_path = @kmz.image_path(@id,relative_to_dae)
124
+ target_path = File.join(root_path,@id)
125
+ FileUtils.mkdir_p target_path
126
+
127
+ target = open(File.join(target_path,File.basename(img_path)),'w')
128
+ target.write(File.read(img_path))
129
+ target.close
130
+ @images_dict[relative_to_dae] = relative_to_dae[3..-1]
131
+ end
132
+ end
133
+
134
+ def adjust_paths!
135
+ images = @xml.find(LIBRARY_IMAGES)
136
+
137
+ images.each do |image|
138
+ init_from = image.find('init_from').first
139
+ img_path = @kmz.image_path(@id,init_from.content,true)
140
+ init_from.content = img_path
141
+ end
142
+ open(@dae_path, 'w') do |f|
143
+ f.write @xml.to_s
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def namespace!(node)
150
+ node['id'] = "#{@name}_#{node['id']}" if node['id']
151
+ node['sid'] = "#{@name}_#{node['sid']}" if node['sid'] && node['sid'] != 'COMMON'
152
+ node['name'] = "#{@name}_#{node['name']}" if node['name'] && !NO_NS_NAME.include?(node.name)
153
+ node['symbol'] = "#{@name}_#{node['symbol']}" if node['symbol']
154
+ node['material'] = "#{@name}_#{node['material']}" if node['material']
155
+
156
+
157
+ node['url'] = "##{@name}_#{node['url'].gsub('#','')}" if node['url']
158
+ node['target'] = "##{@name}_#{node['target'].gsub('#','')}" if node['target']
159
+ node['source'] = "##{@name}_#{node['source'].gsub('#','')}" if node['source']
160
+ node['texture'] = "#{@name}_#{node['texture'].gsub('#','')}" if node['texture']
161
+
162
+ if node.name == 'surface'
163
+ n = node.find('init_from').first
164
+ n.content = "#{@name}_#{n.content}"
165
+ end
166
+
167
+ if node.name == 'sampler2D'
168
+ n = node.find('source').first
169
+ n.content = "#{@name}_#{n.content}"
170
+ end
171
+
172
+ node.children.each do |children|
173
+ namespace!(children)
174
+ end
175
+ node
176
+ end
177
+
178
+ # updates image path to export output folder
179
+ def update_path!(node)
180
+ init_from_node = node.find('init_from').first
181
+ relative = init_from_node.content
182
+ init_from_node.content = @images_dict[relative]
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,118 @@
1
+ module Floorplanner
2
+ class Document
3
+
4
+ def to_dae(out_path,xrefs=false)
5
+ @design = Design.new(@xml)
6
+ @design.build_geometries
7
+ @design.save_textures(File.dirname(out_path)) unless xrefs
8
+ dae = File.new(out_path,'w')
9
+ dae.write(@design.to_dae(xrefs))
10
+ dae.close
11
+ end
12
+
13
+ end
14
+
15
+ module ColladaExport
16
+ DESIGN_QUERY = "/design"
17
+ ASSET_QUERY = DESIGN_QUERY+"/assets/asset[@id='%s']"
18
+ ASSETS_QUERY = DESIGN_QUERY+"/assets/asset"
19
+ OBJECTS_QUERY = DESIGN_QUERY+"/objects/object"
20
+
21
+ def to_dae(xrefs=false)
22
+ raise "No geometries to export. Call build_geometries first" unless @areas && @walls
23
+ @assets = assets
24
+ @elements = objects
25
+
26
+ # somehow...
27
+ @walls.reverse
28
+ @areas.each {|a| a.reverse}
29
+ @xrefs = xrefs
30
+
31
+ template = ERB.new(
32
+ File.read(
33
+ File.join(File.dirname(__FILE__), '..', '..', 'views', 'design.dae.erb')))
34
+ template.result(binding)
35
+ end
36
+
37
+ def assets
38
+ return @assets if @assets
39
+ @assets = {}
40
+ @xml.find(ASSETS_QUERY).each do |asset_node|
41
+ asset_id = asset_node.attributes['id']
42
+ name = asset_node.find('name').first.content
43
+ url3d = asset_node.find('url3d').first
44
+ next unless url3d
45
+ url3d = url3d.content
46
+
47
+ # TODO: store asset bounding box
48
+ asset = Floorplanner::Asset.get(asset_id,name,url3d)
49
+ next unless asset
50
+ @assets.store(asset_id, asset)
51
+ end
52
+ @assets
53
+ end
54
+
55
+ def objects
56
+ result = []
57
+ @xml.find(OBJECTS_QUERY).each do |object|
58
+ begin
59
+ refid = object.find('asset').first.attributes['refid']
60
+ next unless assets[refid]
61
+
62
+ asset = assets[refid]
63
+ position = Geom::Number3D.from_str(object.find('points').first.content)
64
+ # correct Flash axis issues
65
+ position.y *= -1.0
66
+
67
+ # correct Flash rotation issues
68
+ rotation = unless object.find('rotation').empty?
69
+ object.find('rotation').first.content
70
+ else
71
+ '0 0 0'
72
+ end
73
+ rotation = Geom::Number3D.from_str(rotation)
74
+ rotation.z += 360 if rotation.z < 0
75
+ rotation.z += 180
76
+
77
+ # find proper scale for object
78
+ size = object.find('size').first.content
79
+ scale = asset.scale_ratio(Geom::Number3D.from_str(size))
80
+
81
+ mirrored = object.find('mirrored').first
82
+ reflection = Geom::Matrix3D.reflection(Geom::Plane.new(Geom::Number3D.new(0.0,1.0,0.0), Geom::Number3D.new))
83
+ if mirrored
84
+ mirror = Geom::Number3D.from_str(mirrored)
85
+ if mirror.x != 0 || mirror.y != 0 || mirror.z != 0
86
+ mirror.x = mirror.x > 0 ? 1 : 0
87
+ mirror.y = mirror.y > 0 ? 1 : 0
88
+ mirror.z = mirror.z > 0 ? 1 : 0
89
+
90
+ origin = Geom::Number3D.new
91
+ plane = Geom::Plane.new(mirror,origin)
92
+ reflection = Geom::Matrix3D.reflection(plane).multiply reflection
93
+ end
94
+ end
95
+
96
+ result << {
97
+ :asset => asset,
98
+ :position => position,
99
+ :rotation => rotation,
100
+ :scale => scale,
101
+ :matrix => reflection
102
+ }
103
+ rescue
104
+ # TODO: handle text
105
+ end
106
+ end
107
+ result
108
+ end
109
+
110
+ def save_textures(root_path)
111
+ img_path = File.join(root_path,'textures')
112
+ FileUtils.mkdir_p img_path
113
+ assets.each_value do |asset|
114
+ asset.save_textures img_path
115
+ end
116
+ end
117
+ end
118
+ end