fml 0.2.1

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