floorplanner-fml 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1 @@
1
+ TODO: write some docs
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[1])
14
+ doc.to_dae(ARGV[0],ARGV[2],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
@@ -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,15 @@
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
+ geom_snap: 0.1
6
+ uniq_snap: 0.0000005
7
+ openings:
8
+ window_base: 0.65
9
+ window_height: 1.6
10
+ door_height: 2.2
11
+
12
+ svg:
13
+ width: 1024
14
+ height: 1024
15
+ padding: 0.8
@@ -0,0 +1,46 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+ require 'find'
4
+ require 'open-uri'
5
+ require 'net/http'
6
+ require 'fileutils'
7
+
8
+ require 'rubygems'
9
+ require 'zip/zip'
10
+ require 'xml'
11
+
12
+ $LOAD_PATH.push(File.dirname(__FILE__))
13
+
14
+ module Floorplanner
15
+ def self.config
16
+ @@config ||= YAML.load_file(File.join(File.dirname(__FILE__),'config.yml'))
17
+ end
18
+
19
+ def self.config=(yaml)
20
+ @@config = yaml
21
+ end
22
+ end
23
+
24
+ module XML
25
+ class Node
26
+ def get_floats
27
+ content.split(/\s/).delete_if {|s| s.empty?}.map! {|f| f.to_f}
28
+ end
29
+ end
30
+ end
31
+
32
+ require 'geom'
33
+ require 'floorplanner/asset'
34
+ require 'floorplanner/document'
35
+ require 'floorplanner/collada_export'
36
+ require 'floorplanner/rib_export'
37
+ require 'floorplanner/obj_export'
38
+ require 'floorplanner/svg_export'
39
+ require 'floorplanner/design'
40
+ require 'floorplanner/wall3d'
41
+ require 'floorplanner/opening3d'
42
+ require 'floorplanner/wall_builder'
43
+ require 'floorplanner/area_builder'
44
+ require 'collada/document'
45
+ require 'collada/geometry'
46
+ require 'keyhole/archive'
@@ -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
+ $stderr.puts("Cached asset: %s" % asset_id)
23
+ @kmz = Keyhole::Archive.new(cached_path)
24
+ Asset.new(asset_id,asset_title,@kmz)
25
+ else
26
+ $stderr.puts("Downloading asset: %s" % 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