Skip to content

Commit 93e2e68

Browse files
Initial commit
1 parent ddde9e8 commit 93e2e68

File tree

9 files changed

+592
-0
lines changed

9 files changed

+592
-0
lines changed

src/bt_ifcjson.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# bt_ifcjson.rb
2+
#
3+
# Copyright 2020 Jan Brouwer <[email protected]>
4+
#
5+
# This program is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation; either version 2 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program; if not, write to the Free Software
17+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18+
# MA 02110-1301, USA.
19+
#
20+
#
21+
22+
# Create an entry in the Extension list that loads a script called
23+
# loader.rb.
24+
require 'sketchup.rb'
25+
require 'extensions.rb'
26+
27+
module BimTools
28+
PLUGIN_ROOT_PATH = File.dirname(__FILE__) unless defined? PLUGIN_ROOT_PATH
29+
30+
module IfcJson
31+
VERSION = '0.1.0'.freeze
32+
33+
# load plugin only if SketchUp version is PRO
34+
if Sketchup.is_pro? && Sketchup.version_number>1600000000
35+
PLUGIN_PATH = File.join(PLUGIN_ROOT_PATH, 'bt_ifcjson')
36+
PLUGIN_IMAGE_PATH = File.join(PLUGIN_PATH, 'images')
37+
38+
ifcmanager_extension = SketchupExtension.new("ifcJSON", File.join(PLUGIN_PATH, 'loader.rb'))
39+
ifcmanager_extension.version = VERSION
40+
ifcmanager_extension.description = 'ifcJSON exporter for SketchUp.'
41+
ifcmanager_extension.creator = 'BIM-Tools'
42+
ifcmanager_extension.copyright = '2020'
43+
Sketchup.register_extension(ifcmanager_extension, true)
44+
IFCMANAGER_EXTENSION = ifcmanager_extension
45+
else
46+
UI.messagebox "You need at least SketchUp Pro 2016 to use this extension."
47+
end
48+
end # module IfcJson
49+
end # module BimTools
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# IfcGloballyUniqueId.rb
2+
#
3+
# Copyright 2017 Jan Brouwer <[email protected]>
4+
#
5+
# This program is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation; either version 2 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program; if not, write to the Free Software
17+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18+
# MA 02110-1301, USA.
19+
#
20+
#
21+
22+
# (!) Note: securerandom takes very long to load
23+
require 'securerandom'
24+
25+
module BimTools
26+
module IfcManager
27+
28+
class IfcGloballyUniqueId
29+
30+
# possible characters in GUID
31+
GUID64 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$'
32+
33+
def initialize( sketchup = nil )
34+
@sketchup = sketchup
35+
36+
# if sketchup object has a GUID, then use that, otherwise create new
37+
if @sketchup && defined?( @sketchup.guid )
38+
@hex_guid = unformat_guid( @sketchup.guid )
39+
else
40+
@hex_guid = new_guid
41+
end
42+
end
43+
44+
def set_parent_guid( parent_hex_guid )
45+
@hex_guid = combined_guid( @hex_guid, parent_hex_guid )
46+
end # def set_parent
47+
48+
# return IfcGloballyUniqueId within quotes
49+
def step()
50+
return hex_to_ifc_guid( @hex_guid )
51+
end # def step
52+
53+
def to_s()
54+
return @hex_guid
55+
end # def to_s
56+
57+
def to_json(arg=nil)
58+
return "#{@hex_guid[0..7]}-#{@hex_guid[8..11]}-#{@hex_guid[12..15]}-#{@hex_guid[16..19]}-#{@hex_guid[20..31]}"
59+
end # def to_s
60+
61+
# recognize guid type and reformat to unformatted hex version
62+
def unformat_guid( guid )
63+
64+
# check if ifc_guid(length is 22) or uuid
65+
if guid.length == 22
66+
guid = ifc_guid_to_hex( guid )
67+
else
68+
69+
# tr('-', ''): removes the dashes from the hex string
70+
guid.tr('-', '')
71+
end
72+
end # def unformat_guid
73+
74+
# combine guid with parent guid
75+
def combined_guid( sketchup_guid, parent_guid )
76+
guid = (sketchup_guid.to_i(16) ^ parent_guid.to_i(16)).to_s(16).rjust(32, '0')
77+
78+
# The digit at position 1 above is always "4"
79+
# set the four most significant bits of the 7th byte to 0100'B, so the high nibble is "4"
80+
guid[12] = "4"
81+
82+
# and the digit at position 2 is always one of "8", "9", "A" or "B".
83+
# set the two most significant bits of the 9th byte to 10'B, so the high nibble will be one of "8", "9", "A", or "B".
84+
h_val = guid[16]
85+
b_val = [h_val].pack('H*').unpack('B*')[0]
86+
b_val[0] = "1"
87+
b_val[1] = "0"
88+
guid[16] = [b_val].pack('B*').unpack('H*')[0]
89+
return guid
90+
end # def combined_guid
91+
92+
# convert IfcGloballyUniqueId into unformatted hex number
93+
def ifc_guid_to_hex( ifc_guid )
94+
bin = ""
95+
length = 2
96+
ifc_guid.each_char do | char |
97+
n = GUID64.index( char.to_s )
98+
bin = bin + n.to_s( 2 ).rjust( length, "0" )
99+
length = 6
100+
end
101+
return [bin].pack('B*').unpack('H*')[0]
102+
end # def ifc_guid_to_hex
103+
104+
# # convert IfcGloballyUniqueId into UUID
105+
# def ifc_guid_to_uuid( ifc_guid )
106+
# return ifc_guid_to_hex( ifc_guid ).insert(20, '-').insert(16, '-').insert(12, '-').insert(8, '-')
107+
# end # def ifc_guid_to_uuid
108+
109+
# convert unformatted hex number into IfcGloballyUniqueId
110+
def hex_to_ifc_guid( hex_guid )
111+
ifc_guid = ""
112+
113+
# https://www.cryptosys.net/pki/uuid-rfc4122.html
114+
# pack('H*'): converts the hex string to a binary number (high nibble first)
115+
# unpack('B*'): converts the binary number to a bit string (128 0's and 1's) and places it into an array (Most Significant Block first)
116+
# [0]: gets the first (and only) value from the array
117+
bit_string = [hex_guid].pack('H*').unpack('B*')[0].to_s
118+
119+
# take the number (0 - 63) and find the matching character in guid64, add the found character to the guid string
120+
# start with the 2 leftover bits
121+
char_num = bit_string[0,2].to_i(2)
122+
ifc_guid << GUID64[char_num]
123+
block_counter = 2
124+
while block_counter < 128 do
125+
char_num = bit_string[ block_counter, 6 ].to_i( 2 )
126+
ifc_guid << GUID64[char_num]
127+
block_counter += 6
128+
end
129+
return "'#{ifc_guid}'"
130+
end # def hex_to_ifc_guid
131+
132+
def new_guid
133+
134+
# Old method: faster, but not a correct IfcGloballyUniqueId, just a random number
135+
# the leading 0 is added because the first character is only 1 bit and can only contain a 0,1,2 or 3.
136+
# while all other characters are 6 bits (64 possible values)
137+
#guid = '';21.times{|i|guid<<'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$'[rand(64)]}
138+
#first = rand(0...3).to_s
139+
#guid = "#{first}#{guid}"
140+
141+
# SecureRandom.uuid: creates a 128 bit UUID hex string
142+
# convert to hex
143+
return unformat_guid( SecureRandom.uuid )
144+
end
145+
end # class IfcGloballyUniqueId
146+
end # module IfcManager
147+
end # module BimTools

src/bt_ifcjson/exporter.rb

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# exporter.rb
2+
#
3+
# Copyright 2020 Jan Brouwer <[email protected]>
4+
#
5+
# This program is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation; either version 2 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program; if not, write to the Free Software
17+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18+
# MA 02110-1301, USA.
19+
#
20+
21+
module BimTools
22+
module IfcJson
23+
require File.join(PLUGIN_PATH, 'obj.rb')
24+
require File.join(PLUGIN_PATH, 'IfcGloballyUniqueId.rb')
25+
class IfcJsonExporter
26+
attr_accessor :root_objects
27+
def initialize( entities )
28+
@root_objects = Array.new
29+
export_path = get_export_path()
30+
31+
# only start export if path is valid
32+
unless export_path.nil?
33+
@root_objects.concat( collect_objects( entities, Geom::Transformation.new() )[0] )
34+
end
35+
36+
to_file( export_path )
37+
end # def initialize
38+
39+
def collect_objects(entities, parent_transformation, parent_guid=nil)
40+
child_objects = Array.new()
41+
faces = Array.new()
42+
entities.each do |entity|
43+
if entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
44+
object_hash = Hash.new
45+
# object_hash["Name"] = entity.definition.name
46+
transformation = entity.transformation * parent_transformation
47+
object_hash.merge! get_properties(entity)
48+
49+
# create unique guid
50+
guid = BimTools::IfcManager::IfcGloballyUniqueId.new(object_hash["GlobalId"])
51+
if parent_guid
52+
guid.set_parent_guid( parent_guid )
53+
end
54+
object_hash["GlobalId"] = guid.to_json()
55+
56+
isDecomposedBy, child_faces = collect_objects(entity.definition.entities, transformation, guid.to_s)
57+
58+
unless isDecomposedBy.empty?
59+
object_hash["IsDecomposedBy"] = isDecomposedBy
60+
end
61+
62+
# only add representation if there are any faces
63+
if child_faces.length > 0
64+
obj = OBJ.new(child_faces, parent_transformation)
65+
object_hash["Representation"] = obj.to_s
66+
end
67+
child_objects << object_hash
68+
elsif entity.is_a?(Sketchup::Face)
69+
faces << entity
70+
end
71+
end
72+
return child_objects, faces
73+
end # def collect_objects
74+
75+
def get_properties(entity)
76+
properties = Hash.new()
77+
definition = entity.definition
78+
ifc_type = definition.get_attribute "AppliedSchemaTypes", "IFC 2x3"
79+
if ifc_type
80+
properties["Class"] = ifc_type
81+
if definition.attribute_dictionaries['IFC 2x3']
82+
if props_ifc = definition.attribute_dictionaries['IFC 2x3'].attribute_dictionaries
83+
props_ifc.each do |prop_dict|
84+
prop = prop_dict.name
85+
86+
# get data for objects with additional nesting levels
87+
# like: path = ["IFC 2x3", "IfcWindow", "OverallWidth", "IfcPositiveLengthMeasure", "IfcLengthMeasure"]
88+
val_dict = return_value_dict( prop_dict )
89+
if val_dict["value"] && !val_dict["is_hidden"]
90+
value = val_dict["value"]
91+
if value != ""
92+
properties[prop_dict.name] = val_dict["value"]
93+
end
94+
end
95+
end
96+
end
97+
end
98+
end
99+
return properties
100+
end # get_properties
101+
102+
# find the dictionary containing "value" field
103+
def return_value_dict( dict )
104+
105+
# if a field "value" exists then we are at the data level and data can be retrieved, otherwise dig deeper
106+
if dict.keys.include?("value")
107+
return dict
108+
else
109+
dict.attribute_dictionaries.each do | sub_dict |
110+
unless sub_dict.name == "instanceAttributes"
111+
return return_value_dict( sub_dict )
112+
end
113+
end
114+
end
115+
end # def return_value_dict
116+
117+
def get_export_path()
118+
119+
# get model current path
120+
model_path = Sketchup.active_model.path
121+
dirname = File.dirname(model_path)
122+
123+
# get model file name
124+
if File.basename(model_path) == ""
125+
filename = "Untitled.json"
126+
else
127+
filename = File.basename(model_path, ".*") + ".json"
128+
end
129+
130+
# enter save path
131+
UI.savepanel('Export Model', dirname, filename)
132+
end # get_export_path
133+
134+
def to_file( file_path )
135+
File.open( file_path,"w" ) do | file |
136+
file.write( @root_objects.to_json )
137+
end
138+
end # def to_file
139+
end # IfcJsonExporter
140+
end # module IfcJson
141+
end # module BimTools
142+
1.8 KB
Binary file not shown.

0 commit comments

Comments
 (0)