fml 0.2.3 → 0.2.4
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.
- data/.gitignore +1 -0
- data/TODO +2 -0
- data/bin/fml2dae.rb +14 -9
- data/fml.gemspec +10 -7
- data/lib/collada/document.rb +21 -23
- data/lib/collada/geometry.rb +22 -23
- data/lib/config.yml +4 -4
- data/lib/floorplanner/area_builder.rb +3 -8
- data/lib/floorplanner/asset.rb +32 -27
- data/lib/floorplanner/collada_export.rb +76 -71
- data/lib/floorplanner/design.rb +42 -76
- data/lib/floorplanner/document.rb +16 -43
- data/lib/floorplanner/opening3d.rb +5 -4
- data/lib/floorplanner/wall_builder.rb +6 -5
- data/lib/floorplanner/xml.rb +151 -0
- data/lib/floorplanner.rb +11 -9
- data/lib/geom/edge.rb +1 -1
- data/lib/geom/matrix3d.rb +7 -2
- data/lib/geom/number.rb +8 -0
- data/lib/geom/polygon.rb +18 -22
- data/lib/geom/vertex.rb +8 -0
- data/tasks/github-gem.rake +47 -4
- data/views/design.dae.erb +37 -92
- metadata +53 -40
- data/bin/fml2obj.rb +0 -10
- data/lib/floorplanner/obj_export.rb +0 -24
- data/lib/floorplanner/rib_export.rb +0 -24
- data/views/design.obj.erb +0 -17
- data/views/design.rib.erb +0 -17
data/.gitignore
CHANGED
data/bin/fml2dae.rb
CHANGED
@@ -3,13 +3,18 @@ $: << File.join(File.dirname(__FILE__), "/../lib" )
|
|
3
3
|
require 'floorplanner'
|
4
4
|
|
5
5
|
if ARGV.length < 2
|
6
|
-
puts "\n Usage: fml2dae.rb [-xrefs]
|
7
|
-
|
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)
|
6
|
+
puts "\n Usage: fml2dae.rb [-xrefs] path/to/fml out.dae"
|
7
|
+
exit
|
15
8
|
end
|
9
|
+
|
10
|
+
conf = {
|
11
|
+
:ceiling => false,
|
12
|
+
:window_glass => true
|
13
|
+
}
|
14
|
+
|
15
|
+
if ARGV[0] == "-xrefs"
|
16
|
+
ARGV.shift
|
17
|
+
conf[:xrefs] = true
|
18
|
+
end
|
19
|
+
doc = Floorplanner::XML::Document.from_xml(open(ARGV[0]))
|
20
|
+
doc.to_dae(ARGV[1], conf)
|
data/fml.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "fml"
|
5
|
-
s.version = "0.2.
|
6
|
-
s.date = "2010-
|
5
|
+
s.version = "0.2.4"
|
6
|
+
s.date = "2010-11-25"
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.3.0") if s.respond_to? :required_rubygems_version=
|
9
9
|
|
10
10
|
s.authors = ["Dusan Maliarik"]
|
11
11
|
s.description = %q{Floor plan document toolkit}
|
12
12
|
s.email = %q{dusan.maliarik@gmail.com}
|
13
|
-
s.executables = ["fml2dae.rb"
|
14
|
-
s.files = %w(
|
13
|
+
s.executables = ["fml2dae.rb"]
|
14
|
+
s.files = %w(.gitignore README Rakefile TODO bin/fml2dae.rb fml.gemspec lib/collada/document.rb lib/collada/geometry.rb lib/config.yml lib/floorplanner.rb lib/floorplanner/area_builder.rb lib/floorplanner/asset.rb lib/floorplanner/collada_export.rb lib/floorplanner/design.rb lib/floorplanner/document.rb lib/floorplanner/opening3d.rb lib/floorplanner/svg_export.rb lib/floorplanner/wall3d.rb lib/floorplanner/wall_builder.rb lib/floorplanner/xml.rb lib/geom.rb lib/geom/connection.rb lib/geom/ear_trim.rb lib/geom/edge.rb lib/geom/glu_tess.rb lib/geom/intersection.rb lib/geom/matrix3d.rb lib/geom/number.rb lib/geom/plane.rb lib/geom/polygon.rb lib/geom/triangle.rb lib/geom/triangle_mesh.rb lib/geom/vertex.rb lib/keyhole/archive.rb tasks/github-gem.rake views/design.dae.erb views/design.svg.erb xml/collada_schema_1_4.xsd xml/fml.rng xml/fml2kml.xsl)
|
15
15
|
s.homepage = %q{http://floorplanner.com/}
|
16
16
|
s.require_paths = ["lib"]
|
17
17
|
s.rubygems_version = %q{1.3.1}
|
@@ -22,15 +22,18 @@ Gem::Specification.new do |s|
|
|
22
22
|
s.specification_version = 2
|
23
23
|
|
24
24
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
25
|
-
s.add_runtime_dependency(%q<
|
25
|
+
s.add_runtime_dependency(%q<nokogiri>)
|
26
26
|
s.add_runtime_dependency(%q<rubyzip>)
|
27
|
+
s.add_runtime_dependency(%q<roxml>)
|
27
28
|
else
|
28
|
-
s.add_dependency(%q<
|
29
|
+
s.add_dependency(%q<nokogiri>)
|
29
30
|
s.add_dependency(%q<rubyzip>)
|
31
|
+
s.add_dependency(%q<roxml>)
|
30
32
|
end
|
31
33
|
else
|
32
|
-
s.add_dependency(%q<
|
34
|
+
s.add_dependency(%q<nokogiri>)
|
33
35
|
s.add_dependency(%q<rubyzip>)
|
36
|
+
s.add_dependency(%q<roxml>)
|
34
37
|
end
|
35
38
|
end
|
36
39
|
|
data/lib/collada/document.rb
CHANGED
@@ -1,34 +1,32 @@
|
|
1
1
|
require 'cgi'
|
2
2
|
|
3
3
|
module Collada
|
4
|
-
class Document < XML::Document
|
5
|
-
|
6
|
-
|
4
|
+
class Document < Nokogiri::XML::Document
|
5
|
+
COLLADA_NS = "http://www.collada.org/2005/11/COLLADASchema"
|
6
|
+
COLLADA_VERSION = "1.4.1"
|
7
7
|
|
8
|
-
COLLADA = 'COLLADA'
|
9
8
|
ASSET = 'asset'
|
10
|
-
LIBRARY_MATERIALS
|
11
|
-
LIBRARY_EFFECTS
|
12
|
-
LIBRARY_GEOMETRIES
|
9
|
+
LIBRARY_MATERIALS = 'library_materials'
|
10
|
+
LIBRARY_EFFECTS = 'library_effects'
|
11
|
+
LIBRARY_GEOMETRIES = 'library_geometries'
|
13
12
|
LIBRARY_VISUAL_SCENES = 'library_visual_scenes'
|
14
|
-
SCENE
|
15
|
-
VISUAL_SCENE
|
13
|
+
SCENE = 'scene'
|
14
|
+
VISUAL_SCENE = 'visual_scene'
|
16
15
|
INSTANCE_VISUAL_SCENE = 'instance_visual_scene'
|
17
16
|
|
18
17
|
attr_accessor :assets_libraries
|
19
18
|
|
20
19
|
def initialize
|
21
20
|
super
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
root = create_element 'COLLADA'
|
22
|
+
root.namespace = COLLADA_NS
|
23
|
+
root.attr['version'] = COLLADA_VERSION
|
25
24
|
|
26
25
|
create_structure
|
27
|
-
|
28
|
-
self.assets_libraries = []
|
26
|
+
assets_libraries = []
|
29
27
|
end
|
30
28
|
|
31
|
-
def <<
|
29
|
+
def << node
|
32
30
|
root << node
|
33
31
|
end
|
34
32
|
|
@@ -51,18 +49,18 @@ module Collada
|
|
51
49
|
|
52
50
|
private
|
53
51
|
def create_structure
|
54
|
-
self <<
|
52
|
+
self << create_element(ASSET)
|
55
53
|
|
56
|
-
self <<
|
57
|
-
self <<
|
58
|
-
self <<
|
59
|
-
self << (visual_scenes =
|
60
|
-
visual_scenes << (visual_scene =
|
54
|
+
self << create_element(LIBRARY_MATERIALS)
|
55
|
+
self << create_element(LIBRARY_EFFECTS)
|
56
|
+
self << create_element(LIBRARY_GEOMETRIES)
|
57
|
+
self << (visual_scenes = create_element(LIBRARY_VISUAL_SCENES))
|
58
|
+
visual_scenes << (visual_scene = create_element(VISUAL_SCENE))
|
61
59
|
visual_scene['id'] = 'MainScene'
|
62
60
|
visual_scene['name'] = 'MainScene'
|
63
61
|
|
64
|
-
self << (scene =
|
65
|
-
scene << (scene_node =
|
62
|
+
self << (scene = create_element(SCENE))
|
63
|
+
scene << (scene_node = create_element(INSTANCE_VISUAL_SCENE))
|
66
64
|
scene_node['url'] = '#MainScene'
|
67
65
|
end
|
68
66
|
|
data/lib/collada/geometry.rb
CHANGED
@@ -6,14 +6,14 @@ module Collada
|
|
6
6
|
GEOMETRY_QUERY = '/COLLADA/library_geometries/geometry[@id="%s"]'
|
7
7
|
NODE_QUERY = '/COLLADA/library_nodes//node[@id="%s"]'
|
8
8
|
|
9
|
-
def self.doc
|
9
|
+
def self.doc doc
|
10
10
|
result = Geometry.new
|
11
11
|
result.instance_eval do
|
12
|
-
@
|
12
|
+
@doc = doc
|
13
13
|
@cache = {}
|
14
14
|
|
15
|
-
scene_id =
|
16
|
-
scene =
|
15
|
+
scene_id = @doc.xpath(SCENE_QUERY).first.attribute('url').value[1..-1]
|
16
|
+
scene = @doc.xpath(VISUAL_SCENE_QUERY % scene_id).first
|
17
17
|
scene.children.each do |child|
|
18
18
|
eval_node(child) if child.name == "node"
|
19
19
|
end
|
@@ -23,7 +23,7 @@ module Collada
|
|
23
23
|
|
24
24
|
private
|
25
25
|
|
26
|
-
def eval_node
|
26
|
+
def eval_node node, parent=nil
|
27
27
|
parent = node.parent unless parent
|
28
28
|
parent_matrix = Geom::Matrix3D.identity
|
29
29
|
if parent.name == "node"
|
@@ -43,23 +43,22 @@ module Collada
|
|
43
43
|
when "node"
|
44
44
|
eval_node(child,node)
|
45
45
|
when "instance_geometry"
|
46
|
-
load_geometry(child.
|
46
|
+
load_geometry(child.attribute('url').value[1..-1],node_matrix)
|
47
47
|
when "instance_node"
|
48
48
|
eval_node(expand_node(child),node)
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
def load_geometry
|
54
|
-
geometry = @
|
53
|
+
def load_geometry gid, matrix=nil
|
54
|
+
geometry = @doc.xpath(GEOMETRY_QUERY % gid).first
|
55
55
|
mesh = Geom::TriangleMesh.new
|
56
56
|
|
57
57
|
floats = Array.new
|
58
|
-
geometry.
|
59
|
-
vertices_id = triangles.
|
60
|
-
source_id = geometry.
|
61
|
-
|
62
|
-
floats.concat geometry.find('mesh/source[@id="%s"]/float_array' % source_id).first.get_floats
|
58
|
+
geometry.xpath('mesh/triangles').each do |triangles|
|
59
|
+
vertices_id = triangles.xpath('input[@semantic="VERTEX"]').first.attribute('source').value[1..-1]
|
60
|
+
source_id = geometry.xpath('mesh/vertices[@id="%s"]/input[@semantic="POSITION"]' % vertices_id).first.attribute('source').value[1..-1]
|
61
|
+
floats.concat geometry.xpath('mesh/source[@id="%s"]/float_array' % source_id).first.content.split.map(&:to_f)
|
63
62
|
end
|
64
63
|
|
65
64
|
(floats.length/3).times do |i|
|
@@ -71,36 +70,36 @@ module Collada
|
|
71
70
|
@meshes << mesh
|
72
71
|
end
|
73
72
|
|
74
|
-
def expand_node
|
75
|
-
node_id = instance.
|
76
|
-
@
|
73
|
+
def expand_node instance
|
74
|
+
node_id = instance.attribute('url').value[1..-1]
|
75
|
+
@doc.xpath(NODE_QUERY % node_id).first
|
77
76
|
end
|
78
77
|
|
79
|
-
def get_node_matrix
|
78
|
+
def get_node_matrix node, source=nil
|
80
79
|
result = source ? source : Geom::Matrix3D.identity
|
81
80
|
node.children.each do |child|
|
82
81
|
case child.name
|
83
82
|
when "translate"
|
84
83
|
f = child.get_floats
|
85
84
|
t = Geom::Matrix3D.translation(f[0],f[1],f[2])
|
86
|
-
result
|
85
|
+
result *= t
|
87
86
|
when "scale"
|
88
87
|
f = child.get_floats
|
89
88
|
t = Geom::Matrix3D.scale(f[0],f[1],f[2])
|
90
|
-
result
|
89
|
+
result *= t
|
91
90
|
when "rotate"
|
92
91
|
f = child.get_floats
|
93
|
-
t = Geom::Matrix3D.
|
94
|
-
result
|
92
|
+
t = Geom::Matrix3D.rotation(f[0],f[1],f[2],f[3])
|
93
|
+
result *= t
|
95
94
|
when "matrix"
|
96
|
-
f = child.
|
95
|
+
f = child.content.split.map(&:to_f)
|
97
96
|
t = Geom::Matrix3D[
|
98
97
|
[ *f[0..3] ],
|
99
98
|
[ *f[4..7] ],
|
100
99
|
[ *f[8..11] ],
|
101
100
|
[ *f[12..15] ]
|
102
101
|
]
|
103
|
-
result
|
102
|
+
result *= t
|
104
103
|
end
|
105
104
|
end
|
106
105
|
result
|
data/lib/config.yml
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
content_base_url: http://cdn.floorplanner.com/assets/
|
2
|
-
dae_cache_path:
|
3
|
-
area_textures_path:
|
2
|
+
dae_cache_path: fml2dae_cache/
|
3
|
+
area_textures_path: fml2dae_cache/textures_2d/
|
4
4
|
|
5
5
|
geom_snap: 0.1
|
6
6
|
uniq_snap: 0.0000005
|
7
7
|
openings:
|
8
8
|
window_base: 0.65
|
9
|
-
window_height: 1.
|
10
|
-
door_height: 2.
|
9
|
+
window_height: 1.5
|
10
|
+
door_height: 2.1
|
11
11
|
|
12
12
|
svg:
|
13
13
|
width: 1024
|
@@ -21,14 +21,9 @@ module Floorplanner
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
24
|
+
def area(vertices, params=nil)
|
25
|
+
a_id = vertices.hash.abs.to_s + "_" + params[:type].to_s
|
26
|
+
params[:color] = Floorplanner.read_color params[:color]
|
32
27
|
@meshes[a_id] = Geom::Polygon.new(vertices, nil, params.merge({:id => a_id}))
|
33
28
|
end
|
34
29
|
|
data/lib/floorplanner/asset.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Floorplanner
|
2
|
-
class
|
2
|
+
class DAE
|
3
3
|
LIBRARY_GEOMETRIES = '/COLLADA/library_geometries/geometry'
|
4
4
|
LIBRARY_EFFECTS = '/COLLADA/library_effects/effect'
|
5
5
|
LIBRARY_MATERIALS = '/COLLADA/library_materials/material'
|
@@ -13,15 +13,15 @@ module Floorplanner
|
|
13
13
|
|
14
14
|
attr_reader :id, :name, :title, :dae_path
|
15
15
|
|
16
|
-
def self.get(
|
16
|
+
def self.get(asset)
|
17
17
|
FileUtils.mkdir_p(CACHE_PATH)
|
18
|
-
asset_url = Floorplanner.config['content_base_url'] + URI.escape(
|
18
|
+
asset_url = Floorplanner.config['content_base_url'] + URI.escape(asset.url3d)
|
19
19
|
|
20
|
-
cached_path = File.join(CACHE_PATH,
|
20
|
+
cached_path = File.join(CACHE_PATH, asset.id)
|
21
21
|
if File.exists?(cached_path)
|
22
|
-
$stderr.puts("Cached asset: %s" %
|
22
|
+
$stderr.puts("Cached asset: %s" % asset.id)
|
23
23
|
@kmz = Keyhole::Archive.new(cached_path)
|
24
|
-
|
24
|
+
DAE.new(asset.id, asset.name, @kmz)
|
25
25
|
else
|
26
26
|
$stderr.puts("Downloading asset: %s" % asset_url)
|
27
27
|
cached = File.new(cached_path,'w')
|
@@ -30,19 +30,24 @@ module Floorplanner
|
|
30
30
|
cached.close
|
31
31
|
|
32
32
|
@kmz = Keyhole::Archive.new(cached_path)
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
dae = DAE.new(asset.id, asset.name, @kmz)
|
34
|
+
dae.adjust_paths!
|
35
|
+
dae
|
36
36
|
end
|
37
|
+
rescue
|
38
|
+
$stderr.puts "Failed to get asset: %s, %s" % [asset.id, asset_url]
|
39
|
+
raise
|
37
40
|
end
|
38
41
|
|
39
|
-
def initialize(id,title,kmz)
|
42
|
+
def initialize(id, title, kmz)
|
40
43
|
@dae_path = kmz.dae_path(id)
|
41
44
|
@kmz = kmz
|
42
45
|
@id = id
|
43
46
|
@title = title
|
44
|
-
@xml = XML::Document.string(File.read(@dae_path).gsub(/xmlns=".+"/, ''))
|
45
47
|
@name = File.basename(@dae_path.gsub(/\.|dae/,''))
|
48
|
+
|
49
|
+
@doc = Nokogiri::XML.parse(open(@dae_path))
|
50
|
+
@doc.remove_namespaces!
|
46
51
|
@images_dict = {}
|
47
52
|
end
|
48
53
|
|
@@ -51,46 +56,46 @@ module Floorplanner
|
|
51
56
|
|
52
57
|
def library_materials
|
53
58
|
return @materials if @materials
|
54
|
-
materials = @
|
59
|
+
materials = @doc.xpath(LIBRARY_MATERIALS)
|
55
60
|
materials.each {|mat| namespace!(mat)}
|
56
61
|
@materials = materials
|
57
62
|
end
|
58
63
|
|
59
64
|
def library_effects
|
60
65
|
return @effects if @effects
|
61
|
-
effects = @
|
66
|
+
effects = @doc.xpath(LIBRARY_EFFECTS)
|
62
67
|
effects.each {|eff| namespace!(eff)}
|
63
68
|
@effects = effects
|
64
69
|
end
|
65
70
|
|
66
71
|
def library_geometries
|
67
72
|
return @geometries if @geometries
|
68
|
-
geometries = @
|
73
|
+
geometries = @doc.xpath(LIBRARY_GEOMETRIES)
|
69
74
|
geometries.each{|geo| namespace!(geo)}
|
70
75
|
@geometries = geometries
|
71
76
|
end
|
72
77
|
|
73
78
|
def library_nodes
|
74
79
|
return @nodes if @nodes
|
75
|
-
nodes = @
|
80
|
+
nodes = @doc.xpath(LIBRARY_NODES)
|
76
81
|
nodes.each{|nod| namespace!(nod)}
|
77
82
|
@nodes = nodes
|
78
83
|
end
|
79
84
|
|
80
85
|
def library_images
|
81
86
|
return @images if @images
|
82
|
-
images = @
|
87
|
+
images = @doc.xpath(LIBRARY_IMAGES)
|
83
88
|
images.each{|img| namespace!(img) && update_path!(img)}
|
84
89
|
@images = images
|
85
90
|
end
|
86
91
|
|
87
92
|
def visual_scene_node
|
88
93
|
return @scene_node if @scene_node
|
89
|
-
@scene_node = namespace!(@
|
94
|
+
@scene_node = namespace!(@doc.xpath(VISUAL_SCENE_QUERY).first)
|
90
95
|
end
|
91
96
|
|
92
97
|
def bounding_box
|
93
|
-
mesh = Collada::Geometry.doc @
|
98
|
+
mesh = Collada::Geometry.doc @doc
|
94
99
|
mesh.bounding_box
|
95
100
|
end
|
96
101
|
|
@@ -115,11 +120,11 @@ module Floorplanner
|
|
115
120
|
end
|
116
121
|
|
117
122
|
def save_textures(root_path)
|
118
|
-
images = @
|
119
|
-
FileUtils.mkdir_p(root_path)
|
123
|
+
images = @doc.xpath(LIBRARY_IMAGES)
|
124
|
+
FileUtils.mkdir_p(root_path)
|
120
125
|
|
121
126
|
images.each do |image|
|
122
|
-
relative_to_dae = image.
|
127
|
+
relative_to_dae = image.xpath('init_from').first.content
|
123
128
|
img_path = @kmz.image_path(@id,relative_to_dae)
|
124
129
|
target_path = File.join(root_path,@id)
|
125
130
|
FileUtils.mkdir_p target_path
|
@@ -132,15 +137,15 @@ module Floorplanner
|
|
132
137
|
end
|
133
138
|
|
134
139
|
def adjust_paths!
|
135
|
-
images = @
|
140
|
+
images = @doc.xpath(LIBRARY_IMAGES)
|
136
141
|
|
137
142
|
images.each do |image|
|
138
|
-
init_from = image.
|
143
|
+
init_from = image.xpath('init_from').first
|
139
144
|
img_path = @kmz.image_path(@id,init_from.content,true)
|
140
145
|
init_from.content = img_path
|
141
146
|
end
|
142
147
|
open(@dae_path, 'w') do |f|
|
143
|
-
f.write @
|
148
|
+
f.write @doc.to_s
|
144
149
|
end
|
145
150
|
end
|
146
151
|
|
@@ -160,12 +165,12 @@ module Floorplanner
|
|
160
165
|
node['texture'] = "#{@name}_#{node['texture'].gsub('#','')}" if node['texture']
|
161
166
|
|
162
167
|
if node.name == 'surface'
|
163
|
-
n = node.
|
168
|
+
n = node.xpath('init_from').first
|
164
169
|
n.content = "#{@name}_#{n.content}"
|
165
170
|
end
|
166
171
|
|
167
172
|
if node.name == 'sampler2D'
|
168
|
-
n = node.
|
173
|
+
n = node.xpath('source').first
|
169
174
|
n.content = "#{@name}_#{n.content}"
|
170
175
|
end
|
171
176
|
|
@@ -177,7 +182,7 @@ module Floorplanner
|
|
177
182
|
|
178
183
|
# updates image path to export output folder
|
179
184
|
def update_path!(node)
|
180
|
-
init_from_node = node.
|
185
|
+
init_from_node = node.xpath('init_from').first
|
181
186
|
relative = init_from_node.content
|
182
187
|
init_from_node.content = @images_dict[relative]
|
183
188
|
end
|
@@ -1,32 +1,37 @@
|
|
1
|
-
module Floorplanner
|
1
|
+
module Floorplanner::XML
|
2
2
|
class Document
|
3
3
|
|
4
|
-
def to_dae
|
5
|
-
@design = Design.new(
|
4
|
+
def to_dae thing, conf={}
|
5
|
+
@design = Floorplanner::Design.new(self)
|
6
6
|
@design.build_geometries
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
if thing.kind_of? String
|
8
|
+
unless conf[:xrefs]
|
9
|
+
@design.save_textures(File.dirname(thing))
|
10
|
+
end
|
11
|
+
dae = File.new(thing,'w')
|
12
|
+
dae.write @design.to_dae(conf)
|
13
|
+
dae.close
|
14
|
+
elsif thing.respond_to? :write
|
15
|
+
thing.write @design.to_dae(conf)
|
16
|
+
end
|
11
17
|
end
|
12
|
-
|
13
18
|
end
|
19
|
+
end
|
14
20
|
|
21
|
+
module Floorplanner
|
15
22
|
module ColladaExport
|
16
|
-
DESIGN_QUERY = "/project/floors/floor/designs/design[id='%s']"
|
17
|
-
ASSET_QUERY = DESIGN_QUERY+"/assets/asset[@id='%s']"
|
18
|
-
ASSETS_QUERY = DESIGN_QUERY+"/assets/asset"
|
19
|
-
OBJECTS_QUERY = DESIGN_QUERY+"/objects/object"
|
20
23
|
|
21
|
-
|
22
|
-
|
24
|
+
CACHE_PATH = File.join(Floorplanner.config['dae_cache_path'], 'textures_2d')
|
25
|
+
|
26
|
+
def to_dae conf
|
27
|
+
raise "No geometries to export" unless @areas && @walls
|
23
28
|
@assets = assets
|
24
29
|
@elements = objects
|
25
30
|
|
26
31
|
# somehow...
|
27
32
|
@walls.reverse
|
28
33
|
@areas.each {|a| a.reverse}
|
29
|
-
@
|
34
|
+
@conf = conf
|
30
35
|
|
31
36
|
template = ERB.new(
|
32
37
|
File.read(
|
@@ -37,82 +42,82 @@ module Floorplanner
|
|
37
42
|
def assets
|
38
43
|
return @assets if @assets
|
39
44
|
@assets = {}
|
40
|
-
@
|
41
|
-
|
42
|
-
name = asset_node.find('name').first.content
|
43
|
-
url3d = asset_node.find('url3d').first
|
44
|
-
next unless url3d
|
45
|
-
url3d = url3d.content
|
45
|
+
@doc.assets.each do |asset|
|
46
|
+
next unless asset.url3d && !asset.url3d.empty?
|
46
47
|
|
47
48
|
# TODO: store asset bounding box
|
48
|
-
|
49
|
-
next unless
|
50
|
-
@assets.store(
|
49
|
+
dae = Floorplanner::DAE.get(asset)
|
50
|
+
next unless dae
|
51
|
+
@assets.store(asset.id, dae)
|
51
52
|
end
|
52
53
|
@assets
|
53
54
|
end
|
54
55
|
|
55
56
|
def objects
|
56
57
|
result = []
|
57
|
-
@
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
58
|
+
@doc.objects.each do |item|
|
59
|
+
next unless assets[item.asset]
|
60
|
+
asset = assets[item.asset]
|
61
|
+
|
62
|
+
pos = item.position
|
63
|
+
rot = item.rotation || Geom::Number3D.new
|
64
|
+
scale = asset.scale_ratio(item.size)
|
65
|
+
|
66
|
+
if item.mirrored
|
67
|
+
m_mirror = Geom::Matrix3D.reflection(
|
68
|
+
Geom::Plane.new(item.mirrored, Geom::Number3D.new))
|
94
69
|
end
|
95
70
|
|
71
|
+
m_scale = Geom::Matrix3D.scale(scale.x, scale.y, scale.z)
|
72
|
+
m_rotate = Geom::Matrix3D.rotation(0, 0, 1, (Math::PI/180)*rot.z)
|
73
|
+
m_translate = Geom::Matrix3D.translation(pos.x, pos.y, pos.z)
|
74
|
+
|
75
|
+
m_combined = m_rotate * m_scale
|
76
|
+
m_combined = m_mirror * m_combined if m_mirror
|
77
|
+
m_combined = m_translate * m_combined
|
78
|
+
|
96
79
|
result << {
|
97
|
-
:asset
|
98
|
-
:
|
99
|
-
:
|
100
|
-
:scale => scale,
|
101
|
-
:matrix => reflection
|
80
|
+
:asset => asset,
|
81
|
+
:matrix => m_combined,
|
82
|
+
:color => Floorplanner.read_color(item.color)
|
102
83
|
}
|
103
|
-
rescue
|
104
|
-
# TODO: handle text
|
105
|
-
end
|
106
84
|
end
|
107
85
|
result
|
108
86
|
end
|
109
87
|
|
110
|
-
def save_textures
|
111
|
-
img_path = File.join(root_path,'textures')
|
88
|
+
def save_textures root_path
|
89
|
+
img_path = File.join(root_path, 'textures')
|
112
90
|
FileUtils.mkdir_p img_path
|
113
91
|
assets.each_value do |asset|
|
114
92
|
asset.save_textures img_path
|
115
93
|
end
|
94
|
+
|
95
|
+
t2d_path = File.join(img_path, 'textures_2d')
|
96
|
+
FileUtils.mkdir_p t2d_path
|
97
|
+
@areas.each do |area|
|
98
|
+
next unless area.data[:texture]
|
99
|
+
texture_url = area.data[:texture]
|
100
|
+
fn = texture_url.match(/.*\/(.*)/)[1]
|
101
|
+
tex_path = File.join(t2d_path, fn)
|
102
|
+
area.data[:texture] = File.join('textures', 'textures_2d', fn)
|
103
|
+
next if File.exists?(tex_path)
|
104
|
+
|
105
|
+
FileUtils.mkdir_p CACHE_PATH
|
106
|
+
cached_path = File.join(CACHE_PATH, fn)
|
107
|
+
unless File.exists?(cached_path)
|
108
|
+
puts "Downloading texture: %s" % texture_url
|
109
|
+
begin
|
110
|
+
cached = File.new(cached_path, 'w')
|
111
|
+
remote = open(Floorplanner.config['content_base_url'] +
|
112
|
+
URI.escape(texture_url))
|
113
|
+
cached.write(remote.read)
|
114
|
+
cached.close
|
115
|
+
rescue
|
116
|
+
$stderr.puts "Error downloading texture: %s" % fn
|
117
|
+
end
|
118
|
+
end
|
119
|
+
FileUtils.cp(cached_path, tex_path)
|
120
|
+
end
|
116
121
|
end
|
117
122
|
end
|
118
123
|
end
|