Color Rebar by property with IfcOpenShell

edited November 2020 in General

There are a lot of nested objects to go through to get to the colours of objects in IFC, and there are two paths to follow:

print("Colour in material:")
print(rebars[1].HasAssociations[0].RelatingMaterial.HasRepresentation[0].Representations[0].Items[0].Styles[0].Styles[0].Styles[0].SurfaceColour)

print("Colour in representation:")
print(rebars[1].Representation.Representations[0].Items[0].StyledByItem[0].Styles[0].Styles[0].Styles[0].SurfaceColour)

Here is a script I made to set colors to rebars based on different values of an attribute. I need one IfcStyledItem per Rebar instance, but do I need one new of the three styles (IfcSurfaceStyleRendering, IfcSurfaceStyle, IfcPresentationStyleAssignment) for each color, or could I reuse them?

import ifcopenshell
f = ifcopenshell.open(r"Rebar-diameter.ifc")
rebars = f.by_type('IfcReinforcingBar')

colours = (
    ("darkslategray", "#2f4f4f"),
    ("darkolivegreen", "#556b2f"),
    ("saddlebrown", "#8b4513"),
    ("olivedrab", "#6b8e23"),
    ("seagreen", "#2e8b57"),
    ("maroon", "#800000"),
    ("midnightblue", "#191970"),
    ("slategray", "#708090"),
    ("green", "#008000"),
    ("peru", "#cd853f"),
    ("steelblue", "#4682b4"),
    ("navy", "#000080"),
    ("yellowgreen", "#9acd32"),
    ("lightseagreen", "#20b2aa"),
    ("indianred", "#cd5c5c"),
    ("limegreen", "#32cd32"),
    ("goldenrod", "#daa520"),
    ("darkseagreen", "#8fbc8f"),
    ("purple", "#800080"),
    ("darkorchid", "#9932cc"),
    ("red", "#ff0000"),
    ("darkorange", "#ff8c00"),
    ("gold", "#ffd700"),
    ("yellow", "#ffff00"),
    ("mediumblue", "#0000cd"),
    ("burlywood", "#deb887"),
    ("lime", "#00ff00"),
    ("springgreen", "#00ff7f"),
    ("royalblue", "#4169e1"),
    ("darksalmon", "#e9967a"),
    ("crimson", "#dc143c"),
    ("aqua", "#00ffff"),
    ("deepskyblue", "#00bfff"),
    ("blue", "#0000ff"),
    ("purple3", "#a020f0"),
    ("greenyellow", "#adff2f"),
    ("tomato", "#ff6347"),
    ("fuchsia", "#ff00ff"),
    ("palevioletred", "#db7093"),
    ("khaki", "#f0e68c"),
    ("cornflower", "#6495ed"),
    ("plum", "#dda0dd"),
    ("skyblue", "#87ceeb"),
    ("deeppink", "#ff1493"),
    ("violet", "#ee82ee"),
    ("palegreen", "#98fb98"),
    ("aquamarine", "#7fffd4"),
    ("hotpink", "#ff69b4"),
    ("pink", "#ffc0cb")
)

import itertools, math

rebar_groups = []
for k,g in itertools.groupby(rebars, lambda x: x.NominalDiameter):
    rebar_groups.append(list(g))

num_groups = len(rebar_groups)
colours = colours*math.ceil(len(rebar_groups)/len(colours))
colours = colours[:len(rebar_groups)]

rgb_colours = list()
for hex in colours:
    h = hex[1].lstrip('#')
    rgb = tuple(int(h[i:i+2], 16)/255 for i in (0, 2, 4))
    rgb_colours.append(f.createIfcColourRgb(hex[0], rgb[0], rgb[1], rgb[2]))

i = 0
for i, group in enumerate(rebar_groups):
    colour = rgb_colours[i]
    surfaceStyleRendering = f.createIfcSurfaceStyleRendering()
    surfaceStyleRendering.SurfaceColour = colour
    surfaceStyle = f.createIfcSurfaceStyle(colour.Name, "BOTH",(surfaceStyleRendering,))
    presStyleAssign = f.createIfcPresentationStyleAssignment((surfaceStyle,))
    i=i+1
    for rebar in group:
        item = rebar.Representation.Representations[0].Items[0]
        f.createIfcStyledItem(item, (presStyleAssign,), colour.Name)

f.write(r"Rebar-diameter-modiefied.ifc")

Comments

  • Before I answer - are you sure you want to do this? Most IFC viewing has a function to colour by attribute / property without needing to patch the IFC file, including the BlenderBIM Add-on. Wouldn't that be simpler?

  • For instance Dalux BIM viewer can not. And rebars are so small and densly placed that it is almost not possible to tell the different bars apart without color.

  • I see. It'd be cool to turn this into an IfcPatch recipe then! Let's workshop it!

    IfcPresentationStyleAssignment is deprecated. However, I do know that in Revit, they ignore the deprecation and actually colours fail if you don't use it. So you should offer a boolean toggle to use it or not.

    As for IfcSurfaceStyle, I see nothing in the spec that prohibits reuse. Go ahead! As for IfcSurfaceStyleRendering, can I recommend IfcSurfaceStyleShading instead, as you are only dealing with simple colours? Both can be reused I think, I didn't spot anything in the spec that prohibits it.

  • edited November 2020

    IfcSurfaceStyleShading works just as good!
    As long as the IfcColorRgb-object is a direct attribute of the IfcSurfaceStyleShading-object and so on, it seems like they need to be unique entities per color? (Still not too steady on the IFC terminology)

  • @Einar yes. Unique per colour.

  • Since IfcPresentationStyleAssignment is deprecated the IfcSurfaceStyle should be plugged directly into IfcStyledItem right? That doesn't work in Solibri with my test file IFC 2x3 CV2.0, maybe it would work with IFC4. Here is a recipie that can be used with ifcpatch:

    import math
    from collections import defaultdict
    
    class Patcher:
        def __init__(self, src, file, logger, args=None):
            self.src = src
            self.file = file
            self.logger = logger
            self.args = args
    
        def patch(self):
            pset_name = self.args[0]
            property_name = self.args[1]
    
            rebars = self.file.by_type('IfcReinforcingBar')
    
            colours = (
                ("blue", "#0000ff"),
                ("green", "#008000"),
                ("red", "#ff0000"),
                ("purple", "#800080"),
                ("darkolivegreen", "#556b2f"),
                ("saddlebrown", "#8b4513"),
                ("olivedrab", "#6b8e23"),
                ("seagreen", "#2e8b57"),
                ("maroon", "#800000"),
                ("midnightblue", "#191970"),
                ("slategray", "#708090"),
                ("peru", "#cd853f"),
                ("steelblue", "#4682b4"),
                ("navy", "#000080"),
                ("yellowgreen", "#9acd32"),
                ("lightseagreen", "#20b2aa"),
                ("indianred", "#cd5c5c"),
                ("limegreen", "#32cd32"),
                ("goldenrod", "#daa520"),
                ("darkseagreen", "#8fbc8f"),
                ("darkorchid", "#9932cc"),
                ("darkorange", "#ff8c00"),
                ("gold", "#ffd700"),
                ("yellow", "#ffff00"),
                ("mediumblue", "#0000cd"),
                ("burlywood", "#deb887"),
                ("lime", "#00ff00"),
                ("springgreen", "#00ff7f"),
                ("royalblue", "#4169e1"),
                ("darksalmon", "#e9967a"),
                ("crimson", "#dc143c"),
                ("aqua", "#00ffff"),
                ("deepskyblue", "#00bfff"),
                ("purple3", "#a020f0"),
                ("greenyellow", "#adff2f"),
                ("tomato", "#ff6347"),
                ("fuchsia", "#ff00ff"),
                ("palevioletred", "#db7093"),
                ("khaki", "#f0e68c"),
                ("cornflower", "#6495ed"),
                ("plum", "#dda0dd"),
                ("skyblue", "#87ceeb"),
                ("deeppink", "#ff1493"),
                ("violet", "#ee82ee"),
                ("palegreen", "#98fb98"),
                ("aquamarine", "#7fffd4"),
                ("hotpink", "#ff69b4"),
                ("pink", "#ffc0cb")
            )
    
            rebar_dict = defaultdict(list)
            print("# Grouping rebar by property value..")
            for k,v in zip([self.get_propery_value(rebar, pset_name, property_name) for rebar in rebars], rebars):
                rebar_dict[k].append(v)
    
            num_groups = len(rebar_dict)
    
            colours = colours*math.ceil(num_groups/len(colours))
            colours = colours[:num_groups]
    
            rgb_colours = list()
            for hex in colours:
                h = hex[1].lstrip('#')
                rgb = tuple(int(h[i:i+2], 16)/255 for i in (0, 2, 4))
                rgb_colours.append(self.file.createIfcColourRgb(hex[0], rgb[0], rgb[1], rgb[2]))
    
            i=0
            print("# Creating styled items for rebar color..")
            for k, group in rebar_dict.items():
                colour = rgb_colours[i]
                print("# {} rebars with {}={} is coloured with {} ".format(len(group), property_name, k, colour.Name))
                surfaceStyleShading = self.file.createIfcSurfaceStyleShading()
                surfaceStyleShading.SurfaceColour = colour
                surfaceStyle = self.file.createIfcSurfaceStyle(colour.Name, "BOTH",(surfaceStyleShading,))
                presStyleAssign = self.file.createIfcPresentationStyleAssignment((surfaceStyle,))
                i=i+1
                for rebar in group:
                    item = rebar.Representation.Representations[0].Items[0]
                    self.file.createIfcStyledItem(item, (presStyleAssign,), colour.Name)
    
        def get_propery_value(self, rebar, pset_name, p_name):
            for pset in rebar.IsDefinedBy:
                if pset.RelatingPropertyDefinition.Name == pset_name:
                    for property in pset.RelatingPropertyDefinition.HasProperties:
                        if property.Name == p_name:
                            return property.NominalValue[0]
    
  • @Einar Correct, it should plug directly. The assignment keyword is like a useless middle layer.

    I think I recall that Revit has a bug where if you don't use the assignment, it won't read colour. IfcOpenShell actually also had this bug until it was fixed half a year ago or so. So maybe Solibri has this bug too?

    The best way to know if colours are correct is to test with the latest version of the BlenderBIM Add-on (use the latest v0.6.0 branch, as I've recently pushed a bunch of fixes to materials and colours since the last release). If colours show, your IFC is correct and its likely a bug with other software.

Sign In or Register to comment.