Getting BIM data into Unity (Part 9 - using IfcConvert)

This is part 9 of a series of posts about getting BIM data into Unity.

In this post, we’ll discuss the IfcConvert utility from the IfcOpenShell Open Source IFC Library to preprocess an IFC model for integration with Unity.
This is (finally?) again a coding post, with some scripts which are shared to build upon.
Conversion of IFC into Unity-friendly formats

The strategy with this approach is that you preprocess the IFC-file into more manageable formats for Unity integration. Most Web-platforms do some sort of pre-processing anyway, so what you see in your browsers is almost never an IFC-file, but an optimised Mesh-based geometric representation. However, it wouldn’t be BIM-related if we’d limit ourselves to the geometry, so we will parse the model information as well, albeit using another, pre-processed file.

IFC to Wavefront OBJ

I used a test IFC-model and used the IfcConvert-utility converted it into OBJ en XML formats. The default way to use it is very simple: name of input model and name of output model. I used following arguments, to ensure the object naming uses the element-guide, which we can use later on to relate geometry back to their information.

IfcConvert.exe model.ifc model.obj --use-element-guids

Here is a fragment of the file, to see how the geometry gets stored:

# File generated by IfcOpenShell 0.5.0-dev
mtllib Mod2.mtl
g 2YVkZP68MrGvInuJaUhVut
s 1
v 7.999999997765 3.029999992102 -0.05
v 7.999999997765 3.049999992251 -0.05
v 0.02999999687 3.049999992251 -0.0500000010430001
v 0.02999999687 3.029999992102 -0.0500000010430001
v 7.999999997765 3.049999992251 -0.799999997764824
v 7.999999997765 3.049999992251 -0.05
v 7.999999997765 3.029999992102 -0.05
v 7.999999997765 3.029999992102 -0.799999997764824
v 7.999999997765 3.049999992251 -0.05
v 7.999999997765 3.049999992251 -0.799999997764824
v 0.02999999687 3.049999992251 -0.799999997764824
v 0.02999999687 3.049999992251 -0.0500000010430001
v 0.02999999687 3.029999992102 -0.0500000010430001
v 0.02999999687 3.049999992251 -0.0500000010430001
v 0.02999999687 3.049999992251 -0.799999997764824
v 0.02999999687 3.029999992102 -0.799999997764824
v 7.999999997765 3.029999992102 -0.799999997764824
v 7.999999997765 3.029999992102 -0.05

As you can read (more or less) is that the geometry (g in the file) gets the guid as name. Not as friendly for the end user, but essential to be able to link it to our model information later on. The following lines are vertex coordinates (v). 

The Wavefront format (OBJ) is pretty old, but still very usable and widely supported in applications.

By the way, Unity does support OBJ-files in recent versions. The right material colours are read from the separate MTL-file, which gets exported alongside the OBJ-file by IfcConvert. So ensure you use both files.




As you can see, our model is turned on its side. This is common when you convert between 3D and CAD software. Unity uses Y as up-direction. In IFC and most CAD and BIM software, we prefer the Z as upwards. No big deal. A simple rotation of 90 degrees around the X-axis suffices.

Notice that the model file has several “children” each named with the GUID derived from the IFC-file.

However, we don’t have any hierarchical spatial structure. We tackle that in a moment.

IFC to Collada DAE

An alternative is using the Collada (DAE) format which Unity supports directly.

IfcConvert.exe model.ifc model.dae --sew-shells --use-material-names --use-element-guids --use-element-hierarchy

Beware that if you compile IfcOpenShell yourself and want Collada support, you’d have to compile the Collada libraries as well. With the downloadable precompiled version, this is the case.

Here is the result in the same Unity-scene.



With the chosen arguments, we see that more of the model hierarchy survives the translation. In the Scenegraph, we notice that we can identify Project > Site > Building > Storey > Element (albeit with a generic product-guid naming). Interesting.

IFC to XML

The final step is using the same convertor tool but export to XML format. If you use a recent version, you can recover elements, property sets, quantity sets, layer information and information about the used material. And not to be too modest, but the addition of quantity sets was from my personal commit to the IfcOpenShell source code.

IfcConvert.exe model.ifc model.xml --use-element-guids

The result looks something like this in an XML-compatible text-editor.



Not that fundamentally different from the IFC file content, but much easier to parse. We get sections (nodes in the XML) for units, properties, quantities, types, layers, materials and the actual objects. And most relations are simplified into child nodes.

Here is another fragment, showing the objects and the implied Spatial decomposition. Each “node” has the name of the IFC-class and some (but not all) attributes are included, such as name and id. Almost any XML-library can easily browse through this structure.



Beware that this custom XML-format is defined solely for IfcOpenShell and does not contain any geometry nor does it follow a specific XSD (XML specification structure to validate its contents). But it is easier to integrate in your own workflows than regular IfcXML, which requires a very extensive XSD to cater for all possible entities and classes.

IFC does not really have a hierarchy as it is mostly a flat list of “entities”, but for most people this is how you experience it and most IFC-viewers show you this “tree-like” view.

Converting the OBJ file into Unity Geometry

When you load the OBJ-file directly inside Unity, you can already start visualising the model. However, if you would have to load the model or update models, we need “runtime” support: to be able to load the geometry while the project is running. And for that, I used an OBJ-script from the Unity Asset Store: 

That one is free and can also be used at runtime, so it could be called from a model loading script if you want.

Let’s get started with a custom C# script and begin with loading the OBJ file that is stored in our assets folder. For “real integration”, you’d have to download the model from a server, but that would overcomplicate our scripts and would be totally different for everybody. So I prefer to keep it simple and making something you can use.

Add the OBJ-importer asset into our project and create a new script, called IfcOpenShellParser.cs
Here is the first version of my script that calls into this library. We declare two public  variables to store the name of the file and the name of the folder in our Assets in which the file resides. In the Start() method, we expand the folder name with the actual file name and than add the “Application.dataPath” variable to get a file-system path that tells us the exact location of the file. As we don’t add the file extension to the fileName, we can now add “.obj” (and reuse the path when we later add “.xml”).

The Start method calls upon a second 

//using System.Collections;
//using System.Collections.Generic;
using UnityEngine;

using System.IO;

public class IfcOpenShellParser : MonoBehaviour
{
    public string fileName;
  public string filePath;
  private GameObject loadedOBJ;

    // Use this for initialization
    void Start()
    {
        string fileToParse = filePath;
        fileToParse = Path.Combine(fileToParse, fileName);
        fileToParse = Path.Combine(Application.dataPath, fileToParse);
        LoadOBJ(fileToParse + ".obj");
    }

The Start method calls upon a second method, called LoadOBJ. That simply asks our OBJLoader class (from the OBJ Importer asset) to load this file into a GameObject variable and we add the rotation of -90° to have Z point up instead of Y.

   
private void LoadOBJ(string file)
    {
        loadedOBJ =
OBJLoader.LoadOBJFile(file);
       
if (loadedOBJ != null)
        {
           
// turn -90 on the X-Axis (CAD/BIM uses Z up)
            loadedOBJ.transform.Rotate(-90, 0, 0);
        }
    }
}

Nothing is visible in the editor, as this all happens at runtime. When we press play, the OBJ file is loaded by the OBJImport script and then turned around the X-axis and can than inspect our result in the Scene view. Our Empty GameObject script shows our configuration (File Name = Mod2 and File Path = Models - adapt to your own configuration).


Looking at the scene hierarchy of this loaded OBJ file, we get the flat list of entities as shown previously.



While for our geometry, this is about it (thanks to IfcConvert to do the heavy work), we do want to dive into the model content. For that we can’t use the OBJ (+ MTL) file and thus will rely on the XML file (also created by IfcConvert).

Converting the XML file into Unity “Hierarchy” 

So we add a LoadXML function to our code and expand on that.
Lets try to browse through the model tree first, as most BIM IFC viewers use the tree structure as a visual compelling way to navigate a model (even though the IFC file itself is a flat list of entities in the end).

What we want to achieve is that we look in the XML-file to pick up entities by their unique name (based on their GUID). These elements will contain information in the XML-file but also correspond to geometry from the OBJ-file, instanced as GameObject “children” of our main file empty parent GameObject.

Let’s add some code to support XML (bold in following fragments)

We add a “using XML” statement to be able to call upon the XML-libraries. 

using UnityEngine;
using System.IO;
using System.Xml;

public class IfcOpenShellParser : MonoBehaviour
{
    public string fileName;
    public string filePath;
    private GameObject loadedOBJ;

We add a private (local) variable to store the document and refer to it in our methods.

    private XmlDocument loadedXML;

    // Use this for initialization
    void Start()
    {
        string fileToParse = filePath;
        fileToParse = Path.Combine(fileToParse, fileName);
        fileToParse = Path.Combine(Application.dataPath, fileToParse);
        LoadOBJ(fileToParse + ".obj");
        LoadXML(fileToParse + ".xml");
    }

    private void LoadOBJ(string file)
    {
        loadedOBJ = OBJLoader.LoadOBJFile(file);
        if (loadedOBJ != null)
        {
            // turn -90 on the X-Axis (CAD/BIM uses Z up)
            loadedOBJ.transform.Rotate(-90, 0, 0);
        }
    }
We add a similar loadXML function as the LoadOBJ and make a new XML Document to load the content of the XML file. This assumes that the OBJ and XML files are both at the same location with the same name (apart from extension). This is source code without error checks.

    private void LoadXML(string file)
    {
        loadedXML =
new XmlDocument();
        loadedXML.Load(file);

To point to the right place in the XML file, we define the base path string. This points us to the Root XML node from where we can start navigating the XML file. 

       
// basepath
        string basePath = @"//ifc/decomposition";

Now we add a new GameObject and give an appropriate name. This will be used as the Root or Parent GameObject where we want to move all the other IFC geometric entities to properly reflect the IFC Spatial Structure hierarchy. 

       
GameObject root = new GameObject();
        root.name = fileName +
" (IFC)";

Lets step through the different XML nodes under “IfcProject” and for each of the nodes collect some information. We do that in a “AddElements” function which we give the actual XML node as input.

       
foreach (XmlNode node in loadedXML.SelectNodes(basePath + "/IfcProject"))
            AddElements(node);
    }


The AddElements Function is where all the clever stuff happens. It is written to be called recursively, so we can repeat this for all children and grandchildren and so on.

    private void AddElements(XmlNode node)
    {
       
if (node.Attributes.GetNamedItem("id") != null)
        {
           
Debug.Log(string.Format("{0} => {1}",
                                    node.Attributes.GetNamedItem(
"id").Value,
                                    node.Attributes.GetNamedItem(
"Name").Value)
                     );
           
// Go through children (recursively)
            foreach (XmlNode child in node.ChildNodes)
                AddElements(child);
        }
// end if "id" attribute
    }
}

When you run this now, we get a list of all entities in the Console, showing the GUID and the Name of each entity.



We want to make an actual hierarchy. We can do this in Unity by creating new (empty) GameObjects and linking them underneath their parent. This makes the hierarchy visible in the Unity Editor. 

However, as we already loaded the OBJ file, we want to reuse the objects from their, as they already contain geometry and materials. We do this by searching the GameObject which are named by their IFC GUID and reparent them underneath their parent node.

To keep track of the parents, we also pass along the GameObject in the AddElements function, so we slightly adapt it.


    private void AddElements(XmlNode node, GameObject parent)
    {
        if (node.Attributes.GetNamedItem("id") != null)
        {
            Debug.Log(string.Format("{0} => {1}",
                                    node.Attributes.GetNamedItem("id").Value,
                                    node.Attributes.GetNamedItem("Name").Value)
                     );

Here we want to re-use the 3D objects we imported already from the OBJ-file, based on the name (the GUID). We may have to create one where no object can be found (e.g. for Project, Site, Storey etc…).

            // Search an existing GameObject with this name
            // This would apply only to elements which have
            // a geometric representation and which are
            // extracted from the 3D file.
            string searchPath = fileName + "/" +
                node.Attributes.GetNamedItem(
"id").Value;
           
GameObject goElement = null;
            goElement =
GameObject.Find(searchPath);

           
// What if we can't find any? We need to create
            // a new empty object
            if (goElement == null)
                goElement =
new GameObject();

If we succeed, give it the proper name and link it to the parent we received.

           
if (goElement != null)
            {
               
// Set name from the IFC Name field
                goElement.name = node.Attributes.GetNamedItem("Name").Value;
               
// Link the object to the parent we received
                if (parent != null)
                    goElement.transform.SetParent(parent.transform);

                // Go through children (recursively)
                foreach (XmlNode child in node.ChildNodes)
                    AddElements(child, goElement);
            }
        } // end if "id" attribute
    }

Nice. This is what we have right now:



At this point, we recreated the whole Spatial Structure, as reflected in the XML file, into our Scenegraph in Unity. Select a Storey name in the Unity Hierarchy, you can see that the geometric objects from that story become selected.

Extracting the IFC Property Sets from the XML-file 

In one of the earlier posts, I used a very simple “Metadata” script with two lists of strings (keys and values). At this point, this has become a bit too simplistic and we prefer something more. For that purpose, we created a slightly more structured class, called IFCData (as a new C# script).

We remove the Start and Update methods and add a few public variables. This is the easiest way to store some information in the script, without relying on specific methods or elaborate file structures. 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class IFCData : MonoBehaviour {

   
public string IFCClass;
   
public string STEPName;
   
public string STEPId;
   
public string STEPIndex;
   
public string IFCLayer;
}


You’d have to understand that this is not the most robust nor safe way to store data in a C# class. But it suffices for our example script and is easy to understand.
We will add a few strings for the Class, the Name and unique Id and also the Index, which is the #number you can see at the beginning of each line in an IFC STEP file. The Layer can refer to the Representation Layer, although we may not use this for now.

When you attach that script as a component to any GameObject, it becomes a place to store the information from the element.



We don’t want to manually fill these fields, so we will parse the IFC-model, the way it is exported into the XML-file.

At each step in our AddElements, we can add properties if we can find them in the XML file. We call this function AddProperties and pass the same XmlNode and GameObject as arguments.

    private void AddProperties(XmlNode node, GameObject go)
    {
        IFCData ifcData = go.AddComponent(typeof(IFCData)) as IFCData;

        ifcData.IFCClass = node.Name;
        ifcData.STEPId = node.Attributes.GetNamedItem("id").Value;
        ifcData.STEPName = node.Attributes.GetNamedItem("Name").Value;
    }

To link that up, we add the AddProperties(…) call in our AddElements function.

    private void AddElements(XmlNode node, GameObject parent)
    {
        if (node.Attributes.GetNamedItem("id") != null)
        {

[...]

            if (goElement != null)
            {
                // Set name from the IFC Name field
                goElement.name = node.Attributes.GetNamedItem("Name").Value;
                // Link the object to the parent we received
                if (parent != null)
                    goElement.transform.SetParent(parent.transform);

                // Add properties
                AddProperties(node, goElement);

                // Go through children (recursively)
                foreach (XmlNode child in node.ChildNodes)
                    AddElements(child, goElement);
            }
        } // end if "id" attribute
    }

If we launch are Project now, we see that each Object in our “tree” received the IFCData component automatically and some information has been filled in, begin the IFC Class Name (e.g. IfcBuilding), the STEP Name (e.g. “Naam gebouw”) and the STEP id or GUID (e.g. 0012323232whatever).



Almost there… just one more thing.

Now the actual properties. Within IFC, they are captured in PropertySets (groups or sets of properties). Each property would at least be recognisable with a Name and a Value and the PropertySet itself also has a name.

Recognise this? We’re getting closer.



But now the properties. And (while we’re at it) the Base quantities, which look like property sets, but are pre-calculated common quantities, such as area, volume or height. Just ensure that your BIM software can export them and that you use an up-to-date IfcConvert, which adds them in the XML-conversion.

Expand IFCData to cover Properties and PropertySets

Before we can store the properties in our custom IFCData structure, we have to expand it a bit. We add two now classes (IFCProperty and IFCPropertySet) and then add them into a List collection.

This is not the most robust or fastest or bullet-proof approach, but will work nicely for our purposes.

public class IFCData : MonoBehaviour {

    public string IFCClass;
    public string STEPName;
    public string STEPId;
    public string STEPIndex;
    public string IFCLayer;

    public List<IFCProperty> properties;
}

[System.Serializable]
public class IFCProperty
{
   
public string propName = "";
   
public string propValue = "";
}
We start with a simple implementation. We define a new Class “IFCProperty” and not derive it from Monobehaviour. That way, we can’t drag it to a Unity GameObject as a Component, but we can use it in our main IFCData class. Within that class, we add a public variable as a list of properties (List properties).

To be able to see that custom class in the Unity Editor inspector, we need to add the [System.Serializable] instruction to the class definition. Otherwise, it wouldn’t show up.

This is what it looks like when we an IFCData script component to a random GameObject. A “Properties” list which can be expanded and if we write 1 in the field which says 0, an item is inserted into the list, with our two fields Prop Name and Prop Value.



However, this is too simplistic for IFC. To actually support Property Sets, we introduce a custom IFCPropertySet class containing a List of Properties and add to the IFCData class a variable containing a list of Property Sets.

public class IFCData : MonoBehaviour
{

    public string IFCClass;
    public string STEPName;
    public string STEPId;
    public string STEPIndex;
    public string IFCLayer;

    public List<IFCPropertySet> propertySets;
}

[System.Serializable]
public class IFCPropertySet
{
   
public string propSetName = "";
   
public string propSetId = "";

   
public List<IFCProperty> properties;
}

[System.Serializable]
public class IFCProperty
{
    public string propName = "";
    public string propValue = "";
}

I hope you understand the code by now. We abuse the same class to add another list of Property Sets called Quantities, which is enough for our purposes. Besides, in IFC they derive from the same class, so they do work the same way.

    public List<IFCPropertySet> propertySets;    
    public List<IFCPropertySet> quantitySets;

Parsing the XML to find the properties and quantities

In the XML file we receive from IfcConvert, the Properties (and Quantities) are captured as linked XMLnodes. In the element breakdown structure, we only get a reference in the form of an attribute in the node to one of the entries elsewhere in the XML file.

First we have to add the “System” namespace as we will access some String-methods.

    using System;

The second step is expanding the AddProperties method to run through the different property sets. We have to prepare a string with the correct Path to where in the XML structure these nodes can be found. We use the “href-link” attribute to get to the right referenced node from within our elements. IfcConvert places the PropertySets and Properties in a different section of the XML.

    private void AddProperties(XmlNode node, GameObject go)
    {
        IFCData ifcData = go.AddComponent(typeof(IFCData)) as IFCData;

        ifcData.IFCClass = node.Name;
        ifcData.STEPId = node.Attributes.GetNamedItem("id").Value;
        ifcData.STEPName = node.Attributes.GetNamedItem("Name").Value;


        // Go through Properties (and Quantities and ...)
        foreach (XmlNode child in node.ChildNodes)
        {
           
switch (child.Name)
            {
               
case "IfcPropertySet":
               
case "IfcElementQuantity":
                   
// we only receive a link beware of # character
                    string link = child.Attributes.GetNamedItem("xlink:href").Value.TrimStart('#');
                   
string path = String.Format("//ifc/properties/IfcPropertySet[@id='{0}']", link);
                   
if (child.Name == "IfcElementQuantity")
                        path =
String.Format("//ifc/quantities/IfcElementQuantity[@id='{0}']", link);
                   
XmlNode propertySet = loadedXML.SelectSingleNode(path);
                   
if (propertySet != null)
                    {
                       
Debug.Log(
                           
string.Format("PropertySet = {0}",
                                          propertySet.Attributes.GetNamedItem(
"Name").Value));
                    }
// end if PropertySet
                    break;
               
default:
                   
// all the rest...
                    break;
            }
// end switch
        } // end foreach
    }

If we press Play and look in the Console, you’ll see the Names of the different PropertySets passing by, such as Pset_WallCommon, but also BaseQuantities, which we can also capture as a PropertySet (if the IFC model contains these quantities).



The next step is making our own custom IFCPropertySet and IFCProperty classes based on what we receive from the XML-file and add them into the IFCData class.

As we will have to initialise the List of properties (List) we will also add the Collections namespace in the beginning of our file.

using System.Collections;
using System.Collections.Generic;

Then we add the creation of our custom IFCPropertySet class into the AddProperties method and initialise its list of properties at the same time, to not have an empty list object. This is not the best approach and we’d better move this initialisation to the classes themselves.

    private void AddProperties(XmlNode node, GameObject go)
    {
        IFCData ifcData = go.AddComponent(typeof(IFCData)) as IFCData;

        ifcData.IFCClass = node.Name;
        ifcData.STEPId = node.Attributes.GetNamedItem("id").Value;
        ifcData.STEPName = node.Attributes.GetNamedItem("Name").Value;
        // Initialize PropertySets and QuantitySets
        if (ifcData.propertySets == null)
            ifcData.propertySets = new List<IFCPropertySet>();
        if (ifcData.quantitySets == null)
            ifcData.quantitySets = new List<IFCPropertySet>();


        // Go through Properties (and Quantities and ...)
        foreach (XmlNode child in node.ChildNodes)
        {
            switch (child.Name)
            {
                case "IfcPropertySet":
                case "IfcElementQuantity":
                    // we only receive a link beware of # character
                    string link = child.Attributes.GetNamedItem("xlink:href").Value.TrimStart('#');
                    string path = String.Format("//ifc/properties/IfcPropertySet[@id='{0}']", link);
                    if (child.Name == "IfcElementQuantity")
                        path = String.Format("//ifc/quantities/IfcElementQuantity[@id='{0}']", link);
                    XmlNode propertySet = loadedXML.SelectSingleNode(path);
                    if (propertySet != null)
                    {
                        Debug.Log(
                            string.Format("PropertySet = {0}",
                                          propertySet.Attributes.GetNamedItem("Name").Value));

                        // initialize this propertyset (Name, Id)
                        IFCPropertySet myPropertySet = new IFCPropertySet();
                        myPropertySet.propSetName = propertySet.Attributes.GetNamedItem("Name").Value;
                        myPropertySet.propSetId = propertySet.Attributes.GetNamedItem("id").Value;
                        if (myPropertySet.properties == null)
                            myPropertySet.properties = new List<IFCProperty>();

                        // add propertyset to IFCData
                        if (child.Name == "IfcPropertySet")
                            ifcData.propertySets.Add(myPropertySet);
                        if (child.Name == "IfcElementQuantity")
                            ifcData.quantitySets.Add(myPropertySet);

                    } // end if PropertySet
                    break;
                default:
                    // all the rest...
                    break;
            } // end switch
        } // end foreach
    }
The result is that our IFCData component now contains the PropertySets, but not the properties themselves.



To also add the properties, we expand our AddProperties method. We run through all the Childnodes of the XMLnode propertySet and initialise a new IFCProperty (our own class). We get the name and then the value. To be able to add the value, we will need to make the distinction between some types of values, based on the Name of the property. Beware that we either capture the single value (for a property) or a quantity.

                        // initialize this propertyset (Name, Id)
                        IFCPropertySet myPropertySet = new IFCPropertySet();
                        myPropertySet.propSetName = propertySet.Attributes.GetNamedItem("Name").Value;
                        myPropertySet.propSetId = propertySet.Attributes.GetNamedItem("id").Value;
                        if (myPropertySet.properties == null)
                            myPropertySet.properties = new List<IFCProperty>();

                        // run through property values
                        foreach (XmlNode property in propertySet.ChildNodes)
                        {
                           
string propName, propValue = "";
                           
IFCProperty myProp = new IFCProperty();
                            propName = property.Attributes.GetNamedItem(
"Name").Value;

                           
if (property.Name == "IfcPropertySingleValue")
                                propValue = property.Attributes.GetNamedItem(
"NominalValue").Value;
                           
if (property.Name == "IfcQuantityLength")
                                propValue = property.Attributes.GetNamedItem(
"LengthValue").Value;
                           
if (property.Name == "IfcQuantityArea")
                                propValue = property.Attributes.GetNamedItem(
"AreaValue").Value;
                           
if (property.Name == "IfcQuantityVolume")
                                propValue = property.Attributes.GetNamedItem(
"VolumeValue").Value;
                           
// Write property (name & value)
                            myProp.propName = propName;
                            myProp.propValue = propValue;
                            myPropertySet.properties.Add(myProp);
                        }


                        // add propertyset to IFCData
                        if (child.Name == "IfcPropertySet")
                            ifcData.propertySets.Add(myPropertySet);
                        if (child.Name == "IfcElementQuantity")
                            ifcData.quantitySets.Add(myPropertySet);

And there we are… We reconstructed the IFC Spatial Structure in the Unity Scenegraph and attached most properties and quantities into a custom IFCData class. If you remember one of the earlier blog posts, this method can be reused to also show something on screen when we click on an object. For that to work, we can expand our script to also generate Mesh Colliders when parsing the element tree, to ensure that Raycasts can actually hit objects.




That’s all for now. And as usual, not 1-click script download. Please collect everything from this post so you get a good grip on how it works and what happens. Learn some scripting. This is all still fairly basic stuff. But can form the basis for your own project.


And remember… IFC may be a bit complex. OK, probably very complex, but with the help of some clever tools, much of the complexity can be removed and turned into something effective.