fml 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/README +1 -0
- data/Rakefile +3 -0
- data/bin/fml2dae.rb +15 -0
- data/bin/fml2obj.rb +10 -0
- data/fml.gemspec +36 -0
- data/lib/collada/document.rb +101 -0
- data/lib/collada/geometry.rb +110 -0
- data/lib/config.yml +17 -0
- data/lib/floorplanner/area_builder.rb +42 -0
- data/lib/floorplanner/asset.rb +185 -0
- data/lib/floorplanner/collada_export.rb +118 -0
- data/lib/floorplanner/design.rb +108 -0
- data/lib/floorplanner/document.rb +68 -0
- data/lib/floorplanner/obj_export.rb +24 -0
- data/lib/floorplanner/opening3d.rb +140 -0
- data/lib/floorplanner/rib_export.rb +24 -0
- data/lib/floorplanner/svg_export.rb +25 -0
- data/lib/floorplanner/wall3d.rb +97 -0
- data/lib/floorplanner/wall_builder.rb +165 -0
- data/lib/floorplanner.rb +52 -0
- data/lib/geom/connection.rb +14 -0
- data/lib/geom/ear_trim.rb +52 -0
- data/lib/geom/edge.rb +89 -0
- data/lib/geom/glu_tess.rb +34 -0
- data/lib/geom/intersection.rb +38 -0
- data/lib/geom/matrix3d.rb +141 -0
- data/lib/geom/number.rb +104 -0
- data/lib/geom/plane.rb +36 -0
- data/lib/geom/polygon.rb +264 -0
- data/lib/geom/triangle.rb +38 -0
- data/lib/geom/triangle_mesh.rb +94 -0
- data/lib/geom/vertex.rb +33 -0
- data/lib/geom.rb +13 -0
- data/lib/keyhole/archive.rb +36 -0
- data/tasks/github-gem.rake +315 -0
- data/views/design.dae.erb +439 -0
- data/views/design.obj.erb +17 -0
- data/views/design.rib.erb +17 -0
- data/views/design.svg.erb +42 -0
- data/xml/collada_schema_1_4.xsd +11046 -0
- data/xml/fml.rng +268 -0
- data/xml/fml2kml.xsl +59 -0
- metadata +117 -0
data/.gitignore
ADDED
data/README
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
TODO: write some docs
|
data/Rakefile
ADDED
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
|