Write docs for style API module

This commit is contained in:
Dion Moult
2023-01-04 16:09:16 +11:00
parent 9e45435024
commit b86818ead3
9 changed files with 414 additions and 44 deletions
@@ -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")
@@ -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"])
@@ -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)
@@ -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"]
@@ -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"]:
@@ -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():
@@ -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():
@@ -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"])
@@ -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()