How to write IFC info to a skp file by SketchUp Ruby

I want to generate the su model by ruby and save the ifc information at the same time.

That’s my code:

#1. create a group by l, b, h and transform group to component
l = 5000
b = 600
h = 800
model = Sketchup.active_model
entities = model.active_entities
status = entities.clear!         #clear the entities!!!
group = entities.add_group
pts = []
pts[0] = [0, 0, 0]
pts[1] = [l, 0, 0]
pts[2] = [l, b, 0]
pts[3] = [0, b, 0]
face = group.entities.add_face(pts)
status = face.pushpull(h)
componentinstance = group.to_component

#2. get component's defination and add ifc classification. for example: IfcBeam
definition = componentinstance.definition
ifc_format = "IFC 2x3"
ifc_classification = "IfcBeam" 
success = definition.add_classification(ifc_format, ifc_classification)
puts "ifc classification  " + success.to_s

#3. define Ifc attributes
def setAndGetIfc(path, ifc_prop, definition)
  definition.set_classification_value(path, ifc_prop)
  success = definition.get_classification_value(path)
  puts path.to_s + "  " + success.to_s
  return success, definition
end
#3.1 define component's name
definition.name = "beamTypeA"
componentinstance.name = definition.name + "-001"
#3.2 define component's type
path = ["IFC 2x3", "IfcBeam", "ObjectType", "IfcLabel"]
ifc_prop = "Steel Beam"
success, definition = setAndGetIfc(path, ifc_prop, definition)
#3.3 define ifc's name
path = ["IFC 2x3", "IfcBeam", "Name", "IfcLabel"]
ifc_prop = "workshop_beam_001"
success, definition = setAndGetIfc(path, ifc_prop, definition)
#3.4 define ifc's description
path = ["IFC 2x3", "IfcBeam", "Description", "IfcText"]
ifc_prop = "25 tons; crane beam"
success, definition = setAndGetIfc(path, ifc_prop, definition)

It can save the name, descritpion.
My question is: How to save my User-defined properties? For example, how to save the IfcBeam’s l, b ,h attributes.

1 Like

Have a look at the IFC exporter on github:

Thanks for your reply. I try to understand that plugin’s code.

The solution:

# 1. create a group by l, b, h and transform group to component
l = 5000
b = 600
h = 800
model = Sketchup.active_model
entities = model.active_entities
status = entities.clear!         #clear the entities!!!
group = entities.add_group
pts = []
pts[0] = [0, 0, 0]
pts[1] = [l, 0, 0]
pts[2] = [l, b, 0]
pts[3] = [0, b, 0]
face = group.entities.add_face(pts)
status = face.pushpull(h)
componentinstance = group.to_component

# 2. get component's defination and add ifc classification. for example: IfcBeam
definition = componentinstance.definition
ifc_format = "IFC 2x3"
ifc_classification = "IfcBeam" 
success = definition.add_classification(ifc_format, ifc_classification)
puts "ifc classification  " + success.to_s

# 3. define Ifc attributes
def setAndGetIfc(path, ifc_prop, definition)
  success = definition.set_classification_value(path, ifc_prop)
  puts path.to_s + "  " + success.to_s
  success = definition.get_classification_value(path)
  puts path.to_s + "  " + success.to_s
  return success, definition
end
# 3.1 define component's name
definition.name = "beamTypeA"
componentinstance.name = definition.name + "-001"
# 3.2 define component's type
path = ["IFC 2x3", "IfcBeam", "ObjectType", "IfcLabel"]
ifc_prop = "Steel Beam"
success, definition = setAndGetIfc(path, ifc_prop, definition)
# 3.3 define ifc's name
path = ["IFC 2x3", "IfcBeam", "Name", "IfcLabel"]
ifc_prop = "workshop_beam_001"
success, definition = setAndGetIfc(path, ifc_prop, definition)
# 3.4 define ifc's description
path = ["IFC 2x3", "IfcBeam", "Description", "IfcText"]
ifc_prop = "25 tons; crane beam"
success, definition = setAndGetIfc(path, ifc_prop, definition)

# 4. define User-Defined attributes
status = componentinstance.set_attribute "Size", "Width", b.to_s
status = componentinstance.set_attribute "Size", "Length", l.to_s
status = componentinstance.set_attribute "Size", "Height", h.to_s

Then,use the sketchup’s export function(choose the ifc option) . Don’t use the SketchUp-IFC-Manager.

Please note that if you do not use the IFC Manager extension, the IFC export does not fully meet the “requirements” of buildingSMART.

1 Like

Thanks for attention.
In the IFC Manager extension, I think \bt_ifcmanager\lib\lib_ifc\IfcRelDefinesByProperties_su.rb is the key file. I post the code from line 43 to line 107:

if sketchup.is_a?( Sketchup::AttributeDictionary )
        attr_dict = sketchup
        if attr_dict.name == "BaseQuantities" # export as elementquantities
          
          qty = BimTools::IFC2X3::IfcElementQuantity.new( ifc_model, attr_dict )
          @relatingpropertydefinition = qty
          qty.name = BimTools::IfcManager::IfcLabel.new( attr_dict.name ) unless attr_dict.name.nil?
          qty.quantities = IfcManager::Ifc_Set.new()
          attr_dict.attribute_dictionaries.each { | dict |
            case dict.name
            when "Area", "GrossArea"
              prop = BimTools::IFC2X3::IfcQuantityArea.new( ifc_model, attr_dict )
              prop.name = BimTools::IfcManager::IfcIdentifier.new( dict.name )
              prop.areavalue = BimTools::IfcManager::IfcReal.new( dict['value'] ) # real should be IfcLengthMeasure
              qty.quantities.add( prop )
            when "Volume"
              prop = BimTools::IFC2X3::IfcQuantityVolume.new( ifc_model, attr_dict )
              prop.name = BimTools::IfcManager::IfcIdentifier.new( dict.name )
              prop.volumevalue = BimTools::IfcManager::IfcReal.new( dict['value'] ) # real should be IfcLengthMeasure
              qty.quantities.add( prop )
            when "Width", "Height", "Depth", "Perimeter"
              prop = BimTools::IFC2X3::IfcQuantityLength.new( ifc_model, attr_dict )
              prop.name = BimTools::IfcManager::IfcIdentifier.new( dict.name )
              prop.lengthvalue = BimTools::IfcManager::IfcReal.new( dict['value'] ) # real should be IfcLengthMeasure
              qty.quantities.add( prop )
            #else
            end
          }
          
        else # export as propertyset
          pset = BimTools::IFC2X3::IfcPropertySet.new( ifc_model, attr_dict )
          @relatingpropertydefinition = pset
          pset.name = BimTools::IfcManager::IfcLabel.new( attr_dict.name ) unless attr_dict.name.nil?
          pset.hasproperties = IfcManager::Ifc_Set.new()
          if attr_dict.length == 0 && attr_dict.attribute_dictionaries
            attr_dict.attribute_dictionaries.each { | dict |
              
              prop = BimTools::IFC2X3::IfcPropertySingleValue.new( ifc_model, attr_dict )
              prop.name = BimTools::IfcManager::IfcIdentifier.new( dict.name )
              
              # get value type
              case dict['attribute_type']
              when "double"
                prop.nominalvalue = BimTools::IfcManager::IfcReal.new( dict['value'] )
              when "boolean"
                prop.nominalvalue = BimTools::IfcManager::IfcBoolean.new( dict['value'] )
              #when "string"
              #  prop.nominalvalue = BimTools::IfcManager::IfcLabel.new( dict['value'] ) # (!) not always IfcLabel
              else
                prop.nominalvalue = BimTools::IfcManager::IfcLabel.new( dict['value'] ) # (!) not always IfcLabel
              end
              prop.nominalvalue.long = true # adding long = true returns a full object string, necessary for propertyset
              pset.hasproperties.add( prop )
            }
          else
            attr_dict.each { | key, value |
              prop = BimTools::IFC2X3::IfcPropertySingleValue.new( ifc_model, attr_dict )
              prop.name = BimTools::IfcManager::IfcIdentifier.new( key )
              prop.nominalvalue = BimTools::IfcManager::IfcLabel.new( value ) # (!) not always IfcLabel
              prop.nominalvalue.long = true # adding long = true returns a full object string
              pset.hasproperties.add( prop )
            }
          end
        end
      end

I think the IFC Manager accept the dictionary named as “BaseQuantities” and accept the keyname as “Area”, “GrossArea”, “Width”, “Height”, “Depth”, “Perimeter” and so on.
But when I try to write down:

status = componentinstance.set_attribute "BaseQuantities", "Width", b.to_s
status = componentinstance.set_attribute "BaseQuantities", "Depth", l.to_s
status = componentinstance.set_attribute "BaseQuantities", "Height", h.to_s

and use the IFC Manager to export an IFC file, I can’t find the “BaseQuantities” dictionary and the “Width”, “Height”, “Depth” in the IFC file. But if I use the sketchup’s export, I can find them.

I am really puzzled.

I would appreciate it if you could give me some suggestion.

The reply of SketchUp IFC manager plugin’s author:

At the moment there are two supported methods for writing properties.

  1. Add AttributeDictionaries under the “IFC 2x3” dictionary. So they are part of the IFC data. Like when you import an IFC containing additional property sets.
  2. Add AttributeDictionaries under the “dynamic_attributes” dictionary. To create parametric components (Only user-visible parameters starting with “CPset_” are exported). Like: https://3dwarehouse.sketchup.com/model/cf631169-453a-4f23-9a07-e1be52dfee38/ProvisionForVoid-Rectangle-IFC2X3

The IFC data set uses nested attribute dictionaries.
"Width", "Height", "Depth" are not attribute keys, they are nested dictionary names, and their values are in stored in the key named "Value".

The IFC data may also be assigned to the definition and not the instance.


Try something like this …

def quantities(obj)
  return false if obj.attribute_dictionaries['IFC 2x3'].nil?
  dict = obj.attribute_dictionary('IFC 2x3')
  return false if dict.attribute_dictionaries['BaseQuantities'].nil?
  dict = dict.attribute_dictionary('BaseQuantities')
  # Return an array of the quantity values as [w,d,h]
  [ dict.attribute_dictionary('Width')['Value']  ,
    dict.attribute_dictionary('Depth')['Value']  ,
    dict.attribute_dictionary('Height')['Value'] ]
end

This causes major import problems, because each element has to be unique, regardless if it is the same ‘geometry’ or representation of.

That would seriously reduce the amount of simular components were the only difference is the name…

Edit: link to another post:

1 Like

It’s so kind of you!

Is my understanding correct?
I think there is a dictionary named “'IFC 2x3”, and the “IFC 2x3” dictionary has a dictionary named “BaseQuantities”.
Then the “BaseQuantities” dictionary has the dictionary named “Width”, “Depth”, “Height”.
Each of “Width”, “Depth”, “Height” has the key named “Value”.

Can you teach me more about how to realize the nested dictionary?
I can’t find any threads about it in the SketchUp Ruby API documents.

For example, I creat “Width”, “Depth”, “Height” dictionary in the lowest level. How to nest them inside the “BaseQuantities” dictionary and “IFC 2x3” dictionary.

status = componentinstance.set_attribute "Width", "Value", b.to_s
status = componentinstance.set_attribute "Depth", "Value", l.to_s
status = componentinstance.set_attribute "Height", "Value", h.to_s

I believe based upon the code you quoted from BIMTools that you are correct, … IF it uses the same IFC dictionary that SketchUp uses. (It could use it’s own dictionary if the author so desired.)

You should use an attribute dictionary viewer extension to view dictionaries. It will help in development.
The 3 top extensions here you will find valuable for viewing attribute dictionaries …
http://extensions.sketchup.com/en/search/site/attribute

I gave a straight forward example above.

You need to realize that Ruby is a class based language that uses inheritance.

All Sketchup::Entity subclasses inherit their superclasses’ methods.

Sketchup::AttributeDictionary class and the Sketchup::AttributeDictionaries collection class, are BOTH a subclass of Sketchup::Entity.

ANY Sketchup::Entity subclass object can have an Sketchup::AttributeDictionaries collection instance with any amount (within reason) of Sketchup::AttributeDictionary instances.

But because AN Sketchup::AttributeDictionary is a subclass of Sketchup::Entity, it therefor can ALSO ITSELF have an Sketchup::AttributeDictionaries collection instance with any amount (within reason) of Sketchup::AttributeDictionary instances.

AND SO ON … … and so on … and so forth

1 Like

Thank you for your time and patience.
I’ll try to follow your instructions.

1 Like

Hi all,

I’m reading this post long time after.
Because , im not very in english and in IFC, I required validation for you.

In an extension that draw wall, I try to write IFC attribut for a Wall like the code below, according to what I have understand from the post: ( may be it could help a next reader)

	def set_ifc_attributes

		set_dimensions # set @length , @heigth,  @width

		definition = @instance.definition
		success = definition.add_classification("IFC 2x3", "IfcWall" )

		definition.set_attribute "IFC 2x3", "value", "-"
		ifc2x3_dic = definition.attribute_dictionaries["IFC 2x3"]

		ifc2x3_dic.set_attribute "BaseQuantities", "value", "-"
		base_quantity_dic = ifc2x3_dic&.attribute_dictionaries["BaseQuantities"]
		
		if base_quantity_dic

			dico = base_quantity_dic["BaseQuantities"]

			if dico
				dico.set_attribute "Width", "Value", @width.to_s
				dico.set_attribute "Depth", "Value", @length.to_s
				dico.set_attribute "Height", "Value", @heigth.to_s
			end
		end

		ifc_wall_parameter = {
			"Depth" => @length.to_s,
			"Height" => @heigth.to_s,
			"Width" => @width.to_s,
			"Label" => LH["wall"]
		}

		ifc_wall_parameter.each{|ifc_attribute_suffix,value|

			racine = "Pset_IFCWall_"
			ifc_attribute = "#{racine}#{ifc_attribute_suffix}"

			dc_attributes =	{
				"_#{ifc_attribute}_formulaunits"=>"STRING",
				"_#{ifc_attribute}_label"=>ifc_attribute,
				"_#{ifc_attribute}_formlabel"=>"=#{ifc_attribute}",
				"_#{ifc_attribute}_units"=>"STRING",			
				"_#{ifc_attribute}_formula"=>"=#{value}",			
				"#{ifc_attribute}"=>"#{value}"
			}
			dc_attributes.each{|k,v| definition.set_attribute "dynamic_attributes", k.downcase, v}

		}

		path = ["IFC 2x3", "IfcWall", "ObjectType", "IfcLabel"]
		definition.set_classification_value(path, LH["wall"])

		path = ["IFC 2x3", "IfcWall", "ObjectType", "Width"]
		definition.set_classification_value(path, @width.to_s)

		path = ["IFC 2x3", "IfcWall", "ObjectType", "Depth"]
		definition.set_classification_value(path, @length.to_s)

		path = ["IFC 2x3", "IfcWall", "ObjectType", "Height"]
		definition.set_classification_value(path, @heigth.to_s)

	end

Thanks for your remark