diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_tree.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_tree.rst index 485113d735..0069dd341b 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_tree.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_tree.rst @@ -1,14 +1,16 @@ Geometry tree ============= -IfcOpenShell includes a utility to build a unbalanced binary tree of geometry -and their bounding boxes. After a tree is built, you can efficiently select -geometry by specifying a point, radius, or bounding box. +IfcOpenShell includes a utility to build trees of geometry and their bounding +boxes. Geometry trees can be used to efficiently select geometry or collide +geometry with one another. .. image:: images/geometry-tree.png -The most efficient way to build tree is by using the iterator, as shown in the -example below: +The most efficient way to build a tree is by using the iterator. If the native +OpenCASCADE shape is added to the tree, a **UB Tree** is built. Alternatively, +if triangulation is added to the tree, a **BVH Tree** is built. The type of +tree determines the type of operation you can perform. .. code-block:: python @@ -21,27 +23,208 @@ example below: iterator = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count()) if iterator.initialize(): while True: - tree.add_element(iterator.get_native()) + # Use triangulation to build a BVH tree + tree.add_element(iterator.get()) + + # Alternatively, use this code to build an unbalanced binary tree + # tree.add_element(iterator.get_native()) + if not iterator.next(): break -Once built, there are three methods you can use to select elements in the tree: -``select_box``, ``select``, and ``select_ray``. +Clashing or selecting geometry from a geometry tree +--------------------------------------------------- -``select_box`` lets you query for elements that contain a point or another -element. However, it only checks the bounding box of elements instead of their -exact geometry. This is the fastest approach and is recommended if you don't -need precise geometry selection. +With a **BVH Tree**, you can efficiently clash sets of elements with other +elements. You can find elements that intersect, collide, or are within a +clearance distance threshold of one another. There are three methods you can +use to clash elements in the tree. Each function collides one set of elements +with another set of elements. -``select`` lets you query for elements that contain a point, a sphere, or -another element. ``select`` is similar to select box, but additionally -considers the actual geometry of the object. This is slower but more precise. +- `Detecting intersection clashes between elements`_ detects when an element intersects with another + element. This is the most common type of clash detection used when + coordinating designs. For example, you might want to know if any pipes go + through structural columns or beams. +- `Detecting collision clashes between elements`_ detects when an element + touches another element. It is the fastest type of clash detection but does + not consider the distance that an element goes inside another element. This + considers surfaces only so it works on non-manifold geometry but will not + detect if an element is completely within another element. +- `Detecting clearance clashes between elements`_ detects when an element comes + near to another element within a clearance threshold. This is the slowest + type of clash detection. It works on non-manifold geometry and does not + consider inside vs outside. Elements like pipe and ducts with insulation, + structural openings, and equipment will typically require clearance checks. -``select_ray`` lets you query for elements that intersect with a ray. +With a **UB Tree**, you can efficiently select geometry by specifying a point, +radius, or bounding box. There are three methods you can use to select elements +in the tree. + +- `Selecting elements using bounding boxes`_ lets you query for elements that + contain a point or another element. However, it only checks the bounding box + of elements instead of their exact geometry. This is the fastest approach and + is recommended if you don't need precise geometry selection. +- `Selecting elements using precise geometry`_ lets you query for elements that + contain a point, a sphere, or another element. This is similar to selecting + using bounding boxes, but additionally considers the actual geometry of the + element. This is slower but more precise. +- `Selecting elements using a ray`_ lets you query for elements that intersect + with a ray. + +Detecting intersection clashes between elements +----------------------------------------------- + +``clash_intersection_many`` detects when an element intersects with or is +contained within another element. + +.. code-block:: python + + clashes = tree.clash_intersection_many( + group_a_elements, # e.g. from model.by_type("IfcWall") + group_b_elements, # Group b can be the same as group a if you want to clash within a single set + tolerance=0.002, # Any protrusions less than 2mm are ignored + check_all=True, # Keep on checking all potential intersections to find a worst case protrusion distance + ) + + for clash in clashes: + # Get the two elements that clash and their metadata + element1 = clash.a + element2 = clash.b + a_global_id = element1.get_argument(0) + b_global_id = element2.get_argument(0) + a_ifc_class = element1.is_a() + b_ifc_class = element2.is_a() + a_name = element1.get_argument(2) + b_name = element2.get_argument(2) + + # Potential clash types that can be detected are protrusions, pierces, and collisions + clash_type = ["protrusion", "pierce", "collision", "clearance"][clash.clash_type], + + # P1 and P2 represents two XYZ coordinates. The meaning of the coordinate depends on the clash type. + p1 = list(clash.p1) + p2 = list(clash.p2) + + # This represents the protrusion or piercing distance in meters. + # It is also the distance between P1 and P2. + distance = clash.distance + +If you specify a ``tolerance`` value, intersections with a protrusion distance +smaller than this tolerance are excluded. It is recommended to specify a +non-zero tolerance to distinguish between when elements merely touch (e.g. a +GPO on a wall) versus if they are truly intersecting (e.g. a pipe going through +a beam). + +If ``check_all`` is ``False``, the clash check will return as soon as an +intersection is found. This is faster but may not return the worst-case +protrusion distance. If you are not interested in the protrusion distance, it +is recommended to set this to ``False``. If you want the protrusion distance, +such as to prioritise which clashes are more severe, set this to ``True``. + +This includes: + +1. When an element X protrudes inside element Y, where element Y is manifold. + In this case, a protrusion distance is calculated as the deepest point of + element X to the closest surface of element Y. ``P1`` is defined as the XYZ + coordinate on element X, and ``P2`` is defined as the nearest point on the + surface of element Y. +2. When an element X pierces element Y, such that an edge of element X enters + element Y and leaves through another face. In this case, a piercing distance + is calculated as the distance where that edge is inside element Y. ``P1`` is + defined as the point on an edge of element X which enters element Y, and + ``P2`` is the point where that edge leaves element Y. +3. When neither X or Y is manifold, we cannot detect protrusion or piercing, so + instead when X and Y have any touching face. This is the same as the + ``clash_collision_many`` check below. The distance is considered to be zero + and ignores your specified tolerance. ``P1`` and ``P2`` are equal and + represent an arbitrary XYZ point where the two elements touch. + +Detecting collision clashes between elements +-------------------------------------------- + +``clash_collision_many`` detects when the surface of an element collides with +another element. The surfaces may either merely touch (e.g. are coplanar) or +intersect. + +.. code-block:: python + + clashes = tree.clash_collision_many( + group_a_elements, # e.g. from model.by_type("IfcWall") + group_b_elements, # Group b can be the same as group a if you want to clash within a single set + allow_touching=True, # Include results where faces merely touch but do not intersect + ) + + for clash in clashes: + # Get the two elements that clash and their metadata + element1 = clash.a + element2 = clash.b + a_global_id = element1.get_argument(0) + b_global_id = element2.get_argument(0) + a_ifc_class = element1.is_a() + b_ifc_class = element2.is_a() + a_name = element1.get_argument(2) + b_name = element2.get_argument(2) + + # P1 and P2 represents two possible arbitrary points where a collision is found. + # P1 may or may not be equal to P2. + p1 = list(clash.p1) + p2 = list(clash.p2) + +A collision between two surface triangles may be "touching" or "intersecting". +Two touching triangles may be coplanar or merely have a single edge or vertex +touching the other triangle. An intersecting triangle will have at least one +edge that goes through the other triangle. + +Detecting clearance clashes between elements +-------------------------------------------- + +``clash_clearance_many`` detects with the surface of an element comes within a +clearance distance threshold of another element. + +.. code-block:: python + + clashes = tree.clash_clearance_many( + group_a_elements, # e.g. from model.by_type("IfcWall") + group_b_elements, # Group b can be the same as group a if you want to clash within a single set + clearance=0.1, # Any surface closer than than 100mm is a clash + check_all=False, # Stop measuring distances once the first clearance violation is found per element. + ) + + for clash in clashes: + # Get the two elements that clash and their metadata + element1 = clash.a + element2 = clash.b + a_global_id = element1.get_argument(0) + b_global_id = element2.get_argument(0) + a_ifc_class = element1.is_a() + b_ifc_class = element2.is_a() + a_name = element1.get_argument(2) + b_name = element2.get_argument(2) + + # P1 and P2 represents the two XYZ coordinates between element1 and element2. + p1 = list(clash.p1) + p2 = list(clash.p2) + + # This represents the distance between element1 and element2 that is less than the clearance. + # It is the distance between P1 and P2. It cannot be less than 0. + distance = clash.distance + +You cannot specify a ``clearance`` less than 0. + +If ``check_all`` is ``False``, the clash check will return as soon as a +clearance violation is found. This is faster but may not return the worst-case +distance. If you only interested whether there is a clearance issue, it is +recommended to set this to ``False``. If you want the exact worst case +clearance distance, such as to prioritise which clashes are more severe, set +this to ``True``. Selecting elements using bounding boxes --------------------------------------- +Elements may be queried using an axis aligned bounding box. An axis aligned +bounding box is the bounding box using global XYZ axes, not the element's local +XYZ axes. If you have a vertical construction project, this means that your +model should be oriented to project north to get the best results. + You may select all elements that have a bounding box containing the point with XYZ coordinates of ``(0., 0., 0.)``. @@ -128,11 +311,11 @@ geometry. It will return: .. code-block:: python - elements = tree.select_box(wall, completely_within=True) + elements = tree.select(wall, completely_within=True) # Alternatively, you may also specify an extension to dilate the geometry # of the wall. - elements = tree.select_box(wall, completely_within=True, extend=5.) + elements = tree.select(wall, completely_within=True, extend=5.) Selecting elements using a ray ------------------------------