Missing transformation of some IFC elements?

Hi there,

I’m trying to access all the faces of an IFC model loaded in SketchUp. I want to retrieve (through Ruby) their coordinates exactly as they are displayed in the viewer, no matter what transformation happened.

To do so, I go through all entities of a model looking for the Sketchup::Face ones. I am aware of the required successive transformations of the faces based on the ComponentInstance/Group that they belong to. So for each of them, I go down to it’s root ComponentDefinition.

This is where things get funny. While this seems to work fine for other input formats, for IFC things go wrong, specifically for the IfcFurnishingElement entities (I tried a few IFC2x3 files, same issue). I took 2 top view images (with the model in green and the points of the retrieved faces in orange) to illustrate the cases where I apply the successive transformations (with-transformation hosted at ImgBB — ImgBB) and the one I don’t (Without_transformation hosted at ImgBB — ImgBB).

When there is no transformation applied, all the shapes are concentrated around the origin (expected because IFC entities have their own local coordinates and need to be placed in a the global coordinate ref of the model itself, which SketchUp is already doing as everything is in place in the input model). But when the transformations are applied, all the points of the model are at the right place, except for the furnishing elements.

What am I missing here? Looks like there is transformation bit that I am missing on those specific entities.

There is no way really for anyone to know without a test model and a code snippet you are using.

Fair enough!
Here is the model: https://file.io/pkA8CrnFz1ze

For the code snippet, this is how I get the transformation for each face:

def self.getFaceTransform(face)
  resTransform = IDENTITY # Identity transformation
  
  curEntity = face
  while !curEntity.is_a?(Sketchup::Model)
    # Get transformation if the entity is a ComponentInstance or a Group
    if curEntity.is_a?(Sketchup::ComponentInstance) || curEntity.is_a?(Sketchup::Group)
      resTransform *= curEntity.transformation
    end
    # Move up the hierarchy
    curEntity = curEntity.parent
    if curEntity.is_a?(Sketchup::ComponentDefinition)
        curEntity = curEntity.instances.first
    end
  end
  
  resTransform
end

After further investigations, the problem is not limited to furnishing elements only, but other classes seem affected as well. For example, in this new picture (Screenshot-2024-05-31-111806 hosted at ImgBB — ImgBB) where I exported the transformed faces (rather than points), some window components, the roof slabs, etc., can be seen in wrong position, while the model from which the faces are taken in SketchUp is completely fine (Screenshot-2024-05-31-112012 hosted at ImgBB — ImgBB).

How do you know that you are dealing with the first instance of a definition in any particular instance path ?

Normally it works best to “walk the model” from the root downwards and build instance paths as you “dig down”. Once you reach a face “leaf” you can use the InstancePath.transformation method to get the global transform.

Well I suspected that too. But for this model (and the others I tried so far) the curEntity.instances.length of all ComponentDefinition objects is 1. So looping through the instances doesn’t make a difference (although I agree that it’s a better way of implementing it).

Really puzzling to me… Just to make sure I got all the path right, I printed all the components that are checked with the function above (in my second post), and below is the output for a face belonging to an IfcFurnishingElement:

ComponentInstance: (the face I assume)
	Transformation: [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
ComponentInstance: IfcFurnishingElement - Laufband 
	Transformation: [-0.8670275173498774, -0.4982602574539816, 0.0, 0.0, 0.4982602574539816, -0.8670275173498774, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 365.9515905511811, 136.33532283464567, 0.0, 1.0]
ComponentInstance: IfcSpace - 7 
	Transformation: [-1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 460.6299212598425, 381.8897637795275, 0.0, 1.0]
ComponentInstance: IfcBuildingStorey - Dachgeschoss 
	Transformation: [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 106.2992125984252, 1.0]
ComponentInstance: IfcBuilding - FZK-Haus 
	Transformation: [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
ComponentInstance: IfcSite - Gelaende 
	Transformation: [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
ComponentInstance: IfcProject - Projekt-FZK-Haus 
	Transformation: [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]
ComponentInstance:  (the root/model I assume)
	Transformation: [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]

This path is conformal to an IFC hieararchy. The only non Identity transformations are those of IfcFurnishingElement, IfcSpace and IfcBuildingStorey, and I am capturing all of them in the final transformation that I am applying to the face. Yet, some components are not aligned and I noticed that the problem seems to be specifically related to a missing/wrong translation component of the transformation. I know the model enough to say that all the misaligned elements are properly oriented, but just not at the correct place (Good_orientation_wrong_location hosted at ImgBB — ImgBB).

As I pointed out, most models will have more than one instance of a component, and you cannot tell from going up from the leaf which instance path you are in.

Sorry I just edited my last deleted post to add new insights. I agree but adding a loop through the instances did not solve the problem as expected.

Ok, after going coconuts on this, I turned to that old good mate ChatGPT and it pointed out to a potential issue leading me to the actual problem<=>solution. Indeed, my transformations were not behaving properly because of my bottom-up process (face up to root) opposed to the top-down (root down to face) approach suggested by @DanRathbun. My initial line:

resTransform *= curEntity.transformation

was therefore putting the last transformations first. Hence, it should be

resTransform = curEntity.transformation * resTransform

to ensure a “reversed” transformation order. Everything looks in place now. Thanks for the help DanRathbun. Here is the final working version:

def self.getFaceTransform(face)
  # Start with the identity transformation
  resTransform = Geom::Transformation.new
  
  curEntity = face
  while !curEntity.is_a?(Sketchup::Model)
    # Get transformation if the entity is a ComponentInstance or a Group
    if curEntity.is_a?(Sketchup::ComponentInstance) || curEntity.is_a?(Sketchup::Group)
      resTransform = curEntity.transformation * resTransform
    end
    # Move up the hierarchy
    parent = curEntity.parent
    if parent.is_a?(Sketchup::ComponentDefinition)
      # Find the instance containing the current entity
      curEntity = parent.instances.find { |inst| inst.definition.entities.include?(curEntity) }
    else
      curEntity = parent
    end
  end
  resTransform
end
1 Like

You always learn better when it’s you that figures it out (even with a little hint or two.)

1 Like