diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/add_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/add_style.py index 503dc63b84..40682c8fec 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/add_style.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/add_style.py @@ -18,12 +18,45 @@ class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, name=None, ifc_class="IfcSurfaceStyle"): + """Add a new presentation style + + A presentation style is a container of visual settings (called + presentation items) that affect the appearance of objects. There are + four types of style: + + - Surface styles, which give 3D objects (which have surfaces / faces) + their colours and textures. This is the most common type of style. + - Curve styles, which give 2D and 3D curves, lines, polylines, their + stroke thickness and colour. + - Fill area styles, which gives 2D polygons and flat 3D planes their + colours, hatch patterns, tiled patterns, and pattern scales. + - Text styles, which gives text their font family, weight, variant, + size, indentation, alignment, decoration, spacing, and transformation. + + Once you have created a presentation style object, you can further + define the properties of your style using other API functions by adding + presentation items, such as ifcopenshell.api.style.add_surface_style. + + :param name: The name of the style. Used to easily identify it using a + style library. + :type name: str,optional + :param ifc_class: Choose from IfcSurfaceStyle, IfcCurveStyle, + IfcFillAreaStyle, or IfcTextStyle. + :type ifc_class: str + :return: The newly created style element, based on the provided + ifc_class. + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Create a new surface style + style = ifcopenshell.api.run("style.add_style", model) + """ self.file = file - self.settings = {"name": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"name": name, "ifc_class": ifc_class} def execute(self): - # Name is filled out because Revit treats this incorrectly as the material name - return self.file.createIfcSurfaceStyle(self.settings["name"], "BOTH") + if self.settings["ifc_class"] == "IfcSurfaceStyle": + # Name is filled out because Revit treats this incorrectly as the material name + return self.file.createIfcSurfaceStyle(self.settings["name"], "BOTH") diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_style.py index f859419239..ee76d41419 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_style.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_style.py @@ -21,11 +21,98 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, style=None, ifc_class="IfcSurfaceStyleShading", attributes=None): + """Adds a new presentation item to a surface style + + A surface style can have multiple different types of presentation items + assigned to it: + + - Shading, this is the simplest item, which defines a single basic + colour and transparency that can be used to display the object on a + screen. It is an indicative colour of what the object would be in real + life. It is commonly incorrectly abused to colour code systems for MEP + equipment or object types for structural steel. If you just want to + give something a colour, this is what you need. + - Rendering, this is an advanced extension of shading, which includes + the definition of a shader for a rendering engine. You may select the + reflectance / lighting model such as PHYSICAL, for PBR style + rendering, or FLAT, for flat shading, or PHONG for older biased + rendering workflows. Based on the chosen lighting model, you may then + specify the appropriate colour maps, such as diffuse colours, + specularity, emissive component, etc. These lighting models are fully + compatible with glTF and X3D. This should be used if your model is + prepared to be rendered by a rendering engine which is compatible with + glTF / X3D shader descriptions. If you are doing archviz or 3D + rendering, this is what you need. + - Textures, this is a special type of Rendering presentation item that + uses image textures instead of single colours. Textures may be either + mapped using a bounding box stretch mapping, or with UV coordinates + for mesh-like geometry. + - Lighting, this is used to define photometrically accurate colour + parameters used in lighting simulation. If you are a simulationist, + this is what you need. + - Reflectance, this is a special type of Lighting presentation item + which includes some lesser used photometric properties, typically + required for advanced materials like glazing. + - External, this is for any other surface style defined using an + external URI. This is relevant if you are using a third-party non-glTF + compatible shader definition such as for Cycles, Renderman, V-Ray, + etc, or a complex lighting simulation definition, such as for + Radiance. + + Shading is sufficient for the majority of basic models. + + The attributes you specify will depend on the type of presentation item + you are adding. An example is shown below, but for full details please + refer to the IFC documentation. + + :param style: The IfcSurfaceStyle you want to add to presentation item + to. See ifcopenshell.api.style.add_style. + :type style: ifcopenshell.entity_instance.entity_instance + :param ifc_class: Choose from IfcSurfaceStyleShading, + IfcSurfaceStyleRendering, IfcSurfaceStyleWithTextures, + IfcSurfaceStyleLighting, IfcSurfaceStyleReflectance, or + IfcExternallyDefinedSurfaceStyle. + :type ifc_class: str + :param attributes: a dictionary of attribute names and values. + :type attributes: dict, optional + :return: The newly created presentation item based on the provided + ifc_class. + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Create a new surface style + style = ifcopenshell.api.run("style.add_style", model) + + # Create a simple shading colour and transparency. + ifcopenshell.api.run("style.add_surface_style", model, + style=style, ifc_class="IfcSurfaceStyleShading", attributes={ + "SurfaceColour": { "Name": None, "Red": 1.0, "Green": 0.8, "Blue": 0.8 }, + "Transparency": 0., # 0 is opaque, 1 is transparent + }) + + # Alternatively, create a rendering style. + ifcopenshell.api.run("style.add_surface_style", model, + style=style, ifc_class="IfcSurfaceStyleRendering", attributes={ + # A surface colour and transparency is still supplied for + # viewport display only. This will supersede the shading + # presentation item. + "SurfaceColour": { "Name": None, "Red": 1.0, "Green": 0.8, "Blue": 0.8 }, + "Transparency": 0., # 0 is opaque, 1 is transparent + + # NOTDEFINED is assumed to be a PHYSICAL (PBR) lighting + # model. In IFC4X3, you may choose PHYSICAL directly. + "ReflectanceMethod": "NOTDEFINED", + + # For PBR shading, you may specify these parameters: + "DiffuseColour": { "Name": None, "Red": 0.9, "Green": 0.8, "Blue": 0.8 }, + "SpecularColour": 0.1, # Metallic factor + "SpecularHighlight": {"SpecularRoughness": 0.5}, # Roughness factor + }) + """ self.file = file - self.settings = {"style": None, "ifc_class": "IfcSurfaceStyleRendering", "attributes": {}} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"style": style, "ifc_class": ifc_class, "attributes": attributes or {}} def execute(self): style_item = self.file.create_entity(self.settings["ifc_class"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py index 546cd0417f..21a45fa29f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py @@ -21,12 +21,25 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, material=None, uv_maps=None): + """Add a surface texture based on a Blender material definition + + Warning: this API can only be used with Blender data structures. + + :param material: The Blender material definition with a node tree that + is compatible with glTF. See one of the valid combinations here: + https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html + :type material: bpy.types.Material + :param uv_maps: A list of IfcIndexedTextureMap for any + IfcTessellatedFaceSets that the representation has, obtained from + the HasTextures attribute. + :type uv_maps: list[ifcopenshell.entity_instance.entity_instance] + :return: A list of IfcImageTexture + :rtype: list[ifcopenshell.entity_instance.entity_instance] + """ # TODO: This usecase currently depends on Blender's data model self.file = file - self.settings = {"material": None, "uv_maps": []} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"material": material, "uv_maps": uv_maps or []} def execute(self): if self.file.schema == "IFC2X3": @@ -133,7 +146,6 @@ class Usecase: self.apply_uv_map_to_texture(texture) def apply_uv_map_to_texture(self, texture): - print('texture', texture) for uv_map in self.settings["uv_maps"]: maps = set(uv_map.Maps or []) maps.add(texture) diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/assign_material_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/assign_material_style.py index 6deab8578b..152d9f42c7 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/assign_material_style.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/assign_material_style.py @@ -18,16 +18,87 @@ class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, material=None, style=None, context=None, should_use_presentation_style_assignment=False): + """Assigns a style to a material + + A style may either be assigned directly to an object's representation, + or to a material which is then associated with the object. If both + exist, then the style assigned directly to the object's representation + takes precedence. It is recommended to use materials and assign styles + to materials. This API function provides that capability. + + :param material: The IfcMaterial which you want to assign the style to. + :type material: ifcopenshell.entity_instance.entity_instance + :param style: The IfcPresentationStyle (typically IfcSurfaceStyle) that + you want to assign to the material. This will then be applied to all + objects that have that material. + :type style: ifcopenshell.entity_instance.entity_instance + :param context: The IfcGeometricRepresentationSubContext at which this + style should be used. Typically this is the Model BODY context. + :type context: ifcopenshell.entity_instance.entity_instance + :param should_use_presentation_style_assignment: This is a technical + detail to accomodate a bug in Revit. This should always be left as + the default of False, unless you are finding that colours aren't + showing up in Revit. In that case, set it to True, but keep in mind + that this is no longer a valid IFC. Blame Autodesk. + :type should_use_presentation_style_assignment: bool + :return: None + :rtype: None + + Example:: + + # A model context is needed to store 3D geometry + model3d = ifcopenshell.api.run("context.add_context", model, context_type="Model") + + # Specifically, we want to store body geometry + body = ifcopenshell.api.run("context.add_context", model, + context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d) + + # Let's create a new wall. The wall does not have any geometry yet. + wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall") + + # Let's use the "3D Body" representation we created earlier to add a + # new wall-like body geometry, 5 meters long, 3 meters high, and + # 200mm thick + representation = ifcopenshell.api.run("geometry.add_wall_representation", model, + context=body, length=5, height=3, thickness=0.2) + + # Assign our new body geometry back to our wall + ifcopenshell.api.run("geometry.assign_representation", model, + product=wall, representation=representation) + + # Place our wall at the origin + ifcopenshell.api.run("geometry.edit_object_placement", model, product=wall) + + # Let's prepare a concrete material. Note that our concrete material + # does not have any colours (styles) at this point. + concrete = ifcopenshell.api.run("material.add_material", model, name="CON01", category="concrete") + + # Assign our concrete material to our wall + ifcopenshell.api.run("material.assign_material", model, + product=wall, type="IfcMaterial", material=concrete) + + # Create a new surface style + style = ifcopenshell.api.run("style.add_style", model) + + # Create a simple grey shading colour and transparency. + ifcopenshell.api.run("style.add_surface_style", model, + style=style, ifc_class="IfcSurfaceStyleShading", attributes={ + "SurfaceColour": { "Name": None, "Red": 0.5, "Green": 0.5, "Blue": 0.5 }, + "Transparency": 0., # 0 is opaque, 1 is transparent + }) + + # Now any element (like our wall) with a concrete material will have + # a grey colour applied. + ifcopenshell.api.run("style.assign_material_style", model, material=concrete, style=style, context=body) + """ self.file = file self.settings = { - "material": None, - "style": None, - "context": None, - "should_use_presentation_style_assignment": False, + "material": material, + "style": style, + "context": context, + "should_use_presentation_style_assignment": should_use_presentation_style_assignment, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): self.style = self.settings["style"] diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/assign_representation_styles.py b/src/ifcopenshell-python/ifcopenshell/api/style/assign_representation_styles.py index 55da846050..52db9b9c73 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/assign_representation_styles.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/assign_representation_styles.py @@ -18,15 +18,83 @@ class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, shape_representation=None, styles=None, should_use_presentation_style_assignment=False): + """Assigns a style directly to an object representation + + A style may either be assigned directly to an object's representation, + or to a material which is then associated with the object. If both + exist, then the style assigned directly to the object's representation + takes precedence. It is recommended to use materials and assign styles + to materials. However, sometimes you may want to assign colours directly + to the object representation as an override. This API function provides + that capability. + + If you want to assign styles to a material instead (recommended), then + please see ifcopenshell.api.style.assign_material_style. + + :param shape_representation: The IfcShapeRepresentation of the object + that you want to assign styles to. This implicitly defines the + context at which the styles should be used. + :type shape_representation: ifcopenshell.entity_instance.entity_instance + :param styles: A list of presentation styles, typically IfcSurfaceStyle. + The number of items in the list should correlate with the number of + items in the shape_representation's Items attribute. If you have + more items than styles, the last style is used. + :type styles: list[ifcopenshell.entity_instance.entity_instance] + :param should_use_presentation_style_assignment: This is a technical + detail to accomodate a bug in Revit. This should always be left as + the default of False, unless you are finding that colours aren't + showing up in Revit. In that case, set it to True, but keep in mind + that this is no longer a valid IFC. Blame Autodesk. + :type should_use_presentation_style_assignment: bool + :return: None + :rtype: None + + Example:: + + # A model context is needed to store 3D geometry + model3d = ifcopenshell.api.run("context.add_context", model, context_type="Model") + + # Specifically, we want to store body geometry + body = ifcopenshell.api.run("context.add_context", model, + context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d) + + # Let's create a new wall. The wall does not have any geometry yet. + wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall") + + # Let's use the "3D Body" representation we created earlier to add a + # new wall-like body geometry, 5 meters long, 3 meters high, and + # 200mm thick + representation = ifcopenshell.api.run("geometry.add_wall_representation", model, + context=body, length=5, height=3, thickness=0.2) + + # Assign our new body geometry back to our wall + ifcopenshell.api.run("geometry.assign_representation", model, + product=wall, representation=representation) + + # Place our wall at the origin + ifcopenshell.api.run("geometry.edit_object_placement", model, product=wall) + + # Create a new surface style + style = ifcopenshell.api.run("style.add_style", model) + + # Create a simple grey shading colour and transparency. + ifcopenshell.api.run("style.add_surface_style", model, + style=style, ifc_class="IfcSurfaceStyleShading", attributes={ + "SurfaceColour": { "Name": None, "Red": 0.5, "Green": 0.5, "Blue": 0.5 }, + "Transparency": 0., # 0 is opaque, 1 is transparent + }) + + # Now specifically our wall only will be coloured grey. + ifcopenshell.api.run("style.assign_representation_styles", model, + shape_representation=representation, styles=[style]) + """ self.file = file self.settings = { - "shape_representation": None, - "styles": [], - "should_use_presentation_style_assignment": False, + "shape_representation": shape_representation, + "styles": styles or [], + "should_use_presentation_style_assignment": should_use_presentation_style_assignment, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): if not self.settings["styles"]: diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/edit_presentation_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/edit_presentation_style.py index 48a44a1d28..119159d9a8 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/edit_presentation_style.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/edit_presentation_style.py @@ -18,11 +18,29 @@ class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, style=None, attributes=None): + """Edits the attributes of an IfcPresentationStyle + + For more information about the attributes and data types of an + IfcPresentationStyle, consult the IFC documentation. + + :param style: The IfcPresentationStyle entity you want to edit + :type style: ifcopenshell.entity_instance.entity_instance + :param attributes: a dictionary of attribute names and values. + :type attributes: dict, optional + :return: None + :rtype: None + + Example:: + + # Create a new surface style + style = ifcopenshell.api.run("style.add_style", model) + + # Change the name of the style to "Foo" + ifcopenshell.api.run("style.edit_presentation_style", model, style=style, attributes={"Name": "Foo"}) + """ self.file = file - self.settings = {"style": None, "attributes": {}} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"style": style, "attributes": attributes or {}} def execute(self): for name, value in self.settings["attributes"].items(): diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py index c58f5f905f..f908727083 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/edit_surface_style.py @@ -18,11 +18,57 @@ class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, style=None, attributes=None): + """Edits the attributes of an IfcPresentationItem + + For more information about the attributes and data types of an + IfcPresentationItem, consult the IFC documentation. + + The IfcPresentationItem is expected to be one of IfcSurfaceStyleShading, + IfcSurfaceStyleRendering, IfcSurfaceStyleWithTextures, + IfcSurfaceStyleLighting, IfcSurfaceStyleReflectance, or + IfcExternallyDefinedSurfaceStyle. + + To represent a colour, a nested dictionary should be used. See the + example below. + + :param style: The IfcPresentationStyle entity you want to edit + :type style: ifcopenshell.entity_instance.entity_instance + :param attributes: a dictionary of attribute names and values. + :type attributes: dict, optional + :return: None + :rtype: None + + Example:: + + # Create a new surface style + style = ifcopenshell.api.run("style.add_style", model) + + # Create a blank rendering style. + rendering = ifcopenshell.api.run("style.add_surface_style", model, + style=style, ifc_class="IfcSurfaceStyleRendering") + + # Edit the attributes of the rendering style. + ifcopenshell.api.run("style.edit_surface_style", model, + style=rendering, attributes={ + # A surface colour and transparency is still supplied for + # viewport display only. This will supersede the shading + # presentation item. + "SurfaceColour": { "Name": None, "Red": 1.0, "Green": 0.8, "Blue": 0.8 }, + "Transparency": 0., # 0 is opaque, 1 is transparent + + # NOTDEFINED is assumed to be a PHYSICAL (PBR) lighting + # model. In IFC4X3, you may choose PHYSICAL directly. + "ReflectanceMethod": "NOTDEFINED", + + # For PBR shading, you may specify these parameters: + "DiffuseColour": { "Name": None, "Red": 0.9, "Green": 0.8, "Blue": 0.8 }, + "SpecularColour": 0.1, # Metallic factor + "SpecularHighlight": {"SpecularRoughness": 0.5}, # Roughness factor + }) + """ self.file = file - self.settings = {"style": None, "attributes": {}} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"style": style, "attributes": attributes or {}} def execute(self): for key, value in self.settings["attributes"].items(): diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/remove_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/remove_style.py index 761b00fbf8..64419b6e0c 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/remove_style.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/remove_style.py @@ -20,11 +20,26 @@ import ifcopenshell.util.element class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, style=None): + """Removes a presentation style + + All of the presentation items of the style will also be removed. + + :param style: The IfcPresentationStyle to remove. + :type style: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Create a new surface style + style = ifcopenshell.api.run("style.add_style", model) + + # Not anymore! + ifcopenshell.api.run("style.remove_style", model, style=style) + """ self.file = file - self.settings = {"style": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"style": style} def execute(self): self.purge_styled_items(self.settings["style"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/remove_surface_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/remove_surface_style.py index da43861851..476f4f1a36 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/remove_surface_style.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/remove_surface_style.py @@ -20,11 +20,31 @@ import ifcopenshell.util.element class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, style=None): + """Removes a presentation item from a presentation style + + :param style: The IfcPresentationItem to remove. + :type style: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Create a new surface style + style = ifcopenshell.api.run("style.add_style", model) + + # Create a simple shading colour and transparency. + shading = ifcopenshell.api.run("style.add_surface_style", model, + style=style, ifc_class="IfcSurfaceStyleShading", attributes={ + "SurfaceColour": { "Name": None, "Red": 1.0, "Green": 0.8, "Blue": 0.8 }, + "Transparency": 0., # 0 is opaque, 1 is transparent + }) + + # Remove the shading item + ifcopenshell.api.run("style.remove_surface_style", model, style=shading) + """ self.file = file - self.settings = {"style": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"style": style} def execute(self): to_delete = set()