diff --git a/src/blenderbim/blenderbim/tool/loader.py b/src/blenderbim/blenderbim/tool/loader.py index ca363461e2..389adbbe46 100644 --- a/src/blenderbim/blenderbim/tool/loader.py +++ b/src/blenderbim/blenderbim/tool/loader.py @@ -241,10 +241,6 @@ class Loader(blenderbim.core.tool.Loader): mode = texture.get("Mode", None) node = None - if texture["type"] == "IfcPixelTexture": - print(f"WARNING. Texture of type {texture['type']} is not currently supported, it will be skipped.") - continue - image_url = None def get_image(): @@ -284,9 +280,35 @@ class Loader(blenderbim.core.tool.Loader): ).ravel() return bpy_image - # TODO: IfcPixelTexture - print(f"WARNING. Texture of type {texture['type']} is not yet supported.") - return + # IfcPixelTexture + n_components = texture["ColourComponents"] + width, height = texture["Width"], texture["Height"] + blender_pixel_data = np.ones(width * height * 4, dtype=np.float32) + + # according to https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcPixelTexture.htm + # 1 component - grey scale intensity value + # 2 components - grey scale + alpha + # 3 components - RGB + # 4 components - RGBA + for i, pixel_str in enumerate(iterable=texture["Pixel"]): + pixel_bytes = int(pixel_str, 2).to_bytes(len(pixel_str) // 8, "big") + pixel_values = np.array(list(pixel_bytes)) / 255 + cur_pos = i * 4 + + if n_components in (1, 2): + blender_pixel_data[cur_pos : cur_pos + 3] = pixel_values[0] + if n_components == 2: + blender_pixel_data[cur_pos + 3] = pixel_values[1] + continue + + # 3, 4 components + blender_pixel_data[cur_pos : cur_pos + 3] = pixel_values[:3] + if n_components == 4: + blender_pixel_data[cur_pos + 3] = pixel_values[3] + + bpy_image = bpy.data.images.new("pixel_texture", width=width, height=height) + bpy_image.pixels[:] = blender_pixel_data + return bpy_image if reflectance_method in ["PHYSICAL", "NOTDEFINED"]: bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED") diff --git a/src/blenderbim/test/tool/test_loader.py b/src/blenderbim/test/tool/test_loader.py index 8baf34bf30..5dd73ced6f 100644 --- a/src/blenderbim/test/tool/test_loader.py +++ b/src/blenderbim/test/tool/test_loader.py @@ -254,6 +254,120 @@ class TestCreatingStyles(NewFile): assert image_node.image.pixels[:] == (0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0) # fmt: on + def test_create_surface_style_with_textures_from_ifc_pixel_image(self): + # fmt: off + pixel_data_for_components = { + 1: ( + 0.5, 0.5, 0.5, 1.0, + 0.6, 0.6, 0.6, 1.0, + 0.7, 0.7, 0.7, 1.0, + 1.0, 1.0, 1.0, 1.0, + ), + 2: ( + 0.5, 0.5, 0.5, 0.5, + 0.6, 0.6, 0.6, 0.5, + 0.7, 0.7, 0.7, 0.5, + 1.0, 1.0, 1.0, 0.5, + ), + 3: ( + 0.5, 0, 0, 1.0, + 0, 0.6, 0, 1.0, + 0, 0, 0.7, 1.0, + 1, 1, 1, 1.0, + ), + 4: ( + 0.5, 0, 0, 0.5, + 0, 0.6, 0, 0.5, + 0, 0, 0.7, 0.5, + 1, 1, 1, 0.5, + ) + } + # fmt: on + for i in range(1, 5): + self.run_test_create_surface_style_with_textures_from_ifc_pixel_image(i, pixel_data_for_components[i]) + + def run_test_create_surface_style_with_textures_from_ifc_pixel_image(self, n_components, pixel_data): + # this case occurs when we edit shader properties without saving them to IFC + bpy.ops.bim.create_project() + + ifc_file = tool.Ifc.get() + style = tool.Ifc.run("style.add_style", name="test") + material = bpy.data.materials.new(style.Name) + tool.Ifc.link(style, material) + get_color = lambda value: {color: value for color in ("Red", "Green", "Blue")} + rendering_attributes = { + "ReflectanceMethod": "NOTDEFINED", + "DiffuseColour": get_color(0.5), + "SurfaceColour": get_color(0.3), + "Transparency": 0.3, + "SpecularHighlight": {"IfcSpecularRoughness": 0.4}, + "SpecularColour": 0.03, + } + rendering_style = tool.Ifc.run( + "style.add_surface_style", + style=style, + ifc_class="IfcSurfaceStyleRendering", + attributes=rendering_attributes, + ) + + def get_pixels_binary(n_components): + # just 4 pixels - red, green, blue, white + # fmt: off + pixel_data = ( + 0.5, 0, 0, 0.5, + 0, 0.6, 0, 0.5, + 0, 0, 0.7, 0.5, + 1, 1, 1, 0.5, + ) + # fmt: on + pixels = np.array(pixel_data).reshape(-1, 4) + alpha = pixels[:, 3] + if n_components in (1, 2): + pixels = [[p[min(i, 2)]] for i, p in enumerate(pixels)] + if n_components == 2: + for i in range(4): + pixels[i].append(alpha[i]) + elif n_components == 3: + pixels = pixels[:, :3] + + pixels = np.array(pixels) * 255 + return ["".join(["{:08b}".format(int(i)) for i in pixel]) for pixel in pixels] + + texture_data = { + "Pixel": get_pixels_binary(n_components), + "ColourComponents": n_components, + "Width": 2, + "Height": 2, + "Mode": "DIFFUSE", + "RepeatS": True, + "RepeatT": True, + } + # setup texture manually as `style.add_surface_textures` doesn't support non-IfcImageTexture textures + textures = [ifc_file.create_entity("IfcPixelTexture", **texture_data)] + # setup UV + ifc_file.create_entity("IfcTextureCoordinateGenerator", Maps=textures, Mode="COORD") + + texture_style = tool.Ifc.run( + "style.add_surface_style", + style=style, + ifc_class="IfcSurfaceStyleWithTextures", + attributes={"Textures": textures}, + ) + + subject.create_surface_style_rendering(material, rendering_style) + subject.create_surface_style_with_textures(material, rendering_style, texture_style) + + used_node_types = set([n.type for n in material.node_tree.nodes[:]]) + assert used_node_types == set((["OUTPUT_MATERIAL", "BSDF_PRINCIPLED", "TEX_IMAGE", "TEX_COORD"])) + + image_node = tool.Blender.get_material_node(material, "TEX_IMAGE") + assert image_node.outputs["Color"].links[0].to_socket.name == "Base Color" + assert image_node.inputs["Vector"].links[0].from_socket.name == "Generated" + assert image_node.image.filepath == "" + assert np.allclose( + image_node.image.pixels[:], pixel_data, atol=0.01 + ), f"Failed to match pixels for {n_components}.\nBlender pixel_data: {image_node.image.pixels[:]}.\nExpected data: {pixel_data}" + class TestLoadingIndexedTextureMap(NewFile): def test_run(self):