From 0db28917a45339b4ed25cb8334913d89e3187b05 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 6 Oct 2022 17:06:37 +1100 Subject: [PATCH] Write docs on IOS geometry processing. See #671. --- .../docs/users/dealing_with_large_models.rst | 4 +- .../docs/ifcopenshell-python.rst | 2 +- .../ifcopenshell-python/geometry_iterator.rst | 53 ----- .../geometry_processing.rst | 202 ++++++++++++++++++ src/ifcopenshell-python/docs/ifcpatch.rst | 4 +- 5 files changed, 207 insertions(+), 58 deletions(-) delete mode 100644 src/ifcopenshell-python/docs/ifcopenshell-python/geometry_iterator.rst create mode 100644 src/ifcopenshell-python/docs/ifcopenshell-python/geometry_processing.rst diff --git a/src/blenderbim/docs/users/dealing_with_large_models.rst b/src/blenderbim/docs/users/dealing_with_large_models.rst index 74ac75c1de..abb4c13527 100644 --- a/src/blenderbim/docs/users/dealing_with_large_models.rst +++ b/src/blenderbim/docs/users/dealing_with_large_models.rst @@ -125,8 +125,8 @@ elements starting at item number 30,000 and ending at item number 50,000. This allows you to arbitrarily break down large models into submodels. This can be combined with other filters. -`Beta versions of Blender 3.3 `__ -may be used which will result in a faster load time (~50%). +Using Blender 3.3 and above will result in a faster load time (~50%) compared to +older Blender versions. Coordination only models ------------------------ diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python.rst b/src/ifcopenshell-python/docs/ifcopenshell-python.rst index 8cf57134cc..9e9471369e 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python.rst @@ -12,5 +12,5 @@ system, as well as high level analysis and authoring functions. ifcopenshell-python/installation ifcopenshell-python/hello_world ifcopenshell-python/code_examples - ifcopenshell-python/geometry_iterator + ifcopenshell-python/geometry_processing ifcopenshell-python/developer_guide diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_iterator.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_iterator.rst deleted file mode 100644 index faa717a2e0..0000000000 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_iterator.rst +++ /dev/null @@ -1,53 +0,0 @@ -Geometry iterator -================= - -IfcOpenShell provides a geometry iterator function to efficiently process -geometry in an IFC model. The iterator is always used in IfcConvert, and may -also be invoked in C++ or in Python. - -The geometry iterator makes it easy to collect possible geometry in a model, -supports multicore processing, and implements caching and reuse to improve the -efficiency of geometry processing. It is also possible to process geometry one -by one using ``create_shape()``, but is significantly less efficient. - -By default, the geometry iterator processes all 3D geometry in a model from all -elements, and returns a list of X Y Z vertex ordinates in a flattend list, as -well as a flattend list of triangulated faces denoted by vertex indices. - -There are a variety of configuration settings to get different output. For -example, you may filter elements from processing, extract 2D data, or return -non-triangulated OpenCASCADE BReps. For more information on the various -settings, see :doc:`Geometry Settings<../ifcopenshell/geometry_settings>`. - -Here is a simple example in Python: - -.. code-block:: python - - import multiprocessing - import ifcopenshell - import ifcopenshell.geom - - ifc_file = ifcopenshell.open('model.ifc') - - settings = ifcopenshell.geom.settings() - iterator = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count()) - if iterator.initialize(): - while True: - shape = iterator.get() - # Get the current IFC element we are iterating over - element = ifc_file.by_guid(shape.guid) - # Indices of vertices per triangle face e.g. [f1v1, f1v2, f1v3, f2v1, f2v2, f2v3, ...] - faces = shape.geometry.faces - # X Y Z of vertices in flattened list e.g. [v1x, v1y, v1z, v2x, v2y, v2z, ...] - verts = shape.geometry.verts - # Material names and colour style information that are relevant to this shape - materials = shape.geometry.materials - # Indices of material applied per triangle face e.g. [f1m, f2m, ...] - material_ids = shape.geometry.material_ids - - # Since the lists are flattened, you may prefer to group them per - # face like so depending on your geometry kernel - grouped_verts = [[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)] - grouped_faces = [[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)] - if not iterator.next(): - break diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_processing.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_processing.rst new file mode 100644 index 0000000000..987cb23dae --- /dev/null +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_processing.rst @@ -0,0 +1,202 @@ +Geometry processing +=================== + +Geometry is specified in many ways in IFC. Some geometry is defined explicitly +with coordinates, vertices, and faces. Some geometry is defined implicitly with +equations, boolean operations, and parametric shapes. + +Individual processing +--------------------- + +The simplest way to process any geometry in a standardised fashion is to use the +IfcOpenShell ``create_shape()`` function. This will provide a list of vertices, +edges, and faces, or alternatively an OpenCASCADE BRep. + +Here is a simple example of processing a single wall into a list of vertices and +faces. In this example, a ``shape`` variable is returned, which holds geometry +related information in ``shape.geometry``: + +.. code-block:: python + + import ifcopenshell + import ifcopenshell.geom + + ifc_file = ifcopenshell.open('model.ifc') + element = ifc_file.by_type('IfcWall')[0] + + settings = ifcopenshell.geom.settings() + shape = ifcopenshell.geom.create_shape(settings, element) + + # The GUID of the element we processed + print(shape.guid) + + # The ID of the element we processed + print(shape.id) + + # The element we are processing + print(ifc_file.by_guid(shape.guid)) + + # A unique geometry ID, useful to check whether or not two geometries are + # identical for caching and reuse. The naming scheme is: + # IfcShapeRepresentation.id{-layerset-LayerSet.id}{-material-Material.id}{-openings-[Opening n.id ...]}{-world-coords} + print(shape.geometry.id()) + + # Indices of vertices per triangle face e.g. [f1v1, f1v2, f1v3, f2v1, f2v2, f2v3, ...] + faces = shape.geometry.faces + + # X Y Z of vertices in flattened list e.g. [v1x, v1y, v1z, v2x, v2y, v2z, ...] + verts = shape.geometry.verts + + # Since the lists are flattened, you may prefer to group them per face like so depending on your geometry kernel + grouped_verts = [[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)] + grouped_faces = [[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)] + + # A list of styles that are relevant to this shape + styles = shape.geometry.materials + + for style in styles: + # Each style is named after the entity class if a default + # material is applied. Otherwise, it is named "surface-style-{SurfaceStyle.name}" + # All non-alphanumeric characters are replaced with a "-". + print(style.original_name) + + # A more human readable name + print(style.name) + + # Each style may have diffuse colour RGB codes + if style.has_diffuse: + print(style.diffuse) + + # Each style may have transparency data + if style.has_transparency: + print(style.transparency) + + # Indices of material applied per triangle face e.g. [f1m, f2m, ...] + material_ids = shape.geometry.material_ids + +Alternatively, you may choose to retrieve an OpenCASCADE BRep: + +.. code-block:: python + + import ifcopenshell + import ifcopenshell.geom + + ifc_file = ifcopenshell.open('model.ifc') + element = ifc_file.by_type('IfcWall')[0] + + settings = ifcopenshell.geom.settings() + settings.set(settings.USE_PYTHON_OPENCASCADE, True) + + try: + shape = geom.create_shape(settings, element) + geometry = shape.geometry # see #1124 + # These are methods of the TopoDS_Shape class from pythonOCC + shape_gpXYZ = geometry.Location().Transformation().TranslationPart() + # These are methods of the gpXYZ class from pythonOCC + print(shape_gpXYZ.X(), shape_gpXYZ.Y(), shape_gpXYZ.Z()) + except: + print("Shape creation failed") + +When an entire element is passed into ``create_shape()``, the 3D representation +is processed by default with all openings applied. However, it is also possible +to only process a single shape representation with no openings, representation +item, or profile definition. + +In these scenarios, a ``geometry`` is returned directly, equivalent to +``shape.geometry`` in the example above. + +.. code-block:: python + + ifc_file = ifcopenshell.open('model.ifc') + element = ifc_file.by_type('IfcWall')[0] + + # Process a shape representation + body = ifcopenshell.util.representation.get_representation(element, "Model", "Body") + + # Note: geometry is returned directly, equivalent to shape.geometry when passing in an element + geometry = geom.create_shape(settings, body) + + # Process a representation item + geometry = geom.create_shape(settings, ifc_file.by_type("IfcExtrudedAreaSolid")[0]) + + # Process a profile + geometry = geom.create_shape(settings, ifc_file.by_type("IfcProfileDef")[0]) + +Geometry iterator +----------------- + +IfcOpenShell provides a geometry iterator function to efficiently process +geometry in an IFC model. The iterator is always used in IfcConvert, and may +also be invoked in C++ or in Python. It offers the same features as the +``create_shape()`` function for `Individual processing`_. + +The geometry iterator makes it easy to collect possible geometry in a model, +supports multicore processing, and implements caching and reuse to improve the +efficiency of geometry processing. For any bulk geometry processing, it is +always recommended to use the iterator. + +By default, the geometry iterator processes all 3D geometry in a model from all +elements, and returns a list of X Y Z vertex ordinates in a flattened list, as +well as a flattened list of triangulated faces denoted by vertex indices. + +There are a variety of configuration settings to get different output. For +example, you may filter elements from processing, extract 2D data, or return +non-triangulated OpenCASCADE BReps. For more information on the various +settings, see :doc:`Geometry Settings<../ifcopenshell/geometry_settings>`. + +Here is a simple example in Python: + +.. code-block:: python + + import multiprocessing + import ifcopenshell + import ifcopenshell.geom + + ifc_file = ifcopenshell.open('model.ifc') + + settings = ifcopenshell.geom.settings() + iterator = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count()) + if iterator.initialize(): + while True: + shape = iterator.get() + faces = shape.geometry.faces + verts = shape.geometry.verts + materials = shape.geometry.materials + material_ids = shape.geometry.material_ids + # ... write code to process geometry here ... + if not iterator.next(): + break + +.. note:: + + The iterator can only be used to process whole elements, not individual + shape representations, representation items, and profiles. + +Manual parsing +-------------- + +IfcOpenShell lets you traverse any IFC entity graph. This means it is possible +for you to manually browse through the ``Representation`` attribute of IFC +elements, and parse the corresponding IFC shape representations yourself instead +of using generic geometric processing such as `Individual processing`_ and the +`Geometry iterator`_. + +This approach requires an in-depth understanding of IFC geometry +representations, as well as its many caveats with units and transformations, but +can be very simple and extremely fast to extract specific types of geometry. For +example, if you know you are dealing with IfcCircle geometry, you can +specifically pinpoint the Radius parameter. + +.. code-block:: python + + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) + + for circle in ifc_file.by_type("IfcCircle"): + # In project length units + print(circle.Radius) + + # In SI meters + print(circle.Radius * unit_scale) + +Given the advanced nature of manual processing, it is generally not recommended +except in specific tasks. diff --git a/src/ifcopenshell-python/docs/ifcpatch.rst b/src/ifcopenshell-python/docs/ifcpatch.rst index af1f3eb5b6..a1efe2b46d 100644 --- a/src/ifcopenshell-python/docs/ifcpatch.rst +++ b/src/ifcopenshell-python/docs/ifcpatch.rst @@ -41,8 +41,8 @@ utility: Exactly how it is run depends on the recipe. A recipe may require zero or more arguments which are specific to the recipe. Here's an example which runs the `ExtractElements` recipe, which, as the same suggests, extracts out elements. -This recipe expects one argument, which uses the [IFC Query -syntax](https://wiki.osarch.org/index.php?title=IfcOpenShell_code_examples#IFC_Query_Syntax). +This recipe expects one argument, which uses the `IFC Query +syntax `_. In this example, we'll extract out all `IfcWall` elements. ::