Skip to content

API Reference

base

BottomUpEdgeIterator

A mixin class that adds the ability to iterate over edges from lower-level entities (vertices).

Source code in src/occwl/base.py
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
class BottomUpEdgeIterator:
    """
    A mixin class that adds the ability to iterate over edges from lower-level entities
    (vertices).
    """
    def edge_continuity(self, edge):
        """
        Get the neighboring faces' continuity at given edge

        Args:
            edge (occwl.edge.Edge): Edge

        Returns:
            GeomAbs_Shape: enum describing the continuity order
        """
        faces = list(self.faces_from_edge(edge))
        # Handle seam edges which only have one face around them
        if len(faces) == 1:
            faces.append(faces[-1])
        return edge.continuity(faces[0], faces[1])

    def edges_from_vertex(self, vertex):
        """
        Get an iterator to go over the edges adjacent to a vertex

        Args:
            face (occwl.face.Face): Input face

        Returns:
            Iterator[occwl.edge.Edge]: Edge iterator
        """
        from occwl.vertex import Vertex
        from occwl.edge import Edge
        assert isinstance(vertex, Vertex)
        return map(Edge, self._top_exp.edges_from_vertex(vertex.topods_shape()))

edge_continuity(edge)

Get the neighboring faces' continuity at given edge

Parameters:

Name Type Description Default
edge Edge

Edge

required

Returns:

Name Type Description
GeomAbs_Shape

enum describing the continuity order

Source code in src/occwl/base.py
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
def edge_continuity(self, edge):
    """
    Get the neighboring faces' continuity at given edge

    Args:
        edge (occwl.edge.Edge): Edge

    Returns:
        GeomAbs_Shape: enum describing the continuity order
    """
    faces = list(self.faces_from_edge(edge))
    # Handle seam edges which only have one face around them
    if len(faces) == 1:
        faces.append(faces[-1])
    return edge.continuity(faces[0], faces[1])

edges_from_vertex(vertex)

Get an iterator to go over the edges adjacent to a vertex

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.edge.Edge]: Edge iterator

Source code in src/occwl/base.py
386
387
388
389
390
391
392
393
394
395
396
397
398
399
def edges_from_vertex(self, vertex):
    """
    Get an iterator to go over the edges adjacent to a vertex

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.edge.Edge]: Edge iterator
    """
    from occwl.vertex import Vertex
    from occwl.edge import Edge
    assert isinstance(vertex, Vertex)
    return map(Edge, self._top_exp.edges_from_vertex(vertex.topods_shape()))

BottomUpFaceIterator

A mixin class that adds the ability to iterate over faces from lower-level entities (vertices and edges).

Source code in src/occwl/base.py
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
class BottomUpFaceIterator:
    """
    A mixin class that adds the ability to iterate over faces from lower-level entities
    (vertices and edges).
    """
    def faces_from_edge(self, edge):
        """
        Get an iterator to go over the faces adjacent to an edge

        Args:
            edge (occwl.edge.Edge): Input edge

        Returns:
            Iterator[occwl.face.Face]: Face iterator
        """
        from occwl.edge import Edge
        from occwl.face import Face
        assert isinstance(edge, Edge)
        return map(Face, self._top_exp.faces_from_edge(edge.topods_shape()))

    def faces_from_vertex(self, vertex):
        """
        Get an iterator to go over the faces adjacent to a vertex

        Args:
            edge (occwl.vertex.Vertex): Input vertex

        Returns:
            Iterator[occwl.face.Face]: Face iterator
        """
        from occwl.vertex import Vertex
        from occwl.face import Face
        assert isinstance(vertex, Vertex)
        return map(Face, self._top_exp.faces_from_vertex(vertex.topods_shape()))

    def edge_continuity(self, edge):
        """
        Get the neighboring faces' continuity at given edge

        Args:
            edge (occwl.edge.Edge): Edge

        Returns:
            GeomAbs_Shape: enum describing the continuity order
        """
        faces = list(self.faces_from_edge(edge))
        # Handle seam edges which only have one face around them
        if len(faces) == 1:
            faces.append(faces[-1])
        return edge.continuity(faces[0], faces[1])

edge_continuity(edge)

Get the neighboring faces' continuity at given edge

Parameters:

Name Type Description Default
edge Edge

Edge

required

Returns:

Name Type Description
GeomAbs_Shape

enum describing the continuity order

Source code in src/occwl/base.py
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
def edge_continuity(self, edge):
    """
    Get the neighboring faces' continuity at given edge

    Args:
        edge (occwl.edge.Edge): Edge

    Returns:
        GeomAbs_Shape: enum describing the continuity order
    """
    faces = list(self.faces_from_edge(edge))
    # Handle seam edges which only have one face around them
    if len(faces) == 1:
        faces.append(faces[-1])
    return edge.continuity(faces[0], faces[1])

faces_from_edge(edge)

Get an iterator to go over the faces adjacent to an edge

Parameters:

Name Type Description Default
edge Edge

Input edge

required

Returns:

Type Description

Iterator[occwl.face.Face]: Face iterator

Source code in src/occwl/base.py
317
318
319
320
321
322
323
324
325
326
327
328
329
330
def faces_from_edge(self, edge):
    """
    Get an iterator to go over the faces adjacent to an edge

    Args:
        edge (occwl.edge.Edge): Input edge

    Returns:
        Iterator[occwl.face.Face]: Face iterator
    """
    from occwl.edge import Edge
    from occwl.face import Face
    assert isinstance(edge, Edge)
    return map(Face, self._top_exp.faces_from_edge(edge.topods_shape()))

faces_from_vertex(vertex)

Get an iterator to go over the faces adjacent to a vertex

Parameters:

Name Type Description Default
edge Vertex

Input vertex

required

Returns:

Type Description

Iterator[occwl.face.Face]: Face iterator

Source code in src/occwl/base.py
332
333
334
335
336
337
338
339
340
341
342
343
344
345
def faces_from_vertex(self, vertex):
    """
    Get an iterator to go over the faces adjacent to a vertex

    Args:
        edge (occwl.vertex.Vertex): Input vertex

    Returns:
        Iterator[occwl.face.Face]: Face iterator
    """
    from occwl.vertex import Vertex
    from occwl.face import Face
    assert isinstance(vertex, Vertex)
    return map(Face, self._top_exp.faces_from_vertex(vertex.topods_shape()))

BoundingBoxMixin

A mixin class that adds the ability to compute approximate and exact bounding box of the Shape.

Source code in src/occwl/base.py
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
class BoundingBoxMixin:
    """
    A mixin class that adds the ability to compute approximate and exact bounding box
    of the Shape.
    """
    def box(self):
        """
        Get a quick bounding box of the Shape

        Returns:
            Box: Bounding box
        """
        from occwl.geometry import geom_utils
        b = Bnd_Box()
        brepbndlib_Add(self.topods_shape(), b)
        return geom_utils.box_to_geometry(b)

    def exact_box(self, use_shapetolerance=False):
        """
        Get a slow, but accurate box for the Shape.

        Args:
            use_shapetolerance (bool, optional) Include the tolerance of edges
                                                and vertices in the box.

        Returns:
            Box: Bounding box
        """
        from occwl.geometry import geom_utils
        b = Bnd_Box()
        use_triangulation = True
        brepbndlib_AddOptimal(self.topods_shape(), b, use_triangulation, use_shapetolerance)
        return geom_utils.box_to_geometry(b)

    def scale_to_box(self, box_side, copy=True):
        """
        Translate and scale the Shape so it fits exactly 
        into the [-box_side, box_side]^3 box

        Args:
            box_side (float) The side length of the box
            copy (bool)      True - Copy entities and apply the transform to
                                    the underlying geometry
                             False - Apply the transform to the topods Locator
                                     if possible 

        Returns:
            occwl.*.*: The scaled version of this Shape
        """
        from occwl.geometry import geom_utils
        # Get an exact box for the Shape
        box = self.exact_box()
        center = box.center()
        longest_length = box.max_box_length()

        orig = gp_Pnt(0.0, 0.0, 0.0)
        center = geom_utils.numpy_to_gp(center)
        vec_center_to_orig = gp_Vec(center, orig)
        move_to_center = gp_Trsf()
        move_to_center.SetTranslation(vec_center_to_orig)

        scale_trsf = gp_Trsf()
        scale_trsf.SetScale(orig, (2.0 * box_side) / longest_length)
        trsf_to_apply = scale_trsf.Multiplied(move_to_center)

        return self._apply_transform(trsf_to_apply, copy=copy)


    def scale_to_unit_box(self, copy=True):
        """
        Translate and scale the Shape so it fits exactly 
        into the [-1, 1]^3 box

        Args:
            copy (bool)      True - Copy entities and apply the transform to
                                        the underlying geometry
                                False - Apply the transform to the topods Locator
                                        if possible 
        Returns:
            The scaled version of this shape
        """
        return self.scale_to_box(1.0, copy=copy)

box()

Get a quick bounding box of the Shape

Returns:

Name Type Description
Box

Bounding box

Source code in src/occwl/base.py
565
566
567
568
569
570
571
572
573
574
575
def box(self):
    """
    Get a quick bounding box of the Shape

    Returns:
        Box: Bounding box
    """
    from occwl.geometry import geom_utils
    b = Bnd_Box()
    brepbndlib_Add(self.topods_shape(), b)
    return geom_utils.box_to_geometry(b)

exact_box(use_shapetolerance=False)

Get a slow, but accurate box for the Shape.

Returns:

Name Type Description
Box

Bounding box

Source code in src/occwl/base.py
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
def exact_box(self, use_shapetolerance=False):
    """
    Get a slow, but accurate box for the Shape.

    Args:
        use_shapetolerance (bool, optional) Include the tolerance of edges
                                            and vertices in the box.

    Returns:
        Box: Bounding box
    """
    from occwl.geometry import geom_utils
    b = Bnd_Box()
    use_triangulation = True
    brepbndlib_AddOptimal(self.topods_shape(), b, use_triangulation, use_shapetolerance)
    return geom_utils.box_to_geometry(b)

scale_to_box(box_side, copy=True)

Translate and scale the Shape so it fits exactly into the [-box_side, box_side]^3 box

Returns:

Type Description

occwl..: The scaled version of this Shape

Source code in src/occwl/base.py
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
def scale_to_box(self, box_side, copy=True):
    """
    Translate and scale the Shape so it fits exactly 
    into the [-box_side, box_side]^3 box

    Args:
        box_side (float) The side length of the box
        copy (bool)      True - Copy entities and apply the transform to
                                the underlying geometry
                         False - Apply the transform to the topods Locator
                                 if possible 

    Returns:
        occwl.*.*: The scaled version of this Shape
    """
    from occwl.geometry import geom_utils
    # Get an exact box for the Shape
    box = self.exact_box()
    center = box.center()
    longest_length = box.max_box_length()

    orig = gp_Pnt(0.0, 0.0, 0.0)
    center = geom_utils.numpy_to_gp(center)
    vec_center_to_orig = gp_Vec(center, orig)
    move_to_center = gp_Trsf()
    move_to_center.SetTranslation(vec_center_to_orig)

    scale_trsf = gp_Trsf()
    scale_trsf.SetScale(orig, (2.0 * box_side) / longest_length)
    trsf_to_apply = scale_trsf.Multiplied(move_to_center)

    return self._apply_transform(trsf_to_apply, copy=copy)

scale_to_unit_box(copy=True)

Translate and scale the Shape so it fits exactly into the [-1, 1]^3 box

Returns: The scaled version of this shape

Source code in src/occwl/base.py
628
629
630
631
632
633
634
635
636
637
638
639
640
641
def scale_to_unit_box(self, copy=True):
    """
    Translate and scale the Shape so it fits exactly 
    into the [-1, 1]^3 box

    Args:
        copy (bool)      True - Copy entities and apply the transform to
                                    the underlying geometry
                            False - Apply the transform to the topods Locator
                                    if possible 
    Returns:
        The scaled version of this shape
    """
    return self.scale_to_box(1.0, copy=copy)

EdgeContainerMixin

A mixin class that adds the ability to perform operations on the edges in the shape

Source code in src/occwl/base.py
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
class EdgeContainerMixin:
    """
    A mixin class that adds the ability to perform operations on the edges
    in the shape
    """
    def num_edges(self):
        """
        Number of edges in the Shape

        Returns:
            int: Number of edges
        """
        return self._top_exp.number_of_edges()

    def edges(self):
        """
        Get an iterator to go over all edges in the Shape

        Returns:
            Iterator[occwl.edge.Edge]: Edge iterator
        """
        from occwl.edge import Edge
        return map(Edge, self._top_exp.edges())

    def vertices_from_edge(self, edge):
        """
        Get an iterator to go over the vertices bounding an edge

        Args:
            edge (occwl.edge.Edge): Input edge

        Returns:
            Iterator[occwl.vertex.Vertex]: Vertex iterator
        """
        from occwl.vertex import Vertex
        from occwl.edge import Edge
        assert isinstance(edge, Edge)
        return map(Vertex, self._top_exp.vertices_from_edge(edge.topods_shape()))

    def find_closest_edge_slow(self, datum):
        """
        Find the closest edge to the given datum point.
        The function is for testing only.  It will be slow 
        as it loops over all edges in the Shape.
        A quick way to find the closest entity is to call
        Shape.find_closest_point_data(), but then you
        may get a face, edge or vertex back.

        Args:
            datum (np.ndarray or tuple): 3D datum point

        Returns:
            Face: The closest face in the Shape
        """
        return _find_closest_shape_in_list(self.edges(), datum)

    def split_all_closed_edges(self, max_tol=0.01, precision=0.01, num_splits=1):
        """
        Split all the closed edges in this shape

        Args:
            max_tol (float, optional): Maximum tolerance allowed. Defaults to 0.01.
            precision (float, optional): Precision of the tool when splitting. Defaults to 0.01.
            num_splits (int, optional): Number of splits to perform. Each split edge will result in num_splits + 1 edges. Defaults to 1.

        Returns:
            occwl.*.*: Shape with closed edges split
        """
        divider = ShapeUpgrade_ShapeDivideClosedEdges(self.topods_shape())
        divider.SetPrecision(precision)
        divider.SetMinTolerance(0.1 * max_tol)
        divider.SetMaxTolerance(max_tol)
        divider.SetNbSplitPoints(num_splits)
        ok = divider.Perform()
        if not ok:
            # Splitting failed or there were no closed edges to split
            # Return the original shape
            return self
        return type(self)(divider.Result())

edges()

Get an iterator to go over all edges in the Shape

Returns:

Type Description

Iterator[occwl.edge.Edge]: Edge iterator

Source code in src/occwl/base.py
59
60
61
62
63
64
65
66
67
def edges(self):
    """
    Get an iterator to go over all edges in the Shape

    Returns:
        Iterator[occwl.edge.Edge]: Edge iterator
    """
    from occwl.edge import Edge
    return map(Edge, self._top_exp.edges())

find_closest_edge_slow(datum)

Find the closest edge to the given datum point. The function is for testing only. It will be slow as it loops over all edges in the Shape. A quick way to find the closest entity is to call Shape.find_closest_point_data(), but then you may get a face, edge or vertex back.

Parameters:

Name Type Description Default
datum ndarray or tuple

3D datum point

required

Returns:

Name Type Description
Face

The closest face in the Shape

Source code in src/occwl/base.py
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def find_closest_edge_slow(self, datum):
    """
    Find the closest edge to the given datum point.
    The function is for testing only.  It will be slow 
    as it loops over all edges in the Shape.
    A quick way to find the closest entity is to call
    Shape.find_closest_point_data(), but then you
    may get a face, edge or vertex back.

    Args:
        datum (np.ndarray or tuple): 3D datum point

    Returns:
        Face: The closest face in the Shape
    """
    return _find_closest_shape_in_list(self.edges(), datum)

num_edges()

Number of edges in the Shape

Returns:

Name Type Description
int

Number of edges

Source code in src/occwl/base.py
50
51
52
53
54
55
56
57
def num_edges(self):
    """
    Number of edges in the Shape

    Returns:
        int: Number of edges
    """
    return self._top_exp.number_of_edges()

split_all_closed_edges(max_tol=0.01, precision=0.01, num_splits=1)

Split all the closed edges in this shape

Parameters:

Name Type Description Default
max_tol float

Maximum tolerance allowed. Defaults to 0.01.

0.01
precision float

Precision of the tool when splitting. Defaults to 0.01.

0.01
num_splits int

Number of splits to perform. Each split edge will result in num_splits + 1 edges. Defaults to 1.

1

Returns:

Type Description

occwl..: Shape with closed edges split

Source code in src/occwl/base.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def split_all_closed_edges(self, max_tol=0.01, precision=0.01, num_splits=1):
    """
    Split all the closed edges in this shape

    Args:
        max_tol (float, optional): Maximum tolerance allowed. Defaults to 0.01.
        precision (float, optional): Precision of the tool when splitting. Defaults to 0.01.
        num_splits (int, optional): Number of splits to perform. Each split edge will result in num_splits + 1 edges. Defaults to 1.

    Returns:
        occwl.*.*: Shape with closed edges split
    """
    divider = ShapeUpgrade_ShapeDivideClosedEdges(self.topods_shape())
    divider.SetPrecision(precision)
    divider.SetMinTolerance(0.1 * max_tol)
    divider.SetMaxTolerance(max_tol)
    divider.SetNbSplitPoints(num_splits)
    ok = divider.Perform()
    if not ok:
        # Splitting failed or there were no closed edges to split
        # Return the original shape
        return self
    return type(self)(divider.Result())

vertices_from_edge(edge)

Get an iterator to go over the vertices bounding an edge

Parameters:

Name Type Description Default
edge Edge

Input edge

required

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def vertices_from_edge(self, edge):
    """
    Get an iterator to go over the vertices bounding an edge

    Args:
        edge (occwl.edge.Edge): Input edge

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    from occwl.edge import Edge
    assert isinstance(edge, Edge)
    return map(Vertex, self._top_exp.vertices_from_edge(edge.topods_shape()))

FaceContainerMixin

A mixin class that adds the ability to perform operations on the faces in the shape

Source code in src/occwl/base.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
class FaceContainerMixin:
    """
    A mixin class that adds the ability to perform operations on the faces
    in the shape
    """
    def num_faces(self):
        """
        Number of faces in the Shape

        Returns:
            int: Number of faces
        """
        return self._top_exp.number_of_faces()

    def faces(self):
        """
        Get an iterator to go over all faces in the Shape

        Returns:
            Iterator[occwl.face.Face]: Face iterator
        """
        from occwl.face import Face
        return map(Face, self._top_exp.faces())

    def vertices_from_face(self, face):
        """
        Get an iterator to go over the vertices in a face

        Args:
            face (occwl.face.Face): Input face

        Returns:
            Iterator[occwl.vertex.Vertex]: Vertex iterator
        """
        from occwl.vertex import Vertex
        from occwl.face import Face
        assert isinstance(face, Face)
        return map(Vertex, self._top_exp.vertices_from_face(face.topods_shape()))

    def edges_from_face(self, face):
        """
        Get an iterator to go over the edges in a face

        Args:
            face (occwl.face.Face): Input face

        Returns:
            Iterator[occwl.edge.Edge]: Edge iterator
        """
        from occwl.edge import Edge
        from occwl.face import Face
        assert isinstance(face, Face)
        return map(Edge, self._top_exp.edges_from_face(face.topods_shape()))

    def wires_from_face(self, face):
        """
        Get an iterator to go over the wires bounding a face

        Args:
            face (occwl.face.Face): Input face

        Returns:
            Iterator[occwl.wire.Wire]: Wire iterator
        """
        from occwl.wire import Wire
        from occwl.face import Face
        assert isinstance(face, Face)
        return map(Wire, self._top_exp.wires_from_face(face.topods_shape()))

    def find_closest_face_slow(self, datum):
        """
        Find the closest face to the given datum point.
        The function is for testing only. It will be slow 
        as it loops over all faces in the Shape.
        A quick way to find the closest entity is to call
        Shape.find_closest_point_data(), but then you
        may get a face, edge or vertex back.

        Args:
            datum (np.ndarray or tuple): 3D datum point

        Returns:
            Face: The closest face in the Shape
        """
        return _find_closest_shape_in_list(self.faces(), datum)

    def split_all_closed_faces(self, max_tol=0.01, precision=0.01, num_splits=1):
        """
        Split all the closed faces in this shape

        Args:
            max_tol (float, optional): Maximum tolerance allowed. Defaults to 0.01.
            precision (float, optional): Precision of the tool when splitting. Defaults to 0.01.
            num_splits (int, optional): Number of splits to perform. Each split face will result in num_splits + 1 faces. Defaults to 1.

        Returns:
            occwl.*.*: Shape with closed faces split
        """
        divider = ShapeUpgrade_ShapeDivideClosed(self.topods_shape())
        divider.SetPrecision(precision)
        divider.SetMinTolerance(0.1 * max_tol)
        divider.SetMaxTolerance(max_tol)
        divider.SetNbSplitPoints(num_splits)
        ok = divider.Perform()
        if not ok:
            # Splitting failed or there were no closed faces to split
            # Return the original shape
            return self
        return type(self)(divider.Result())

edges_from_face(face)

Get an iterator to go over the edges in a face

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.edge.Edge]: Edge iterator

Source code in src/occwl/base.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def edges_from_face(self, face):
    """
    Get an iterator to go over the edges in a face

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.edge.Edge]: Edge iterator
    """
    from occwl.edge import Edge
    from occwl.face import Face
    assert isinstance(face, Face)
    return map(Edge, self._top_exp.edges_from_face(face.topods_shape()))

faces()

Get an iterator to go over all faces in the Shape

Returns:

Type Description

Iterator[occwl.face.Face]: Face iterator

Source code in src/occwl/base.py
165
166
167
168
169
170
171
172
173
def faces(self):
    """
    Get an iterator to go over all faces in the Shape

    Returns:
        Iterator[occwl.face.Face]: Face iterator
    """
    from occwl.face import Face
    return map(Face, self._top_exp.faces())

find_closest_face_slow(datum)

Find the closest face to the given datum point. The function is for testing only. It will be slow as it loops over all faces in the Shape. A quick way to find the closest entity is to call Shape.find_closest_point_data(), but then you may get a face, edge or vertex back.

Parameters:

Name Type Description Default
datum ndarray or tuple

3D datum point

required

Returns:

Name Type Description
Face

The closest face in the Shape

Source code in src/occwl/base.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def find_closest_face_slow(self, datum):
    """
    Find the closest face to the given datum point.
    The function is for testing only. It will be slow 
    as it loops over all faces in the Shape.
    A quick way to find the closest entity is to call
    Shape.find_closest_point_data(), but then you
    may get a face, edge or vertex back.

    Args:
        datum (np.ndarray or tuple): 3D datum point

    Returns:
        Face: The closest face in the Shape
    """
    return _find_closest_shape_in_list(self.faces(), datum)

num_faces()

Number of faces in the Shape

Returns:

Name Type Description
int

Number of faces

Source code in src/occwl/base.py
156
157
158
159
160
161
162
163
def num_faces(self):
    """
    Number of faces in the Shape

    Returns:
        int: Number of faces
    """
    return self._top_exp.number_of_faces()

split_all_closed_faces(max_tol=0.01, precision=0.01, num_splits=1)

Split all the closed faces in this shape

Parameters:

Name Type Description Default
max_tol float

Maximum tolerance allowed. Defaults to 0.01.

0.01
precision float

Precision of the tool when splitting. Defaults to 0.01.

0.01
num_splits int

Number of splits to perform. Each split face will result in num_splits + 1 faces. Defaults to 1.

1

Returns:

Type Description

occwl..: Shape with closed faces split

Source code in src/occwl/base.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def split_all_closed_faces(self, max_tol=0.01, precision=0.01, num_splits=1):
    """
    Split all the closed faces in this shape

    Args:
        max_tol (float, optional): Maximum tolerance allowed. Defaults to 0.01.
        precision (float, optional): Precision of the tool when splitting. Defaults to 0.01.
        num_splits (int, optional): Number of splits to perform. Each split face will result in num_splits + 1 faces. Defaults to 1.

    Returns:
        occwl.*.*: Shape with closed faces split
    """
    divider = ShapeUpgrade_ShapeDivideClosed(self.topods_shape())
    divider.SetPrecision(precision)
    divider.SetMinTolerance(0.1 * max_tol)
    divider.SetMaxTolerance(max_tol)
    divider.SetNbSplitPoints(num_splits)
    ok = divider.Perform()
    if not ok:
        # Splitting failed or there were no closed faces to split
        # Return the original shape
        return self
    return type(self)(divider.Result())

vertices_from_face(face)

Get an iterator to go over the vertices in a face

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def vertices_from_face(self, face):
    """
    Get an iterator to go over the vertices in a face

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    from occwl.face import Face
    assert isinstance(face, Face)
    return map(Vertex, self._top_exp.vertices_from_face(face.topods_shape()))

wires_from_face(face)

Get an iterator to go over the wires bounding a face

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.wire.Wire]: Wire iterator

Source code in src/occwl/base.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
def wires_from_face(self, face):
    """
    Get an iterator to go over the wires bounding a face

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.wire.Wire]: Wire iterator
    """
    from occwl.wire import Wire
    from occwl.face import Face
    assert isinstance(face, Face)
    return map(Wire, self._top_exp.wires_from_face(face.topods_shape()))

ShellContainerMixin

A mixin class that adds the ability to perform operations on the shells in the shape

Source code in src/occwl/base.py
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
class ShellContainerMixin:
    """
    A mixin class that adds the ability to perform operations on the shells
    in the shape
    """
    def num_shells(self):
        """
        Number of shells in the Shape

        Returns:
            int: Number of shells
        """
        return self._top_exp.number_of_shells()

    def shells(self):
        """
        Get an iterator to go over all shells in the Shape

        Returns:
            Iterator[occwl.shell.Shell]: Shell iterator
        """
        from occwl.shell import Shell
        return map(Shell, self._top_exp.shells())

num_shells()

Number of shells in the Shape

Returns:

Name Type Description
int

Number of shells

Source code in src/occwl/base.py
267
268
269
270
271
272
273
274
def num_shells(self):
    """
    Number of shells in the Shape

    Returns:
        int: Number of shells
    """
    return self._top_exp.number_of_shells()

shells()

Get an iterator to go over all shells in the Shape

Returns:

Type Description

Iterator[occwl.shell.Shell]: Shell iterator

Source code in src/occwl/base.py
276
277
278
279
280
281
282
283
284
def shells(self):
    """
    Get an iterator to go over all shells in the Shape

    Returns:
        Iterator[occwl.shell.Shell]: Shell iterator
    """
    from occwl.shell import Shell
    return map(Shell, self._top_exp.shells())

SolidContainerMixin

A mixin class that adds the ability to perform operations on the solids in the shape

Source code in src/occwl/base.py
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
class SolidContainerMixin:
    """
    A mixin class that adds the ability to perform operations on the solids
    in the shape
    """
    def num_solids(self):
        """
        Number of solids in the Compound

        Returns:
            int: Number of solids
        """
        return self._top_exp.number_of_solids()

    def solids(self):
        """
        Get an iterator to go over all solids in the Compound

        Returns:
            Iterator[occwl.solid.Solid]: Solid iterator
        """
        from occwl.solid import Solid
        return map(Solid, self._top_exp.solids())

num_solids()

Number of solids in the Compound

Returns:

Name Type Description
int

Number of solids

Source code in src/occwl/base.py
292
293
294
295
296
297
298
299
def num_solids(self):
    """
    Number of solids in the Compound

    Returns:
        int: Number of solids
    """
    return self._top_exp.number_of_solids()

solids()

Get an iterator to go over all solids in the Compound

Returns:

Type Description

Iterator[occwl.solid.Solid]: Solid iterator

Source code in src/occwl/base.py
301
302
303
304
305
306
307
308
309
def solids(self):
    """
    Get an iterator to go over all solids in the Compound

    Returns:
        Iterator[occwl.solid.Solid]: Solid iterator
    """
    from occwl.solid import Solid
    return map(Solid, self._top_exp.solids())

SurfacePropertiesMixin

A mixin class that adds the ability to query surface properties (e.g. area).

Source code in src/occwl/base.py
489
490
491
492
493
494
495
496
497
498
499
500
501
502
class SurfacePropertiesMixin:
    """
    A mixin class that adds the ability to query surface properties (e.g. area).
    """
    def area(self):
        """
        Compute the area of the Shape

        Returns:
            float: Area
        """
        geometry_properties = GProp_GProps()
        brepgprop_SurfaceProperties(self.topods_shape(), geometry_properties)
        return geometry_properties.Mass()

area()

Compute the area of the Shape

Returns:

Name Type Description
float

Area

Source code in src/occwl/base.py
493
494
495
496
497
498
499
500
501
502
def area(self):
    """
    Compute the area of the Shape

    Returns:
        float: Area
    """
    geometry_properties = GProp_GProps()
    brepgprop_SurfaceProperties(self.topods_shape(), geometry_properties)
    return geometry_properties.Mass()

TriangulatorMixin

A mixin class that adds the ability to triangulate the faces that are present in the shape.

Source code in src/occwl/base.py
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
class TriangulatorMixin:
    """
    A mixin class that adds the ability to triangulate the faces that are present
    in the shape.
    """
    def triangulate_all_faces(
        self,
        triangle_face_tol=0.01,  # Tolerance between triangle and surface
        tol_relative_to_face=True,  # The tolerance value is relative to the face size
        angle_tol_rads=0.1,  # Angle between normals/tangents at triangle vertices
    ):
        return self.triangulate(
            triangle_face_tol=triangle_face_tol,
            tol_relative_to_face=tol_relative_to_face,
            angle_tol_rads=angle_tol_rads,
        )

    def triangulate(
        self,
        triangle_face_tol=0.01,  # Tolerance between triangle and surface
        tol_relative_to_face=True,  # The tolerance value is relative to the face size
        angle_tol_rads=0.1,  # Angle between normals/tangents at triangle vertices
    ):
        """
        Triangulate all the faces in the shape. You can then get the triangles 
        from each face separately using face.get_triangles().
        If you wanted triangles for the entire shape then call
        shape.get_triangles() below.
        For more details see 
        https://old.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11

        Args:
            triangle_face_tol (float, optional): Tolerance between triangle and surface. Defaults to 0.01.
            tol_relative_to_face (bool): Whether tolerance is relative to face size
            angle_tol_rads (float, optional): Angle tolerance in radians. Defaults to 0.1.

        Returns:
            bool: Is successful
        """
        mesh = BRepMesh_IncrementalMesh(
            self.topods_shape(),
            triangle_face_tol,
            tol_relative_to_face,
            angle_tol_rads,
            True,
        )
        mesh.Perform()
        return mesh.IsDone()

    def get_triangles(
        self,
        triangle_face_tol=0.01,  # Tolerance between triangle and surface
        tol_relative_to_face=True,  # The tolerance value is relative to the face size
        angle_tol_rads=0.1,  # Angle between normals/tangents at triangle vertices
    ):
        """
        Compute and get the tessellation of the entire shape

        Args:
            triangle_face_tol (float, optional): Toelrance between triangle and surface. Defaults to 0.01.
            tol_relative_to_face (bool): Whether tolerance is relative to face size
            angle_tol_rads (float, optional): Angle tolerance in radians. Defaults to 0.1.

        Returns:
            2D np.ndarray (float): Vertices or None if triangulation failed
            2D np.ndarray (int): Faces or None if triangulation failed
        """
        ok = self.triangulate_all_faces(
            triangle_face_tol, tol_relative_to_face, angle_tol_rads
        )
        if not ok:
            # Failed to triangulate
            return None, None
        verts = []
        tris = []
        faces = self.faces()
        last_vert_index = 0
        for face in faces:
            fverts, ftris = face.get_triangles()
            verts.extend(fverts)
            for tri in ftris:
                new_indices = [index + last_vert_index for index in tri]
                tris.append(new_indices)
            last_vert_index = len(verts)
        return np.asarray(verts, dtype=np.float32), np.asarray(tris, dtype=np.int32)

get_triangles(triangle_face_tol=0.01, tol_relative_to_face=True, angle_tol_rads=0.1)

Compute and get the tessellation of the entire shape

Parameters:

Name Type Description Default
triangle_face_tol float

Toelrance between triangle and surface. Defaults to 0.01.

0.01
tol_relative_to_face bool

Whether tolerance is relative to face size

True
angle_tol_rads float

Angle tolerance in radians. Defaults to 0.1.

0.1

Returns:

Type Description

2D np.ndarray (float): Vertices or None if triangulation failed

2D np.ndarray (int): Faces or None if triangulation failed

Source code in src/occwl/base.py
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
def get_triangles(
    self,
    triangle_face_tol=0.01,  # Tolerance between triangle and surface
    tol_relative_to_face=True,  # The tolerance value is relative to the face size
    angle_tol_rads=0.1,  # Angle between normals/tangents at triangle vertices
):
    """
    Compute and get the tessellation of the entire shape

    Args:
        triangle_face_tol (float, optional): Toelrance between triangle and surface. Defaults to 0.01.
        tol_relative_to_face (bool): Whether tolerance is relative to face size
        angle_tol_rads (float, optional): Angle tolerance in radians. Defaults to 0.1.

    Returns:
        2D np.ndarray (float): Vertices or None if triangulation failed
        2D np.ndarray (int): Faces or None if triangulation failed
    """
    ok = self.triangulate_all_faces(
        triangle_face_tol, tol_relative_to_face, angle_tol_rads
    )
    if not ok:
        # Failed to triangulate
        return None, None
    verts = []
    tris = []
    faces = self.faces()
    last_vert_index = 0
    for face in faces:
        fverts, ftris = face.get_triangles()
        verts.extend(fverts)
        for tri in ftris:
            new_indices = [index + last_vert_index for index in tri]
            tris.append(new_indices)
        last_vert_index = len(verts)
    return np.asarray(verts, dtype=np.float32), np.asarray(tris, dtype=np.int32)

triangulate(triangle_face_tol=0.01, tol_relative_to_face=True, angle_tol_rads=0.1)

Triangulate all the faces in the shape. You can then get the triangles from each face separately using face.get_triangles(). If you wanted triangles for the entire shape then call shape.get_triangles() below. For more details see https://old.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11

Parameters:

Name Type Description Default
triangle_face_tol float

Tolerance between triangle and surface. Defaults to 0.01.

0.01
tol_relative_to_face bool

Whether tolerance is relative to face size

True
angle_tol_rads float

Angle tolerance in radians. Defaults to 0.1.

0.1

Returns:

Name Type Description
bool

Is successful

Source code in src/occwl/base.py
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
def triangulate(
    self,
    triangle_face_tol=0.01,  # Tolerance between triangle and surface
    tol_relative_to_face=True,  # The tolerance value is relative to the face size
    angle_tol_rads=0.1,  # Angle between normals/tangents at triangle vertices
):
    """
    Triangulate all the faces in the shape. You can then get the triangles 
    from each face separately using face.get_triangles().
    If you wanted triangles for the entire shape then call
    shape.get_triangles() below.
    For more details see 
    https://old.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11

    Args:
        triangle_face_tol (float, optional): Tolerance between triangle and surface. Defaults to 0.01.
        tol_relative_to_face (bool): Whether tolerance is relative to face size
        angle_tol_rads (float, optional): Angle tolerance in radians. Defaults to 0.1.

    Returns:
        bool: Is successful
    """
    mesh = BRepMesh_IncrementalMesh(
        self.topods_shape(),
        triangle_face_tol,
        tol_relative_to_face,
        angle_tol_rads,
        True,
    )
    mesh.Perform()
    return mesh.IsDone()

VertexContainerMixin

A mixin class that adds the ability to perform operations on the vertices in the shape

Source code in src/occwl/base.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class VertexContainerMixin:
    """
    A mixin class that adds the ability to perform operations on the vertices
    in the shape
    """
    def num_vertices(self):
        """
        Number of vertices in the Shape

        Returns:
            int: Number of vertices
        """
        return self._top_exp.number_of_vertices()

    def vertices(self):
        """
        Get an iterator to go over all vertices in the Shape

        Returns:
            Iterator[occwl.vertex.Vertex]: Vertex iterator
        """
        from occwl.vertex import Vertex
        return map(Vertex, self._top_exp.vertices())

num_vertices()

Number of vertices in the Shape

Returns:

Name Type Description
int

Number of vertices

Source code in src/occwl/base.py
25
26
27
28
29
30
31
32
def num_vertices(self):
    """
    Number of vertices in the Shape

    Returns:
        int: Number of vertices
    """
    return self._top_exp.number_of_vertices()

vertices()

Get an iterator to go over all vertices in the Shape

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
34
35
36
37
38
39
40
41
42
def vertices(self):
    """
    Get an iterator to go over all vertices in the Shape

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    return map(Vertex, self._top_exp.vertices())

VolumePropertiesMixin

A mixin class that adds the ability to query volumetric properties (e.g. volume, center of mass, etc.).

Source code in src/occwl/base.py
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
class VolumePropertiesMixin:
    """
    A mixin class that adds the ability to query volumetric properties (e.g. volume, center of mass, etc.).
    """
    def volume(self, tolerance=1e-9):
        """
        Compute the volume of the Shape

        Args:
            tolerance (float, optional): Tolerance. Defaults to 1e-9.

        Returns:
            float: Volume
        """
        props = GProp_GProps()
        brepgprop_VolumeProperties(self.topods_shape(), props, tolerance)
        return props.Mass()

    def center_of_mass(self, tolerance=1e-9):
        """
        Compute the center of mass of the Shape

        Args:
            tolerance (float, optional): Tolerance. Defaults to 1e-9.

        Returns:
            np.ndarray: 3D point
        """
        from occwl.geometry import geom_utils
        props = GProp_GProps()
        brepgprop_VolumeProperties(self.topods_shape(), props, tolerance)
        com = props.CentreOfMass()
        return geom_utils.gp_to_numpy(com)

    def moment_of_inertia(self, point, direction, tolerance=1e-9):
        """
        Compute the moment of inertia about an axis

        Args:
            point (np.ndarray): 3D point (origin of the axis)
            direction (np.ndarray): 3D direction of the axis
            tolerance (float, optional): Tolerance. Defaults to 1e-9.

        Returns:
            float: Moment of inertia
        """
        from occwl.geometry import geom_utils
        props = GProp_GProps()
        brepgprop_VolumeProperties(self.topods_shape(), props, tolerance)
        axis = gp_Ax1(
            geom_utils.numpy_to_gp(point), geom_utils.numpy_to_gp_dir(direction)
        )
        return props.MomentOfInertia(axis)

center_of_mass(tolerance=1e-09)

Compute the center of mass of the Shape

Parameters:

Name Type Description Default
tolerance float

Tolerance. Defaults to 1e-9.

1e-09

Returns:

Type Description

np.ndarray: 3D point

Source code in src/occwl/base.py
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
def center_of_mass(self, tolerance=1e-9):
    """
    Compute the center of mass of the Shape

    Args:
        tolerance (float, optional): Tolerance. Defaults to 1e-9.

    Returns:
        np.ndarray: 3D point
    """
    from occwl.geometry import geom_utils
    props = GProp_GProps()
    brepgprop_VolumeProperties(self.topods_shape(), props, tolerance)
    com = props.CentreOfMass()
    return geom_utils.gp_to_numpy(com)

moment_of_inertia(point, direction, tolerance=1e-09)

Compute the moment of inertia about an axis

Parameters:

Name Type Description Default
point ndarray

3D point (origin of the axis)

required
direction ndarray

3D direction of the axis

required
tolerance float

Tolerance. Defaults to 1e-9.

1e-09

Returns:

Name Type Description
float

Moment of inertia

Source code in src/occwl/base.py
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
def moment_of_inertia(self, point, direction, tolerance=1e-9):
    """
    Compute the moment of inertia about an axis

    Args:
        point (np.ndarray): 3D point (origin of the axis)
        direction (np.ndarray): 3D direction of the axis
        tolerance (float, optional): Tolerance. Defaults to 1e-9.

    Returns:
        float: Moment of inertia
    """
    from occwl.geometry import geom_utils
    props = GProp_GProps()
    brepgprop_VolumeProperties(self.topods_shape(), props, tolerance)
    axis = gp_Ax1(
        geom_utils.numpy_to_gp(point), geom_utils.numpy_to_gp_dir(direction)
    )
    return props.MomentOfInertia(axis)

volume(tolerance=1e-09)

Compute the volume of the Shape

Parameters:

Name Type Description Default
tolerance float

Tolerance. Defaults to 1e-9.

1e-09

Returns:

Name Type Description
float

Volume

Source code in src/occwl/base.py
509
510
511
512
513
514
515
516
517
518
519
520
521
def volume(self, tolerance=1e-9):
    """
    Compute the volume of the Shape

    Args:
        tolerance (float, optional): Tolerance. Defaults to 1e-9.

    Returns:
        float: Volume
    """
    props = GProp_GProps()
    brepgprop_VolumeProperties(self.topods_shape(), props, tolerance)
    return props.Mass()

WireContainerMixin

A mixin class that adds the ability to perform operations on the wires in the shape

Source code in src/occwl/base.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
class WireContainerMixin:
    """
    A mixin class that adds the ability to perform operations on the wires
    in the shape
    """
    def num_wires(self):
        """
        Number of wires in the Shape

        Returns:
            int: Number of wires
        """
        return self._top_exp.number_of_wires()

    def wires(self):
        """
        Get an iterator to go over all wires in the Shape

        Returns:
            Iterator[occwl.wire.Wire]: Wire iterator
        """
        from occwl.wire import Wire
        return map(Wire, self._top_exp.wires())

num_wires()

Number of wires in the Shape

Returns:

Name Type Description
int

Number of wires

Source code in src/occwl/base.py
131
132
133
134
135
136
137
138
def num_wires(self):
    """
    Number of wires in the Shape

    Returns:
        int: Number of wires
    """
    return self._top_exp.number_of_wires()

wires()

Get an iterator to go over all wires in the Shape

Returns:

Type Description

Iterator[occwl.wire.Wire]: Wire iterator

Source code in src/occwl/base.py
140
141
142
143
144
145
146
147
148
def wires(self):
    """
    Get an iterator to go over all wires in the Shape

    Returns:
        Iterator[occwl.wire.Wire]: Wire iterator
    """
    from occwl.wire import Wire
    return map(Wire, self._top_exp.wires())

compound

Compound

Bases: Shape, BottomUpFaceIterator, BoundingBoxMixin, BottomUpEdgeIterator, EdgeContainerMixin, FaceContainerMixin, SolidContainerMixin, ShellContainerMixin, SurfacePropertiesMixin, TriangulatorMixin, VertexContainerMixin, VolumePropertiesMixin, WireContainerMixin

A compound which can be worked with as many shapes lumped together.

Source code in src/occwl/compound.py
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
class Compound(Shape, BottomUpFaceIterator, BoundingBoxMixin, BottomUpEdgeIterator,
    EdgeContainerMixin, FaceContainerMixin, SolidContainerMixin, ShellContainerMixin, SurfacePropertiesMixin,
    TriangulatorMixin, VertexContainerMixin, VolumePropertiesMixin, WireContainerMixin):
    """
    A compound which can be worked with as many shapes
    lumped together.
    """
    def __init__(self, shape):
        assert isinstance(shape, TopoDS_Compound)
        super().__init__(shape)

    @staticmethod
    def load_from_step(filename, verbosity=False):
        """
        Load everything from a STEP file as a single Compound

        Args:
            filename (str or pathlib.Path): STEP filename
            verbosity (bool): Whether to print detailed information while loading

        Returns:
            occwl.compound.Compound: Compound shape
        """
        shp = read_step_file(str(filename), as_compound=True, verbosity=verbosity)
        if not isinstance(shp, TopoDS_Compound):
            shp, success = list_of_shapes_to_compound([shp])
            assert success
        return Compound(shp)

    @staticmethod
    def load_step_with_attributes(step_filename):
        """Load shapes from a step file with the
        name information.   Other attributes could be
        retro-fitted

        Args:
            step_filename (str): Path to STEP file

        Returns:
            occwl.Compound, dict occwl.Shape to attributes 
        """        
        # Read the file and get the shape
        reader = STEPControl_Reader()
        tr = reader.WS().TransferReader()
        reader.ReadFile(str(step_filename))
        reader.TransferRoots()
        shape = reader.OneShape()

        occwl_shape_to_attributes = {}
        def check_shape_type(shape_type):
            exp = TopExp_Explorer(shape, shape_type)
            while exp.More():
                s = exp.Current()
                exp.Next()
                item = tr.EntityFromShapeResult(s, 1)
                if item is None:
                    continue
                item = StepRepr_RepresentationItem.DownCast(item)
                if item is None:
                    continue
                name = item.Name().ToCString()
                occwl_shape = Shape.occwl_shape(s)
                occwl_shape_to_attributes[occwl_shape] = {
                    "name": name
                }

        check_shape_type(TopAbs_FACE)
        check_shape_type(TopAbs_EDGE)
        check_shape_type(TopAbs_SHELL)
        check_shape_type(TopAbs_SOLID)
        check_shape_type(TopAbs_COMPOUND)
        check_shape_type(TopAbs_COMPSOLID)

        shp, success = list_of_shapes_to_compound([shape])
        assert success, "Failed to convert to a single compound"
        return Compound(shp), occwl_shape_to_attributes


    @staticmethod
    def load_from_occ_native(filename, verbosity=False):
        """
        Load everything from the OCC native .brep file 
        format into a single occwl.compound.Compound.

        Note:  Saving to and loading from the native file format 
               is between one and two orders of magnitude faster 
               than loading from STEP, so it is recommended for 
               large scale data processing

        Args:
            filename (str or pathlib.Path): .brep filename
            verbosity (bool): Whether to print detailed information while loading

        Returns:
            occwl.compound.Compound: Compound shape
        """
        shape_set = BRepTools_ShapeSet()
        with open(filename, "r") as fp:
            shape_set.ReadFromString(fp.read())
        shapes = []
        for i in range(shape_set.NbShapes()):
            shapes.append(shape_set.Shape(i+1))
        shp, success = list_of_shapes_to_compound(shapes)
        assert success
        return Compound(shp)

__eq__(other)

Equality check for the shape

NOTE: This function only checks if the shape is the same. It doesn't check the edge orienation for example, so

edge1 == edge2

does not necessarily mean

edge1.reversed() == edge2.reversed()

Source code in src/occwl/shape.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def __eq__(self, other):
    """
    Equality check for the shape

    NOTE: This function only checks if the shape is the same.
    It doesn't check the edge orienation for example, so 

    edge1 == edge2

    does not necessarily mean 

    edge1.reversed() == edge2.reversed()
    """
    return self.topods_shape().__hash__() == other.topods_shape().__hash__()

__hash__()

Hash for the shape

Returns:

Name Type Description
int

Hash value

Source code in src/occwl/shape.py
135
136
137
138
139
140
141
142
def __hash__(self):
    """
    Hash for the shape

    Returns:
        int: Hash value
    """
    return self.topods_shape().__hash__()

area()

Compute the area of the Shape

Returns:

Name Type Description
float

Area

Source code in src/occwl/base.py
493
494
495
496
497
498
499
500
501
502
def area(self):
    """
    Compute the area of the Shape

    Returns:
        float: Area
    """
    geometry_properties = GProp_GProps()
    brepgprop_SurfaceProperties(self.topods_shape(), geometry_properties)
    return geometry_properties.Mass()

box()

Get a quick bounding box of the Shape

Returns:

Name Type Description
Box

Bounding box

Source code in src/occwl/base.py
565
566
567
568
569
570
571
572
573
574
575
def box(self):
    """
    Get a quick bounding box of the Shape

    Returns:
        Box: Bounding box
    """
    from occwl.geometry import geom_utils
    b = Bnd_Box()
    brepbndlib_Add(self.topods_shape(), b)
    return geom_utils.box_to_geometry(b)

center_of_mass(tolerance=1e-09)

Compute the center of mass of the Shape

Parameters:

Name Type Description Default
tolerance float

Tolerance. Defaults to 1e-9.

1e-09

Returns:

Type Description

np.ndarray: 3D point

Source code in src/occwl/base.py
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
def center_of_mass(self, tolerance=1e-9):
    """
    Compute the center of mass of the Shape

    Args:
        tolerance (float, optional): Tolerance. Defaults to 1e-9.

    Returns:
        np.ndarray: 3D point
    """
    from occwl.geometry import geom_utils
    props = GProp_GProps()
    brepgprop_VolumeProperties(self.topods_shape(), props, tolerance)
    com = props.CentreOfMass()
    return geom_utils.gp_to_numpy(com)

convert_geometric_identity_transforms_to_identity()

Open Cascade models sometimes contain transforms which are "geometrically" identify transforms, but the identity flag is not set.

This function checks each transform and sets the flag if the appropriate.

Source code in src/occwl/shape.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def convert_geometric_identity_transforms_to_identity(self):
    """
    Open Cascade models sometimes contain transforms which
    are "geometrically" identify transforms, but the identity
    flag is not set.

    This function checks each transform and sets the flag if 
    the appropriate.
    """
    identity = TopLoc_Location()
    if geom_utils.is_geometric_identity(
        self.topods_shape().Location().Transformation()
    ):
        self.topods_shape().Location(identity)
        self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)

    for face in self._top_exp.faces():
        if geom_utils.is_geometric_identity(face.Location().Transformation()):
            face.Location(identity)

    for edge in self._top_exp.edges():
        if geom_utils.is_geometric_identity(edge.Location().Transformation()):
            edge.Location(identity)

    for vertex in self._top_exp.vertices():
        if geom_utils.is_geometric_identity(vertex.Location().Transformation()):
            vertex.Location(identity)

edge_continuity(edge)

Get the neighboring faces' continuity at given edge

Parameters:

Name Type Description Default
edge Edge

Edge

required

Returns:

Name Type Description
GeomAbs_Shape

enum describing the continuity order

Source code in src/occwl/base.py
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
def edge_continuity(self, edge):
    """
    Get the neighboring faces' continuity at given edge

    Args:
        edge (occwl.edge.Edge): Edge

    Returns:
        GeomAbs_Shape: enum describing the continuity order
    """
    faces = list(self.faces_from_edge(edge))
    # Handle seam edges which only have one face around them
    if len(faces) == 1:
        faces.append(faces[-1])
    return edge.continuity(faces[0], faces[1])

edges()

Get an iterator to go over all edges in the Shape

Returns:

Type Description

Iterator[occwl.edge.Edge]: Edge iterator

Source code in src/occwl/base.py
59
60
61
62
63
64
65
66
67
def edges(self):
    """
    Get an iterator to go over all edges in the Shape

    Returns:
        Iterator[occwl.edge.Edge]: Edge iterator
    """
    from occwl.edge import Edge
    return map(Edge, self._top_exp.edges())

edges_from_face(face)

Get an iterator to go over the edges in a face

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.edge.Edge]: Edge iterator

Source code in src/occwl/base.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def edges_from_face(self, face):
    """
    Get an iterator to go over the edges in a face

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.edge.Edge]: Edge iterator
    """
    from occwl.edge import Edge
    from occwl.face import Face
    assert isinstance(face, Face)
    return map(Edge, self._top_exp.edges_from_face(face.topods_shape()))

edges_from_vertex(vertex)

Get an iterator to go over the edges adjacent to a vertex

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.edge.Edge]: Edge iterator

Source code in src/occwl/base.py
386
387
388
389
390
391
392
393
394
395
396
397
398
399
def edges_from_vertex(self, vertex):
    """
    Get an iterator to go over the edges adjacent to a vertex

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.edge.Edge]: Edge iterator
    """
    from occwl.vertex import Vertex
    from occwl.edge import Edge
    assert isinstance(vertex, Vertex)
    return map(Edge, self._top_exp.edges_from_vertex(vertex.topods_shape()))

exact_box(use_shapetolerance=False)

Get a slow, but accurate box for the Shape.

Returns:

Name Type Description
Box

Bounding box

Source code in src/occwl/base.py
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
def exact_box(self, use_shapetolerance=False):
    """
    Get a slow, but accurate box for the Shape.

    Args:
        use_shapetolerance (bool, optional) Include the tolerance of edges
                                            and vertices in the box.

    Returns:
        Box: Bounding box
    """
    from occwl.geometry import geom_utils
    b = Bnd_Box()
    use_triangulation = True
    brepbndlib_AddOptimal(self.topods_shape(), b, use_triangulation, use_shapetolerance)
    return geom_utils.box_to_geometry(b)

faces()

Get an iterator to go over all faces in the Shape

Returns:

Type Description

Iterator[occwl.face.Face]: Face iterator

Source code in src/occwl/base.py
165
166
167
168
169
170
171
172
173
def faces(self):
    """
    Get an iterator to go over all faces in the Shape

    Returns:
        Iterator[occwl.face.Face]: Face iterator
    """
    from occwl.face import Face
    return map(Face, self._top_exp.faces())

faces_from_edge(edge)

Get an iterator to go over the faces adjacent to an edge

Parameters:

Name Type Description Default
edge Edge

Input edge

required

Returns:

Type Description

Iterator[occwl.face.Face]: Face iterator

Source code in src/occwl/base.py
317
318
319
320
321
322
323
324
325
326
327
328
329
330
def faces_from_edge(self, edge):
    """
    Get an iterator to go over the faces adjacent to an edge

    Args:
        edge (occwl.edge.Edge): Input edge

    Returns:
        Iterator[occwl.face.Face]: Face iterator
    """
    from occwl.edge import Edge
    from occwl.face import Face
    assert isinstance(edge, Edge)
    return map(Face, self._top_exp.faces_from_edge(edge.topods_shape()))

faces_from_vertex(vertex)

Get an iterator to go over the faces adjacent to a vertex

Parameters:

Name Type Description Default
edge Vertex

Input vertex

required

Returns:

Type Description

Iterator[occwl.face.Face]: Face iterator

Source code in src/occwl/base.py
332
333
334
335
336
337
338
339
340
341
342
343
344
345
def faces_from_vertex(self, vertex):
    """
    Get an iterator to go over the faces adjacent to a vertex

    Args:
        edge (occwl.vertex.Vertex): Input vertex

    Returns:
        Iterator[occwl.face.Face]: Face iterator
    """
    from occwl.vertex import Vertex
    from occwl.face import Face
    assert isinstance(vertex, Vertex)
    return map(Face, self._top_exp.faces_from_vertex(vertex.topods_shape()))

find_closest_edge_slow(datum)

Find the closest edge to the given datum point. The function is for testing only. It will be slow as it loops over all edges in the Shape. A quick way to find the closest entity is to call Shape.find_closest_point_data(), but then you may get a face, edge or vertex back.

Parameters:

Name Type Description Default
datum ndarray or tuple

3D datum point

required

Returns:

Name Type Description
Face

The closest face in the Shape

Source code in src/occwl/base.py
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def find_closest_edge_slow(self, datum):
    """
    Find the closest edge to the given datum point.
    The function is for testing only.  It will be slow 
    as it loops over all edges in the Shape.
    A quick way to find the closest entity is to call
    Shape.find_closest_point_data(), but then you
    may get a face, edge or vertex back.

    Args:
        datum (np.ndarray or tuple): 3D datum point

    Returns:
        Face: The closest face in the Shape
    """
    return _find_closest_shape_in_list(self.edges(), datum)

find_closest_face_slow(datum)

Find the closest face to the given datum point. The function is for testing only. It will be slow as it loops over all faces in the Shape. A quick way to find the closest entity is to call Shape.find_closest_point_data(), but then you may get a face, edge or vertex back.

Parameters:

Name Type Description Default
datum ndarray or tuple

3D datum point

required

Returns:

Name Type Description
Face

The closest face in the Shape

Source code in src/occwl/base.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def find_closest_face_slow(self, datum):
    """
    Find the closest face to the given datum point.
    The function is for testing only. It will be slow 
    as it loops over all faces in the Shape.
    A quick way to find the closest entity is to call
    Shape.find_closest_point_data(), but then you
    may get a face, edge or vertex back.

    Args:
        datum (np.ndarray or tuple): 3D datum point

    Returns:
        Face: The closest face in the Shape
    """
    return _find_closest_shape_in_list(self.faces(), datum)

find_closest_point_data(datum)

Find the information about the closest point on this shape

Parameters:

Name Type Description Default
datum ndarray

3D Point

required

Returns:

Name Type Description
ClosestPointData

Data about the closest point on this shape

None

if error

Source code in src/occwl/shape.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
def find_closest_point_data(self, datum):
    """
    Find the information about the closest point on this shape

    Args:
        datum (np.ndarray): 3D Point

    Returns:
        ClosestPointData: Data about the closest point on this shape
        None: if error
    """
    # Folowing https://dev.opencascade.org/content/how-retrieve-nearest-face-shape-given-gppnt
    # Create a vertex from the point
    occ_point = geom_utils.numpy_to_gp(datum)
    vertex_maker = BRepBuilderAPI_MakeVertex(occ_point)
    vertex = vertex_maker.Shape()
    dist_shape_shape = BRepExtrema_DistShapeShape(
        vertex, self.topods_shape(), Extrema_ExtFlag_MIN
    )
    ok = dist_shape_shape.Perform()
    if not ok:
        return None

    return ClosestPointData(dist_shape_shape)

get_triangles(triangle_face_tol=0.01, tol_relative_to_face=True, angle_tol_rads=0.1)

Compute and get the tessellation of the entire shape

Parameters:

Name Type Description Default
triangle_face_tol float

Toelrance between triangle and surface. Defaults to 0.01.

0.01
tol_relative_to_face bool

Whether tolerance is relative to face size

True
angle_tol_rads float

Angle tolerance in radians. Defaults to 0.1.

0.1

Returns:

Type Description

2D np.ndarray (float): Vertices or None if triangulation failed

2D np.ndarray (int): Faces or None if triangulation failed

Source code in src/occwl/base.py
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
def get_triangles(
    self,
    triangle_face_tol=0.01,  # Tolerance between triangle and surface
    tol_relative_to_face=True,  # The tolerance value is relative to the face size
    angle_tol_rads=0.1,  # Angle between normals/tangents at triangle vertices
):
    """
    Compute and get the tessellation of the entire shape

    Args:
        triangle_face_tol (float, optional): Toelrance between triangle and surface. Defaults to 0.01.
        tol_relative_to_face (bool): Whether tolerance is relative to face size
        angle_tol_rads (float, optional): Angle tolerance in radians. Defaults to 0.1.

    Returns:
        2D np.ndarray (float): Vertices or None if triangulation failed
        2D np.ndarray (int): Faces or None if triangulation failed
    """
    ok = self.triangulate_all_faces(
        triangle_face_tol, tol_relative_to_face, angle_tol_rads
    )
    if not ok:
        # Failed to triangulate
        return None, None
    verts = []
    tris = []
    faces = self.faces()
    last_vert_index = 0
    for face in faces:
        fverts, ftris = face.get_triangles()
        verts.extend(fverts)
        for tri in ftris:
            new_indices = [index + last_vert_index for index in tri]
            tris.append(new_indices)
        last_vert_index = len(verts)
    return np.asarray(verts, dtype=np.float32), np.asarray(tris, dtype=np.int32)

load_from_occ_native(filename, verbosity=False) staticmethod

Load everything from the OCC native .brep file format into a single occwl.compound.Compound.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
verbosity bool

Whether to print detailed information while loading

False

Returns:

Type Description

occwl.compound.Compound: Compound shape

Source code in src/occwl/compound.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
@staticmethod
def load_from_occ_native(filename, verbosity=False):
    """
    Load everything from the OCC native .brep file 
    format into a single occwl.compound.Compound.

    Note:  Saving to and loading from the native file format 
           is between one and two orders of magnitude faster 
           than loading from STEP, so it is recommended for 
           large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename
        verbosity (bool): Whether to print detailed information while loading

    Returns:
        occwl.compound.Compound: Compound shape
    """
    shape_set = BRepTools_ShapeSet()
    with open(filename, "r") as fp:
        shape_set.ReadFromString(fp.read())
    shapes = []
    for i in range(shape_set.NbShapes()):
        shapes.append(shape_set.Shape(i+1))
    shp, success = list_of_shapes_to_compound(shapes)
    assert success
    return Compound(shp)

load_from_step(filename, verbosity=False) staticmethod

Load everything from a STEP file as a single Compound

Parameters:

Name Type Description Default
filename str or Path

STEP filename

required
verbosity bool

Whether to print detailed information while loading

False

Returns:

Type Description

occwl.compound.Compound: Compound shape

Source code in src/occwl/compound.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@staticmethod
def load_from_step(filename, verbosity=False):
    """
    Load everything from a STEP file as a single Compound

    Args:
        filename (str or pathlib.Path): STEP filename
        verbosity (bool): Whether to print detailed information while loading

    Returns:
        occwl.compound.Compound: Compound shape
    """
    shp = read_step_file(str(filename), as_compound=True, verbosity=verbosity)
    if not isinstance(shp, TopoDS_Compound):
        shp, success = list_of_shapes_to_compound([shp])
        assert success
    return Compound(shp)

load_step_with_attributes(step_filename) staticmethod

Load shapes from a step file with the name information. Other attributes could be retro-fitted

Parameters:

Name Type Description Default
step_filename str

Path to STEP file

required

Returns:

Type Description

occwl.Compound, dict occwl.Shape to attributes

Source code in src/occwl/compound.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
@staticmethod
def load_step_with_attributes(step_filename):
    """Load shapes from a step file with the
    name information.   Other attributes could be
    retro-fitted

    Args:
        step_filename (str): Path to STEP file

    Returns:
        occwl.Compound, dict occwl.Shape to attributes 
    """        
    # Read the file and get the shape
    reader = STEPControl_Reader()
    tr = reader.WS().TransferReader()
    reader.ReadFile(str(step_filename))
    reader.TransferRoots()
    shape = reader.OneShape()

    occwl_shape_to_attributes = {}
    def check_shape_type(shape_type):
        exp = TopExp_Explorer(shape, shape_type)
        while exp.More():
            s = exp.Current()
            exp.Next()
            item = tr.EntityFromShapeResult(s, 1)
            if item is None:
                continue
            item = StepRepr_RepresentationItem.DownCast(item)
            if item is None:
                continue
            name = item.Name().ToCString()
            occwl_shape = Shape.occwl_shape(s)
            occwl_shape_to_attributes[occwl_shape] = {
                "name": name
            }

    check_shape_type(TopAbs_FACE)
    check_shape_type(TopAbs_EDGE)
    check_shape_type(TopAbs_SHELL)
    check_shape_type(TopAbs_SOLID)
    check_shape_type(TopAbs_COMPOUND)
    check_shape_type(TopAbs_COMPSOLID)

    shp, success = list_of_shapes_to_compound([shape])
    assert success, "Failed to convert to a single compound"
    return Compound(shp), occwl_shape_to_attributes

moment_of_inertia(point, direction, tolerance=1e-09)

Compute the moment of inertia about an axis

Parameters:

Name Type Description Default
point ndarray

3D point (origin of the axis)

required
direction ndarray

3D direction of the axis

required
tolerance float

Tolerance. Defaults to 1e-9.

1e-09

Returns:

Name Type Description
float

Moment of inertia

Source code in src/occwl/base.py
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
def moment_of_inertia(self, point, direction, tolerance=1e-9):
    """
    Compute the moment of inertia about an axis

    Args:
        point (np.ndarray): 3D point (origin of the axis)
        direction (np.ndarray): 3D direction of the axis
        tolerance (float, optional): Tolerance. Defaults to 1e-9.

    Returns:
        float: Moment of inertia
    """
    from occwl.geometry import geom_utils
    props = GProp_GProps()
    brepgprop_VolumeProperties(self.topods_shape(), props, tolerance)
    axis = gp_Ax1(
        geom_utils.numpy_to_gp(point), geom_utils.numpy_to_gp_dir(direction)
    )
    return props.MomentOfInertia(axis)

num_edges()

Number of edges in the Shape

Returns:

Name Type Description
int

Number of edges

Source code in src/occwl/base.py
50
51
52
53
54
55
56
57
def num_edges(self):
    """
    Number of edges in the Shape

    Returns:
        int: Number of edges
    """
    return self._top_exp.number_of_edges()

num_faces()

Number of faces in the Shape

Returns:

Name Type Description
int

Number of faces

Source code in src/occwl/base.py
156
157
158
159
160
161
162
163
def num_faces(self):
    """
    Number of faces in the Shape

    Returns:
        int: Number of faces
    """
    return self._top_exp.number_of_faces()

num_shells()

Number of shells in the Shape

Returns:

Name Type Description
int

Number of shells

Source code in src/occwl/base.py
267
268
269
270
271
272
273
274
def num_shells(self):
    """
    Number of shells in the Shape

    Returns:
        int: Number of shells
    """
    return self._top_exp.number_of_shells()

num_solids()

Number of solids in the Compound

Returns:

Name Type Description
int

Number of solids

Source code in src/occwl/base.py
292
293
294
295
296
297
298
299
def num_solids(self):
    """
    Number of solids in the Compound

    Returns:
        int: Number of solids
    """
    return self._top_exp.number_of_solids()

num_vertices()

Number of vertices in the Shape

Returns:

Name Type Description
int

Number of vertices

Source code in src/occwl/base.py
25
26
27
28
29
30
31
32
def num_vertices(self):
    """
    Number of vertices in the Shape

    Returns:
        int: Number of vertices
    """
    return self._top_exp.number_of_vertices()

num_wires()

Number of wires in the Shape

Returns:

Name Type Description
int

Number of wires

Source code in src/occwl/base.py
131
132
133
134
135
136
137
138
def num_wires(self):
    """
    Number of wires in the Shape

    Returns:
        int: Number of wires
    """
    return self._top_exp.number_of_wires()

occwl_shape(topods_shape) staticmethod

Static method to create an occwl shape of the appropriate class from the given topods_shape Args: topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

Returns:

Type Description

One of occwl.compound.Compound occwl.solid.Solid occwl.face.Face occwl.edge.Edge occwl.vertex.Vertex occwl.wire.Wire occwl.shell.Shell

Raises: Exception: [description]

Source code in src/occwl/shape.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@staticmethod
def occwl_shape(topods_shape):
    """
    Static method to create an occwl shape of the appropriate 
    class from the given topods_shape
    Args:
        topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

    Returns:
        One of
            occwl.compound.Compound
            occwl.solid.Solid
            occwl.face.Face
            occwl.edge.Edge
            occwl.vertex.Vertex
            occwl.wire.Wire
            occwl.shell.Shell
    Raises:
        Exception: [description]
    """
    from occwl.compound import Compound
    from occwl.solid import Solid
    from occwl.face import Face
    from occwl.edge import Edge
    from occwl.vertex import Vertex
    from occwl.wire import Wire
    from occwl.shell import Shell

    if isinstance(topods_shape, TopoDS_Vertex):
        return Vertex(topods_shape)
    if isinstance(topods_shape, TopoDS_Edge):
        return Edge(topods_shape)
    if isinstance(topods_shape, TopoDS_Face):
        return Face(topods_shape)
    if isinstance(topods_shape, TopoDS_Wire):
        return Wire(topods_shape)
    if isinstance(topods_shape, TopoDS_Shell):
        return Shell(topods_shape)
    if isinstance(topods_shape, TopoDS_Solid):
        return Solid(topods_shape)
    if isinstance(topods_shape, (TopoDS_Compound, TopoDS_CompSolid)):
        return Compound(topods_shape)
    raise Exception(
        "Shape must be one of TopoDS_Vertex, TopoDS_Edge, TopoDS_Face, TopoDS_Shell, TopoDS_Solid, TopoDS_Compound, TopoDS_CompSolid"
    )

reversed()

Whether this shape is reversed.

  • For an edge this is whether the edge is reversed with respect to the curve geometry
  • For a face this is whether the face is reversed with respect to the surface geometry
  • For a vertex this is whether the vertex is at the upper or lower parameter value on the edges curve

Returns:

Name Type Description
bool

If rational

Source code in src/occwl/shape.py
236
237
238
239
240
241
242
243
244
245
246
247
248
def reversed(self):
    """
    Whether this shape is reversed.

    - For an edge this is whether the edge is reversed with respect to the curve geometry
    - For a face this is whether the face is reversed with respect to the surface geometry
    - For a vertex this is whether the vertex is at the upper or lower parameter value on the
      edges curve

    Returns:
        bool: If rational
    """
    return self.topods_shape().Orientation() == TopAbs_REVERSED

rotate_axis_angle(axis, angle_radians, origin=np.zeros(3, dtype=(np.float32)))

Rotate the shape about the given axis by the given angle in radians

Parameters:

Name Type Description Default
axis ndarray

Rotation axis

required
angle_radians float

Angle in radians

required
Source code in src/occwl/shape.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
def rotate_axis_angle(
    self, axis, angle_radians, origin=np.zeros(3, dtype=np.float32)
):
    """
    Rotate the shape about the given axis by the given angle in radians

    Args:
        axis (np.ndarray): Rotation axis
        angle_radians (float): Angle in radians
    """
    self._shape = rotate_shape(
        self._shape,
        gp_Ax1(geom_utils.numpy_to_gp(origin), geom_utils.numpy_to_gp_dir(axis)),
        angle_radians,
        unite="rad",
    )

rotate_euler_angles(angles_xyz_radians)

Rotate the shape by the given Euler angles in radians

Parameters:

Name Type Description Default
angle_xyz_radians ndarray

3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians

required
Source code in src/occwl/shape.py
302
303
304
305
306
307
308
309
310
311
312
313
314
315
def rotate_euler_angles(self, angles_xyz_radians):
    """
    Rotate the shape by the given Euler angles in radians

    Args:
        angle_xyz_radians (np.ndarray): 3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians
    """
    self._shape = rotate_shp_3_axis(
        self._shape,
        angles_xyz_radians[0],
        angles_xyz_radians[1],
        angles_xyz_radians[2],
        unity="rad",
    )

save_shapes_to_occ_native(filename, shapes, with_triangles=False, with_normals=False, format_version=None) staticmethod

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
@staticmethod
def save_shapes_to_occ_native(
        filename, 
        shapes,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
            is between one and two orders of magnitude faster 
            than loading from STEP, so it is recommended for 
            large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename

        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    new_api = False
    shapes_set = BRepTools_ShapeSet(with_triangles)
    # shapes_set.SetWithNormals(with_normals) # Not in OCC 7.5.0

    for shp in shapes:
        shapes_set.Add(shp.topods_shape())
    if format_version is not None:
        shapes_set.SetFormatNb(format_version)


    with open(filename, "w") as fp:
        s = shapes_set.WriteToString()
        fp.write(s)

save_to_occ_native(filename, verbosity=False, with_triangles=False, with_normals=False, format_version=None)

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def save_to_occ_native(
        self, 
        filename, 
        verbosity=False,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
           is between one and two orders of magnitude faster 
           than loading from STEP, so it is recommended for 
           large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename
        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    self.save_shapes_to_occ_native(
        filename, 
        [ self ],
        with_triangles=with_triangles,
        with_normals=with_normals,
        format_version=format_version
    )

scale(scale_vector)

Scale the shape by the given 3D vector

Parameters:

Name Type Description Default
scale_vector ndarray

3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively

required
Source code in src/occwl/shape.py
317
318
319
320
321
322
323
324
325
326
def scale(self, scale_vector):
    """
    Scale the shape by the given 3D vector

    Args:
        scale_vector (np.ndarray): 3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively
    """
    self._shape = scale_shape(
        self._shape, scale_vector[0], scale_vector[1], scale_vector[2]
    )

scale_to_box(box_side, copy=True)

Translate and scale the Shape so it fits exactly into the [-box_side, box_side]^3 box

Returns:

Type Description

occwl..: The scaled version of this Shape

Source code in src/occwl/base.py
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
def scale_to_box(self, box_side, copy=True):
    """
    Translate and scale the Shape so it fits exactly 
    into the [-box_side, box_side]^3 box

    Args:
        box_side (float) The side length of the box
        copy (bool)      True - Copy entities and apply the transform to
                                the underlying geometry
                         False - Apply the transform to the topods Locator
                                 if possible 

    Returns:
        occwl.*.*: The scaled version of this Shape
    """
    from occwl.geometry import geom_utils
    # Get an exact box for the Shape
    box = self.exact_box()
    center = box.center()
    longest_length = box.max_box_length()

    orig = gp_Pnt(0.0, 0.0, 0.0)
    center = geom_utils.numpy_to_gp(center)
    vec_center_to_orig = gp_Vec(center, orig)
    move_to_center = gp_Trsf()
    move_to_center.SetTranslation(vec_center_to_orig)

    scale_trsf = gp_Trsf()
    scale_trsf.SetScale(orig, (2.0 * box_side) / longest_length)
    trsf_to_apply = scale_trsf.Multiplied(move_to_center)

    return self._apply_transform(trsf_to_apply, copy=copy)

scale_to_unit_box(copy=True)

Translate and scale the Shape so it fits exactly into the [-1, 1]^3 box

Returns: The scaled version of this shape

Source code in src/occwl/base.py
628
629
630
631
632
633
634
635
636
637
638
639
640
641
def scale_to_unit_box(self, copy=True):
    """
    Translate and scale the Shape so it fits exactly 
    into the [-1, 1]^3 box

    Args:
        copy (bool)      True - Copy entities and apply the transform to
                                    the underlying geometry
                            False - Apply the transform to the topods Locator
                                    if possible 
    Returns:
        The scaled version of this shape
    """
    return self.scale_to_box(1.0, copy=copy)

set_transform_to_identity()

When an assembly is loaded from a STEP file the solids will be transformed relative to their local coordinate system. i.e. they are placed in the assembly root components coordinate system.

When working with individual bodies you often want them to be axis aligned, in which case you want to remove the assembly transform. This function removes it for you.

If however you want to bake the transform into the bodies and suppress the asserts from parts of occwl which don't cope with transforms then use the transform() function below with copy=True

Source code in src/occwl/shape.py
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def set_transform_to_identity(self):
    """
    When an assembly is loaded from a STEP file
    the solids will be transformed relative to
    their local coordinate system.   i.e. they
    are placed in the assembly root components 
    coordinate system.

    When working with individual bodies you often
    want them to be axis aligned, in which case 
    you want to remove the assembly transform.
    This function removes it for you.

    If however you want to bake the transform
    into the bodies and suppress the asserts 
    from parts of occwl which don't cope with
    transforms then use the transform() function
    below with copy=True
    """
    identity = TopLoc_Location()
    self.topods_shape().Location(identity)
    self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)
    self.convert_geometric_identity_transforms_to_identity()

shells()

Get an iterator to go over all shells in the Shape

Returns:

Type Description

Iterator[occwl.shell.Shell]: Shell iterator

Source code in src/occwl/base.py
276
277
278
279
280
281
282
283
284
def shells(self):
    """
    Get an iterator to go over all shells in the Shape

    Returns:
        Iterator[occwl.shell.Shell]: Shell iterator
    """
    from occwl.shell import Shell
    return map(Shell, self._top_exp.shells())

solids()

Get an iterator to go over all solids in the Compound

Returns:

Type Description

Iterator[occwl.solid.Solid]: Solid iterator

Source code in src/occwl/base.py
301
302
303
304
305
306
307
308
309
def solids(self):
    """
    Get an iterator to go over all solids in the Compound

    Returns:
        Iterator[occwl.solid.Solid]: Solid iterator
    """
    from occwl.solid import Solid
    return map(Solid, self._top_exp.solids())

split_all_closed_edges(max_tol=0.01, precision=0.01, num_splits=1)

Split all the closed edges in this shape

Parameters:

Name Type Description Default
max_tol float

Maximum tolerance allowed. Defaults to 0.01.

0.01
precision float

Precision of the tool when splitting. Defaults to 0.01.

0.01
num_splits int

Number of splits to perform. Each split edge will result in num_splits + 1 edges. Defaults to 1.

1

Returns:

Type Description

occwl..: Shape with closed edges split

Source code in src/occwl/base.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def split_all_closed_edges(self, max_tol=0.01, precision=0.01, num_splits=1):
    """
    Split all the closed edges in this shape

    Args:
        max_tol (float, optional): Maximum tolerance allowed. Defaults to 0.01.
        precision (float, optional): Precision of the tool when splitting. Defaults to 0.01.
        num_splits (int, optional): Number of splits to perform. Each split edge will result in num_splits + 1 edges. Defaults to 1.

    Returns:
        occwl.*.*: Shape with closed edges split
    """
    divider = ShapeUpgrade_ShapeDivideClosedEdges(self.topods_shape())
    divider.SetPrecision(precision)
    divider.SetMinTolerance(0.1 * max_tol)
    divider.SetMaxTolerance(max_tol)
    divider.SetNbSplitPoints(num_splits)
    ok = divider.Perform()
    if not ok:
        # Splitting failed or there were no closed edges to split
        # Return the original shape
        return self
    return type(self)(divider.Result())

split_all_closed_faces(max_tol=0.01, precision=0.01, num_splits=1)

Split all the closed faces in this shape

Parameters:

Name Type Description Default
max_tol float

Maximum tolerance allowed. Defaults to 0.01.

0.01
precision float

Precision of the tool when splitting. Defaults to 0.01.

0.01
num_splits int

Number of splits to perform. Each split face will result in num_splits + 1 faces. Defaults to 1.

1

Returns:

Type Description

occwl..: Shape with closed faces split

Source code in src/occwl/base.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def split_all_closed_faces(self, max_tol=0.01, precision=0.01, num_splits=1):
    """
    Split all the closed faces in this shape

    Args:
        max_tol (float, optional): Maximum tolerance allowed. Defaults to 0.01.
        precision (float, optional): Precision of the tool when splitting. Defaults to 0.01.
        num_splits (int, optional): Number of splits to perform. Each split face will result in num_splits + 1 faces. Defaults to 1.

    Returns:
        occwl.*.*: Shape with closed faces split
    """
    divider = ShapeUpgrade_ShapeDivideClosed(self.topods_shape())
    divider.SetPrecision(precision)
    divider.SetMinTolerance(0.1 * max_tol)
    divider.SetMaxTolerance(max_tol)
    divider.SetNbSplitPoints(num_splits)
    ok = divider.Perform()
    if not ok:
        # Splitting failed or there were no closed faces to split
        # Return the original shape
        return self
    return type(self)(divider.Result())

topods_shape()

Get the underlying OCC shape

Returns:

Type Description

OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*

Source code in src/occwl/shape.py
80
81
82
83
84
85
86
87
def topods_shape(self):
    """
    Get the underlying OCC shape

    Returns:
        OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*
    """
    return self._shape

transform(a, copy=True)

Apply the given 3x4 transform matrix to the solid.

 copy (bool)    True - Copy entities and apply the transform to
                       the underlying geometry
                False - Apply the transform to the topods Locator
                        if possible
Source code in src/occwl/shape.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
def transform(self, a: np.ndarray, copy=True):
    """
    Apply the given 3x4 transform matrix to the solid.

    Args: a (nd.array) - Homogeneous transform matrix
                         The transform that will be applied is

                         x' =  a[0,0]*x + a[0,1]*y + a[0,2]*z + a[0, 3]
                         y' =  a[1,0]*x + a[1,1]*y + a[1,2]*z + a[1, 3]
                         z' =  a[2,0]*x + a[2,1]*y + a[2,2]*z + a[2, 3]

         copy (bool)    True - Copy entities and apply the transform to
                               the underlying geometry
                        False - Apply the transform to the topods Locator
                                if possible 
    """
    assert (a.shape == (3, 4)), "Transform matrix must be 3x4"
    a = a.astype(np.float64)

    # Create an identity transform
    trsf = gp_Trsf()

    # If the matrix is an identity matrix then
    # we don't want to set the values as this
    # would give us a geometric identity without
    # the identity flag set
    if not np.allclose(a, np.eye(3, 4)):
        trsf.SetValues(
            a[0,0], a[0,1], a[0,2], a[0, 3],
            a[1,0], a[1,1], a[1,2], a[1, 3],
            a[2,0], a[2,1], a[2,2], a[2, 3]
        )
    return self._apply_transform(trsf, copy=copy)

translate(offset)

Translate the shape by an offset vector

Parameters:

Name Type Description Default
offset ndarray

Offset vector

required
Source code in src/occwl/shape.py
276
277
278
279
280
281
282
283
def translate(self, offset):
    """
    Translate the shape by an offset vector

    Args:
        offset (np.ndarray): Offset vector
    """
    self._shape = translate_shp(self._shape, geom_utils.numpy_to_gp_vec(offset))

triangulate(triangle_face_tol=0.01, tol_relative_to_face=True, angle_tol_rads=0.1)

Triangulate all the faces in the shape. You can then get the triangles from each face separately using face.get_triangles(). If you wanted triangles for the entire shape then call shape.get_triangles() below. For more details see https://old.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11

Parameters:

Name Type Description Default
triangle_face_tol float

Tolerance between triangle and surface. Defaults to 0.01.

0.01
tol_relative_to_face bool

Whether tolerance is relative to face size

True
angle_tol_rads float

Angle tolerance in radians. Defaults to 0.1.

0.1

Returns:

Name Type Description
bool

Is successful

Source code in src/occwl/base.py
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
def triangulate(
    self,
    triangle_face_tol=0.01,  # Tolerance between triangle and surface
    tol_relative_to_face=True,  # The tolerance value is relative to the face size
    angle_tol_rads=0.1,  # Angle between normals/tangents at triangle vertices
):
    """
    Triangulate all the faces in the shape. You can then get the triangles 
    from each face separately using face.get_triangles().
    If you wanted triangles for the entire shape then call
    shape.get_triangles() below.
    For more details see 
    https://old.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11

    Args:
        triangle_face_tol (float, optional): Tolerance between triangle and surface. Defaults to 0.01.
        tol_relative_to_face (bool): Whether tolerance is relative to face size
        angle_tol_rads (float, optional): Angle tolerance in radians. Defaults to 0.1.

    Returns:
        bool: Is successful
    """
    mesh = BRepMesh_IncrementalMesh(
        self.topods_shape(),
        triangle_face_tol,
        tol_relative_to_face,
        angle_tol_rads,
        True,
    )
    mesh.Perform()
    return mesh.IsDone()

valid(return_analyzer=False)

Check if the shape is valid

Parameters:

Name Type Description Default
return_analyzer bool

Whether to return the BRepCheck_Analyzer object for more inspection

False

Returns:

Name Type Description
bool

Whether the shape is valid

BRepCheck_Analyzer [optional]: if return_analyzer is True

Source code in src/occwl/shape.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def valid(self, return_analyzer=False):
    """
    Check if the shape is valid

    Args:
        return_analyzer (bool): Whether to return the BRepCheck_Analyzer object for more inspection

    Returns:
        bool: Whether the shape is valid
        BRepCheck_Analyzer [optional]: if return_analyzer is True
    """
    analyzer = BRepCheck_Analyzer(self.topods_shape())
    if return_analyzer:
        return analyzer.IsValid(), analyzer
    return analyzer.IsValid()

vertices()

Get an iterator to go over all vertices in the Shape

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
34
35
36
37
38
39
40
41
42
def vertices(self):
    """
    Get an iterator to go over all vertices in the Shape

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    return map(Vertex, self._top_exp.vertices())

vertices_from_edge(edge)

Get an iterator to go over the vertices bounding an edge

Parameters:

Name Type Description Default
edge Edge

Input edge

required

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def vertices_from_edge(self, edge):
    """
    Get an iterator to go over the vertices bounding an edge

    Args:
        edge (occwl.edge.Edge): Input edge

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    from occwl.edge import Edge
    assert isinstance(edge, Edge)
    return map(Vertex, self._top_exp.vertices_from_edge(edge.topods_shape()))

vertices_from_face(face)

Get an iterator to go over the vertices in a face

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def vertices_from_face(self, face):
    """
    Get an iterator to go over the vertices in a face

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    from occwl.face import Face
    assert isinstance(face, Face)
    return map(Vertex, self._top_exp.vertices_from_face(face.topods_shape()))

volume(tolerance=1e-09)

Compute the volume of the Shape

Parameters:

Name Type Description Default
tolerance float

Tolerance. Defaults to 1e-9.

1e-09

Returns:

Name Type Description
float

Volume

Source code in src/occwl/base.py
509
510
511
512
513
514
515
516
517
518
519
520
521
def volume(self, tolerance=1e-9):
    """
    Compute the volume of the Shape

    Args:
        tolerance (float, optional): Tolerance. Defaults to 1e-9.

    Returns:
        float: Volume
    """
    props = GProp_GProps()
    brepgprop_VolumeProperties(self.topods_shape(), props, tolerance)
    return props.Mass()

wires()

Get an iterator to go over all wires in the Shape

Returns:

Type Description

Iterator[occwl.wire.Wire]: Wire iterator

Source code in src/occwl/base.py
140
141
142
143
144
145
146
147
148
def wires(self):
    """
    Get an iterator to go over all wires in the Shape

    Returns:
        Iterator[occwl.wire.Wire]: Wire iterator
    """
    from occwl.wire import Wire
    return map(Wire, self._top_exp.wires())

wires_from_face(face)

Get an iterator to go over the wires bounding a face

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.wire.Wire]: Wire iterator

Source code in src/occwl/base.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
def wires_from_face(self, face):
    """
    Get an iterator to go over the wires bounding a face

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.wire.Wire]: Wire iterator
    """
    from occwl.wire import Wire
    from occwl.face import Face
    assert isinstance(face, Face)
    return map(Wire, self._top_exp.wires_from_face(face.topods_shape()))

edge

Edge

Bases: Shape, VertexContainerMixin, BoundingBoxMixin

A topological edge in a solid model Represents a 3D curve bounded by vertices

Source code in src/occwl/edge.py
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
class Edge(Shape, VertexContainerMixin, BoundingBoxMixin):
    """
    A topological edge in a solid model
    Represents a 3D curve bounded by vertices
    """

    def __init__(self, topods_edge):
        assert isinstance(topods_edge, TopoDS_Edge)
        super().__init__(topods_edge)

    @staticmethod
    def make_line_from_points(start_point, end_point):
        """
        Make an edge from two given points

        Args:
            start_point (np.ndarray 3D vector): Starting point
            end_point (np.ndarray 3D vector): Ending point

        Returns:
            occwl.Edge: Edge joining the two given vertices
            or None: if error
        """
        edge_builder = BRepBuilderAPI_MakeEdge(geom_utils.to_gp_pnt(start_point), geom_utils.to_gp_pnt(end_point))
        if not edge_builder.IsDone():
            return None
        return Edge(edge_builder.Edge())

    @staticmethod
    def make_line_from_vertices(start_vertex, end_vertex):
        """
        Make an edge from two given vertices

        Args:
            start_vertex (occwl.vertex.Vertex): Starting vertex
            end_vertex (occwl.vertex.Vertex): Ending vertex

        Returns:
            occwl.Edge: Edge joining the two given vertices
            or None: if error
        """
        edge_builder = BRepBuilderAPI_MakeEdge(start_vertex.topods_shape(), end_vertex.topods_shape())
        if not edge_builder.IsDone():
            return None
        return Edge(edge_builder.Edge())

    @staticmethod
    def make_circle(center, radius, direction=(0, 0, 1)):
        """
        Make a circular edge

        Args:
            center (np.ndarray or list or tuple with 3D point): Center of the circle
            radius (float): Radius of the circle
            direction (np.ndarray or list or tuple with 3D unit vector, optional): Normal of the circle's face. Defaults to (0, 0, 1).

        Returns:
            occwl.edge.Edge: Edge
            or None: if error
        """
        circle = gp_Circ(
            gp_Ax2(geom_utils.to_gp_pnt(center), geom_utils.to_gp_dir(direction)),
            float(radius),
        )
        edge_builder = BRepBuilderAPI_MakeEdge(circle)
        if not edge_builder.IsDone():
            return None
        return Edge(edge_builder.Edge())

    @staticmethod
    def make_arc_of_circle(pt1, pt2, pt3):
        """
        Make an arc edge

        Args:
            pt1 (np.ndarray or list or tuple with 3D point): Start point
            pt2 (np.ndarray or list or tuple with 3D point): Mid (not necessarily at the middle) point
            pt3 (np.ndarray or list or tuple with 3D point): End point

        Returns:
            occwl.edge.Edge: Edge
            or None: if error
        """
        arc = GC_MakeArcOfCircle(geom_utils.to_gp_pnt(pt1), geom_utils.to_gp_pnt(pt2), geom_utils.to_gp_pnt(pt3)).Value()
        edge_builder = BRepBuilderAPI_MakeEdge(arc)
        if not edge_builder.IsDone():
            return None
        return Edge(edge_builder.Edge())

    def point(self, u):
        """
        Evaluate the edge geometry at given parameter

        Args:
            u (float): Curve parameter

        Returns:
            np.ndarray: 3D Point
        """
        if self.has_curve():
            pt = self.curve().Value(u)
            return geom_utils.gp_to_numpy(pt)
        # If the edge has no curve then return a point
        # at the origin.
        # It would ne nice to return the location of the
        # vertex
        return np.array([0, 0, 0])

    def start_vertex(self):
        """
        Returns the starting vertex of the edge while considering the edge orientation

        Returns:
            occwl.vertex.Vertex: Start vertex
        """
        return occwl.vertex.Vertex(ShapeAnalysis_Edge().FirstVertex(self.topods_shape()))

    def end_vertex(self):
        """
        Returns the ending vertex of the edge while considering the edge orientation

        Returns:
            occwl.vertex.Vertex: End vertex
        """
        return occwl.vertex.Vertex(ShapeAnalysis_Edge().LastVertex(self.topods_shape()))

    def tangent(self, u):
        """
        Compute the tangent of the edge geometry at given parameter

        Args:
            u (float): Curve parameter

        Returns:
            np.ndarray: 3D unit vector
        """
        if self.has_curve():
            pt = gp_Pnt()
            der = gp_Vec()
            self.curve().D1(u, pt, der)
            der.Normalize()
            tangent = geom_utils.gp_to_numpy(der)
            if self.reversed():
                tangent = -tangent
            return tangent
        # If the edge has no curve then return
        # a zero vector
        return np.array([0, 0, 0])

    def first_derivative(self, u):
        """
        Compute the first derivative of the edge geometry at given parameter

        Args:
            u (float): Curve parameter

        Returns:
            np.ndarray: 3D vector
        """
        if self.has_curve():
            pt = gp_Pnt()
            der = gp_Vec()
            self.curve().D1(u, pt, der)
            return geom_utils.gp_to_numpy(der)
        # If the edge has no curve then return
        # a zero vector
        return np.array([0, 0, 0])

    def length(self):
        """
        Compute the length of the edge curve

        Returns:
            float: Length of the edge curve
        """
        if not self.has_curve():
            return 0.0
        geometry_properties = GProp_GProps()
        brepgprop_LinearProperties(self.topods_shape(), geometry_properties)
        return geometry_properties.Mass()

    def curve(self):
        """
        Get the edge curve geometry

        Returns:
            OCC.Geom.Handle_Geom_Curve: Interface to all curve geometry
        """
        return BRep_Tool_Curve(self.topods_shape())[0]

    def specific_curve(self):
        """
        Get the specific edge curve geometry

        Returns:
            OCC.Geom.Handle_Geom_*: Specific geometry type for the curve geometry
                                    or None if the curve type is GeomAbs_OtherCurve
        """
        brep_adaptor_curve = BRepAdaptor_Curve(self.topods_shape())
        curv_type = brep_adaptor_curve.GetType()
        if curv_type == GeomAbs_Line:
            return brep_adaptor_curve.Line()
        if curv_type == GeomAbs_Circle:
            return brep_adaptor_curve.Circle()
        if curv_type == GeomAbs_Ellipse:
            return brep_adaptor_curve.Ellipse()
        if curv_type == GeomAbs_Hyperbola:
            return brep_adaptor_curve.Hyperbola()
        if curv_type == GeomAbs_Parabola:
            return brep_adaptor_curve.Parabola()
        if curv_type == GeomAbs_BezierCurve:
            return brep_adaptor_curve.Bezier()
        if curv_type == GeomAbs_BSplineCurve:
            return brep_adaptor_curve.BSpline()
        if curv_type == GeomAbs_OffsetCurve:
            return brep_adaptor_curve.OffsetCurve()
        return None

    def has_curve(self):
        """
        Does this edge have a valid curve?
        Some edges don't.  For example the edge at the pole of a sphere.

        Returns:
            bool: Whether this edge has a valid curve
        """
        curve = BRepAdaptor_Curve(self.topods_shape())
        return curve.Is3DCurve()

    def u_bounds(self):
        """
        Parameter domain of the curve

        Returns:
            occwl.geometry.Interval: a 1D interval [u_min, u_max]
        """
        if not self.has_curve():
            # Return an empty interval
            return Interval()
        _, umin, umax = BRep_Tool_Curve(self.topods_shape())
        return Interval(umin, umax)

    def reversed_edge(self):
        """
        Return a copy of this edge with the orientation reversed.

        Returns:
            occwl.edge.Edge: An edge with the opposite orientation to this edge.
        """
        return Edge(self.topods_shape().Reversed())

    def closed_curve(self):
        """
        Returns whether the 3D curve of this edge is closed.
        i.e. the start and edge points are coincident.

        Returns:
            bool: If closed
        """
        return self.curve().IsClosed()

    def closed_edge(self):
        """
        Returns whether the edge forms a closed ring.  i.e.
        whether the start and end vertices are the same.

        Returns:
            bool: If closed
        """
        return BRep_Tool().IsClosed(self.topods_shape())

    def seam(self, face):
        """
        Whether this edge is a seam

        Args:
            face (occwl.face.Face): Face where the edge lives

        Returns:
            bool: If seam
        """
        return ShapeAnalysis_Edge().IsSeam(self.topods_shape(), face.topods_shape())

    def has_pcurve(self, face):
        """
        Whether this edge has a pcurve associated to the given face

        Args:
            face (occwl.face.Face): Face

        Returns:
            bool: If pcurve exists
        """
        return ShapeAnalysis_Edge().HasPCurve(self.topods_shape(), face.topods_shape())

    def periodic(self):
        """
        Whether this edge is periodic

        Returns:
            bool: If periodic
        """
        return BRepAdaptor_Curve(self.topods_shape()).IsPeriodic()

    def rational(self):
        """
        Whether this edge geometry is rational

        Returns:
            bool: If rational
        """
        return BRepAdaptor_Curve(self.topods_shape()).IsRational()

    def continuity(self, face1, face2):
        """
        Get the order of continuity among a pair of faces

        Args:
            face1 (occwl.face.Face): First face
            face2 (occwl.face.Face): Second face

        Returns:
            GeomAbs_Shape: enum describing the continuity order
        """
        return BRep_Tool_Continuity(
            self.topods_shape(), face1.topods_shape(), face2.topods_shape()
        )


    def curve_type(self):
        """
        Get the type of the curve geometry

        Returns:
            str: Type of the curve geometry
        """
        curv_type = BRepAdaptor_Curve(self.topods_shape()).GetType()
        if curv_type == GeomAbs_Line:
            return "line"
        if curv_type == GeomAbs_Circle:
            return "circle"
        if curv_type == GeomAbs_Ellipse:
            return "ellipse"
        if curv_type == GeomAbs_Hyperbola:
            return "hyperbola"
        if curv_type == GeomAbs_Parabola:
            return "parabola"
        if curv_type == GeomAbs_BezierCurve:
            return "bezier"
        if curv_type == GeomAbs_BSplineCurve:
            return "bspline"
        if curv_type == GeomAbs_OffsetCurve:
            return "offset"
        if curv_type == GeomAbs_OtherCurve:
            return "other"
        return "unknown"

    def curve_type_enum(self):
        """
        Get the type of the curve geometry as an OCC.Core.GeomAbs enum

        Returns:
            OCC.Core.GeomAbs: Type of the curve geometry
        """
        return BRepAdaptor_Curve(self.topods_shape()).GetType()

    def tolerance(self):
        """
        Get tolerance of this edge.  The 3d curve of the edge should not
        deviate from the surfaces of adjacent faces by more than this value

        Returns:
            float The edge tolerance
        """
        return BRep_Tool().Tolerance(self.topods_shape())

    def find_left_and_right_faces(self, faces):
        """
        Given a list of 1 or 2 faces which are adjacent to this edge,
        we want to return the left and right face when looking from 
        outside the solid.

                      Edge direction
                            ^
                            |   
                  Left      |   Right 
                  face      |   face
                            |

        In the case of a cylinder the left and right face will be
        the same.

        Args:
            faces (list(occwl.face.Face): The faces

        Returns:
            occwl.face.Face, occwl.face.Face: The left and then right face
            or 
            None, None if the left and right faces cannot be found
        """
        assert len(faces) > 0
        face1 = faces[0]
        if len(faces) == 1:
            face2 = faces[0]
        else:
            face2 = faces[1]

        if face1.is_left_of(self):
            # In some cases (like a cylinder) the left and right faces
            # of the edge are the same face
            if face1 != face2:
                if face2.is_left_of(self):
                    return None, None
            left_face = face1
            right_face = face2
        else:
            if not face2.is_left_of(self):
                return None, None
            left_face = face2
            right_face = face1

        return left_face, right_face

    def get_polyline(self, deflection=0.0005, algorithm="QuasiUniformDeflection"):
        """
        Get a polyline, represented as a sequence of points, from this edge

        Args:
            deflection (float): Lower deflection results in more precision
                                and therefore more points
            algorithm (string): Algorithm to use, can be one of:
                                QuasiUniformDeflection, UniformAbscissa,
                                or UniformDeflection

        Returns:
            2D np.ndarray: Points
        """
        # If we don't have a valid curve, return an empty array
        if not self.has_curve():
            return np.empty(shape=(0,3), dtype=np.float32)

        curve_adaptor = BRepAdaptor_Curve(self.topods_shape())
        first_param = curve_adaptor.FirstParameter()
        last_param = curve_adaptor.LastParameter()

        if algorithm == "QuasiUniformDeflection":
            discretizer = GCPnts_QuasiUniformDeflection()
        elif algorithm == "UniformAbscissa":
            discretizer = GCPnts_UniformAbscissa()
        elif algorithm == "UniformDeflection":
            discretizer = GCPnts_UniformDeflection()
        else:
            raise Exception("Unknown algorithm")
        discretizer.Initialize(curve_adaptor, deflection, first_param, last_param)

        if not discretizer.IsDone():
            raise Exception("Discretizer not done.")
        if not discretizer.NbPoints() > 0:
            raise Exception("Discretizer nb points not > 0.")

        points = []
        for i in range(1, discretizer.NbPoints() + 1):
            p = curve_adaptor.Value(discretizer.Parameter(i))
            points.append(np.array(list(p.Coord())))
        return np.asarray(points, dtype=np.float32)

__eq__(other)

Equality check for the shape

NOTE: This function only checks if the shape is the same. It doesn't check the edge orienation for example, so

edge1 == edge2

does not necessarily mean

edge1.reversed() == edge2.reversed()

Source code in src/occwl/shape.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def __eq__(self, other):
    """
    Equality check for the shape

    NOTE: This function only checks if the shape is the same.
    It doesn't check the edge orienation for example, so 

    edge1 == edge2

    does not necessarily mean 

    edge1.reversed() == edge2.reversed()
    """
    return self.topods_shape().__hash__() == other.topods_shape().__hash__()

__hash__()

Hash for the shape

Returns:

Name Type Description
int

Hash value

Source code in src/occwl/shape.py
135
136
137
138
139
140
141
142
def __hash__(self):
    """
    Hash for the shape

    Returns:
        int: Hash value
    """
    return self.topods_shape().__hash__()

box()

Get a quick bounding box of the Shape

Returns:

Name Type Description
Box

Bounding box

Source code in src/occwl/base.py
565
566
567
568
569
570
571
572
573
574
575
def box(self):
    """
    Get a quick bounding box of the Shape

    Returns:
        Box: Bounding box
    """
    from occwl.geometry import geom_utils
    b = Bnd_Box()
    brepbndlib_Add(self.topods_shape(), b)
    return geom_utils.box_to_geometry(b)

closed_curve()

Returns whether the 3D curve of this edge is closed. i.e. the start and edge points are coincident.

Returns:

Name Type Description
bool

If closed

Source code in src/occwl/edge.py
293
294
295
296
297
298
299
300
301
def closed_curve(self):
    """
    Returns whether the 3D curve of this edge is closed.
    i.e. the start and edge points are coincident.

    Returns:
        bool: If closed
    """
    return self.curve().IsClosed()

closed_edge()

Returns whether the edge forms a closed ring. i.e. whether the start and end vertices are the same.

Returns:

Name Type Description
bool

If closed

Source code in src/occwl/edge.py
303
304
305
306
307
308
309
310
311
def closed_edge(self):
    """
    Returns whether the edge forms a closed ring.  i.e.
    whether the start and end vertices are the same.

    Returns:
        bool: If closed
    """
    return BRep_Tool().IsClosed(self.topods_shape())

continuity(face1, face2)

Get the order of continuity among a pair of faces

Parameters:

Name Type Description Default
face1 Face

First face

required
face2 Face

Second face

required

Returns:

Name Type Description
GeomAbs_Shape

enum describing the continuity order

Source code in src/occwl/edge.py
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def continuity(self, face1, face2):
    """
    Get the order of continuity among a pair of faces

    Args:
        face1 (occwl.face.Face): First face
        face2 (occwl.face.Face): Second face

    Returns:
        GeomAbs_Shape: enum describing the continuity order
    """
    return BRep_Tool_Continuity(
        self.topods_shape(), face1.topods_shape(), face2.topods_shape()
    )

convert_geometric_identity_transforms_to_identity()

Open Cascade models sometimes contain transforms which are "geometrically" identify transforms, but the identity flag is not set.

This function checks each transform and sets the flag if the appropriate.

Source code in src/occwl/shape.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def convert_geometric_identity_transforms_to_identity(self):
    """
    Open Cascade models sometimes contain transforms which
    are "geometrically" identify transforms, but the identity
    flag is not set.

    This function checks each transform and sets the flag if 
    the appropriate.
    """
    identity = TopLoc_Location()
    if geom_utils.is_geometric_identity(
        self.topods_shape().Location().Transformation()
    ):
        self.topods_shape().Location(identity)
        self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)

    for face in self._top_exp.faces():
        if geom_utils.is_geometric_identity(face.Location().Transformation()):
            face.Location(identity)

    for edge in self._top_exp.edges():
        if geom_utils.is_geometric_identity(edge.Location().Transformation()):
            edge.Location(identity)

    for vertex in self._top_exp.vertices():
        if geom_utils.is_geometric_identity(vertex.Location().Transformation()):
            vertex.Location(identity)

curve()

Get the edge curve geometry

Returns:

Type Description

OCC.Geom.Handle_Geom_Curve: Interface to all curve geometry

Source code in src/occwl/edge.py
223
224
225
226
227
228
229
230
def curve(self):
    """
    Get the edge curve geometry

    Returns:
        OCC.Geom.Handle_Geom_Curve: Interface to all curve geometry
    """
    return BRep_Tool_Curve(self.topods_shape())[0]

curve_type()

Get the type of the curve geometry

Returns:

Name Type Description
str

Type of the curve geometry

Source code in src/occwl/edge.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def curve_type(self):
    """
    Get the type of the curve geometry

    Returns:
        str: Type of the curve geometry
    """
    curv_type = BRepAdaptor_Curve(self.topods_shape()).GetType()
    if curv_type == GeomAbs_Line:
        return "line"
    if curv_type == GeomAbs_Circle:
        return "circle"
    if curv_type == GeomAbs_Ellipse:
        return "ellipse"
    if curv_type == GeomAbs_Hyperbola:
        return "hyperbola"
    if curv_type == GeomAbs_Parabola:
        return "parabola"
    if curv_type == GeomAbs_BezierCurve:
        return "bezier"
    if curv_type == GeomAbs_BSplineCurve:
        return "bspline"
    if curv_type == GeomAbs_OffsetCurve:
        return "offset"
    if curv_type == GeomAbs_OtherCurve:
        return "other"
    return "unknown"

curve_type_enum()

Get the type of the curve geometry as an OCC.Core.GeomAbs enum

Returns:

Type Description

OCC.Core.GeomAbs: Type of the curve geometry

Source code in src/occwl/edge.py
399
400
401
402
403
404
405
406
def curve_type_enum(self):
    """
    Get the type of the curve geometry as an OCC.Core.GeomAbs enum

    Returns:
        OCC.Core.GeomAbs: Type of the curve geometry
    """
    return BRepAdaptor_Curve(self.topods_shape()).GetType()

end_vertex()

Returns the ending vertex of the edge while considering the edge orientation

Returns:

Type Description

occwl.vertex.Vertex: End vertex

Source code in src/occwl/edge.py
159
160
161
162
163
164
165
166
def end_vertex(self):
    """
    Returns the ending vertex of the edge while considering the edge orientation

    Returns:
        occwl.vertex.Vertex: End vertex
    """
    return occwl.vertex.Vertex(ShapeAnalysis_Edge().LastVertex(self.topods_shape()))

exact_box(use_shapetolerance=False)

Get a slow, but accurate box for the Shape.

Returns:

Name Type Description
Box

Bounding box

Source code in src/occwl/base.py
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
def exact_box(self, use_shapetolerance=False):
    """
    Get a slow, but accurate box for the Shape.

    Args:
        use_shapetolerance (bool, optional) Include the tolerance of edges
                                            and vertices in the box.

    Returns:
        Box: Bounding box
    """
    from occwl.geometry import geom_utils
    b = Bnd_Box()
    use_triangulation = True
    brepbndlib_AddOptimal(self.topods_shape(), b, use_triangulation, use_shapetolerance)
    return geom_utils.box_to_geometry(b)

find_closest_point_data(datum)

Find the information about the closest point on this shape

Parameters:

Name Type Description Default
datum ndarray

3D Point

required

Returns:

Name Type Description
ClosestPointData

Data about the closest point on this shape

None

if error

Source code in src/occwl/shape.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
def find_closest_point_data(self, datum):
    """
    Find the information about the closest point on this shape

    Args:
        datum (np.ndarray): 3D Point

    Returns:
        ClosestPointData: Data about the closest point on this shape
        None: if error
    """
    # Folowing https://dev.opencascade.org/content/how-retrieve-nearest-face-shape-given-gppnt
    # Create a vertex from the point
    occ_point = geom_utils.numpy_to_gp(datum)
    vertex_maker = BRepBuilderAPI_MakeVertex(occ_point)
    vertex = vertex_maker.Shape()
    dist_shape_shape = BRepExtrema_DistShapeShape(
        vertex, self.topods_shape(), Extrema_ExtFlag_MIN
    )
    ok = dist_shape_shape.Perform()
    if not ok:
        return None

    return ClosestPointData(dist_shape_shape)

find_left_and_right_faces(faces)

Given a list of 1 or 2 faces which are adjacent to this edge, we want to return the left and right face when looking from outside the solid.

          Edge direction
                ^
                |   
      Left      |   Right 
      face      |   face
                |

In the case of a cylinder the left and right face will be the same.

Parameters:

Name Type Description Default
faces list(occwl.face.Face

The faces

required

Returns:

Type Description

occwl.face.Face, occwl.face.Face: The left and then right face

or

None, None if the left and right faces cannot be found

Source code in src/occwl/edge.py
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
def find_left_and_right_faces(self, faces):
    """
    Given a list of 1 or 2 faces which are adjacent to this edge,
    we want to return the left and right face when looking from 
    outside the solid.

                  Edge direction
                        ^
                        |   
              Left      |   Right 
              face      |   face
                        |

    In the case of a cylinder the left and right face will be
    the same.

    Args:
        faces (list(occwl.face.Face): The faces

    Returns:
        occwl.face.Face, occwl.face.Face: The left and then right face
        or 
        None, None if the left and right faces cannot be found
    """
    assert len(faces) > 0
    face1 = faces[0]
    if len(faces) == 1:
        face2 = faces[0]
    else:
        face2 = faces[1]

    if face1.is_left_of(self):
        # In some cases (like a cylinder) the left and right faces
        # of the edge are the same face
        if face1 != face2:
            if face2.is_left_of(self):
                return None, None
        left_face = face1
        right_face = face2
    else:
        if not face2.is_left_of(self):
            return None, None
        left_face = face2
        right_face = face1

    return left_face, right_face

first_derivative(u)

Compute the first derivative of the edge geometry at given parameter

Parameters:

Name Type Description Default
u float

Curve parameter

required

Returns:

Type Description

np.ndarray: 3D vector

Source code in src/occwl/edge.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def first_derivative(self, u):
    """
    Compute the first derivative of the edge geometry at given parameter

    Args:
        u (float): Curve parameter

    Returns:
        np.ndarray: 3D vector
    """
    if self.has_curve():
        pt = gp_Pnt()
        der = gp_Vec()
        self.curve().D1(u, pt, der)
        return geom_utils.gp_to_numpy(der)
    # If the edge has no curve then return
    # a zero vector
    return np.array([0, 0, 0])

get_polyline(deflection=0.0005, algorithm='QuasiUniformDeflection')

Get a polyline, represented as a sequence of points, from this edge

Parameters:

Name Type Description Default
deflection float

Lower deflection results in more precision and therefore more points

0.0005
algorithm string

Algorithm to use, can be one of: QuasiUniformDeflection, UniformAbscissa, or UniformDeflection

'QuasiUniformDeflection'

Returns:

Type Description

2D np.ndarray: Points

Source code in src/occwl/edge.py
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
def get_polyline(self, deflection=0.0005, algorithm="QuasiUniformDeflection"):
    """
    Get a polyline, represented as a sequence of points, from this edge

    Args:
        deflection (float): Lower deflection results in more precision
                            and therefore more points
        algorithm (string): Algorithm to use, can be one of:
                            QuasiUniformDeflection, UniformAbscissa,
                            or UniformDeflection

    Returns:
        2D np.ndarray: Points
    """
    # If we don't have a valid curve, return an empty array
    if not self.has_curve():
        return np.empty(shape=(0,3), dtype=np.float32)

    curve_adaptor = BRepAdaptor_Curve(self.topods_shape())
    first_param = curve_adaptor.FirstParameter()
    last_param = curve_adaptor.LastParameter()

    if algorithm == "QuasiUniformDeflection":
        discretizer = GCPnts_QuasiUniformDeflection()
    elif algorithm == "UniformAbscissa":
        discretizer = GCPnts_UniformAbscissa()
    elif algorithm == "UniformDeflection":
        discretizer = GCPnts_UniformDeflection()
    else:
        raise Exception("Unknown algorithm")
    discretizer.Initialize(curve_adaptor, deflection, first_param, last_param)

    if not discretizer.IsDone():
        raise Exception("Discretizer not done.")
    if not discretizer.NbPoints() > 0:
        raise Exception("Discretizer nb points not > 0.")

    points = []
    for i in range(1, discretizer.NbPoints() + 1):
        p = curve_adaptor.Value(discretizer.Parameter(i))
        points.append(np.array(list(p.Coord())))
    return np.asarray(points, dtype=np.float32)

has_curve()

Does this edge have a valid curve? Some edges don't. For example the edge at the pole of a sphere.

Returns:

Name Type Description
bool

Whether this edge has a valid curve

Source code in src/occwl/edge.py
260
261
262
263
264
265
266
267
268
269
def has_curve(self):
    """
    Does this edge have a valid curve?
    Some edges don't.  For example the edge at the pole of a sphere.

    Returns:
        bool: Whether this edge has a valid curve
    """
    curve = BRepAdaptor_Curve(self.topods_shape())
    return curve.Is3DCurve()

has_pcurve(face)

Whether this edge has a pcurve associated to the given face

Parameters:

Name Type Description Default
face Face

Face

required

Returns:

Name Type Description
bool

If pcurve exists

Source code in src/occwl/edge.py
325
326
327
328
329
330
331
332
333
334
335
def has_pcurve(self, face):
    """
    Whether this edge has a pcurve associated to the given face

    Args:
        face (occwl.face.Face): Face

    Returns:
        bool: If pcurve exists
    """
    return ShapeAnalysis_Edge().HasPCurve(self.topods_shape(), face.topods_shape())

length()

Compute the length of the edge curve

Returns:

Name Type Description
float

Length of the edge curve

Source code in src/occwl/edge.py
210
211
212
213
214
215
216
217
218
219
220
221
def length(self):
    """
    Compute the length of the edge curve

    Returns:
        float: Length of the edge curve
    """
    if not self.has_curve():
        return 0.0
    geometry_properties = GProp_GProps()
    brepgprop_LinearProperties(self.topods_shape(), geometry_properties)
    return geometry_properties.Mass()

make_arc_of_circle(pt1, pt2, pt3) staticmethod

Make an arc edge

Parameters:

Name Type Description Default
pt1 np.ndarray or list or tuple with 3D point

Start point

required
pt2 np.ndarray or list or tuple with 3D point

Mid (not necessarily at the middle) point

required
pt3 np.ndarray or list or tuple with 3D point

End point

required

Returns:

Type Description

occwl.edge.Edge: Edge

or None: if error

Source code in src/occwl/edge.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
@staticmethod
def make_arc_of_circle(pt1, pt2, pt3):
    """
    Make an arc edge

    Args:
        pt1 (np.ndarray or list or tuple with 3D point): Start point
        pt2 (np.ndarray or list or tuple with 3D point): Mid (not necessarily at the middle) point
        pt3 (np.ndarray or list or tuple with 3D point): End point

    Returns:
        occwl.edge.Edge: Edge
        or None: if error
    """
    arc = GC_MakeArcOfCircle(geom_utils.to_gp_pnt(pt1), geom_utils.to_gp_pnt(pt2), geom_utils.to_gp_pnt(pt3)).Value()
    edge_builder = BRepBuilderAPI_MakeEdge(arc)
    if not edge_builder.IsDone():
        return None
    return Edge(edge_builder.Edge())

make_circle(center, radius, direction=(0, 0, 1)) staticmethod

Make a circular edge

Parameters:

Name Type Description Default
center np.ndarray or list or tuple with 3D point

Center of the circle

required
radius float

Radius of the circle

required
direction np.ndarray or list or tuple with 3D unit vector

Normal of the circle's face. Defaults to (0, 0, 1).

(0, 0, 1)

Returns:

Type Description

occwl.edge.Edge: Edge

or None: if error

Source code in src/occwl/edge.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
@staticmethod
def make_circle(center, radius, direction=(0, 0, 1)):
    """
    Make a circular edge

    Args:
        center (np.ndarray or list or tuple with 3D point): Center of the circle
        radius (float): Radius of the circle
        direction (np.ndarray or list or tuple with 3D unit vector, optional): Normal of the circle's face. Defaults to (0, 0, 1).

    Returns:
        occwl.edge.Edge: Edge
        or None: if error
    """
    circle = gp_Circ(
        gp_Ax2(geom_utils.to_gp_pnt(center), geom_utils.to_gp_dir(direction)),
        float(radius),
    )
    edge_builder = BRepBuilderAPI_MakeEdge(circle)
    if not edge_builder.IsDone():
        return None
    return Edge(edge_builder.Edge())

make_line_from_points(start_point, end_point) staticmethod

Make an edge from two given points

Parameters:

Name Type Description Default
start_point np.ndarray 3D vector

Starting point

required
end_point np.ndarray 3D vector

Ending point

required

Returns:

Type Description

occwl.Edge: Edge joining the two given vertices

or None: if error

Source code in src/occwl/edge.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@staticmethod
def make_line_from_points(start_point, end_point):
    """
    Make an edge from two given points

    Args:
        start_point (np.ndarray 3D vector): Starting point
        end_point (np.ndarray 3D vector): Ending point

    Returns:
        occwl.Edge: Edge joining the two given vertices
        or None: if error
    """
    edge_builder = BRepBuilderAPI_MakeEdge(geom_utils.to_gp_pnt(start_point), geom_utils.to_gp_pnt(end_point))
    if not edge_builder.IsDone():
        return None
    return Edge(edge_builder.Edge())

make_line_from_vertices(start_vertex, end_vertex) staticmethod

Make an edge from two given vertices

Parameters:

Name Type Description Default
start_vertex Vertex

Starting vertex

required
end_vertex Vertex

Ending vertex

required

Returns:

Type Description

occwl.Edge: Edge joining the two given vertices

or None: if error

Source code in src/occwl/edge.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
@staticmethod
def make_line_from_vertices(start_vertex, end_vertex):
    """
    Make an edge from two given vertices

    Args:
        start_vertex (occwl.vertex.Vertex): Starting vertex
        end_vertex (occwl.vertex.Vertex): Ending vertex

    Returns:
        occwl.Edge: Edge joining the two given vertices
        or None: if error
    """
    edge_builder = BRepBuilderAPI_MakeEdge(start_vertex.topods_shape(), end_vertex.topods_shape())
    if not edge_builder.IsDone():
        return None
    return Edge(edge_builder.Edge())

num_vertices()

Number of vertices in the Shape

Returns:

Name Type Description
int

Number of vertices

Source code in src/occwl/base.py
25
26
27
28
29
30
31
32
def num_vertices(self):
    """
    Number of vertices in the Shape

    Returns:
        int: Number of vertices
    """
    return self._top_exp.number_of_vertices()

occwl_shape(topods_shape) staticmethod

Static method to create an occwl shape of the appropriate class from the given topods_shape Args: topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

Returns:

Type Description

One of occwl.compound.Compound occwl.solid.Solid occwl.face.Face occwl.edge.Edge occwl.vertex.Vertex occwl.wire.Wire occwl.shell.Shell

Raises: Exception: [description]

Source code in src/occwl/shape.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@staticmethod
def occwl_shape(topods_shape):
    """
    Static method to create an occwl shape of the appropriate 
    class from the given topods_shape
    Args:
        topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

    Returns:
        One of
            occwl.compound.Compound
            occwl.solid.Solid
            occwl.face.Face
            occwl.edge.Edge
            occwl.vertex.Vertex
            occwl.wire.Wire
            occwl.shell.Shell
    Raises:
        Exception: [description]
    """
    from occwl.compound import Compound
    from occwl.solid import Solid
    from occwl.face import Face
    from occwl.edge import Edge
    from occwl.vertex import Vertex
    from occwl.wire import Wire
    from occwl.shell import Shell

    if isinstance(topods_shape, TopoDS_Vertex):
        return Vertex(topods_shape)
    if isinstance(topods_shape, TopoDS_Edge):
        return Edge(topods_shape)
    if isinstance(topods_shape, TopoDS_Face):
        return Face(topods_shape)
    if isinstance(topods_shape, TopoDS_Wire):
        return Wire(topods_shape)
    if isinstance(topods_shape, TopoDS_Shell):
        return Shell(topods_shape)
    if isinstance(topods_shape, TopoDS_Solid):
        return Solid(topods_shape)
    if isinstance(topods_shape, (TopoDS_Compound, TopoDS_CompSolid)):
        return Compound(topods_shape)
    raise Exception(
        "Shape must be one of TopoDS_Vertex, TopoDS_Edge, TopoDS_Face, TopoDS_Shell, TopoDS_Solid, TopoDS_Compound, TopoDS_CompSolid"
    )

periodic()

Whether this edge is periodic

Returns:

Name Type Description
bool

If periodic

Source code in src/occwl/edge.py
337
338
339
340
341
342
343
344
def periodic(self):
    """
    Whether this edge is periodic

    Returns:
        bool: If periodic
    """
    return BRepAdaptor_Curve(self.topods_shape()).IsPeriodic()

point(u)

Evaluate the edge geometry at given parameter

Parameters:

Name Type Description Default
u float

Curve parameter

required

Returns:

Type Description

np.ndarray: 3D Point

Source code in src/occwl/edge.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
def point(self, u):
    """
    Evaluate the edge geometry at given parameter

    Args:
        u (float): Curve parameter

    Returns:
        np.ndarray: 3D Point
    """
    if self.has_curve():
        pt = self.curve().Value(u)
        return geom_utils.gp_to_numpy(pt)
    # If the edge has no curve then return a point
    # at the origin.
    # It would ne nice to return the location of the
    # vertex
    return np.array([0, 0, 0])

rational()

Whether this edge geometry is rational

Returns:

Name Type Description
bool

If rational

Source code in src/occwl/edge.py
346
347
348
349
350
351
352
353
def rational(self):
    """
    Whether this edge geometry is rational

    Returns:
        bool: If rational
    """
    return BRepAdaptor_Curve(self.topods_shape()).IsRational()

reversed()

Whether this shape is reversed.

  • For an edge this is whether the edge is reversed with respect to the curve geometry
  • For a face this is whether the face is reversed with respect to the surface geometry
  • For a vertex this is whether the vertex is at the upper or lower parameter value on the edges curve

Returns:

Name Type Description
bool

If rational

Source code in src/occwl/shape.py
236
237
238
239
240
241
242
243
244
245
246
247
248
def reversed(self):
    """
    Whether this shape is reversed.

    - For an edge this is whether the edge is reversed with respect to the curve geometry
    - For a face this is whether the face is reversed with respect to the surface geometry
    - For a vertex this is whether the vertex is at the upper or lower parameter value on the
      edges curve

    Returns:
        bool: If rational
    """
    return self.topods_shape().Orientation() == TopAbs_REVERSED

reversed_edge()

Return a copy of this edge with the orientation reversed.

Returns:

Type Description

occwl.edge.Edge: An edge with the opposite orientation to this edge.

Source code in src/occwl/edge.py
284
285
286
287
288
289
290
291
def reversed_edge(self):
    """
    Return a copy of this edge with the orientation reversed.

    Returns:
        occwl.edge.Edge: An edge with the opposite orientation to this edge.
    """
    return Edge(self.topods_shape().Reversed())

rotate_axis_angle(axis, angle_radians, origin=np.zeros(3, dtype=(np.float32)))

Rotate the shape about the given axis by the given angle in radians

Parameters:

Name Type Description Default
axis ndarray

Rotation axis

required
angle_radians float

Angle in radians

required
Source code in src/occwl/shape.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
def rotate_axis_angle(
    self, axis, angle_radians, origin=np.zeros(3, dtype=np.float32)
):
    """
    Rotate the shape about the given axis by the given angle in radians

    Args:
        axis (np.ndarray): Rotation axis
        angle_radians (float): Angle in radians
    """
    self._shape = rotate_shape(
        self._shape,
        gp_Ax1(geom_utils.numpy_to_gp(origin), geom_utils.numpy_to_gp_dir(axis)),
        angle_radians,
        unite="rad",
    )

rotate_euler_angles(angles_xyz_radians)

Rotate the shape by the given Euler angles in radians

Parameters:

Name Type Description Default
angle_xyz_radians ndarray

3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians

required
Source code in src/occwl/shape.py
302
303
304
305
306
307
308
309
310
311
312
313
314
315
def rotate_euler_angles(self, angles_xyz_radians):
    """
    Rotate the shape by the given Euler angles in radians

    Args:
        angle_xyz_radians (np.ndarray): 3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians
    """
    self._shape = rotate_shp_3_axis(
        self._shape,
        angles_xyz_radians[0],
        angles_xyz_radians[1],
        angles_xyz_radians[2],
        unity="rad",
    )

save_shapes_to_occ_native(filename, shapes, with_triangles=False, with_normals=False, format_version=None) staticmethod

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
@staticmethod
def save_shapes_to_occ_native(
        filename, 
        shapes,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
            is between one and two orders of magnitude faster 
            than loading from STEP, so it is recommended for 
            large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename

        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    new_api = False
    shapes_set = BRepTools_ShapeSet(with_triangles)
    # shapes_set.SetWithNormals(with_normals) # Not in OCC 7.5.0

    for shp in shapes:
        shapes_set.Add(shp.topods_shape())
    if format_version is not None:
        shapes_set.SetFormatNb(format_version)


    with open(filename, "w") as fp:
        s = shapes_set.WriteToString()
        fp.write(s)

save_to_occ_native(filename, verbosity=False, with_triangles=False, with_normals=False, format_version=None)

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def save_to_occ_native(
        self, 
        filename, 
        verbosity=False,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
           is between one and two orders of magnitude faster 
           than loading from STEP, so it is recommended for 
           large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename
        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    self.save_shapes_to_occ_native(
        filename, 
        [ self ],
        with_triangles=with_triangles,
        with_normals=with_normals,
        format_version=format_version
    )

scale(scale_vector)

Scale the shape by the given 3D vector

Parameters:

Name Type Description Default
scale_vector ndarray

3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively

required
Source code in src/occwl/shape.py
317
318
319
320
321
322
323
324
325
326
def scale(self, scale_vector):
    """
    Scale the shape by the given 3D vector

    Args:
        scale_vector (np.ndarray): 3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively
    """
    self._shape = scale_shape(
        self._shape, scale_vector[0], scale_vector[1], scale_vector[2]
    )

scale_to_box(box_side, copy=True)

Translate and scale the Shape so it fits exactly into the [-box_side, box_side]^3 box

Returns:

Type Description

occwl..: The scaled version of this Shape

Source code in src/occwl/base.py
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
def scale_to_box(self, box_side, copy=True):
    """
    Translate and scale the Shape so it fits exactly 
    into the [-box_side, box_side]^3 box

    Args:
        box_side (float) The side length of the box
        copy (bool)      True - Copy entities and apply the transform to
                                the underlying geometry
                         False - Apply the transform to the topods Locator
                                 if possible 

    Returns:
        occwl.*.*: The scaled version of this Shape
    """
    from occwl.geometry import geom_utils
    # Get an exact box for the Shape
    box = self.exact_box()
    center = box.center()
    longest_length = box.max_box_length()

    orig = gp_Pnt(0.0, 0.0, 0.0)
    center = geom_utils.numpy_to_gp(center)
    vec_center_to_orig = gp_Vec(center, orig)
    move_to_center = gp_Trsf()
    move_to_center.SetTranslation(vec_center_to_orig)

    scale_trsf = gp_Trsf()
    scale_trsf.SetScale(orig, (2.0 * box_side) / longest_length)
    trsf_to_apply = scale_trsf.Multiplied(move_to_center)

    return self._apply_transform(trsf_to_apply, copy=copy)

scale_to_unit_box(copy=True)

Translate and scale the Shape so it fits exactly into the [-1, 1]^3 box

Returns: The scaled version of this shape

Source code in src/occwl/base.py
628
629
630
631
632
633
634
635
636
637
638
639
640
641
def scale_to_unit_box(self, copy=True):
    """
    Translate and scale the Shape so it fits exactly 
    into the [-1, 1]^3 box

    Args:
        copy (bool)      True - Copy entities and apply the transform to
                                    the underlying geometry
                            False - Apply the transform to the topods Locator
                                    if possible 
    Returns:
        The scaled version of this shape
    """
    return self.scale_to_box(1.0, copy=copy)

seam(face)

Whether this edge is a seam

Parameters:

Name Type Description Default
face Face

Face where the edge lives

required

Returns:

Name Type Description
bool

If seam

Source code in src/occwl/edge.py
313
314
315
316
317
318
319
320
321
322
323
def seam(self, face):
    """
    Whether this edge is a seam

    Args:
        face (occwl.face.Face): Face where the edge lives

    Returns:
        bool: If seam
    """
    return ShapeAnalysis_Edge().IsSeam(self.topods_shape(), face.topods_shape())

set_transform_to_identity()

When an assembly is loaded from a STEP file the solids will be transformed relative to their local coordinate system. i.e. they are placed in the assembly root components coordinate system.

When working with individual bodies you often want them to be axis aligned, in which case you want to remove the assembly transform. This function removes it for you.

If however you want to bake the transform into the bodies and suppress the asserts from parts of occwl which don't cope with transforms then use the transform() function below with copy=True

Source code in src/occwl/shape.py
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def set_transform_to_identity(self):
    """
    When an assembly is loaded from a STEP file
    the solids will be transformed relative to
    their local coordinate system.   i.e. they
    are placed in the assembly root components 
    coordinate system.

    When working with individual bodies you often
    want them to be axis aligned, in which case 
    you want to remove the assembly transform.
    This function removes it for you.

    If however you want to bake the transform
    into the bodies and suppress the asserts 
    from parts of occwl which don't cope with
    transforms then use the transform() function
    below with copy=True
    """
    identity = TopLoc_Location()
    self.topods_shape().Location(identity)
    self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)
    self.convert_geometric_identity_transforms_to_identity()

specific_curve()

Get the specific edge curve geometry

Returns:

Type Description

OCC.Geom.Handle_Geom_*: Specific geometry type for the curve geometry or None if the curve type is GeomAbs_OtherCurve

Source code in src/occwl/edge.py
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
def specific_curve(self):
    """
    Get the specific edge curve geometry

    Returns:
        OCC.Geom.Handle_Geom_*: Specific geometry type for the curve geometry
                                or None if the curve type is GeomAbs_OtherCurve
    """
    brep_adaptor_curve = BRepAdaptor_Curve(self.topods_shape())
    curv_type = brep_adaptor_curve.GetType()
    if curv_type == GeomAbs_Line:
        return brep_adaptor_curve.Line()
    if curv_type == GeomAbs_Circle:
        return brep_adaptor_curve.Circle()
    if curv_type == GeomAbs_Ellipse:
        return brep_adaptor_curve.Ellipse()
    if curv_type == GeomAbs_Hyperbola:
        return brep_adaptor_curve.Hyperbola()
    if curv_type == GeomAbs_Parabola:
        return brep_adaptor_curve.Parabola()
    if curv_type == GeomAbs_BezierCurve:
        return brep_adaptor_curve.Bezier()
    if curv_type == GeomAbs_BSplineCurve:
        return brep_adaptor_curve.BSpline()
    if curv_type == GeomAbs_OffsetCurve:
        return brep_adaptor_curve.OffsetCurve()
    return None

start_vertex()

Returns the starting vertex of the edge while considering the edge orientation

Returns:

Type Description

occwl.vertex.Vertex: Start vertex

Source code in src/occwl/edge.py
150
151
152
153
154
155
156
157
def start_vertex(self):
    """
    Returns the starting vertex of the edge while considering the edge orientation

    Returns:
        occwl.vertex.Vertex: Start vertex
    """
    return occwl.vertex.Vertex(ShapeAnalysis_Edge().FirstVertex(self.topods_shape()))

tangent(u)

Compute the tangent of the edge geometry at given parameter

Parameters:

Name Type Description Default
u float

Curve parameter

required

Returns:

Type Description

np.ndarray: 3D unit vector

Source code in src/occwl/edge.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
def tangent(self, u):
    """
    Compute the tangent of the edge geometry at given parameter

    Args:
        u (float): Curve parameter

    Returns:
        np.ndarray: 3D unit vector
    """
    if self.has_curve():
        pt = gp_Pnt()
        der = gp_Vec()
        self.curve().D1(u, pt, der)
        der.Normalize()
        tangent = geom_utils.gp_to_numpy(der)
        if self.reversed():
            tangent = -tangent
        return tangent
    # If the edge has no curve then return
    # a zero vector
    return np.array([0, 0, 0])

tolerance()

Get tolerance of this edge. The 3d curve of the edge should not deviate from the surfaces of adjacent faces by more than this value

Returns:

Type Description

float The edge tolerance

Source code in src/occwl/edge.py
408
409
410
411
412
413
414
415
416
def tolerance(self):
    """
    Get tolerance of this edge.  The 3d curve of the edge should not
    deviate from the surfaces of adjacent faces by more than this value

    Returns:
        float The edge tolerance
    """
    return BRep_Tool().Tolerance(self.topods_shape())

topods_shape()

Get the underlying OCC shape

Returns:

Type Description

OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*

Source code in src/occwl/shape.py
80
81
82
83
84
85
86
87
def topods_shape(self):
    """
    Get the underlying OCC shape

    Returns:
        OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*
    """
    return self._shape

transform(a, copy=True)

Apply the given 3x4 transform matrix to the solid.

 copy (bool)    True - Copy entities and apply the transform to
                       the underlying geometry
                False - Apply the transform to the topods Locator
                        if possible
Source code in src/occwl/shape.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
def transform(self, a: np.ndarray, copy=True):
    """
    Apply the given 3x4 transform matrix to the solid.

    Args: a (nd.array) - Homogeneous transform matrix
                         The transform that will be applied is

                         x' =  a[0,0]*x + a[0,1]*y + a[0,2]*z + a[0, 3]
                         y' =  a[1,0]*x + a[1,1]*y + a[1,2]*z + a[1, 3]
                         z' =  a[2,0]*x + a[2,1]*y + a[2,2]*z + a[2, 3]

         copy (bool)    True - Copy entities and apply the transform to
                               the underlying geometry
                        False - Apply the transform to the topods Locator
                                if possible 
    """
    assert (a.shape == (3, 4)), "Transform matrix must be 3x4"
    a = a.astype(np.float64)

    # Create an identity transform
    trsf = gp_Trsf()

    # If the matrix is an identity matrix then
    # we don't want to set the values as this
    # would give us a geometric identity without
    # the identity flag set
    if not np.allclose(a, np.eye(3, 4)):
        trsf.SetValues(
            a[0,0], a[0,1], a[0,2], a[0, 3],
            a[1,0], a[1,1], a[1,2], a[1, 3],
            a[2,0], a[2,1], a[2,2], a[2, 3]
        )
    return self._apply_transform(trsf, copy=copy)

translate(offset)

Translate the shape by an offset vector

Parameters:

Name Type Description Default
offset ndarray

Offset vector

required
Source code in src/occwl/shape.py
276
277
278
279
280
281
282
283
def translate(self, offset):
    """
    Translate the shape by an offset vector

    Args:
        offset (np.ndarray): Offset vector
    """
    self._shape = translate_shp(self._shape, geom_utils.numpy_to_gp_vec(offset))

u_bounds()

Parameter domain of the curve

Returns:

Type Description

occwl.geometry.Interval: a 1D interval [u_min, u_max]

Source code in src/occwl/edge.py
271
272
273
274
275
276
277
278
279
280
281
282
def u_bounds(self):
    """
    Parameter domain of the curve

    Returns:
        occwl.geometry.Interval: a 1D interval [u_min, u_max]
    """
    if not self.has_curve():
        # Return an empty interval
        return Interval()
    _, umin, umax = BRep_Tool_Curve(self.topods_shape())
    return Interval(umin, umax)

valid(return_analyzer=False)

Check if the shape is valid

Parameters:

Name Type Description Default
return_analyzer bool

Whether to return the BRepCheck_Analyzer object for more inspection

False

Returns:

Name Type Description
bool

Whether the shape is valid

BRepCheck_Analyzer [optional]: if return_analyzer is True

Source code in src/occwl/shape.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def valid(self, return_analyzer=False):
    """
    Check if the shape is valid

    Args:
        return_analyzer (bool): Whether to return the BRepCheck_Analyzer object for more inspection

    Returns:
        bool: Whether the shape is valid
        BRepCheck_Analyzer [optional]: if return_analyzer is True
    """
    analyzer = BRepCheck_Analyzer(self.topods_shape())
    if return_analyzer:
        return analyzer.IsValid(), analyzer
    return analyzer.IsValid()

vertices()

Get an iterator to go over all vertices in the Shape

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
34
35
36
37
38
39
40
41
42
def vertices(self):
    """
    Get an iterator to go over all vertices in the Shape

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    return map(Vertex, self._top_exp.vertices())

edge_data_extractor

A class to extract points and normals from the two faces adjacent to an edge. This can then be used to compute the edges convexity.

EdgeDataExtractor

Source code in src/occwl/edge_data_extractor.py
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
class EdgeDataExtractor:
    def __init__(self, edge, faces, num_samples=10, use_arclength_params=True):
        """
        Compute point and normal data for an oriented edge of the model.
        The arrays of points, tangents and normals are all oriented based
        on the orientation flag of the edge.

        You can access the data from the member numpy arrays

            EdgeDataExtractor.points
            EdgeDataExtractor.tangests
            EdgeDataExtractor.left_normals
            EdgeDataExtractor.right_normals

        If a problem was detected during the calculation then 
        EdgeDataExtractor.good == false
        """
        assert num_samples > 0, "num_samples must be bigger than 0"
        assert edge is not None
        assert len(faces) > 0, "Expect 1 or 2 faces adjacent to an edge"

        self.num_samples = num_samples
        self.good = True
        self.left_face, self.right_face = edge.find_left_and_right_faces(faces)
        if self.left_face is None or self.right_face is None:
            # Cope with case where the left and right face cannot be found
            self.good = False
            return

        self.edge = edge
        self.curve3d = edge.curve()
        self.left_pcurve = BRepAdaptor_Curve2d(
            edge.topods_shape(), self.left_face.topods_shape()
        )
        self.right_pcurve = BRepAdaptor_Curve2d(
            edge.topods_shape(), self.right_face.topods_shape()
        )

        # Find the parameters to evaluate.   These will be
        # ordered based on the reverse flag of the edge
        if use_arclength_params:
            self.u_params = self._find_arclength_parameters()
        else:
            self.u_params = self._find_uniform_parameters()
        if not self.good:
            return
        self.left_uvs = self._find_uvs(self.left_pcurve)
        self.right_uvs = self._find_uvs(self.right_pcurve)

        # Find 3d points and tangents.
        # These will be ordered and oriented based on the
        # direction of the edge.  i.e. we will apply the reverse
        # flag
        self.points = self._evaluate_3d_points()
        self.tangents = self._evaluate_curve_tangents()

        # Generate the normals.  These will be ordered
        # based on the direction of the edge and the
        # normals will be reversed based on the orientation
        # of the faces
        self.left_normals = self._evaluate_surface_normals(
            self.left_uvs, self.left_face
        )
        self.right_normals = self._evaluate_surface_normals(
            self.right_uvs, self.right_face
        )

    def edge_convexity(self, angle_tol_rads):
        """
        Compute the convexity of the edge
        """
        assert self.good
        is_smooth = self._check_smooth(angle_tol_rads)
        if is_smooth:
            return EdgeConvexity.SMOOTH

        cross_prod_of_normals = np.cross(self.left_normals, self.right_normals, axis=1)
        dot_product_with_tangents = np.multiply(
            cross_prod_of_normals, self.tangents
        ).sum(1)

        if dot_product_with_tangents.sum() > 0.0:
            return EdgeConvexity.CONVEX
        return EdgeConvexity.CONCAVE

    def sanity_check_uvs(self, uvs, edge_tolerance):
        """
        Assert that the points we get by evaluating uvs on both sides of the edge 
        are within the specified tolerance.

        This function is intended for testing/debugging
        """
        for u, left_uv, right_uv in zip(self.u_params, self.left_uvs, self.right_uvs):
            point = self.edge.point(u)
            point1 = self.left_face.point(left_uv)
            point2 = self.right_face.point(right_uv)
            assert np.linalg.norm(point - point1) < edge_tolerance
            assert np.linalg.norm(point - point2) < edge_tolerance

    """
    Private member functions
    """

    def _check_smooth(self, angle_tol_rads):
        dot_prod = np.multiply(self.left_normals, self.right_normals).sum(1)
        average_dot_product = dot_prod.mean()
        return average_dot_product > np.cos(angle_tol_rads)

    def _find_uniform_parameters(self):
        interval = self.edge.u_bounds()
        if interval.invalid():
            self.good = False
            return
        params = []
        for i in range(self.num_samples):
            t = i / (self.num_samples - 1)
            params.append(interval.interpolate(t))
        # Now we need to check the orientation of the edge and
        # reverse the array is necessary
        if self.edge.reversed():
            params.reverse()
        return params

    def _find_arclength_parameters(self):
        arc_length_finder = ArcLengthParamFinder(edge=self.edge)
        if not arc_length_finder.good:
            self.good = False
            return
        arc_length_params = arc_length_finder.find_arc_length_parameters(
            self.num_samples
        )
        # Now we need to check the orientation of the edge and
        # reverse the array is necessary
        if self.edge.reversed():
            arc_length_params.reverse()
        return arc_length_params

    def _find_uvs(self, pcurve):
        uvs = []
        for u in self.u_params:
            uv = gp_Pnt2d()
            pcurve.D0(u, uv)
            uv = np.array([uv.X(), uv.Y()])
            uvs.append(uv)
        return uvs

    def _evaluate_3d_points(self):
        points = []
        for u in self.u_params:
            point = self.edge.point(u)
            points.append(point)
        return np.stack(points)

    def _evaluate_curve_tangents(self):
        tangents = []
        for u in self.u_params:
            tangent = self.edge.tangent(u)
            tangents.append(tangent)
        return np.stack(tangents)

    def _evaluate_surface_normals(self, uvs, face):
        normals = []
        for uv in uvs:
            normal = face.normal(uv)
            normals.append(normal)
        return np.stack(normals)

__init__(edge, faces, num_samples=10, use_arclength_params=True)

Compute point and normal data for an oriented edge of the model. The arrays of points, tangents and normals are all oriented based on the orientation flag of the edge.

You can access the data from the member numpy arrays

EdgeDataExtractor.points
EdgeDataExtractor.tangests
EdgeDataExtractor.left_normals
EdgeDataExtractor.right_normals

If a problem was detected during the calculation then EdgeDataExtractor.good == false

Source code in src/occwl/edge_data_extractor.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
def __init__(self, edge, faces, num_samples=10, use_arclength_params=True):
    """
    Compute point and normal data for an oriented edge of the model.
    The arrays of points, tangents and normals are all oriented based
    on the orientation flag of the edge.

    You can access the data from the member numpy arrays

        EdgeDataExtractor.points
        EdgeDataExtractor.tangests
        EdgeDataExtractor.left_normals
        EdgeDataExtractor.right_normals

    If a problem was detected during the calculation then 
    EdgeDataExtractor.good == false
    """
    assert num_samples > 0, "num_samples must be bigger than 0"
    assert edge is not None
    assert len(faces) > 0, "Expect 1 or 2 faces adjacent to an edge"

    self.num_samples = num_samples
    self.good = True
    self.left_face, self.right_face = edge.find_left_and_right_faces(faces)
    if self.left_face is None or self.right_face is None:
        # Cope with case where the left and right face cannot be found
        self.good = False
        return

    self.edge = edge
    self.curve3d = edge.curve()
    self.left_pcurve = BRepAdaptor_Curve2d(
        edge.topods_shape(), self.left_face.topods_shape()
    )
    self.right_pcurve = BRepAdaptor_Curve2d(
        edge.topods_shape(), self.right_face.topods_shape()
    )

    # Find the parameters to evaluate.   These will be
    # ordered based on the reverse flag of the edge
    if use_arclength_params:
        self.u_params = self._find_arclength_parameters()
    else:
        self.u_params = self._find_uniform_parameters()
    if not self.good:
        return
    self.left_uvs = self._find_uvs(self.left_pcurve)
    self.right_uvs = self._find_uvs(self.right_pcurve)

    # Find 3d points and tangents.
    # These will be ordered and oriented based on the
    # direction of the edge.  i.e. we will apply the reverse
    # flag
    self.points = self._evaluate_3d_points()
    self.tangents = self._evaluate_curve_tangents()

    # Generate the normals.  These will be ordered
    # based on the direction of the edge and the
    # normals will be reversed based on the orientation
    # of the faces
    self.left_normals = self._evaluate_surface_normals(
        self.left_uvs, self.left_face
    )
    self.right_normals = self._evaluate_surface_normals(
        self.right_uvs, self.right_face
    )

edge_convexity(angle_tol_rads)

Compute the convexity of the edge

Source code in src/occwl/edge_data_extractor.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def edge_convexity(self, angle_tol_rads):
    """
    Compute the convexity of the edge
    """
    assert self.good
    is_smooth = self._check_smooth(angle_tol_rads)
    if is_smooth:
        return EdgeConvexity.SMOOTH

    cross_prod_of_normals = np.cross(self.left_normals, self.right_normals, axis=1)
    dot_product_with_tangents = np.multiply(
        cross_prod_of_normals, self.tangents
    ).sum(1)

    if dot_product_with_tangents.sum() > 0.0:
        return EdgeConvexity.CONVEX
    return EdgeConvexity.CONCAVE

sanity_check_uvs(uvs, edge_tolerance)

Assert that the points we get by evaluating uvs on both sides of the edge are within the specified tolerance.

This function is intended for testing/debugging

Source code in src/occwl/edge_data_extractor.py
109
110
111
112
113
114
115
116
117
118
119
120
121
def sanity_check_uvs(self, uvs, edge_tolerance):
    """
    Assert that the points we get by evaluating uvs on both sides of the edge 
    are within the specified tolerance.

    This function is intended for testing/debugging
    """
    for u, left_uv, right_uv in zip(self.u_params, self.left_uvs, self.right_uvs):
        point = self.edge.point(u)
        point1 = self.left_face.point(left_uv)
        point2 = self.right_face.point(right_uv)
        assert np.linalg.norm(point - point1) < edge_tolerance
        assert np.linalg.norm(point - point2) < edge_tolerance

entity_mapper

The entity mapper allows you to map between occwl entities and integer identifiers which can be used as indices into arrays of feature vectors or the rows and columns of incidence matrices.

NOTE:

Only oriented edges which are used by wires are included in the oriented 
edge map.  In the case of edges which are open (i.e. they are adjacent
to a hole in the solid), only one oriented edge is present. Use the function

EntityMapper.oriented_edge_exists(oriented_edge)

to check if an oriented edge is used by a wire and known to the entity mapper.

EntityMapper

This class allows us to map between occwl entities and integer identifiers which can be used as indices into arrays of feature vectors or the rows and columns of incidence matrices.

Source code in src/occwl/entity_mapper.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
class EntityMapper:
    """
    This class allows us to map between occwl entities and integer
    identifiers which can be used as indices into arrays of feature vectors
    or the rows and columns of incidence matrices. 
    """

    def __init__(self, solid):
        """
        Create a mapper object for solid

        Args:

            solid (occwl.solid.Solid): A single solid
        """

        # Create the dictionaries which will map the
        # objects hash values to the indices.
        self.face_map = dict()
        self.wire_map = dict()
        self.edge_map = dict()
        self.oriented_edge_map = dict()
        self.vertex_map = dict()

        # Build the index lookup tables
        self._append_faces(solid)
        self._append_wires(solid)
        self._append_edges(solid)
        self._append_oriented_edges(solid)
        self._append_vertices(solid)

    # The following functions are the interface for
    # users of the class to access the indices
    # which will reptresent the Open Cascade entities

    def get_num_edges(self):
        return len(self.edge_map.keys())

    def get_num_faces(self):
        return len(self.face_map.keys())

    def face_index(self, face):
        """
        Find the index of a face
        """
        h = self._get_hash(face)
        return self.face_map[h]

    def wire_index(self, wire):
        """
        Find the index of a wire
        """
        h = self._get_hash(wire)
        return self.wire_map[h]

    def edge_index(self, edge):
        """
        Find the index of an edge
        """
        h = self._get_hash(edge)
        return self.edge_map[h]

    def oriented_edge_index(self, oriented_edge):
        """
        Find the index of a oriented edge.  i.e. a coedge
        """
        h = self._get_hash(oriented_edge)
        is_reversed = oriented_edge.reversed()
        tup = (h, is_reversed)
        return self.oriented_edge_map[tup]

    def oriented_edge_exists(self, oriented_edge):
        h = self._get_hash(oriented_edge)
        is_reversed = oriented_edge.reversed()
        tup = (h, is_reversed)
        return tup in self.oriented_edge_map

    def vertex_index(self, vertex):
        """
        Find the index of a vertex
        """
        h = self._get_hash(vertex)
        return self.vertex_map[h]

    # These functions are used internally to build the map

    def _get_hash(self, ent):
        return ent.__hash__()

    def _append_faces(self, solid):
        faces = solid.faces()
        for face in faces:
            self._append_face(face)

    def _append_face(self, face):
        h = self._get_hash(face)
        index = len(self.face_map)
        assert not h in self.face_map
        self.face_map[h] = index

    def _append_wires(self, solid):
        wires = solid.wires()
        for wire in wires:
            self._append_wire(wire)

    def _append_wire(self, wire):
        h = self._get_hash(wire)
        index = len(self.wire_map)
        assert not h in self.wire_map
        self.wire_map[h] = index

    def _append_edges(self, solid):
        edges = solid.edges()
        for edge in edges:
            self._append_edge(edge)

    def _append_edge(self, edge):
        h = self._get_hash(edge)
        index = len(self.edge_map)
        assert not h in self.edge_map
        self.edge_map[h] = index

    def _append_oriented_edges(self, solid):
        wires = solid.wires()
        for wire in wires:
            oriented_edges = wire.ordered_edges()
            for oriented_edge in oriented_edges:
                self._append_oriented_edge(oriented_edge)

    def _append_oriented_edge(self, oriented_edge):
        h = self._get_hash(oriented_edge)
        is_reversed = oriented_edge.reversed()
        tup = (h, is_reversed)
        index = len(self.oriented_edge_map)
        if tup in self.oriented_edge_map:
            print("Warning! - The same oriented edge appears twice in the same solid")
        if not tup in self.oriented_edge_map:
            self.oriented_edge_map[tup] = index

    def _append_vertices(self, solid):
        vertices = solid.vertices()
        for vertex in vertices:
            self._append_vertex(vertex)

    def _append_vertex(self, vertex):
        h = self._get_hash(vertex)
        index = len(self.vertex_map)
        assert not h in self.vertex_map
        self.vertex_map[h] = index

__init__(solid)

Create a mapper object for solid

Args:

solid (occwl.solid.Solid): A single solid
Source code in src/occwl/entity_mapper.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def __init__(self, solid):
    """
    Create a mapper object for solid

    Args:

        solid (occwl.solid.Solid): A single solid
    """

    # Create the dictionaries which will map the
    # objects hash values to the indices.
    self.face_map = dict()
    self.wire_map = dict()
    self.edge_map = dict()
    self.oriented_edge_map = dict()
    self.vertex_map = dict()

    # Build the index lookup tables
    self._append_faces(solid)
    self._append_wires(solid)
    self._append_edges(solid)
    self._append_oriented_edges(solid)
    self._append_vertices(solid)

edge_index(edge)

Find the index of an edge

Source code in src/occwl/entity_mapper.py
73
74
75
76
77
78
def edge_index(self, edge):
    """
    Find the index of an edge
    """
    h = self._get_hash(edge)
    return self.edge_map[h]

face_index(face)

Find the index of a face

Source code in src/occwl/entity_mapper.py
59
60
61
62
63
64
def face_index(self, face):
    """
    Find the index of a face
    """
    h = self._get_hash(face)
    return self.face_map[h]

oriented_edge_index(oriented_edge)

Find the index of a oriented edge. i.e. a coedge

Source code in src/occwl/entity_mapper.py
80
81
82
83
84
85
86
87
def oriented_edge_index(self, oriented_edge):
    """
    Find the index of a oriented edge.  i.e. a coedge
    """
    h = self._get_hash(oriented_edge)
    is_reversed = oriented_edge.reversed()
    tup = (h, is_reversed)
    return self.oriented_edge_map[tup]

vertex_index(vertex)

Find the index of a vertex

Source code in src/occwl/entity_mapper.py
 95
 96
 97
 98
 99
100
def vertex_index(self, vertex):
    """
    Find the index of a vertex
    """
    h = self._get_hash(vertex)
    return self.vertex_map[h]

wire_index(wire)

Find the index of a wire

Source code in src/occwl/entity_mapper.py
66
67
68
69
70
71
def wire_index(self, wire):
    """
    Find the index of a wire
    """
    h = self._get_hash(wire)
    return self.wire_map[h]

face

Face

Bases: Shape, BoundingBoxMixin, TriangulatorMixin, WireContainerMixin, EdgeContainerMixin, VertexContainerMixin, SurfacePropertiesMixin

A topological face in a solid model Represents a 3D surface bounded by a Wire

Source code in src/occwl/face.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
class Face(Shape, BoundingBoxMixin, TriangulatorMixin, WireContainerMixin, \
    EdgeContainerMixin, VertexContainerMixin, SurfacePropertiesMixin):
    """
    A topological face in a solid model
    Represents a 3D surface bounded by a Wire
    """

    def __init__(self, topods_face):
        assert isinstance(topods_face, TopoDS_Face)
        super().__init__(topods_face)
        self._trimmed = BRepTopAdaptor_FClass2d(self.topods_shape(), 1e-9)

    @staticmethod
    def make_prism(profile_edge, vector, return_first_last_shapes=False):
        """
        Make a face from a profile edge by sweeping/extrusion

        Args:
            profile_edge (occwl.edge.Edge): Edge
            vector (np.ndarray): Direction and length of extrusion
            return_first_last_shapes (bool, optional): Whether to return the base and top shapes of the result. Defaults to False.

        Returns:
            occwl.Face: Face created by sweeping the edge
            or None: if error
            occwl.Edge, occwl.Edge (optional): Returns the base and top edges of return_first_last_shapes is True.
        """
        assert isinstance(profile_edge, Edge)
        gp_vector = geom_utils.numpy_to_gp_vec(vector)
        prism = BRepPrimAPI_MakePrism(profile_edge.topods_shape(), gp_vector)
        if not prism.IsDone():
            return None
        if return_first_last_shapes:
            return (
                Face(prism.Shape()),
                Edge(prism.FirstShape()),
                Edge(prism.LastShape()),
            )
        return Face(prism.Shape())

    @staticmethod
    def make_nsided(edges, continuity="C0", points=None):
        """
        Make an n-sided fill-in face with the given edges, their continuities, and optionally a
        set of punctual points

        Args:
            edges (List[occwl.edge.Edge]): A list of edges for creating the fill-in face
            continuity (str or List[str]): A single string or a list of strings, one for each given edge.
                                           Must be one of "C0", "C1", "G1", "C2", "G2", "C3"
            points (np.ndarray, optional): Set of points to constrain the fill-in surface. Defaults to None.

        Returns:
            occwl.face.Face: Filled-in face
        """
        fill = BRepFill_Filling()

        # A helper function to convert strings to Geom_Abs_ enums
        def str_to_continuity(string):
            if string == "C0":
                return GeomAbs_C0
            elif string == "C1":
                return GeomAbs_C1
            elif string == "G1":
                return GeomAbs_G1
            elif string == "C2":
                return GeomAbs_C2
            elif string == "G2":
                return GeomAbs_G2
            elif string == "C3":
                return GeomAbs_C3

        if isinstance(continuity, str):
            assert continuity in ("C0", "C1", "C2")
            occ_continuity = str_to_continuity(continuity)
            for edg in edges:
                fill.Add(edg.topods_shape(), occ_continuity)
        elif isinstance(continuity, list):
            assert len(edges) == len(continuity), "Continuity should be provided for each edge"
            for edg, cont in zip(edges, continuity):
                occ_cont = str_to_continuity(cont)
                fill.Add(edg.topods_shape(), occ_cont)

        # Add points to contrain shape if provided
        if points:
            for pt in points:
                fill.Add(geom_utils.to_gp_pnt(pt))
        fill.Build()
        face = fill.Face()
        return Face(face)

    @staticmethod
    def make_from_wires(wires):
        """
        Make a face from PLANAR wires

        Args:
            wires (List[occwl.wire.Wire]): List of wires

        Returns:
            occwl.face.Face or None: Returns a Face or None if the operation failed
        """
        face_builder = BRepBuilderAPI_MakeFace()
        for wire in wires:
            face_builder.Add(wire.topods_shape())
        face_builder.Build()
        if not face_builder.IsDone():
            return None
        return Face(face_builder.Face())

    def inside(self, uv):
        """
        Check if the uv-coordinate is inside the visible region of the face (excludes points that lie on the boundary)

        Args:
            uv (np.ndarray or tuple): Surface parameter

        Returns:
            bool: Is inside
        """
        result = self._trimmed.Perform(gp_Pnt2d(uv[0], uv[1]))
        return result == TopAbs_IN

    def visibility_status(self, uv):
        """
        Check if the uv-coordinate in on the visible region of the face

        Args:
            uv (np.ndarray or tuple): Surface parameter

        Returns:
            int (TopAbs_STATE enum): 0: TopAbs_IN, 1: TopAbs_OUT, 2: TopAbs_ON, 3: TopAbs_UNKNOWN
        """
        result = self._trimmed.Perform(gp_Pnt2d(uv[0], uv[1]))
        return int(result)

    def surface(self):
        """
        Get the face surface geometry

        Returns:
            OCC.Geom.Handle_Geom_Surface: Interface to all surface geometry
        """
        loc = TopLoc_Location()
        surf = BRep_Tool_Surface(self.topods_shape(), loc)
        if not loc.IsIdentity():
            tsf = loc.Transformation()
            np_tsf = geom_utils.to_numpy(tsf)
            assert np.allclose(np_tsf, np.eye(4)), \
                "Requesting surface for transformed face. /n\
                Call solid.set_transform_to_identity() to remove the transform \
                or compound.Transform(np.eye(4)) to bake in the assembly transform"
        return surf

    def reversed_face(self):
        """
        Return a copy of this face with the orientation reversed.

        Returns:
            occwl.face.Face: A face with the opposite orientation to this face.
        """
        return Face(self.topods_shape().Reversed())

    def specific_surface(self):
        """
        Get the specific face surface geometry

        Returns:
            OCC.Geom.Handle_Geom_*: Specific geometry type for the surface geometry
        """
        if not self.topods_shape().Location().IsIdentity():
            tsf = self.topods_shape().Location().Transformation()
            np_tsf = geom_utils.to_numpy(tsf)
            assert np.allclose(np_tsf, np.eye(4)), \
                "Requesting surface for transformed face. /n\
                Call solid.set_transform_to_identity() to remove the transform /n\
                or compound.transform(np.eye(4)) to bake in the assembly transform"
        srf = BRepAdaptor_Surface(self.topods_shape())
        surf_type = self.surface_type()
        if surf_type == "plane":
            return srf.Plane()
        if surf_type == "cylinder":
            return srf.Cylinder()
        if surf_type == "cone":
            return srf.Cone()
        if surf_type == "sphere":
            return srf.Sphere()
        if surf_type == "torus":
            return srf.Torus()
        if surf_type == "bezier":
            return srf.Bezier()
        if surf_type == "bspline":
            return srf.BSpline()
        raise ValueError("Unknown surface type: ", surf_type)


    def point(self, uv):
        """
        Evaluate the face geometry at given parameter

        Args:
            uv (np.ndarray or tuple): Surface parameter

        Returns:
            np.ndarray: 3D Point
        """
        loc = TopLoc_Location()
        surf = BRep_Tool_Surface(self.topods_shape(), loc)
        pt = surf.Value(uv[0], uv[1])
        pt = pt.Transformed(loc.Transformation())
        return geom_utils.gp_to_numpy(pt)

    def tangent(self, uv):
        """
        Compute the tangents of the surface geometry at given parameter

        Args:
            uv (np.ndarray or tuple): Surface parameter

        Returns:
            Pair of np.ndarray or None: 3D unit vectors
        """
        loc = TopLoc_Location()
        surf = BRep_Tool_Surface(self.topods_shape(), loc)
        dU, dV = gp_Dir(), gp_Dir()
        res = GeomLProp_SLProps(surf, uv[0], uv[1], 1, 1e-9)
        if res.IsTangentUDefined() and res.IsTangentVDefined():
            res.TangentU(dU), res.TangentV(dV)
            dU.Transformed(loc.Transformation())
            dV.Transformed(loc.Transformation())
            return (geom_utils.gp_to_numpy(dU)), (geom_utils.gp_to_numpy(dV))
        return None, None

    def normal(self, uv):
        """
        Compute the normal of the surface geometry at given parameter

        Args:
            uv (np.ndarray or tuple): Surface parameter

        Returns:
            np.ndarray: 3D unit normal vector
        """
        loc = TopLoc_Location()
        surf = BRep_Tool_Surface(self.topods_shape(), loc)
        res = GeomLProp_SLProps(surf, uv[0], uv[1], 1, 1e-9)
        if not res.IsNormalDefined():
            return (0, 0, 0)
        gp_normal = res.Normal()
        gp_normal.Transformed(loc.Transformation())
        normal = geom_utils.gp_to_numpy(gp_normal)
        if self.reversed():
            normal = -normal
        return normal

    def is_left_of(self, edge):
        """
        Is this face on the left hand side of the given edge.   We take the 
        orientation of the edge into account here

                     Edge direction
                            ^
                            |   
                  Left      |   Right 
                  face      |   face
                            |
        Args:
            edge (occwl.edge.Edge): Edge

        Returns:
            bool: True if the face is to the left of the edge
        """
        found_edge = False
        for wire in self.wires():
            for edge_from_face in wire.ordered_edges():
                if edge == edge_from_face:
                    if edge.reversed() == edge_from_face.reversed():
                        return True
                    else:
                        # We found the edge, but so far we only found an edge
                        # with orientation such that the face is on the right
                        found_edge = True

        # If we didn't find the edge at all then this function was used incorrectly.
        # To use it you need to pass in a edge around the given face.  Assert and warn 
        # the user
        assert found_edge, "Edge doesn't belong to face"

        # We found an edge for which this face was on the right hand side,
        # but not one of the left hand side
        return False

    def gaussian_curvature(self, uv):
        """
        Compute the gaussian curvature of the surface geometry at given parameter

        Args:
            uv (np.ndarray or tuple): Surface parameter

        Returns:
            float: Gaussian curvature
        """
        return GeomLProp_SLProps(
            self.surface(), uv[0], uv[1], 2, 1e-9
        ).GaussianCurvature()

    def min_curvature(self, uv):
        """
        Compute the minimum curvature of the surface geometry at given parameter

        Args:
            uv (np.ndarray or tuple): Surface parameter

        Returns:
            float: Min. curvature
        """
        min_curv = GeomLProp_SLProps(
            self.surface(), uv[0], uv[1], 2, 1e-9
        ).MinCurvature()
        if self.reversed():
            min_curv *= -1
        return min_curv

    def mean_curvature(self, uv):
        """
        Compute the mean curvature of the surface geometry at given parameter

        Args:
            uv (np.ndarray or tuple): Surface parameter

        Returns:
            float: Mean curvature
        """
        mean_curv = GeomLProp_SLProps(
            self.surface(), uv[0], uv[1], 2, 1e-9
        ).MeanCurvature()
        if self.reversed():
            mean_curv *= -1
        return mean_curv

    def max_curvature(self, uv):
        """
        Compute the maximum curvature of the surface geometry at given parameter

        Args:
            uv (np.ndarray or tuple): Surface parameter

        Returns:
            float: Max. curvature
        """
        max_curv = GeomLProp_SLProps(
            self.surface(), uv[0], uv[1], 2, 1e-9
        ).MaxCurvature()
        if self.reversed():
            max_curv *= -1
        return max_curv

    def pcurve(self, edge):
        """
        Get the given edge's curve geometry as a 2D parametric curve
        on this face

        Args:
            edge (occwl.edge.Edge): Edge

        Returns:
            Geom2d_Curve: 2D curve
            Interval: domain of the parametric curve
        """
        crv, umin, umax = BRep_Tool().CurveOnSurface(
            edge.topods_shape(), self.topods_shape()
        )
        return crv, Interval(umin, umax)

    def uv_bounds(self):
        """
        Get the UV-domain bounds of this face's surface geometry

        Returns:
            Box: UV-domain bounds
        """
        umin, umax, vmin, vmax = breptools_UVBounds(self.topods_shape())
        bounds = Box(np.array([umin, vmin]))
        bounds.encompass_point(np.array([umax, vmax]))
        return bounds

    def point_to_parameter(self, pt):
        """
        Get the UV parameter by projecting the point on this face

        Args:
            pt (np.ndarray): 3D point

        Returns:
            np.ndarray: UV-coordinate
        """
        loc = TopLoc_Location()
        surf = BRep_Tool_Surface(self.topods_shape(), loc)
        gp_pt = gp_Pnt(pt[0], pt[1], pt[2])
        inv = loc.Transformation().Inverted()
        gp_pt.Transformed(inv)
        uv = ShapeAnalysis_Surface(surf).ValueOfUV(
            gp_pt, 1e-9
        )
        return np.array(uv.Coord())

    def surface_type(self):
        """
        Get the type of the surface geometry

        Returns:
            str: Type of the surface geometry
        """
        surf_type = BRepAdaptor_Surface(self.topods_shape()).GetType()
        if surf_type == GeomAbs_Plane:
            return "plane"
        if surf_type == GeomAbs_Cylinder:
            return "cylinder"
        if surf_type == GeomAbs_Cone:
            return "cone"
        if surf_type == GeomAbs_Sphere:
            return "sphere"
        if surf_type == GeomAbs_Torus:
            return "torus"
        if surf_type == GeomAbs_BezierSurface:
            return "bezier"
        if surf_type == GeomAbs_BSplineSurface:
            return "bspline"
        if surf_type == GeomAbs_SurfaceOfRevolution:
            return "revolution"
        if surf_type == GeomAbs_SurfaceOfExtrusion:
            return "extrusion"
        if surf_type == GeomAbs_OffsetSurface:
            return "offset"
        if surf_type == GeomAbs_OtherSurface:
            return "other"
        return "unknown"

    def surface_type_enum(self):
        """
        Get the type of the surface geometry as an OCC.Core.GeomAbs enum

        Returns:
            OCC.Core.GeomAbs: Type of the surface geometry
        """
        return BRepAdaptor_Surface(self.topods_shape()).GetType()

    def closed_u(self):
        """
        Whether the surface is closed along the U-direction

        Returns:
            bool: Is closed along U
        """
        sa = ShapeAnalysis_Surface(self.surface())
        return sa.IsUClosed()

    def closed_v(self):
        """
        Whether the surface is closed along the V-direction

        Returns:
            bool: Is closed along V
        """
        sa = ShapeAnalysis_Surface(self.surface())
        return sa.IsVClosed()

    def periodic_u(self):
        """
        Whether the surface is periodic along the U-direction

        Returns:
            bool: Is periodic along U
        """
        adaptor = BRepAdaptor_Surface(self.topods_shape())
        return adaptor.IsUPeriodic()

    def periodic_v(self):
        """
        Whether the surface is periodic along the V-direction

        Returns:
            bool: Is periodic along V
        """
        adaptor = BRepAdaptor_Surface(self.topods_shape())
        return adaptor.IsVPeriodic()

    def get_triangles(self, return_normals=False):
        """
        Get the tessellation of this face as a triangle mesh
        NOTE: First you must call shape.triangulate_all_faces()
        Then call this method to get the triangles for the
        face.

        Args:
            return_normals (bool): Return vertex normals

        Returns:
            2D np.ndarray: Vertices
            2D np.ndarray: Faces
            2D np.ndarray: Vertex Normals (when return_normals is True)
        """
        location = TopLoc_Location()
        bt = BRep_Tool()
        facing = bt.Triangulation(self.topods_shape(), location)
        if facing == None:
            if return_normals:
                return (
                    np.empty(shape=(0,3), dtype=np.float32),
                    np.empty(shape=(0,3), dtype=np.int32),
                    np.empty(shape=(0,3), dtype=np.float32)
                )
            else:
                return (
                    np.empty(shape=(0,3), dtype=np.float32),
                    np.empty(shape=(0,3), dtype=np.int32)
                )

        vert_nodes = facing.Nodes()
        tri = facing.Triangles()
        uv_nodes = facing.UVNodes()
        verts = []
        normals = []
        for i in range(1, facing.NbNodes() + 1):
            vert = vert_nodes.Value(i).Transformed(location.Transformation())
            verts.append(np.array(list(vert.Coord())))
            if return_normals:
                uv = uv_nodes.Value(i).Coord()
                normal = self.normal(uv)
                normals.append(normal)

        tris = []
        reversed = self.reversed()
        for i in range(1, facing.NbTriangles() + 1):
            # OCC triangle normals point in the surface normal
            # direction
            if reversed:
                index1, index3, index2 = tri.Value(i).Get()
            else:
                index1, index2, index3 = tri.Value(i).Get()

            tris.append([index1 - 1, index2 - 1, index3 - 1])

        np_verts = np.asarray(verts, dtype=np.float32)
        np_tris = np.asarray(tris, dtype=np.int32)

        if return_normals:
            np_normals = np.asarray(normals, dtype=np.float32)
            return np_verts, np_tris, np_normals
        else:
            return np_verts, np_tris

__eq__(other)

Equality check for the shape

NOTE: This function only checks if the shape is the same. It doesn't check the edge orienation for example, so

edge1 == edge2

does not necessarily mean

edge1.reversed() == edge2.reversed()

Source code in src/occwl/shape.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def __eq__(self, other):
    """
    Equality check for the shape

    NOTE: This function only checks if the shape is the same.
    It doesn't check the edge orienation for example, so 

    edge1 == edge2

    does not necessarily mean 

    edge1.reversed() == edge2.reversed()
    """
    return self.topods_shape().__hash__() == other.topods_shape().__hash__()

__hash__()

Hash for the shape

Returns:

Name Type Description
int

Hash value

Source code in src/occwl/shape.py
135
136
137
138
139
140
141
142
def __hash__(self):
    """
    Hash for the shape

    Returns:
        int: Hash value
    """
    return self.topods_shape().__hash__()

area()

Compute the area of the Shape

Returns:

Name Type Description
float

Area

Source code in src/occwl/base.py
493
494
495
496
497
498
499
500
501
502
def area(self):
    """
    Compute the area of the Shape

    Returns:
        float: Area
    """
    geometry_properties = GProp_GProps()
    brepgprop_SurfaceProperties(self.topods_shape(), geometry_properties)
    return geometry_properties.Mass()

box()

Get a quick bounding box of the Shape

Returns:

Name Type Description
Box

Bounding box

Source code in src/occwl/base.py
565
566
567
568
569
570
571
572
573
574
575
def box(self):
    """
    Get a quick bounding box of the Shape

    Returns:
        Box: Bounding box
    """
    from occwl.geometry import geom_utils
    b = Bnd_Box()
    brepbndlib_Add(self.topods_shape(), b)
    return geom_utils.box_to_geometry(b)

closed_u()

Whether the surface is closed along the U-direction

Returns:

Name Type Description
bool

Is closed along U

Source code in src/occwl/face.py
483
484
485
486
487
488
489
490
491
def closed_u(self):
    """
    Whether the surface is closed along the U-direction

    Returns:
        bool: Is closed along U
    """
    sa = ShapeAnalysis_Surface(self.surface())
    return sa.IsUClosed()

closed_v()

Whether the surface is closed along the V-direction

Returns:

Name Type Description
bool

Is closed along V

Source code in src/occwl/face.py
493
494
495
496
497
498
499
500
501
def closed_v(self):
    """
    Whether the surface is closed along the V-direction

    Returns:
        bool: Is closed along V
    """
    sa = ShapeAnalysis_Surface(self.surface())
    return sa.IsVClosed()

convert_geometric_identity_transforms_to_identity()

Open Cascade models sometimes contain transforms which are "geometrically" identify transforms, but the identity flag is not set.

This function checks each transform and sets the flag if the appropriate.

Source code in src/occwl/shape.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def convert_geometric_identity_transforms_to_identity(self):
    """
    Open Cascade models sometimes contain transforms which
    are "geometrically" identify transforms, but the identity
    flag is not set.

    This function checks each transform and sets the flag if 
    the appropriate.
    """
    identity = TopLoc_Location()
    if geom_utils.is_geometric_identity(
        self.topods_shape().Location().Transformation()
    ):
        self.topods_shape().Location(identity)
        self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)

    for face in self._top_exp.faces():
        if geom_utils.is_geometric_identity(face.Location().Transformation()):
            face.Location(identity)

    for edge in self._top_exp.edges():
        if geom_utils.is_geometric_identity(edge.Location().Transformation()):
            edge.Location(identity)

    for vertex in self._top_exp.vertices():
        if geom_utils.is_geometric_identity(vertex.Location().Transformation()):
            vertex.Location(identity)

edges()

Get an iterator to go over all edges in the Shape

Returns:

Type Description

Iterator[occwl.edge.Edge]: Edge iterator

Source code in src/occwl/base.py
59
60
61
62
63
64
65
66
67
def edges(self):
    """
    Get an iterator to go over all edges in the Shape

    Returns:
        Iterator[occwl.edge.Edge]: Edge iterator
    """
    from occwl.edge import Edge
    return map(Edge, self._top_exp.edges())

exact_box(use_shapetolerance=False)

Get a slow, but accurate box for the Shape.

Returns:

Name Type Description
Box

Bounding box

Source code in src/occwl/base.py
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
def exact_box(self, use_shapetolerance=False):
    """
    Get a slow, but accurate box for the Shape.

    Args:
        use_shapetolerance (bool, optional) Include the tolerance of edges
                                            and vertices in the box.

    Returns:
        Box: Bounding box
    """
    from occwl.geometry import geom_utils
    b = Bnd_Box()
    use_triangulation = True
    brepbndlib_AddOptimal(self.topods_shape(), b, use_triangulation, use_shapetolerance)
    return geom_utils.box_to_geometry(b)

find_closest_edge_slow(datum)

Find the closest edge to the given datum point. The function is for testing only. It will be slow as it loops over all edges in the Shape. A quick way to find the closest entity is to call Shape.find_closest_point_data(), but then you may get a face, edge or vertex back.

Parameters:

Name Type Description Default
datum ndarray or tuple

3D datum point

required

Returns:

Name Type Description
Face

The closest face in the Shape

Source code in src/occwl/base.py
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def find_closest_edge_slow(self, datum):
    """
    Find the closest edge to the given datum point.
    The function is for testing only.  It will be slow 
    as it loops over all edges in the Shape.
    A quick way to find the closest entity is to call
    Shape.find_closest_point_data(), but then you
    may get a face, edge or vertex back.

    Args:
        datum (np.ndarray or tuple): 3D datum point

    Returns:
        Face: The closest face in the Shape
    """
    return _find_closest_shape_in_list(self.edges(), datum)

find_closest_point_data(datum)

Find the information about the closest point on this shape

Parameters:

Name Type Description Default
datum ndarray

3D Point

required

Returns:

Name Type Description
ClosestPointData

Data about the closest point on this shape

None

if error

Source code in src/occwl/shape.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
def find_closest_point_data(self, datum):
    """
    Find the information about the closest point on this shape

    Args:
        datum (np.ndarray): 3D Point

    Returns:
        ClosestPointData: Data about the closest point on this shape
        None: if error
    """
    # Folowing https://dev.opencascade.org/content/how-retrieve-nearest-face-shape-given-gppnt
    # Create a vertex from the point
    occ_point = geom_utils.numpy_to_gp(datum)
    vertex_maker = BRepBuilderAPI_MakeVertex(occ_point)
    vertex = vertex_maker.Shape()
    dist_shape_shape = BRepExtrema_DistShapeShape(
        vertex, self.topods_shape(), Extrema_ExtFlag_MIN
    )
    ok = dist_shape_shape.Perform()
    if not ok:
        return None

    return ClosestPointData(dist_shape_shape)

gaussian_curvature(uv)

Compute the gaussian curvature of the surface geometry at given parameter

Parameters:

Name Type Description Default
uv ndarray or tuple

Surface parameter

required

Returns:

Name Type Description
float

Gaussian curvature

Source code in src/occwl/face.py
328
329
330
331
332
333
334
335
336
337
338
339
340
def gaussian_curvature(self, uv):
    """
    Compute the gaussian curvature of the surface geometry at given parameter

    Args:
        uv (np.ndarray or tuple): Surface parameter

    Returns:
        float: Gaussian curvature
    """
    return GeomLProp_SLProps(
        self.surface(), uv[0], uv[1], 2, 1e-9
    ).GaussianCurvature()

get_triangles(return_normals=False)

Get the tessellation of this face as a triangle mesh NOTE: First you must call shape.triangulate_all_faces() Then call this method to get the triangles for the face.

Parameters:

Name Type Description Default
return_normals bool

Return vertex normals

False

Returns:

Type Description

2D np.ndarray: Vertices

2D np.ndarray: Faces

2D np.ndarray: Vertex Normals (when return_normals is True)

Source code in src/occwl/face.py
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
def get_triangles(self, return_normals=False):
    """
    Get the tessellation of this face as a triangle mesh
    NOTE: First you must call shape.triangulate_all_faces()
    Then call this method to get the triangles for the
    face.

    Args:
        return_normals (bool): Return vertex normals

    Returns:
        2D np.ndarray: Vertices
        2D np.ndarray: Faces
        2D np.ndarray: Vertex Normals (when return_normals is True)
    """
    location = TopLoc_Location()
    bt = BRep_Tool()
    facing = bt.Triangulation(self.topods_shape(), location)
    if facing == None:
        if return_normals:
            return (
                np.empty(shape=(0,3), dtype=np.float32),
                np.empty(shape=(0,3), dtype=np.int32),
                np.empty(shape=(0,3), dtype=np.float32)
            )
        else:
            return (
                np.empty(shape=(0,3), dtype=np.float32),
                np.empty(shape=(0,3), dtype=np.int32)
            )

    vert_nodes = facing.Nodes()
    tri = facing.Triangles()
    uv_nodes = facing.UVNodes()
    verts = []
    normals = []
    for i in range(1, facing.NbNodes() + 1):
        vert = vert_nodes.Value(i).Transformed(location.Transformation())
        verts.append(np.array(list(vert.Coord())))
        if return_normals:
            uv = uv_nodes.Value(i).Coord()
            normal = self.normal(uv)
            normals.append(normal)

    tris = []
    reversed = self.reversed()
    for i in range(1, facing.NbTriangles() + 1):
        # OCC triangle normals point in the surface normal
        # direction
        if reversed:
            index1, index3, index2 = tri.Value(i).Get()
        else:
            index1, index2, index3 = tri.Value(i).Get()

        tris.append([index1 - 1, index2 - 1, index3 - 1])

    np_verts = np.asarray(verts, dtype=np.float32)
    np_tris = np.asarray(tris, dtype=np.int32)

    if return_normals:
        np_normals = np.asarray(normals, dtype=np.float32)
        return np_verts, np_tris, np_normals
    else:
        return np_verts, np_tris

inside(uv)

Check if the uv-coordinate is inside the visible region of the face (excludes points that lie on the boundary)

Parameters:

Name Type Description Default
uv ndarray or tuple

Surface parameter

required

Returns:

Name Type Description
bool

Is inside

Source code in src/occwl/face.py
146
147
148
149
150
151
152
153
154
155
156
157
def inside(self, uv):
    """
    Check if the uv-coordinate is inside the visible region of the face (excludes points that lie on the boundary)

    Args:
        uv (np.ndarray or tuple): Surface parameter

    Returns:
        bool: Is inside
    """
    result = self._trimmed.Perform(gp_Pnt2d(uv[0], uv[1]))
    return result == TopAbs_IN

is_left_of(edge)

Is this face on the left hand side of the given edge. We take the orientation of the edge into account here

         Edge direction
                ^
                |   
      Left      |   Right 
      face      |   face
                |

Args: edge (occwl.edge.Edge): Edge

Returns:

Name Type Description
bool

True if the face is to the left of the edge

Source code in src/occwl/face.py
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
def is_left_of(self, edge):
    """
    Is this face on the left hand side of the given edge.   We take the 
    orientation of the edge into account here

                 Edge direction
                        ^
                        |   
              Left      |   Right 
              face      |   face
                        |
    Args:
        edge (occwl.edge.Edge): Edge

    Returns:
        bool: True if the face is to the left of the edge
    """
    found_edge = False
    for wire in self.wires():
        for edge_from_face in wire.ordered_edges():
            if edge == edge_from_face:
                if edge.reversed() == edge_from_face.reversed():
                    return True
                else:
                    # We found the edge, but so far we only found an edge
                    # with orientation such that the face is on the right
                    found_edge = True

    # If we didn't find the edge at all then this function was used incorrectly.
    # To use it you need to pass in a edge around the given face.  Assert and warn 
    # the user
    assert found_edge, "Edge doesn't belong to face"

    # We found an edge for which this face was on the right hand side,
    # but not one of the left hand side
    return False

make_from_wires(wires) staticmethod

Make a face from PLANAR wires

Parameters:

Name Type Description Default
wires List[Wire]

List of wires

required

Returns:

Type Description

occwl.face.Face or None: Returns a Face or None if the operation failed

Source code in src/occwl/face.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
@staticmethod
def make_from_wires(wires):
    """
    Make a face from PLANAR wires

    Args:
        wires (List[occwl.wire.Wire]): List of wires

    Returns:
        occwl.face.Face or None: Returns a Face or None if the operation failed
    """
    face_builder = BRepBuilderAPI_MakeFace()
    for wire in wires:
        face_builder.Add(wire.topods_shape())
    face_builder.Build()
    if not face_builder.IsDone():
        return None
    return Face(face_builder.Face())

make_nsided(edges, continuity='C0', points=None) staticmethod

Make an n-sided fill-in face with the given edges, their continuities, and optionally a set of punctual points

Parameters:

Name Type Description Default
edges List[Edge]

A list of edges for creating the fill-in face

required
continuity str or List[str]

A single string or a list of strings, one for each given edge. Must be one of "C0", "C1", "G1", "C2", "G2", "C3"

'C0'
points ndarray

Set of points to constrain the fill-in surface. Defaults to None.

None

Returns:

Type Description

occwl.face.Face: Filled-in face

Source code in src/occwl/face.py
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@staticmethod
def make_nsided(edges, continuity="C0", points=None):
    """
    Make an n-sided fill-in face with the given edges, their continuities, and optionally a
    set of punctual points

    Args:
        edges (List[occwl.edge.Edge]): A list of edges for creating the fill-in face
        continuity (str or List[str]): A single string or a list of strings, one for each given edge.
                                       Must be one of "C0", "C1", "G1", "C2", "G2", "C3"
        points (np.ndarray, optional): Set of points to constrain the fill-in surface. Defaults to None.

    Returns:
        occwl.face.Face: Filled-in face
    """
    fill = BRepFill_Filling()

    # A helper function to convert strings to Geom_Abs_ enums
    def str_to_continuity(string):
        if string == "C0":
            return GeomAbs_C0
        elif string == "C1":
            return GeomAbs_C1
        elif string == "G1":
            return GeomAbs_G1
        elif string == "C2":
            return GeomAbs_C2
        elif string == "G2":
            return GeomAbs_G2
        elif string == "C3":
            return GeomAbs_C3

    if isinstance(continuity, str):
        assert continuity in ("C0", "C1", "C2")
        occ_continuity = str_to_continuity(continuity)
        for edg in edges:
            fill.Add(edg.topods_shape(), occ_continuity)
    elif isinstance(continuity, list):
        assert len(edges) == len(continuity), "Continuity should be provided for each edge"
        for edg, cont in zip(edges, continuity):
            occ_cont = str_to_continuity(cont)
            fill.Add(edg.topods_shape(), occ_cont)

    # Add points to contrain shape if provided
    if points:
        for pt in points:
            fill.Add(geom_utils.to_gp_pnt(pt))
    fill.Build()
    face = fill.Face()
    return Face(face)

make_prism(profile_edge, vector, return_first_last_shapes=False) staticmethod

Make a face from a profile edge by sweeping/extrusion

Parameters:

Name Type Description Default
profile_edge Edge

Edge

required
vector ndarray

Direction and length of extrusion

required
return_first_last_shapes bool

Whether to return the base and top shapes of the result. Defaults to False.

False

Returns:

Type Description

occwl.Face: Face created by sweeping the edge

or None: if error

occwl.Edge, occwl.Edge (optional): Returns the base and top edges of return_first_last_shapes is True.

Source code in src/occwl/face.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@staticmethod
def make_prism(profile_edge, vector, return_first_last_shapes=False):
    """
    Make a face from a profile edge by sweeping/extrusion

    Args:
        profile_edge (occwl.edge.Edge): Edge
        vector (np.ndarray): Direction and length of extrusion
        return_first_last_shapes (bool, optional): Whether to return the base and top shapes of the result. Defaults to False.

    Returns:
        occwl.Face: Face created by sweeping the edge
        or None: if error
        occwl.Edge, occwl.Edge (optional): Returns the base and top edges of return_first_last_shapes is True.
    """
    assert isinstance(profile_edge, Edge)
    gp_vector = geom_utils.numpy_to_gp_vec(vector)
    prism = BRepPrimAPI_MakePrism(profile_edge.topods_shape(), gp_vector)
    if not prism.IsDone():
        return None
    if return_first_last_shapes:
        return (
            Face(prism.Shape()),
            Edge(prism.FirstShape()),
            Edge(prism.LastShape()),
        )
    return Face(prism.Shape())

max_curvature(uv)

Compute the maximum curvature of the surface geometry at given parameter

Parameters:

Name Type Description Default
uv ndarray or tuple

Surface parameter

required

Returns:

Name Type Description
float

Max. curvature

Source code in src/occwl/face.py
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
def max_curvature(self, uv):
    """
    Compute the maximum curvature of the surface geometry at given parameter

    Args:
        uv (np.ndarray or tuple): Surface parameter

    Returns:
        float: Max. curvature
    """
    max_curv = GeomLProp_SLProps(
        self.surface(), uv[0], uv[1], 2, 1e-9
    ).MaxCurvature()
    if self.reversed():
        max_curv *= -1
    return max_curv

mean_curvature(uv)

Compute the mean curvature of the surface geometry at given parameter

Parameters:

Name Type Description Default
uv ndarray or tuple

Surface parameter

required

Returns:

Name Type Description
float

Mean curvature

Source code in src/occwl/face.py
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
def mean_curvature(self, uv):
    """
    Compute the mean curvature of the surface geometry at given parameter

    Args:
        uv (np.ndarray or tuple): Surface parameter

    Returns:
        float: Mean curvature
    """
    mean_curv = GeomLProp_SLProps(
        self.surface(), uv[0], uv[1], 2, 1e-9
    ).MeanCurvature()
    if self.reversed():
        mean_curv *= -1
    return mean_curv

min_curvature(uv)

Compute the minimum curvature of the surface geometry at given parameter

Parameters:

Name Type Description Default
uv ndarray or tuple

Surface parameter

required

Returns:

Name Type Description
float

Min. curvature

Source code in src/occwl/face.py
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
def min_curvature(self, uv):
    """
    Compute the minimum curvature of the surface geometry at given parameter

    Args:
        uv (np.ndarray or tuple): Surface parameter

    Returns:
        float: Min. curvature
    """
    min_curv = GeomLProp_SLProps(
        self.surface(), uv[0], uv[1], 2, 1e-9
    ).MinCurvature()
    if self.reversed():
        min_curv *= -1
    return min_curv

normal(uv)

Compute the normal of the surface geometry at given parameter

Parameters:

Name Type Description Default
uv ndarray or tuple

Surface parameter

required

Returns:

Type Description

np.ndarray: 3D unit normal vector

Source code in src/occwl/face.py
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
def normal(self, uv):
    """
    Compute the normal of the surface geometry at given parameter

    Args:
        uv (np.ndarray or tuple): Surface parameter

    Returns:
        np.ndarray: 3D unit normal vector
    """
    loc = TopLoc_Location()
    surf = BRep_Tool_Surface(self.topods_shape(), loc)
    res = GeomLProp_SLProps(surf, uv[0], uv[1], 1, 1e-9)
    if not res.IsNormalDefined():
        return (0, 0, 0)
    gp_normal = res.Normal()
    gp_normal.Transformed(loc.Transformation())
    normal = geom_utils.gp_to_numpy(gp_normal)
    if self.reversed():
        normal = -normal
    return normal

num_edges()

Number of edges in the Shape

Returns:

Name Type Description
int

Number of edges

Source code in src/occwl/base.py
50
51
52
53
54
55
56
57
def num_edges(self):
    """
    Number of edges in the Shape

    Returns:
        int: Number of edges
    """
    return self._top_exp.number_of_edges()

num_vertices()

Number of vertices in the Shape

Returns:

Name Type Description
int

Number of vertices

Source code in src/occwl/base.py
25
26
27
28
29
30
31
32
def num_vertices(self):
    """
    Number of vertices in the Shape

    Returns:
        int: Number of vertices
    """
    return self._top_exp.number_of_vertices()

num_wires()

Number of wires in the Shape

Returns:

Name Type Description
int

Number of wires

Source code in src/occwl/base.py
131
132
133
134
135
136
137
138
def num_wires(self):
    """
    Number of wires in the Shape

    Returns:
        int: Number of wires
    """
    return self._top_exp.number_of_wires()

occwl_shape(topods_shape) staticmethod

Static method to create an occwl shape of the appropriate class from the given topods_shape Args: topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

Returns:

Type Description

One of occwl.compound.Compound occwl.solid.Solid occwl.face.Face occwl.edge.Edge occwl.vertex.Vertex occwl.wire.Wire occwl.shell.Shell

Raises: Exception: [description]

Source code in src/occwl/shape.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@staticmethod
def occwl_shape(topods_shape):
    """
    Static method to create an occwl shape of the appropriate 
    class from the given topods_shape
    Args:
        topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

    Returns:
        One of
            occwl.compound.Compound
            occwl.solid.Solid
            occwl.face.Face
            occwl.edge.Edge
            occwl.vertex.Vertex
            occwl.wire.Wire
            occwl.shell.Shell
    Raises:
        Exception: [description]
    """
    from occwl.compound import Compound
    from occwl.solid import Solid
    from occwl.face import Face
    from occwl.edge import Edge
    from occwl.vertex import Vertex
    from occwl.wire import Wire
    from occwl.shell import Shell

    if isinstance(topods_shape, TopoDS_Vertex):
        return Vertex(topods_shape)
    if isinstance(topods_shape, TopoDS_Edge):
        return Edge(topods_shape)
    if isinstance(topods_shape, TopoDS_Face):
        return Face(topods_shape)
    if isinstance(topods_shape, TopoDS_Wire):
        return Wire(topods_shape)
    if isinstance(topods_shape, TopoDS_Shell):
        return Shell(topods_shape)
    if isinstance(topods_shape, TopoDS_Solid):
        return Solid(topods_shape)
    if isinstance(topods_shape, (TopoDS_Compound, TopoDS_CompSolid)):
        return Compound(topods_shape)
    raise Exception(
        "Shape must be one of TopoDS_Vertex, TopoDS_Edge, TopoDS_Face, TopoDS_Shell, TopoDS_Solid, TopoDS_Compound, TopoDS_CompSolid"
    )

pcurve(edge)

Get the given edge's curve geometry as a 2D parametric curve on this face

Parameters:

Name Type Description Default
edge Edge

Edge

required

Returns:

Name Type Description
Geom2d_Curve

2D curve

Interval

domain of the parametric curve

Source code in src/occwl/face.py
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
def pcurve(self, edge):
    """
    Get the given edge's curve geometry as a 2D parametric curve
    on this face

    Args:
        edge (occwl.edge.Edge): Edge

    Returns:
        Geom2d_Curve: 2D curve
        Interval: domain of the parametric curve
    """
    crv, umin, umax = BRep_Tool().CurveOnSurface(
        edge.topods_shape(), self.topods_shape()
    )
    return crv, Interval(umin, umax)

periodic_u()

Whether the surface is periodic along the U-direction

Returns:

Name Type Description
bool

Is periodic along U

Source code in src/occwl/face.py
503
504
505
506
507
508
509
510
511
def periodic_u(self):
    """
    Whether the surface is periodic along the U-direction

    Returns:
        bool: Is periodic along U
    """
    adaptor = BRepAdaptor_Surface(self.topods_shape())
    return adaptor.IsUPeriodic()

periodic_v()

Whether the surface is periodic along the V-direction

Returns:

Name Type Description
bool

Is periodic along V

Source code in src/occwl/face.py
513
514
515
516
517
518
519
520
521
def periodic_v(self):
    """
    Whether the surface is periodic along the V-direction

    Returns:
        bool: Is periodic along V
    """
    adaptor = BRepAdaptor_Surface(self.topods_shape())
    return adaptor.IsVPeriodic()

point(uv)

Evaluate the face geometry at given parameter

Parameters:

Name Type Description Default
uv ndarray or tuple

Surface parameter

required

Returns:

Type Description

np.ndarray: 3D Point

Source code in src/occwl/face.py
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
def point(self, uv):
    """
    Evaluate the face geometry at given parameter

    Args:
        uv (np.ndarray or tuple): Surface parameter

    Returns:
        np.ndarray: 3D Point
    """
    loc = TopLoc_Location()
    surf = BRep_Tool_Surface(self.topods_shape(), loc)
    pt = surf.Value(uv[0], uv[1])
    pt = pt.Transformed(loc.Transformation())
    return geom_utils.gp_to_numpy(pt)

point_to_parameter(pt)

Get the UV parameter by projecting the point on this face

Parameters:

Name Type Description Default
pt ndarray

3D point

required

Returns:

Type Description

np.ndarray: UV-coordinate

Source code in src/occwl/face.py
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
def point_to_parameter(self, pt):
    """
    Get the UV parameter by projecting the point on this face

    Args:
        pt (np.ndarray): 3D point

    Returns:
        np.ndarray: UV-coordinate
    """
    loc = TopLoc_Location()
    surf = BRep_Tool_Surface(self.topods_shape(), loc)
    gp_pt = gp_Pnt(pt[0], pt[1], pt[2])
    inv = loc.Transformation().Inverted()
    gp_pt.Transformed(inv)
    uv = ShapeAnalysis_Surface(surf).ValueOfUV(
        gp_pt, 1e-9
    )
    return np.array(uv.Coord())

reversed()

Whether this shape is reversed.

  • For an edge this is whether the edge is reversed with respect to the curve geometry
  • For a face this is whether the face is reversed with respect to the surface geometry
  • For a vertex this is whether the vertex is at the upper or lower parameter value on the edges curve

Returns:

Name Type Description
bool

If rational

Source code in src/occwl/shape.py
236
237
238
239
240
241
242
243
244
245
246
247
248
def reversed(self):
    """
    Whether this shape is reversed.

    - For an edge this is whether the edge is reversed with respect to the curve geometry
    - For a face this is whether the face is reversed with respect to the surface geometry
    - For a vertex this is whether the vertex is at the upper or lower parameter value on the
      edges curve

    Returns:
        bool: If rational
    """
    return self.topods_shape().Orientation() == TopAbs_REVERSED

reversed_face()

Return a copy of this face with the orientation reversed.

Returns:

Type Description

occwl.face.Face: A face with the opposite orientation to this face.

Source code in src/occwl/face.py
190
191
192
193
194
195
196
197
def reversed_face(self):
    """
    Return a copy of this face with the orientation reversed.

    Returns:
        occwl.face.Face: A face with the opposite orientation to this face.
    """
    return Face(self.topods_shape().Reversed())

rotate_axis_angle(axis, angle_radians, origin=np.zeros(3, dtype=(np.float32)))

Rotate the shape about the given axis by the given angle in radians

Parameters:

Name Type Description Default
axis ndarray

Rotation axis

required
angle_radians float

Angle in radians

required
Source code in src/occwl/shape.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
def rotate_axis_angle(
    self, axis, angle_radians, origin=np.zeros(3, dtype=np.float32)
):
    """
    Rotate the shape about the given axis by the given angle in radians

    Args:
        axis (np.ndarray): Rotation axis
        angle_radians (float): Angle in radians
    """
    self._shape = rotate_shape(
        self._shape,
        gp_Ax1(geom_utils.numpy_to_gp(origin), geom_utils.numpy_to_gp_dir(axis)),
        angle_radians,
        unite="rad",
    )

rotate_euler_angles(angles_xyz_radians)

Rotate the shape by the given Euler angles in radians

Parameters:

Name Type Description Default
angle_xyz_radians ndarray

3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians

required
Source code in src/occwl/shape.py
302
303
304
305
306
307
308
309
310
311
312
313
314
315
def rotate_euler_angles(self, angles_xyz_radians):
    """
    Rotate the shape by the given Euler angles in radians

    Args:
        angle_xyz_radians (np.ndarray): 3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians
    """
    self._shape = rotate_shp_3_axis(
        self._shape,
        angles_xyz_radians[0],
        angles_xyz_radians[1],
        angles_xyz_radians[2],
        unity="rad",
    )

save_shapes_to_occ_native(filename, shapes, with_triangles=False, with_normals=False, format_version=None) staticmethod

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
@staticmethod
def save_shapes_to_occ_native(
        filename, 
        shapes,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
            is between one and two orders of magnitude faster 
            than loading from STEP, so it is recommended for 
            large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename

        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    new_api = False
    shapes_set = BRepTools_ShapeSet(with_triangles)
    # shapes_set.SetWithNormals(with_normals) # Not in OCC 7.5.0

    for shp in shapes:
        shapes_set.Add(shp.topods_shape())
    if format_version is not None:
        shapes_set.SetFormatNb(format_version)


    with open(filename, "w") as fp:
        s = shapes_set.WriteToString()
        fp.write(s)

save_to_occ_native(filename, verbosity=False, with_triangles=False, with_normals=False, format_version=None)

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def save_to_occ_native(
        self, 
        filename, 
        verbosity=False,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
           is between one and two orders of magnitude faster 
           than loading from STEP, so it is recommended for 
           large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename
        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    self.save_shapes_to_occ_native(
        filename, 
        [ self ],
        with_triangles=with_triangles,
        with_normals=with_normals,
        format_version=format_version
    )

scale(scale_vector)

Scale the shape by the given 3D vector

Parameters:

Name Type Description Default
scale_vector ndarray

3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively

required
Source code in src/occwl/shape.py
317
318
319
320
321
322
323
324
325
326
def scale(self, scale_vector):
    """
    Scale the shape by the given 3D vector

    Args:
        scale_vector (np.ndarray): 3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively
    """
    self._shape = scale_shape(
        self._shape, scale_vector[0], scale_vector[1], scale_vector[2]
    )

scale_to_box(box_side, copy=True)

Translate and scale the Shape so it fits exactly into the [-box_side, box_side]^3 box

Returns:

Type Description

occwl..: The scaled version of this Shape

Source code in src/occwl/base.py
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
def scale_to_box(self, box_side, copy=True):
    """
    Translate and scale the Shape so it fits exactly 
    into the [-box_side, box_side]^3 box

    Args:
        box_side (float) The side length of the box
        copy (bool)      True - Copy entities and apply the transform to
                                the underlying geometry
                         False - Apply the transform to the topods Locator
                                 if possible 

    Returns:
        occwl.*.*: The scaled version of this Shape
    """
    from occwl.geometry import geom_utils
    # Get an exact box for the Shape
    box = self.exact_box()
    center = box.center()
    longest_length = box.max_box_length()

    orig = gp_Pnt(0.0, 0.0, 0.0)
    center = geom_utils.numpy_to_gp(center)
    vec_center_to_orig = gp_Vec(center, orig)
    move_to_center = gp_Trsf()
    move_to_center.SetTranslation(vec_center_to_orig)

    scale_trsf = gp_Trsf()
    scale_trsf.SetScale(orig, (2.0 * box_side) / longest_length)
    trsf_to_apply = scale_trsf.Multiplied(move_to_center)

    return self._apply_transform(trsf_to_apply, copy=copy)

scale_to_unit_box(copy=True)

Translate and scale the Shape so it fits exactly into the [-1, 1]^3 box

Returns: The scaled version of this shape

Source code in src/occwl/base.py
628
629
630
631
632
633
634
635
636
637
638
639
640
641
def scale_to_unit_box(self, copy=True):
    """
    Translate and scale the Shape so it fits exactly 
    into the [-1, 1]^3 box

    Args:
        copy (bool)      True - Copy entities and apply the transform to
                                    the underlying geometry
                            False - Apply the transform to the topods Locator
                                    if possible 
    Returns:
        The scaled version of this shape
    """
    return self.scale_to_box(1.0, copy=copy)

set_transform_to_identity()

When an assembly is loaded from a STEP file the solids will be transformed relative to their local coordinate system. i.e. they are placed in the assembly root components coordinate system.

When working with individual bodies you often want them to be axis aligned, in which case you want to remove the assembly transform. This function removes it for you.

If however you want to bake the transform into the bodies and suppress the asserts from parts of occwl which don't cope with transforms then use the transform() function below with copy=True

Source code in src/occwl/shape.py
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def set_transform_to_identity(self):
    """
    When an assembly is loaded from a STEP file
    the solids will be transformed relative to
    their local coordinate system.   i.e. they
    are placed in the assembly root components 
    coordinate system.

    When working with individual bodies you often
    want them to be axis aligned, in which case 
    you want to remove the assembly transform.
    This function removes it for you.

    If however you want to bake the transform
    into the bodies and suppress the asserts 
    from parts of occwl which don't cope with
    transforms then use the transform() function
    below with copy=True
    """
    identity = TopLoc_Location()
    self.topods_shape().Location(identity)
    self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)
    self.convert_geometric_identity_transforms_to_identity()

specific_surface()

Get the specific face surface geometry

Returns:

Type Description

OCC.Geom.Handle_Geom_*: Specific geometry type for the surface geometry

Source code in src/occwl/face.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
def specific_surface(self):
    """
    Get the specific face surface geometry

    Returns:
        OCC.Geom.Handle_Geom_*: Specific geometry type for the surface geometry
    """
    if not self.topods_shape().Location().IsIdentity():
        tsf = self.topods_shape().Location().Transformation()
        np_tsf = geom_utils.to_numpy(tsf)
        assert np.allclose(np_tsf, np.eye(4)), \
            "Requesting surface for transformed face. /n\
            Call solid.set_transform_to_identity() to remove the transform /n\
            or compound.transform(np.eye(4)) to bake in the assembly transform"
    srf = BRepAdaptor_Surface(self.topods_shape())
    surf_type = self.surface_type()
    if surf_type == "plane":
        return srf.Plane()
    if surf_type == "cylinder":
        return srf.Cylinder()
    if surf_type == "cone":
        return srf.Cone()
    if surf_type == "sphere":
        return srf.Sphere()
    if surf_type == "torus":
        return srf.Torus()
    if surf_type == "bezier":
        return srf.Bezier()
    if surf_type == "bspline":
        return srf.BSpline()
    raise ValueError("Unknown surface type: ", surf_type)

split_all_closed_edges(max_tol=0.01, precision=0.01, num_splits=1)

Split all the closed edges in this shape

Parameters:

Name Type Description Default
max_tol float

Maximum tolerance allowed. Defaults to 0.01.

0.01
precision float

Precision of the tool when splitting. Defaults to 0.01.

0.01
num_splits int

Number of splits to perform. Each split edge will result in num_splits + 1 edges. Defaults to 1.

1

Returns:

Type Description

occwl..: Shape with closed edges split

Source code in src/occwl/base.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def split_all_closed_edges(self, max_tol=0.01, precision=0.01, num_splits=1):
    """
    Split all the closed edges in this shape

    Args:
        max_tol (float, optional): Maximum tolerance allowed. Defaults to 0.01.
        precision (float, optional): Precision of the tool when splitting. Defaults to 0.01.
        num_splits (int, optional): Number of splits to perform. Each split edge will result in num_splits + 1 edges. Defaults to 1.

    Returns:
        occwl.*.*: Shape with closed edges split
    """
    divider = ShapeUpgrade_ShapeDivideClosedEdges(self.topods_shape())
    divider.SetPrecision(precision)
    divider.SetMinTolerance(0.1 * max_tol)
    divider.SetMaxTolerance(max_tol)
    divider.SetNbSplitPoints(num_splits)
    ok = divider.Perform()
    if not ok:
        # Splitting failed or there were no closed edges to split
        # Return the original shape
        return self
    return type(self)(divider.Result())

surface()

Get the face surface geometry

Returns:

Type Description

OCC.Geom.Handle_Geom_Surface: Interface to all surface geometry

Source code in src/occwl/face.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def surface(self):
    """
    Get the face surface geometry

    Returns:
        OCC.Geom.Handle_Geom_Surface: Interface to all surface geometry
    """
    loc = TopLoc_Location()
    surf = BRep_Tool_Surface(self.topods_shape(), loc)
    if not loc.IsIdentity():
        tsf = loc.Transformation()
        np_tsf = geom_utils.to_numpy(tsf)
        assert np.allclose(np_tsf, np.eye(4)), \
            "Requesting surface for transformed face. /n\
            Call solid.set_transform_to_identity() to remove the transform \
            or compound.Transform(np.eye(4)) to bake in the assembly transform"
    return surf

surface_type()

Get the type of the surface geometry

Returns:

Name Type Description
str

Type of the surface geometry

Source code in src/occwl/face.py
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
def surface_type(self):
    """
    Get the type of the surface geometry

    Returns:
        str: Type of the surface geometry
    """
    surf_type = BRepAdaptor_Surface(self.topods_shape()).GetType()
    if surf_type == GeomAbs_Plane:
        return "plane"
    if surf_type == GeomAbs_Cylinder:
        return "cylinder"
    if surf_type == GeomAbs_Cone:
        return "cone"
    if surf_type == GeomAbs_Sphere:
        return "sphere"
    if surf_type == GeomAbs_Torus:
        return "torus"
    if surf_type == GeomAbs_BezierSurface:
        return "bezier"
    if surf_type == GeomAbs_BSplineSurface:
        return "bspline"
    if surf_type == GeomAbs_SurfaceOfRevolution:
        return "revolution"
    if surf_type == GeomAbs_SurfaceOfExtrusion:
        return "extrusion"
    if surf_type == GeomAbs_OffsetSurface:
        return "offset"
    if surf_type == GeomAbs_OtherSurface:
        return "other"
    return "unknown"

surface_type_enum()

Get the type of the surface geometry as an OCC.Core.GeomAbs enum

Returns:

Type Description

OCC.Core.GeomAbs: Type of the surface geometry

Source code in src/occwl/face.py
474
475
476
477
478
479
480
481
def surface_type_enum(self):
    """
    Get the type of the surface geometry as an OCC.Core.GeomAbs enum

    Returns:
        OCC.Core.GeomAbs: Type of the surface geometry
    """
    return BRepAdaptor_Surface(self.topods_shape()).GetType()

tangent(uv)

Compute the tangents of the surface geometry at given parameter

Parameters:

Name Type Description Default
uv ndarray or tuple

Surface parameter

required

Returns:

Type Description

Pair of np.ndarray or None: 3D unit vectors

Source code in src/occwl/face.py
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
def tangent(self, uv):
    """
    Compute the tangents of the surface geometry at given parameter

    Args:
        uv (np.ndarray or tuple): Surface parameter

    Returns:
        Pair of np.ndarray or None: 3D unit vectors
    """
    loc = TopLoc_Location()
    surf = BRep_Tool_Surface(self.topods_shape(), loc)
    dU, dV = gp_Dir(), gp_Dir()
    res = GeomLProp_SLProps(surf, uv[0], uv[1], 1, 1e-9)
    if res.IsTangentUDefined() and res.IsTangentVDefined():
        res.TangentU(dU), res.TangentV(dV)
        dU.Transformed(loc.Transformation())
        dV.Transformed(loc.Transformation())
        return (geom_utils.gp_to_numpy(dU)), (geom_utils.gp_to_numpy(dV))
    return None, None

topods_shape()

Get the underlying OCC shape

Returns:

Type Description

OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*

Source code in src/occwl/shape.py
80
81
82
83
84
85
86
87
def topods_shape(self):
    """
    Get the underlying OCC shape

    Returns:
        OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*
    """
    return self._shape

transform(a, copy=True)

Apply the given 3x4 transform matrix to the solid.

 copy (bool)    True - Copy entities and apply the transform to
                       the underlying geometry
                False - Apply the transform to the topods Locator
                        if possible
Source code in src/occwl/shape.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
def transform(self, a: np.ndarray, copy=True):
    """
    Apply the given 3x4 transform matrix to the solid.

    Args: a (nd.array) - Homogeneous transform matrix
                         The transform that will be applied is

                         x' =  a[0,0]*x + a[0,1]*y + a[0,2]*z + a[0, 3]
                         y' =  a[1,0]*x + a[1,1]*y + a[1,2]*z + a[1, 3]
                         z' =  a[2,0]*x + a[2,1]*y + a[2,2]*z + a[2, 3]

         copy (bool)    True - Copy entities and apply the transform to
                               the underlying geometry
                        False - Apply the transform to the topods Locator
                                if possible 
    """
    assert (a.shape == (3, 4)), "Transform matrix must be 3x4"
    a = a.astype(np.float64)

    # Create an identity transform
    trsf = gp_Trsf()

    # If the matrix is an identity matrix then
    # we don't want to set the values as this
    # would give us a geometric identity without
    # the identity flag set
    if not np.allclose(a, np.eye(3, 4)):
        trsf.SetValues(
            a[0,0], a[0,1], a[0,2], a[0, 3],
            a[1,0], a[1,1], a[1,2], a[1, 3],
            a[2,0], a[2,1], a[2,2], a[2, 3]
        )
    return self._apply_transform(trsf, copy=copy)

translate(offset)

Translate the shape by an offset vector

Parameters:

Name Type Description Default
offset ndarray

Offset vector

required
Source code in src/occwl/shape.py
276
277
278
279
280
281
282
283
def translate(self, offset):
    """
    Translate the shape by an offset vector

    Args:
        offset (np.ndarray): Offset vector
    """
    self._shape = translate_shp(self._shape, geom_utils.numpy_to_gp_vec(offset))

triangulate(triangle_face_tol=0.01, tol_relative_to_face=True, angle_tol_rads=0.1)

Triangulate all the faces in the shape. You can then get the triangles from each face separately using face.get_triangles(). If you wanted triangles for the entire shape then call shape.get_triangles() below. For more details see https://old.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11

Parameters:

Name Type Description Default
triangle_face_tol float

Tolerance between triangle and surface. Defaults to 0.01.

0.01
tol_relative_to_face bool

Whether tolerance is relative to face size

True
angle_tol_rads float

Angle tolerance in radians. Defaults to 0.1.

0.1

Returns:

Name Type Description
bool

Is successful

Source code in src/occwl/base.py
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
def triangulate(
    self,
    triangle_face_tol=0.01,  # Tolerance between triangle and surface
    tol_relative_to_face=True,  # The tolerance value is relative to the face size
    angle_tol_rads=0.1,  # Angle between normals/tangents at triangle vertices
):
    """
    Triangulate all the faces in the shape. You can then get the triangles 
    from each face separately using face.get_triangles().
    If you wanted triangles for the entire shape then call
    shape.get_triangles() below.
    For more details see 
    https://old.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11

    Args:
        triangle_face_tol (float, optional): Tolerance between triangle and surface. Defaults to 0.01.
        tol_relative_to_face (bool): Whether tolerance is relative to face size
        angle_tol_rads (float, optional): Angle tolerance in radians. Defaults to 0.1.

    Returns:
        bool: Is successful
    """
    mesh = BRepMesh_IncrementalMesh(
        self.topods_shape(),
        triangle_face_tol,
        tol_relative_to_face,
        angle_tol_rads,
        True,
    )
    mesh.Perform()
    return mesh.IsDone()

uv_bounds()

Get the UV-domain bounds of this face's surface geometry

Returns:

Name Type Description
Box

UV-domain bounds

Source code in src/occwl/face.py
410
411
412
413
414
415
416
417
418
419
420
def uv_bounds(self):
    """
    Get the UV-domain bounds of this face's surface geometry

    Returns:
        Box: UV-domain bounds
    """
    umin, umax, vmin, vmax = breptools_UVBounds(self.topods_shape())
    bounds = Box(np.array([umin, vmin]))
    bounds.encompass_point(np.array([umax, vmax]))
    return bounds

valid(return_analyzer=False)

Check if the shape is valid

Parameters:

Name Type Description Default
return_analyzer bool

Whether to return the BRepCheck_Analyzer object for more inspection

False

Returns:

Name Type Description
bool

Whether the shape is valid

BRepCheck_Analyzer [optional]: if return_analyzer is True

Source code in src/occwl/shape.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def valid(self, return_analyzer=False):
    """
    Check if the shape is valid

    Args:
        return_analyzer (bool): Whether to return the BRepCheck_Analyzer object for more inspection

    Returns:
        bool: Whether the shape is valid
        BRepCheck_Analyzer [optional]: if return_analyzer is True
    """
    analyzer = BRepCheck_Analyzer(self.topods_shape())
    if return_analyzer:
        return analyzer.IsValid(), analyzer
    return analyzer.IsValid()

vertices()

Get an iterator to go over all vertices in the Shape

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
34
35
36
37
38
39
40
41
42
def vertices(self):
    """
    Get an iterator to go over all vertices in the Shape

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    return map(Vertex, self._top_exp.vertices())

vertices_from_edge(edge)

Get an iterator to go over the vertices bounding an edge

Parameters:

Name Type Description Default
edge Edge

Input edge

required

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def vertices_from_edge(self, edge):
    """
    Get an iterator to go over the vertices bounding an edge

    Args:
        edge (occwl.edge.Edge): Input edge

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    from occwl.edge import Edge
    assert isinstance(edge, Edge)
    return map(Vertex, self._top_exp.vertices_from_edge(edge.topods_shape()))

visibility_status(uv)

Check if the uv-coordinate in on the visible region of the face

Parameters:

Name Type Description Default
uv ndarray or tuple

Surface parameter

required

Returns:

Name Type Description
int TopAbs_STATE enum

0: TopAbs_IN, 1: TopAbs_OUT, 2: TopAbs_ON, 3: TopAbs_UNKNOWN

Source code in src/occwl/face.py
159
160
161
162
163
164
165
166
167
168
169
170
def visibility_status(self, uv):
    """
    Check if the uv-coordinate in on the visible region of the face

    Args:
        uv (np.ndarray or tuple): Surface parameter

    Returns:
        int (TopAbs_STATE enum): 0: TopAbs_IN, 1: TopAbs_OUT, 2: TopAbs_ON, 3: TopAbs_UNKNOWN
    """
    result = self._trimmed.Perform(gp_Pnt2d(uv[0], uv[1]))
    return int(result)

wires()

Get an iterator to go over all wires in the Shape

Returns:

Type Description

Iterator[occwl.wire.Wire]: Wire iterator

Source code in src/occwl/base.py
140
141
142
143
144
145
146
147
148
def wires(self):
    """
    Get an iterator to go over all wires in the Shape

    Returns:
        Iterator[occwl.wire.Wire]: Wire iterator
    """
    from occwl.wire import Wire
    return map(Wire, self._top_exp.wires())

geometry

arc_length_param_finder

Tools for finding an arc-length parameterization from an edge, curve interval or surface iso-parametric curve

ArcLengthParamFinder

Source code in src/occwl/geometry/arc_length_param_finder.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
class ArcLengthParamFinder:
    def __init__(self, points=None, us=None, edge=None, num_arc_length_samples=100):
        """
        Create a class to generate arc-length parameters

        Args:
            points (list(np.array)): Point samples on the curve
            us (list(float)): u parameters on the curve

            or

            edge (occwl.edge.Edge): An edge
            num_arc_length_samples (int): The number of samples to use the the calculation
        """
        self.good = True
        if edge is not None:
            # For an edge we can sample points directly
            self._generate_data_from_edge(edge, num_arc_length_samples)
        else:
            # This code could be expanded to sample from surface iso-parametric lines
            assert points is not None
            assert us is not None
            self.points = points
            self.us = us

    def find_arc_length_parameters(self, num_samples):
        """
        Find a list of u parameters which provides an equal arc length
        list for the given points and us
        """
        assert num_samples >= 2
        assert len(self.points) >= 2
        assert len(self.us) == len(self.points)

        # Find the arc lengths between the sample points
        lengths = []
        total_length = 0
        prev_point = None
        for point in self.points:
            if prev_point is not None:
                length = np.linalg.norm(point - prev_point)
                lengths.append(length)
                total_length += length
            prev_point = point

        # Find the cumulative arc length over the points
        arc_length_fraction = [0.0]
        cumulative_length = 0.0
        for length in lengths:
            cumulative_length += length
            arc_length_fraction.append(cumulative_length / total_length)

        # Build the arc-length parameterization
        arc_length_params = []
        arc_length_index = 0
        for i in range(num_samples):
            desired_arc_length_fraction = i / (num_samples - 1)

            # Find the correct span of the polyline
            while arc_length_fraction[arc_length_index] < desired_arc_length_fraction:
                arc_length_index += 1
                if arc_length_index >= len(arc_length_fraction) - 1:
                    break

            # Handle the special case at the first point
            if arc_length_index == 0:
                u_low = self.us[0]
                frac_low = arc_length_fraction[0]
            else:
                u_low = self.us[arc_length_index - 1]
                frac_low = arc_length_fraction[arc_length_index - 1]

            # Find the arc length parameter by interpolation
            u_high = self.us[arc_length_index]
            frac_high = arc_length_fraction[arc_length_index]

            # Check we found the correct range
            assert desired_arc_length_fraction >= frac_low
            assert desired_arc_length_fraction <= frac_high

            d_frac = frac_high - frac_low
            if d_frac <= 0.0:
                u_param = u_low
            else:
                u_interval = Interval(u_low, u_high)
                position_in_interval = (desired_arc_length_fraction - frac_low) / (
                    d_frac
                )
                u_param = u_interval.interpolate(position_in_interval)
            arc_length_params.append(u_param)

        return arc_length_params

    def _generate_data_from_edge(self, edge, num_points_arclength):
        interval = edge.u_bounds()
        if interval.invalid():
            self.good = False
            return
        self.points = []
        self.us = []
        for i in range(num_points_arclength):
            u = interval.interpolate(i / (num_points_arclength - 1))
            self.points.append(edge.point(u))
            self.us.append(u)

    def _check_non_decreasing(self, us):
        prev = us[0]
        for u in us:
            if u < prev:
                return False
            prev = u
        return True
__init__(points=None, us=None, edge=None, num_arc_length_samples=100)

Create a class to generate arc-length parameters

Parameters:

Name Type Description Default
points list(np.array

Point samples on the curve

None
us list(float

u parameters on the curve

None
edge Edge

An edge

None
num_arc_length_samples int

The number of samples to use the the calculation

100
Source code in src/occwl/geometry/arc_length_param_finder.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def __init__(self, points=None, us=None, edge=None, num_arc_length_samples=100):
    """
    Create a class to generate arc-length parameters

    Args:
        points (list(np.array)): Point samples on the curve
        us (list(float)): u parameters on the curve

        or

        edge (occwl.edge.Edge): An edge
        num_arc_length_samples (int): The number of samples to use the the calculation
    """
    self.good = True
    if edge is not None:
        # For an edge we can sample points directly
        self._generate_data_from_edge(edge, num_arc_length_samples)
    else:
        # This code could be expanded to sample from surface iso-parametric lines
        assert points is not None
        assert us is not None
        self.points = points
        self.us = us
find_arc_length_parameters(num_samples)

Find a list of u parameters which provides an equal arc length list for the given points and us

Source code in src/occwl/geometry/arc_length_param_finder.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def find_arc_length_parameters(self, num_samples):
    """
    Find a list of u parameters which provides an equal arc length
    list for the given points and us
    """
    assert num_samples >= 2
    assert len(self.points) >= 2
    assert len(self.us) == len(self.points)

    # Find the arc lengths between the sample points
    lengths = []
    total_length = 0
    prev_point = None
    for point in self.points:
        if prev_point is not None:
            length = np.linalg.norm(point - prev_point)
            lengths.append(length)
            total_length += length
        prev_point = point

    # Find the cumulative arc length over the points
    arc_length_fraction = [0.0]
    cumulative_length = 0.0
    for length in lengths:
        cumulative_length += length
        arc_length_fraction.append(cumulative_length / total_length)

    # Build the arc-length parameterization
    arc_length_params = []
    arc_length_index = 0
    for i in range(num_samples):
        desired_arc_length_fraction = i / (num_samples - 1)

        # Find the correct span of the polyline
        while arc_length_fraction[arc_length_index] < desired_arc_length_fraction:
            arc_length_index += 1
            if arc_length_index >= len(arc_length_fraction) - 1:
                break

        # Handle the special case at the first point
        if arc_length_index == 0:
            u_low = self.us[0]
            frac_low = arc_length_fraction[0]
        else:
            u_low = self.us[arc_length_index - 1]
            frac_low = arc_length_fraction[arc_length_index - 1]

        # Find the arc length parameter by interpolation
        u_high = self.us[arc_length_index]
        frac_high = arc_length_fraction[arc_length_index]

        # Check we found the correct range
        assert desired_arc_length_fraction >= frac_low
        assert desired_arc_length_fraction <= frac_high

        d_frac = frac_high - frac_low
        if d_frac <= 0.0:
            u_param = u_low
        else:
            u_interval = Interval(u_low, u_high)
            position_in_interval = (desired_arc_length_fraction - frac_low) / (
                d_frac
            )
            u_param = u_interval.interpolate(position_in_interval)
        arc_length_params.append(u_param)

    return arc_length_params

box

Box

A 2d or 3d box for points defined as numpy arrays

Source code in src/occwl/geometry/box.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
class Box:
    """A 2d or 3d box for points defined as numpy arrays"""

    def __init__(self, pt=None):
        self.intervals = []
        if pt is not None:
            for value in pt:
                self.intervals.append(Interval(value, value))

    def encompass_box(self, box):
        if len(self.intervals) == 0:
            for interval in box.intervals:
                self.intervals.append(interval)
        else:
            assert len(self.intervals) == len(box.intervals)
            for i, interval in enumerate(box.intervals):
                self.intervals[i].encompass_interval(interval)

    def encompass_point(self, point):
        if len(self.intervals) == 0:
            for i, value in enumerate(point):
                self.intervals.append(Interval(value, value))
        else:
            assert len(self.intervals) == point.size
            for i, value in enumerate(point):
                self.intervals[i].encompass_value(value)

    def contains_point(self, point):
        assert len(self.intervals) == point.size
        for i, value in enumerate(point):
            if not self.intervals[i].contains_value(value):
                return False
        return True

    def contains_box(self, box):
        assert len(self.intervals) == len(box.intervals)
        for i, interval in enumerate(self.intervals):
            if not interval.contains_interval(box.intervals[i]):
                return False
        return True

    def x_length(self):
        assert len(self.intervals) >= 1
        return self.intervals[0].length()

    def y_length(self):
        assert len(self.intervals) >= 2
        return self.intervals[1].length()

    def z_length(self):
        assert len(self.intervals) >= 2
        return self.intervals[2].length()

    def max_box_length(self):
        max_length = 0.0
        for interval in self.intervals:
            length = interval.length()
            if length > max_length:
                max_length = length
        return max_length

    def min_point(self):
        return np.array([interval.a for interval in self.intervals])

    def max_point(self):
        return np.array([interval.b for interval in self.intervals])

    def diagonal(self):
        return self.max_point() - self.min_point()

    def center(self):
        return np.array([interval.middle() for interval in self.intervals])

    def offset(self, dist):
        for i in range(len(self.intervals)):
            self.intervals[i].offset(dist)

interval

Interval

Source code in src/occwl/geometry/interval.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class Interval:
    def __init__(self, a=sys.float_info.max, b=-sys.float_info.max):
        if a == sys.float_info.max or a < b:
            self.a = a
            self.b = b
        else:
            self.a = b
            self.b = a

    def invalid(self):
        """
        An invalid interval is uninitialized.
        It can be thought of as an intervale containing 
        no points
        """
        return self.b < self.a

    def encompass_interval(self, interval):
        if self.a > interval.a:
            self.a = interval.a
        if self.b < interval.b:
            self.b = interval.b

    def encompass_value(self, value):
        if self.a > value:
            self.a = value
        if self.b < value:
            self.b = value

    def contains_value(self, value):
        assert not self.invalid(), "Invalid interval"
        return self.a <= value and value <= self.b

    def contains_interval(self, interval):
        assert not self.invalid(), "Invalid interval"
        assert self.a <= self.b
        assert interval.a <= interval.b
        return self.a <= interval.a and interval.b <= self.b

    def length(self):
        assert not self.invalid()
        l = self.b - self.a
        if l < 0.0:
            return 0.0
        return l

    def middle(self):
        assert not self.invalid()
        return (self.a + self.b) / 2.0

    def interpolate(self, t):
        """Return a position inside the interval 
           which interpolates between a and b.  i.e.
           If t==0 then return a
           If t==1 then return b
           If 0<t<1 then return a value inside the interval
        """
        assert not self.invalid()
        return (1.0 - t) * self.a + t * self.b

    def offset(self, dist):
        assert not self.invalid()
        self.a -= dist
        self.b += dist
interpolate(t)

Return a position inside the interval which interpolates between a and b. i.e. If t==0 then return a If t==1 then return b If 0<t<1 then return a value inside the interval

Source code in src/occwl/geometry/interval.py
54
55
56
57
58
59
60
61
62
def interpolate(self, t):
    """Return a position inside the interval 
       which interpolates between a and b.  i.e.
       If t==0 then return a
       If t==1 then return b
       If 0<t<1 then return a value inside the interval
    """
    assert not self.invalid()
    return (1.0 - t) * self.a + t * self.b
invalid()

An invalid interval is uninitialized. It can be thought of as an intervale containing no points

Source code in src/occwl/geometry/interval.py
13
14
15
16
17
18
19
def invalid(self):
    """
    An invalid interval is uninitialized.
    It can be thought of as an intervale containing 
    no points
    """
    return self.b < self.a

graph

face_adjacency(shape, self_loops=False)

Creates a face adjacency graph from the given shape (Solid or Compound)

Parameters:

Name Type Description Default
shape Shell, Solid, or Compound

Shape

required
self_loops bool

Whether to add self loops in the graph. Defaults to False.

False

Returns:

Name Type Description

nx.DiGraph: Each B-rep face is mapped to a node with its index and each B-rep edge is mapped to an edge in the graph Node attributes: - "face": contains the B-rep face Edge attributes: - "edge": contains the B-rep (ordered) edge - "edge_idx": index of the (ordered) edge in the solid

None

if the shape is non-manifold or open

Source code in src/occwl/graph.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def face_adjacency(shape, self_loops=False):
    """
    Creates a face adjacency graph from the given shape (Solid or Compound)

    Args:
        shape (Shell, Solid, or Compound): Shape
        self_loops (bool, optional): Whether to add self loops in the graph. Defaults to False.

    Returns:
        nx.DiGraph: Each B-rep face is mapped to a node with its index and each B-rep edge is mapped to an edge in the graph
                    Node attributes:
                    - "face": contains the B-rep face
                    Edge attributes:
                    - "edge": contains the B-rep (ordered) edge
                    - "edge_idx": index of the (ordered) edge in the solid
        None: if the shape is non-manifold or open
    """
    assert isinstance(shape, (Shell, Solid, Compound))
    mapper = EntityMapper(shape)
    graph = nx.DiGraph()
    for face in shape.faces():
        face_idx = mapper.face_index(face)
        graph.add_node(face_idx, face=face)

    for edge in shape.edges():
        if not edge.has_curve():
            continue
        connected_faces = list(shape.faces_from_edge(edge))
        if len(connected_faces) < 2:
            if edge.seam(connected_faces[0]) and self_loops:
                face_idx = mapper.face_index(connected_faces[0])
                graph.add_edge(face_idx, face_idx)
        elif len(connected_faces) == 2:
            left_face, right_face = edge.find_left_and_right_faces(connected_faces)
            if left_face is None or right_face is None:
                continue
            edge_idx = mapper.oriented_edge_index(edge)
            edge_reversed = edge.reversed_edge()
            if not mapper.oriented_edge_exists(edge_reversed):
                continue
            edge_reversed_idx = mapper.oriented_edge_index(edge_reversed)
            left_index = mapper.face_index(left_face)
            right_index = mapper.face_index(right_face)
            graph.add_edge(left_index, right_index, edge=edge, edge_index=edge_idx) 
            graph.add_edge(right_index, left_index, edge=edge_reversed, edge_index=edge_reversed_idx)
        else:
            raise RuntimeError("Expected a manifold, an edge must be incident on one/two faces")
    return graph

vertex_adjacency(shape, self_loops=False)

Creates a vertex adjacency graph from the given shape (Wire, Solid or Compound)

Parameters:

Name Type Description Default
shape Wire, Face, Shell, Solid, or Compound

Shape

required
self_loops bool

Whether to add self loops in the graph. Defaults to False.

False

Returns:

Type Description

nx.DiGraph: Each B-rep vertex is mapped to a node with its index and each B-rep edge is mapped to an edge in the graph Node attributes: - "vertex": contains the B-rep vertex Edge attributes: - "edge": contains the B-rep (ordered) edge - "edge_idx": index of the (ordered) edge in the solid

Source code in src/occwl/graph.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def vertex_adjacency(shape, self_loops=False):
    """ 
    Creates a vertex adjacency graph from the given shape (Wire, Solid or Compound)

    Args:
        shape (Wire, Face, Shell, Solid, or Compound): Shape
        self_loops (bool, optional): Whether to add self loops in the graph. Defaults to False.

    Returns:
        nx.DiGraph: Each B-rep vertex is mapped to a node with its index and each B-rep edge is mapped to an edge in the graph
                    Node attributes:
                    - "vertex": contains the B-rep vertex
                    Edge attributes:
                    - "edge": contains the B-rep (ordered) edge
                    - "edge_idx": index of the (ordered) edge in the solid
    """
    assert isinstance(shape, (Wire, Face, Shell, Solid, Compound))
    mapper = EntityMapper(shape)
    graph = nx.DiGraph()
    for vert in shape.vertices():
        vert_idx = mapper.vertex_index(vert)
        graph.add_node(vert_idx, vertex=vert)

    for edge in shape.edges():
        connected_verts = list(shape.vertices_from_edge(edge))
        if not edge.has_curve():
            continue
        if len(connected_verts) == 1:
            if edge.closed_edge() and self_loops:
                vert_idx = mapper.vertex_index(connected_verts[0])
                graph.add_edge(vert_idx, vert_idx)
        elif len(connected_verts) == 2:
            # Don't add an edge if the edge doesn't exist in the model
            if not mapper.oriented_edge_exists(edge):
                continue
            edge_idx = mapper.oriented_edge_index(edge)
            edge_reversed = edge.reversed_edge()
            if not mapper.oriented_edge_exists(edge_reversed):
                continue
            edge_reversed_idx = mapper.oriented_edge_index(edge_reversed)
            vert_i_index = mapper.vertex_index(edge.start_vertex())
            vert_j_index = mapper.vertex_index(edge.end_vertex())
            graph.add_edge(
                vert_i_index, vert_j_index, edge=edge, edge_index=edge_idx
            )
            graph.add_edge(
                vert_j_index, vert_i_index, edge=edge_reversed, edge_index=edge_reversed_idx
            )
        else:
            raise RuntimeError("Expected an edge two connected one/two vertices")

    return graph

io

load_single_compound_from_step(step_filename)

Load data from a STEP file as a single compound

Parameters:

Name Type Description Default
step_filename str

Path to STEP file

required

Returns:

Type Description

List of occwl.Compound: a single compound containing all shapes in the file

Source code in src/occwl/io.py
13
14
15
16
17
18
19
20
21
22
23
24
25
@deprecated(target=None, deprecated_in="0.0.3", remove_in="0.0.5")
def load_single_compound_from_step(step_filename):
    """
    Load data from a STEP file as a single compound

    Args:
        step_filename (str): Path to STEP file

    Returns:
        List of occwl.Compound: a single compound containing all shapes in
                                the file
    """
    return Compound.load_from_step(step_filename)

load_step(step_filename)

Load solids from a STEP file

Parameters:

Name Type Description Default
step_filename str

Path to STEP file

required

Returns:

Type Description

List of occwl.Solid: a list of solid models from the file

Source code in src/occwl/io.py
28
29
30
31
32
33
34
35
36
37
38
39
@deprecated(target=None, deprecated_in="0.0.3", remove_in="0.0.5")
def load_step(step_filename):
    """Load solids from a STEP file

    Args:
        step_filename (str): Path to STEP file

    Returns:
        List of occwl.Solid: a list of solid models from the file
    """
    compound = load_single_compound_from_step(step_filename)
    return list(compound.solids())

save_step(list_of_solids, filename)

Save solids into a STEP file

Parameters:

Name Type Description Default
list_of_solids List[Solid]

List of solids

required
filename Path or str

Output STEP file name

required

Returns:

Name Type Description
bool

Whether successful

Source code in src/occwl/io.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def save_step(list_of_solids, filename):
    """Save solids into a STEP file

    Args:
        list_of_solids (List[occwl.solid.Solid]): List of solids
        filename (pathlib.Path or str): Output STEP file name

    Returns:
        bool: Whether successful
    """
    step_writer = STEPControl_Writer()
    Interface_Static_SetCVal("write.step.schema", "AP203")
    for solid in list_of_solids:
        assert isinstance(solid, (Solid, Compound))
        step_writer.Transfer(solid.topods_shape(), STEPControl_AsIs)
    status = step_writer.Write(str(filename))
    return status == IFSelect_RetDone

save_stl(shape, filename, binary=True)

Saves a tesselated entity as a STL file NOTE: Call Solid.triangulate_all_faces() first

Parameters:

Name Type Description Default
shape [type]

[description]

required
filename [type]

[description]

required
binary bool

[description]. Defaults to True.

True
Source code in src/occwl/io.py
102
103
104
105
106
107
108
109
110
111
112
113
def save_stl(shape, filename, binary=True):
    """Saves a tesselated entity as a STL file
    NOTE: Call Solid.triangulate_all_faces() first

    Args:
        shape ([type]): [description]
        filename ([type]): [description]
        binary (bool, optional): [description]. Defaults to True.
    """
    from OCC.Extend.DataExchange import write_stl_file

    write_stl_file(shape, filename, mode="binary" if binary else "ascii")

save_svg(shape, filename, export_hidden_edges=True, location=(0, 0, 0), direction=(1, 1, 1), color='black', line_width=0.1)

Saves the shape outline as an SVG file

Parameters:

Name Type Description Default
shape Any occwl topology type

Any topological entity

required
filename str

Path to output SVG

required
export_hidden_edges bool

Whether to render hidden edges as dotted lines in the SVG. Defaults to True.

True
location tuple

Location. Defaults to (0, 0, 0).

(0, 0, 0)
direction tuple

Direction. Defaults to (1, 1, 1).

(1, 1, 1)
color str

Color of the paths in SVG. Defaults to "black".

'black'
line_width float

Width of each path. Defaults to 0.1.

0.1
Source code in src/occwl/io.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def save_svg(
    shape,
    filename,
    export_hidden_edges=True,
    location=(0, 0, 0),
    direction=(1, 1, 1),
    color="black",
    line_width=0.1,
):
    """Saves the shape outline as an SVG file

    Args:
        shape (Any occwl topology type): Any topological entity
        filename (str): Path to output SVG
        export_hidden_edges (bool, optional): Whether to render hidden edges as dotted lines in the SVG. Defaults to True.
        location (tuple, optional): Location. Defaults to (0, 0, 0).
        direction (tuple, optional): Direction. Defaults to (1, 1, 1).
        color (str, optional): Color of the paths in SVG. Defaults to "black".
        line_width (float, optional): Width of each path. Defaults to 0.1.
    """
    if isinstance(shape, Solid):
        shape = shape.topods_solid()
    elif isinstance(shape, Face):
        shape = shape.topods_face()
    elif isinstance(shape, Edge):
        shape = shape.topods_edge()
    else:
        raise NotImplementedError
    svg_string = export_shape_to_svg(
        shape,
        filename=filename,
        export_hidden_edges=export_hidden_edges,
        location=gp_Pnt(*location),
        direction=gp_Dir(*direction),
        color=color,
        line_width=line_width,
        margin_left=0,
        margin_top=0,
    )

jupyter_viewer

JupyterViewer

A viewer for models in a Jupyter notebook

Source code in src/occwl/jupyter_viewer.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
class JupyterViewer:
    """
    A viewer for models in a Jupyter notebook
    """

    def __init__(
        self,
        size: Optional[Tuple[int, int]] = (640, 480),
        background_color: Optional[str] ="white"
    ):
        """
        Construct the Viewer

        Args:
        """
        self._renderer = MultiSelectJupyterRenderer(
            size=size, 
            background_color=background_color
        )
        self._renderer.register_select_callback(self._select_callback)
        self._selected_faces = []
        self._selected_edges = []


    def display(
        self, 
        shape, 
        update=False, 
        shape_color=None, 
        render_edges=False,
        edge_color=None, 
        transparency=0.0
    ):
        """
        Display a shape (must be a Solid, Face, or Edge)

        Args:
            shape (Solid, Face, or Edge): Shape to display
            update (bool, optional): Whether to update and repaint. Defaults to False.
            shape_color ([type], optional): Color of the shape.
            edge_color ([type], optional):  Color of the shape's edges.
                                            Can be 'WHITE', 'BLUE', 'RED', 'GREEN', 'YELLOW',
                                            'CYAN', 'BLACK', 'ORANGE' or [r,g,b] 
                                            Defaults to None.
            render_edges (bool): Whether to render edges
            transparency (float, optional): Defaults to 0. (opaque). 0. is fully opaque, 1. is fully transparent.
        """
        shape = shape.topods_shape()

        self._renderer.DisplayShape(
            shape, 
            update=update, 
            shape_color=shape_color,
            render_edges=render_edges,
            edge_color=edge_color,
            transparency=transparency != 0.,
            opacity=1. - transparency
        )

    def display_face_colormap(
        self, 
        solid,
        values_for_faces,
        color_map = "rainbow",
        update=False, 
        render_edges=False,
        edge_color=None,
        transparency=0.
    ):
        """
        Display a solid with the faces colored according to
        some scalar function.

        Args:
            solid (Solid,): Solid to display
            update (bool, optional): Whether to update and repaint. Defaults to False.
            color_map (str): Choose from https://matplotlib.org/stable/tutorials/colors/colormaps.html
            values_for_faces (list, np.array): Array of values, one for each face 
            render_edges (bool): Whether to render edges
            transparency (float, optional): Defaults to 0. (opaque). 0. is fully opaque, 1. is fully transparent.
        """
        if not isinstance(values_for_faces, np.ndarray):
            values_for_faces = np.array(values_for_faces)

        assert values_for_faces.size == solid.num_faces(), "Must have one value for each face"

        norm = Normalize(values_for_faces.min(), values_for_faces.max())
        norm_values_for_faces = norm(values_for_faces)

        color_mapper = get_cmap(color_map)
        face_colors = color_mapper(norm_values_for_faces)[:, :3]

        for face_index, face in enumerate(solid.faces()):
            shape_color=rgb2hex(face_colors[face_index])
            self.display(
                face, 
                update=False, 
                shape_color=shape_color, 
                render_edges=render_edges,
                edge_color=edge_color,
                transparency=transparency
            )

        # Plot the color scale
        ax = plt.subplot()
        color_mapper = get_cmap("rainbow")
        values = np.arange(100)
        values = np.stack([values]*5)
        im = ax.imshow(values, color_mapper)
        plt.tick_params(
            axis='both',       # changes apply to the x-axis
            which='both',      # both major and minor ticks are affected
            bottom=False,      # ticks along the bottom edge are off
            top=False,         # ticks along the top edge are off
            left=False,        # ticks along the left edge are off
            labelbottom=False, # labels along the bottom edge are off
            labelleft=False    # labels along the left edge are off
        ) 
        plt.show()

        if update:
            self.show()

    def display_points(
        self, 
        points, 
        normals=None, 
        point_color="red", 
        point_width=4,
        update=False
    ):
        """
        Display a set of points

        Args:
            points (np.array): Array of points size [ num_points x 3 ] 
        """
        self._renderer.add_points(
            points, 
            vertex_color=point_color, 
            vertex_width=point_width, 
            update=update
        )



    def display_lines(
        self, 
        start_points,
        end_points,
        line_color="blue", 
        line_width=1,
        update=False
    ):
        """
        Display points a set of points

        Args:
            start_points (np.array): Array of start_points size [ num_points x 3 ] 
            end_points (np.array): Array of end_points size [ num_points x 3 ] 
        """
        self._renderer.add_lines(
            start_points,
            end_points,
            line_color=line_color, 
            line_width=line_width, 
            update=update
        )


    def display_unit_vectors(
        self, 
        points,
        directions,
        line_color="blue", 
        line_width=2,
        update=False
    ):
        """
        Display a set of unit vectors

        Args:
            points (np.array): Array of points size [ num_points x 3 ] 
            directions (np.array): Array of directions size [ num_points x 3 ] 
        """
        mins = np.min(points, axis=0)
        maxs = np.max(points, axis=0)
        diag = maxs - mins
        longest = np.max(diag)
        line_length = longest/20
        end_points = points + directions*line_length
        self.display_lines(
                points,
                end_points,
                line_color=line_color, 
                line_width=line_width,
                update=False
            )


    def _select_callback(self, topo_ds_shape):
        """
        Callback called when a selection occurs
        """
        if type(topo_ds_shape) == TopoDS_Edge:
            self._selected_edges.append(Edge(topo_ds_shape))
        elif type(topo_ds_shape) == TopoDS_Face:
            self._selected_faces.append(Face(topo_ds_shape))


    def selected_faces(self):
        """
        Get the selected faces

        Returns:
            List[Face]: List of selected faces
        """
        return self._selected_faces


    def selected_face_indices(self, entity_mapper):
        """
        Get the selected face indices

        Returns:
           np.ndarray(int) : List of indices of selected faces
        """
        selected_face_indices = []
        for f in self._selected_faces:
            selected_face_indices.append(entity_mapper.face_index(f))
        return np.array(selected_face_indices)

    def selected_edges(self):
        """
        Get the selected edges

        Returns:
            List[Face]: List of selected edges
        """
        return self._selected_faces

    def selected_edge_indices(self, entity_mapper):
        """
        Get the selected edge indices

        Returns:
           np.ndarray(int) : List of indices of selected edges
        """
        selected_edge_indices = []
        for e in self._selected_edges:
            selected_edge_indices.append(entity_mapper.edge_index(e))
        return np.array(selected_edge_indices)


    def show(self):
        """
        Show the viewer
        """
        self._renderer.Display()

__init__(size=(640, 480), background_color='white')

Construct the Viewer

Args:

Source code in src/occwl/jupyter_viewer.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
def __init__(
    self,
    size: Optional[Tuple[int, int]] = (640, 480),
    background_color: Optional[str] ="white"
):
    """
    Construct the Viewer

    Args:
    """
    self._renderer = MultiSelectJupyterRenderer(
        size=size, 
        background_color=background_color
    )
    self._renderer.register_select_callback(self._select_callback)
    self._selected_faces = []
    self._selected_edges = []

display(shape, update=False, shape_color=None, render_edges=False, edge_color=None, transparency=0.0)

Display a shape (must be a Solid, Face, or Edge)

Parameters:

Name Type Description Default
shape Solid, Face, or Edge

Shape to display

required
update bool

Whether to update and repaint. Defaults to False.

False
shape_color [type]

Color of the shape.

None
edge_color [type]

Color of the shape's edges. Can be 'WHITE', 'BLUE', 'RED', 'GREEN', 'YELLOW', 'CYAN', 'BLACK', 'ORANGE' or [r,g,b] Defaults to None.

None
render_edges bool

Whether to render edges

False
transparency float

Defaults to 0. (opaque). 0. is fully opaque, 1. is fully transparent.

0.0
Source code in src/occwl/jupyter_viewer.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def display(
    self, 
    shape, 
    update=False, 
    shape_color=None, 
    render_edges=False,
    edge_color=None, 
    transparency=0.0
):
    """
    Display a shape (must be a Solid, Face, or Edge)

    Args:
        shape (Solid, Face, or Edge): Shape to display
        update (bool, optional): Whether to update and repaint. Defaults to False.
        shape_color ([type], optional): Color of the shape.
        edge_color ([type], optional):  Color of the shape's edges.
                                        Can be 'WHITE', 'BLUE', 'RED', 'GREEN', 'YELLOW',
                                        'CYAN', 'BLACK', 'ORANGE' or [r,g,b] 
                                        Defaults to None.
        render_edges (bool): Whether to render edges
        transparency (float, optional): Defaults to 0. (opaque). 0. is fully opaque, 1. is fully transparent.
    """
    shape = shape.topods_shape()

    self._renderer.DisplayShape(
        shape, 
        update=update, 
        shape_color=shape_color,
        render_edges=render_edges,
        edge_color=edge_color,
        transparency=transparency != 0.,
        opacity=1. - transparency
    )

display_face_colormap(solid, values_for_faces, color_map='rainbow', update=False, render_edges=False, edge_color=None, transparency=0.0)

Display a solid with the faces colored according to some scalar function.

Parameters:

Name Type Description Default
solid (Solid,)

Solid to display

required
update bool

Whether to update and repaint. Defaults to False.

False
color_map str

Choose from https://matplotlib.org/stable/tutorials/colors/colormaps.html

'rainbow'
values_for_faces (list, array)

Array of values, one for each face

required
render_edges bool

Whether to render edges

False
transparency float

Defaults to 0. (opaque). 0. is fully opaque, 1. is fully transparent.

0.0
Source code in src/occwl/jupyter_viewer.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
def display_face_colormap(
    self, 
    solid,
    values_for_faces,
    color_map = "rainbow",
    update=False, 
    render_edges=False,
    edge_color=None,
    transparency=0.
):
    """
    Display a solid with the faces colored according to
    some scalar function.

    Args:
        solid (Solid,): Solid to display
        update (bool, optional): Whether to update and repaint. Defaults to False.
        color_map (str): Choose from https://matplotlib.org/stable/tutorials/colors/colormaps.html
        values_for_faces (list, np.array): Array of values, one for each face 
        render_edges (bool): Whether to render edges
        transparency (float, optional): Defaults to 0. (opaque). 0. is fully opaque, 1. is fully transparent.
    """
    if not isinstance(values_for_faces, np.ndarray):
        values_for_faces = np.array(values_for_faces)

    assert values_for_faces.size == solid.num_faces(), "Must have one value for each face"

    norm = Normalize(values_for_faces.min(), values_for_faces.max())
    norm_values_for_faces = norm(values_for_faces)

    color_mapper = get_cmap(color_map)
    face_colors = color_mapper(norm_values_for_faces)[:, :3]

    for face_index, face in enumerate(solid.faces()):
        shape_color=rgb2hex(face_colors[face_index])
        self.display(
            face, 
            update=False, 
            shape_color=shape_color, 
            render_edges=render_edges,
            edge_color=edge_color,
            transparency=transparency
        )

    # Plot the color scale
    ax = plt.subplot()
    color_mapper = get_cmap("rainbow")
    values = np.arange(100)
    values = np.stack([values]*5)
    im = ax.imshow(values, color_mapper)
    plt.tick_params(
        axis='both',       # changes apply to the x-axis
        which='both',      # both major and minor ticks are affected
        bottom=False,      # ticks along the bottom edge are off
        top=False,         # ticks along the top edge are off
        left=False,        # ticks along the left edge are off
        labelbottom=False, # labels along the bottom edge are off
        labelleft=False    # labels along the left edge are off
    ) 
    plt.show()

    if update:
        self.show()

display_lines(start_points, end_points, line_color='blue', line_width=1, update=False)

Display points a set of points

Parameters:

Name Type Description Default
start_points array

Array of start_points size [ num_points x 3 ]

required
end_points array

Array of end_points size [ num_points x 3 ]

required
Source code in src/occwl/jupyter_viewer.py
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
def display_lines(
    self, 
    start_points,
    end_points,
    line_color="blue", 
    line_width=1,
    update=False
):
    """
    Display points a set of points

    Args:
        start_points (np.array): Array of start_points size [ num_points x 3 ] 
        end_points (np.array): Array of end_points size [ num_points x 3 ] 
    """
    self._renderer.add_lines(
        start_points,
        end_points,
        line_color=line_color, 
        line_width=line_width, 
        update=update
    )

display_points(points, normals=None, point_color='red', point_width=4, update=False)

Display a set of points

Parameters:

Name Type Description Default
points array

Array of points size [ num_points x 3 ]

required
Source code in src/occwl/jupyter_viewer.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def display_points(
    self, 
    points, 
    normals=None, 
    point_color="red", 
    point_width=4,
    update=False
):
    """
    Display a set of points

    Args:
        points (np.array): Array of points size [ num_points x 3 ] 
    """
    self._renderer.add_points(
        points, 
        vertex_color=point_color, 
        vertex_width=point_width, 
        update=update
    )

display_unit_vectors(points, directions, line_color='blue', line_width=2, update=False)

Display a set of unit vectors

Parameters:

Name Type Description Default
points array

Array of points size [ num_points x 3 ]

required
directions array

Array of directions size [ num_points x 3 ]

required
Source code in src/occwl/jupyter_viewer.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
def display_unit_vectors(
    self, 
    points,
    directions,
    line_color="blue", 
    line_width=2,
    update=False
):
    """
    Display a set of unit vectors

    Args:
        points (np.array): Array of points size [ num_points x 3 ] 
        directions (np.array): Array of directions size [ num_points x 3 ] 
    """
    mins = np.min(points, axis=0)
    maxs = np.max(points, axis=0)
    diag = maxs - mins
    longest = np.max(diag)
    line_length = longest/20
    end_points = points + directions*line_length
    self.display_lines(
            points,
            end_points,
            line_color=line_color, 
            line_width=line_width,
            update=False
        )

selected_edge_indices(entity_mapper)

Get the selected edge indices

Returns:

Type Description

np.ndarray(int) : List of indices of selected edges

Source code in src/occwl/jupyter_viewer.py
351
352
353
354
355
356
357
358
359
360
361
def selected_edge_indices(self, entity_mapper):
    """
    Get the selected edge indices

    Returns:
       np.ndarray(int) : List of indices of selected edges
    """
    selected_edge_indices = []
    for e in self._selected_edges:
        selected_edge_indices.append(entity_mapper.edge_index(e))
    return np.array(selected_edge_indices)

selected_edges()

Get the selected edges

Returns:

Type Description

List[Face]: List of selected edges

Source code in src/occwl/jupyter_viewer.py
342
343
344
345
346
347
348
349
def selected_edges(self):
    """
    Get the selected edges

    Returns:
        List[Face]: List of selected edges
    """
    return self._selected_faces

selected_face_indices(entity_mapper)

Get the selected face indices

Returns:

Type Description

np.ndarray(int) : List of indices of selected faces

Source code in src/occwl/jupyter_viewer.py
330
331
332
333
334
335
336
337
338
339
340
def selected_face_indices(self, entity_mapper):
    """
    Get the selected face indices

    Returns:
       np.ndarray(int) : List of indices of selected faces
    """
    selected_face_indices = []
    for f in self._selected_faces:
        selected_face_indices.append(entity_mapper.face_index(f))
    return np.array(selected_face_indices)

selected_faces()

Get the selected faces

Returns:

Type Description

List[Face]: List of selected faces

Source code in src/occwl/jupyter_viewer.py
320
321
322
323
324
325
326
327
def selected_faces(self):
    """
    Get the selected faces

    Returns:
        List[Face]: List of selected faces
    """
    return self._selected_faces

show()

Show the viewer

Source code in src/occwl/jupyter_viewer.py
364
365
366
367
368
def show(self):
    """
    Show the viewer
    """
    self._renderer.Display()

MultiSelectJupyterRenderer

Bases: JupyterRenderer

This class derived from JupyterRenderer allows more than one shape to be selected at a time.

Source code in src/occwl/jupyter_viewer.py
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
class MultiSelectJupyterRenderer(JupyterRenderer):
    """
    This class derived from JupyterRenderer allows more than 
    one shape to be selected at a time.
    """

    def __init__(self, *args, **kwargs):
        super(MultiSelectJupyterRenderer, self).__init__(*args, **kwargs)

    def click(self, value):
        """ called whenever a shape  or edge is clicked
        """
        try:
            obj = value.owner.object
            self.clicked_obj = obj
            if self._current_mesh_selection != obj:
                if obj is not None:
                    self._shp_properties_button.disabled = False
                    self._toggle_shp_visibility_button.disabled = False
                    self._remove_shp_button.disabled = False
                    id_clicked = obj.name  # the mesh id clicked
                    self._current_mesh_selection = obj
                    self._current_selection_material_color = obj.material.color
                    obj.material.color = self._selection_color
                    # selected part becomes transparent
                    obj.material.transparent = True
                    obj.material.opacity = 0.5
                    # get the shape from this mesh id
                    selected_shape = self._shapes[id_clicked]
                    self._current_shape_selection = selected_shape
                # then execute calbacks
                for callback in self._select_callbacks:
                    callback(self._current_shape_selection)
        except Exception as e:
            self.html.value = f"{str(e)}"

    def add_points(self, points_array, vertex_color="red", vertex_width=5, update=False):
        """ 
        Args:
            points_array (np.array): A numpy array of points [ num_points x 3 ]
            vertex_color (str): color for the points
            vertex_width (int): vertex width in pixels
            update (bool): Update the display
        """
        point_cloud_id = "%s" % uuid.uuid4().hex
        points_array = np.array(points_array, dtype=np.float32)
        attributes = {"position": BufferAttribute(points_array, normalized=False)}
        mat = PointsMaterial(color=vertex_color, sizeAttenuation=True, size=vertex_width)
        geom = BufferGeometry(attributes=attributes)
        points = Points(geometry=geom, material=mat, name=point_cloud_id)
        self._displayed_pickable_objects.add(points)

        if update:
            self.Display()

    def add_lines(self, start_arr, end_arr, line_color="blue", line_width=2, update=False):
        """ 
        Args:
            start_arr (np.array): A numpy array of points [ num_points x 3 ]
            end_arr (np.array): A numpy array of points [ num_points x 3 ]
            line_color (str): color for the points
            vertex_width (int): vertex width in pixels
            update (bool): Update the display
        """
        line_cloud_id = "%s" % uuid.uuid4().hex
        points = np.stack([start_arr, end_arr], axis=1)       
        points = np.array(points, dtype=np.float32)    
        geom = LineSegmentsGeometry(positions=points)
        mat = LineMaterial(linewidth=line_width, color=line_color)
        lines = LineSegments2(geom, mat, name=line_cloud_id)
        self._displayed_pickable_objects.add(lines)

        if update:
            self.Display()

add_lines(start_arr, end_arr, line_color='blue', line_width=2, update=False)

Parameters:

Name Type Description Default
start_arr array

A numpy array of points [ num_points x 3 ]

required
end_arr array

A numpy array of points [ num_points x 3 ]

required
line_color str

color for the points

'blue'
vertex_width int

vertex width in pixels

required
update bool

Update the display

False
Source code in src/occwl/jupyter_viewer.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def add_lines(self, start_arr, end_arr, line_color="blue", line_width=2, update=False):
    """ 
    Args:
        start_arr (np.array): A numpy array of points [ num_points x 3 ]
        end_arr (np.array): A numpy array of points [ num_points x 3 ]
        line_color (str): color for the points
        vertex_width (int): vertex width in pixels
        update (bool): Update the display
    """
    line_cloud_id = "%s" % uuid.uuid4().hex
    points = np.stack([start_arr, end_arr], axis=1)       
    points = np.array(points, dtype=np.float32)    
    geom = LineSegmentsGeometry(positions=points)
    mat = LineMaterial(linewidth=line_width, color=line_color)
    lines = LineSegments2(geom, mat, name=line_cloud_id)
    self._displayed_pickable_objects.add(lines)

    if update:
        self.Display()

add_points(points_array, vertex_color='red', vertex_width=5, update=False)

Parameters:

Name Type Description Default
points_array array

A numpy array of points [ num_points x 3 ]

required
vertex_color str

color for the points

'red'
vertex_width int

vertex width in pixels

5
update bool

Update the display

False
Source code in src/occwl/jupyter_viewer.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def add_points(self, points_array, vertex_color="red", vertex_width=5, update=False):
    """ 
    Args:
        points_array (np.array): A numpy array of points [ num_points x 3 ]
        vertex_color (str): color for the points
        vertex_width (int): vertex width in pixels
        update (bool): Update the display
    """
    point_cloud_id = "%s" % uuid.uuid4().hex
    points_array = np.array(points_array, dtype=np.float32)
    attributes = {"position": BufferAttribute(points_array, normalized=False)}
    mat = PointsMaterial(color=vertex_color, sizeAttenuation=True, size=vertex_width)
    geom = BufferGeometry(attributes=attributes)
    points = Points(geometry=geom, material=mat, name=point_cloud_id)
    self._displayed_pickable_objects.add(points)

    if update:
        self.Display()

click(value)

called whenever a shape or edge is clicked

Source code in src/occwl/jupyter_viewer.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def click(self, value):
    """ called whenever a shape  or edge is clicked
    """
    try:
        obj = value.owner.object
        self.clicked_obj = obj
        if self._current_mesh_selection != obj:
            if obj is not None:
                self._shp_properties_button.disabled = False
                self._toggle_shp_visibility_button.disabled = False
                self._remove_shp_button.disabled = False
                id_clicked = obj.name  # the mesh id clicked
                self._current_mesh_selection = obj
                self._current_selection_material_color = obj.material.color
                obj.material.color = self._selection_color
                # selected part becomes transparent
                obj.material.transparent = True
                obj.material.opacity = 0.5
                # get the shape from this mesh id
                selected_shape = self._shapes[id_clicked]
                self._current_shape_selection = selected_shape
            # then execute calbacks
            for callback in self._select_callbacks:
                callback(self._current_shape_selection)
    except Exception as e:
        self.html.value = f"{str(e)}"

shape

Base class for faces, edges and vertices

ClosestPointData

A class to record information about the closest point on a shape to some datum point

Source code in src/occwl/shape.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class ClosestPointData:
    """
    A class to record information about the closest point on a shape
    to some datum point
    """

    def __init__(self, dist_shape_shape):
        """
        Args:
            dist_shape_shape (BRepExtrema_DistShapeShape): OCC class for distance to a shape
        """
        assert dist_shape_shape.IsDone()
        self.closest_entity = dist_shape_shape.SupportOnShape2(1)
        self.closest_point = geom_utils.gp_to_numpy(dist_shape_shape.PointOnShape2(1))
        self.distance = dist_shape_shape.Value()

__init__(dist_shape_shape)

Parameters:

Name Type Description Default
dist_shape_shape BRepExtrema_DistShapeShape

OCC class for distance to a shape

required
Source code in src/occwl/shape.py
40
41
42
43
44
45
46
47
48
def __init__(self, dist_shape_shape):
    """
    Args:
        dist_shape_shape (BRepExtrema_DistShapeShape): OCC class for distance to a shape
    """
    assert dist_shape_shape.IsDone()
    self.closest_entity = dist_shape_shape.SupportOnShape2(1)
    self.closest_point = geom_utils.gp_to_numpy(dist_shape_shape.PointOnShape2(1))
    self.distance = dist_shape_shape.Value()

Shape

Source code in src/occwl/shape.py
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
class Shape:
    def __init__(self, topods_shape):
        """
        Construct the Shape (this class is not meant to be instantiated directly)

        Args:
            topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): OCC TopoDS_* as provided by the derived class

        Raises:
            Exception: [description]
        """
        if type(self) == Shape:
            raise Exception("Shape must be subclassed and instantiated.")
        assert isinstance(
            topods_shape,
            (
                TopoDS_Vertex,
                TopoDS_Edge,
                TopoDS_Face,
                TopoDS_Wire,
                TopoDS_Shell,
                TopoDS_Solid,
                TopoDS_Compound,
                TopoDS_CompSolid,
            ),
        )
        self._shape = topods_shape
        self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)

    def topods_shape(self):
        """
        Get the underlying OCC shape

        Returns:
            OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*
        """
        return self._shape

    @staticmethod
    def occwl_shape(topods_shape):
        """
        Static method to create an occwl shape of the appropriate 
        class from the given topods_shape
        Args:
            topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

        Returns:
            One of
                occwl.compound.Compound
                occwl.solid.Solid
                occwl.face.Face
                occwl.edge.Edge
                occwl.vertex.Vertex
                occwl.wire.Wire
                occwl.shell.Shell
        Raises:
            Exception: [description]
        """
        from occwl.compound import Compound
        from occwl.solid import Solid
        from occwl.face import Face
        from occwl.edge import Edge
        from occwl.vertex import Vertex
        from occwl.wire import Wire
        from occwl.shell import Shell

        if isinstance(topods_shape, TopoDS_Vertex):
            return Vertex(topods_shape)
        if isinstance(topods_shape, TopoDS_Edge):
            return Edge(topods_shape)
        if isinstance(topods_shape, TopoDS_Face):
            return Face(topods_shape)
        if isinstance(topods_shape, TopoDS_Wire):
            return Wire(topods_shape)
        if isinstance(topods_shape, TopoDS_Shell):
            return Shell(topods_shape)
        if isinstance(topods_shape, TopoDS_Solid):
            return Solid(topods_shape)
        if isinstance(topods_shape, (TopoDS_Compound, TopoDS_CompSolid)):
            return Compound(topods_shape)
        raise Exception(
            "Shape must be one of TopoDS_Vertex, TopoDS_Edge, TopoDS_Face, TopoDS_Shell, TopoDS_Solid, TopoDS_Compound, TopoDS_CompSolid"
        )

    def __hash__(self):
        """
        Hash for the shape

        Returns:
            int: Hash value
        """
        return self.topods_shape().__hash__()

    def __eq__(self, other):
        """
        Equality check for the shape

        NOTE: This function only checks if the shape is the same.
        It doesn't check the edge orienation for example, so 

        edge1 == edge2

        does not necessarily mean 

        edge1.reversed() == edge2.reversed()
        """
        return self.topods_shape().__hash__() == other.topods_shape().__hash__()

    def save_to_occ_native(
            self, 
            filename, 
            verbosity=False,
            with_triangles=False,
            with_normals=False,
            format_version=None
        ):
        """
        Save this shape into a native OCC binary .brep file.

        Note:  Saving to and loading from the native file format 
               is between one and two orders of magnitude faster 
               than loading from STEP, so it is recommended for 
               large scale data processing

        Args:
            filename (str or pathlib.Path): .brep filename
            with_triangles (bool): Whether to save triangle data cached in the shape.
            with_normals (bool): Whether to save vertex normals cached in the shape
            format_version (int):  Use None to save to the latest version
                1 - first revision
                2 - added storing of CurveOnSurface UV Points
                3 - [OCCT 7.6] added storing of per-vertex normal information
                               and dropped storing of CurveOnSurface UV Points
        """
        self.save_shapes_to_occ_native(
            filename, 
            [ self ],
            with_triangles=with_triangles,
            with_normals=with_normals,
            format_version=format_version
        )

    @staticmethod
    def save_shapes_to_occ_native(
            filename, 
            shapes,
            with_triangles=False,
            with_normals=False,
            format_version=None
        ):
        """
        Save this shape into a native OCC binary .brep file.

        Note:  Saving to and loading from the native file format 
                is between one and two orders of magnitude faster 
                than loading from STEP, so it is recommended for 
                large scale data processing

        Args:
            filename (str or pathlib.Path): .brep filename

            with_triangles (bool): Whether to save triangle data cached in the shape.
            with_normals (bool): Whether to save vertex normals cached in the shape
            format_version (int):  Use None to save to the latest version
                1 - first revision
                2 - added storing of CurveOnSurface UV Points
                3 - [OCCT 7.6] added storing of per-vertex normal information
                               and dropped storing of CurveOnSurface UV Points
        """
        new_api = False
        shapes_set = BRepTools_ShapeSet(with_triangles)
        # shapes_set.SetWithNormals(with_normals) # Not in OCC 7.5.0

        for shp in shapes:
            shapes_set.Add(shp.topods_shape())
        if format_version is not None:
            shapes_set.SetFormatNb(format_version)


        with open(filename, "w") as fp:
            s = shapes_set.WriteToString()
            fp.write(s)



    def reversed(self):
        """
        Whether this shape is reversed.

        - For an edge this is whether the edge is reversed with respect to the curve geometry
        - For a face this is whether the face is reversed with respect to the surface geometry
        - For a vertex this is whether the vertex is at the upper or lower parameter value on the
          edges curve

        Returns:
            bool: If rational
        """
        return self.topods_shape().Orientation() == TopAbs_REVERSED


    def find_closest_point_data(self, datum):
        """
        Find the information about the closest point on this shape

        Args:
            datum (np.ndarray): 3D Point

        Returns:
            ClosestPointData: Data about the closest point on this shape
            None: if error
        """
        # Folowing https://dev.opencascade.org/content/how-retrieve-nearest-face-shape-given-gppnt
        # Create a vertex from the point
        occ_point = geom_utils.numpy_to_gp(datum)
        vertex_maker = BRepBuilderAPI_MakeVertex(occ_point)
        vertex = vertex_maker.Shape()
        dist_shape_shape = BRepExtrema_DistShapeShape(
            vertex, self.topods_shape(), Extrema_ExtFlag_MIN
        )
        ok = dist_shape_shape.Perform()
        if not ok:
            return None

        return ClosestPointData(dist_shape_shape)

    def translate(self, offset):
        """
        Translate the shape by an offset vector

        Args:
            offset (np.ndarray): Offset vector
        """
        self._shape = translate_shp(self._shape, geom_utils.numpy_to_gp_vec(offset))

    def rotate_axis_angle(
        self, axis, angle_radians, origin=np.zeros(3, dtype=np.float32)
    ):
        """
        Rotate the shape about the given axis by the given angle in radians

        Args:
            axis (np.ndarray): Rotation axis
            angle_radians (float): Angle in radians
        """
        self._shape = rotate_shape(
            self._shape,
            gp_Ax1(geom_utils.numpy_to_gp(origin), geom_utils.numpy_to_gp_dir(axis)),
            angle_radians,
            unite="rad",
        )

    def rotate_euler_angles(self, angles_xyz_radians):
        """
        Rotate the shape by the given Euler angles in radians

        Args:
            angle_xyz_radians (np.ndarray): 3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians
        """
        self._shape = rotate_shp_3_axis(
            self._shape,
            angles_xyz_radians[0],
            angles_xyz_radians[1],
            angles_xyz_radians[2],
            unity="rad",
        )

    def scale(self, scale_vector):
        """
        Scale the shape by the given 3D vector

        Args:
            scale_vector (np.ndarray): 3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively
        """
        self._shape = scale_shape(
            self._shape, scale_vector[0], scale_vector[1], scale_vector[2]
        )

    def valid(self, return_analyzer=False):
        """
        Check if the shape is valid

        Args:
            return_analyzer (bool): Whether to return the BRepCheck_Analyzer object for more inspection

        Returns:
            bool: Whether the shape is valid
            BRepCheck_Analyzer [optional]: if return_analyzer is True
        """
        analyzer = BRepCheck_Analyzer(self.topods_shape())
        if return_analyzer:
            return analyzer.IsValid(), analyzer
        return analyzer.IsValid()



    def set_transform_to_identity(self):
        """
        When an assembly is loaded from a STEP file
        the solids will be transformed relative to
        their local coordinate system.   i.e. they
        are placed in the assembly root components 
        coordinate system.

        When working with individual bodies you often
        want them to be axis aligned, in which case 
        you want to remove the assembly transform.
        This function removes it for you.

        If however you want to bake the transform
        into the bodies and suppress the asserts 
        from parts of occwl which don't cope with
        transforms then use the transform() function
        below with copy=True
        """
        identity = TopLoc_Location()
        self.topods_shape().Location(identity)
        self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)
        self.convert_geometric_identity_transforms_to_identity()


    def convert_geometric_identity_transforms_to_identity(self):
        """
        Open Cascade models sometimes contain transforms which
        are "geometrically" identify transforms, but the identity
        flag is not set.

        This function checks each transform and sets the flag if 
        the appropriate.
        """
        identity = TopLoc_Location()
        if geom_utils.is_geometric_identity(
            self.topods_shape().Location().Transformation()
        ):
            self.topods_shape().Location(identity)
            self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)

        for face in self._top_exp.faces():
            if geom_utils.is_geometric_identity(face.Location().Transformation()):
                face.Location(identity)

        for edge in self._top_exp.edges():
            if geom_utils.is_geometric_identity(edge.Location().Transformation()):
                edge.Location(identity)

        for vertex in self._top_exp.vertices():
            if geom_utils.is_geometric_identity(vertex.Location().Transformation()):
                vertex.Location(identity)


    def transform(self, a: np.ndarray, copy=True):
        """
        Apply the given 3x4 transform matrix to the solid.

        Args: a (nd.array) - Homogeneous transform matrix
                             The transform that will be applied is

                             x' =  a[0,0]*x + a[0,1]*y + a[0,2]*z + a[0, 3]
                             y' =  a[1,0]*x + a[1,1]*y + a[1,2]*z + a[1, 3]
                             z' =  a[2,0]*x + a[2,1]*y + a[2,2]*z + a[2, 3]

             copy (bool)    True - Copy entities and apply the transform to
                                   the underlying geometry
                            False - Apply the transform to the topods Locator
                                    if possible 
        """
        assert (a.shape == (3, 4)), "Transform matrix must be 3x4"
        a = a.astype(np.float64)

        # Create an identity transform
        trsf = gp_Trsf()

        # If the matrix is an identity matrix then
        # we don't want to set the values as this
        # would give us a geometric identity without
        # the identity flag set
        if not np.allclose(a, np.eye(3, 4)):
            trsf.SetValues(
                a[0,0], a[0,1], a[0,2], a[0, 3],
                a[1,0], a[1,1], a[1,2], a[1, 3],
                a[2,0], a[2,1], a[2,2], a[2, 3]
            )
        return self._apply_transform(trsf, copy=copy)

    def _apply_transform(self, trsf_to_apply, copy=True):
        """
        Apply the given transform to this Shape
        """
        apply_transform = BRepBuilderAPI_Transform(trsf_to_apply)
        apply_transform.Perform(self.topods_shape(), copy)
        transformed_shape = apply_transform.ModifiedShape(self.topods_shape())

        return type(self)(transformed_shape)

__eq__(other)

Equality check for the shape

NOTE: This function only checks if the shape is the same. It doesn't check the edge orienation for example, so

edge1 == edge2

does not necessarily mean

edge1.reversed() == edge2.reversed()

Source code in src/occwl/shape.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def __eq__(self, other):
    """
    Equality check for the shape

    NOTE: This function only checks if the shape is the same.
    It doesn't check the edge orienation for example, so 

    edge1 == edge2

    does not necessarily mean 

    edge1.reversed() == edge2.reversed()
    """
    return self.topods_shape().__hash__() == other.topods_shape().__hash__()

__hash__()

Hash for the shape

Returns:

Name Type Description
int

Hash value

Source code in src/occwl/shape.py
135
136
137
138
139
140
141
142
def __hash__(self):
    """
    Hash for the shape

    Returns:
        int: Hash value
    """
    return self.topods_shape().__hash__()

__init__(topods_shape)

Construct the Shape (this class is not meant to be instantiated directly)

Parameters:

Name Type Description Default
topods_shape TopoDS_Vertex / Edge / Face / Wire / Shell / Solid

OCC TopoDS_* as provided by the derived class

required

Raises:

Type Description
Exception

[description]

Source code in src/occwl/shape.py
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def __init__(self, topods_shape):
    """
    Construct the Shape (this class is not meant to be instantiated directly)

    Args:
        topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): OCC TopoDS_* as provided by the derived class

    Raises:
        Exception: [description]
    """
    if type(self) == Shape:
        raise Exception("Shape must be subclassed and instantiated.")
    assert isinstance(
        topods_shape,
        (
            TopoDS_Vertex,
            TopoDS_Edge,
            TopoDS_Face,
            TopoDS_Wire,
            TopoDS_Shell,
            TopoDS_Solid,
            TopoDS_Compound,
            TopoDS_CompSolid,
        ),
    )
    self._shape = topods_shape
    self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)

convert_geometric_identity_transforms_to_identity()

Open Cascade models sometimes contain transforms which are "geometrically" identify transforms, but the identity flag is not set.

This function checks each transform and sets the flag if the appropriate.

Source code in src/occwl/shape.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def convert_geometric_identity_transforms_to_identity(self):
    """
    Open Cascade models sometimes contain transforms which
    are "geometrically" identify transforms, but the identity
    flag is not set.

    This function checks each transform and sets the flag if 
    the appropriate.
    """
    identity = TopLoc_Location()
    if geom_utils.is_geometric_identity(
        self.topods_shape().Location().Transformation()
    ):
        self.topods_shape().Location(identity)
        self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)

    for face in self._top_exp.faces():
        if geom_utils.is_geometric_identity(face.Location().Transformation()):
            face.Location(identity)

    for edge in self._top_exp.edges():
        if geom_utils.is_geometric_identity(edge.Location().Transformation()):
            edge.Location(identity)

    for vertex in self._top_exp.vertices():
        if geom_utils.is_geometric_identity(vertex.Location().Transformation()):
            vertex.Location(identity)

find_closest_point_data(datum)

Find the information about the closest point on this shape

Parameters:

Name Type Description Default
datum ndarray

3D Point

required

Returns:

Name Type Description
ClosestPointData

Data about the closest point on this shape

None

if error

Source code in src/occwl/shape.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
def find_closest_point_data(self, datum):
    """
    Find the information about the closest point on this shape

    Args:
        datum (np.ndarray): 3D Point

    Returns:
        ClosestPointData: Data about the closest point on this shape
        None: if error
    """
    # Folowing https://dev.opencascade.org/content/how-retrieve-nearest-face-shape-given-gppnt
    # Create a vertex from the point
    occ_point = geom_utils.numpy_to_gp(datum)
    vertex_maker = BRepBuilderAPI_MakeVertex(occ_point)
    vertex = vertex_maker.Shape()
    dist_shape_shape = BRepExtrema_DistShapeShape(
        vertex, self.topods_shape(), Extrema_ExtFlag_MIN
    )
    ok = dist_shape_shape.Perform()
    if not ok:
        return None

    return ClosestPointData(dist_shape_shape)

occwl_shape(topods_shape) staticmethod

Static method to create an occwl shape of the appropriate class from the given topods_shape Args: topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

Returns:

Type Description

One of occwl.compound.Compound occwl.solid.Solid occwl.face.Face occwl.edge.Edge occwl.vertex.Vertex occwl.wire.Wire occwl.shell.Shell

Raises: Exception: [description]

Source code in src/occwl/shape.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@staticmethod
def occwl_shape(topods_shape):
    """
    Static method to create an occwl shape of the appropriate 
    class from the given topods_shape
    Args:
        topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

    Returns:
        One of
            occwl.compound.Compound
            occwl.solid.Solid
            occwl.face.Face
            occwl.edge.Edge
            occwl.vertex.Vertex
            occwl.wire.Wire
            occwl.shell.Shell
    Raises:
        Exception: [description]
    """
    from occwl.compound import Compound
    from occwl.solid import Solid
    from occwl.face import Face
    from occwl.edge import Edge
    from occwl.vertex import Vertex
    from occwl.wire import Wire
    from occwl.shell import Shell

    if isinstance(topods_shape, TopoDS_Vertex):
        return Vertex(topods_shape)
    if isinstance(topods_shape, TopoDS_Edge):
        return Edge(topods_shape)
    if isinstance(topods_shape, TopoDS_Face):
        return Face(topods_shape)
    if isinstance(topods_shape, TopoDS_Wire):
        return Wire(topods_shape)
    if isinstance(topods_shape, TopoDS_Shell):
        return Shell(topods_shape)
    if isinstance(topods_shape, TopoDS_Solid):
        return Solid(topods_shape)
    if isinstance(topods_shape, (TopoDS_Compound, TopoDS_CompSolid)):
        return Compound(topods_shape)
    raise Exception(
        "Shape must be one of TopoDS_Vertex, TopoDS_Edge, TopoDS_Face, TopoDS_Shell, TopoDS_Solid, TopoDS_Compound, TopoDS_CompSolid"
    )

reversed()

Whether this shape is reversed.

  • For an edge this is whether the edge is reversed with respect to the curve geometry
  • For a face this is whether the face is reversed with respect to the surface geometry
  • For a vertex this is whether the vertex is at the upper or lower parameter value on the edges curve

Returns:

Name Type Description
bool

If rational

Source code in src/occwl/shape.py
236
237
238
239
240
241
242
243
244
245
246
247
248
def reversed(self):
    """
    Whether this shape is reversed.

    - For an edge this is whether the edge is reversed with respect to the curve geometry
    - For a face this is whether the face is reversed with respect to the surface geometry
    - For a vertex this is whether the vertex is at the upper or lower parameter value on the
      edges curve

    Returns:
        bool: If rational
    """
    return self.topods_shape().Orientation() == TopAbs_REVERSED

rotate_axis_angle(axis, angle_radians, origin=np.zeros(3, dtype=(np.float32)))

Rotate the shape about the given axis by the given angle in radians

Parameters:

Name Type Description Default
axis ndarray

Rotation axis

required
angle_radians float

Angle in radians

required
Source code in src/occwl/shape.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
def rotate_axis_angle(
    self, axis, angle_radians, origin=np.zeros(3, dtype=np.float32)
):
    """
    Rotate the shape about the given axis by the given angle in radians

    Args:
        axis (np.ndarray): Rotation axis
        angle_radians (float): Angle in radians
    """
    self._shape = rotate_shape(
        self._shape,
        gp_Ax1(geom_utils.numpy_to_gp(origin), geom_utils.numpy_to_gp_dir(axis)),
        angle_radians,
        unite="rad",
    )

rotate_euler_angles(angles_xyz_radians)

Rotate the shape by the given Euler angles in radians

Parameters:

Name Type Description Default
angle_xyz_radians ndarray

3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians

required
Source code in src/occwl/shape.py
302
303
304
305
306
307
308
309
310
311
312
313
314
315
def rotate_euler_angles(self, angles_xyz_radians):
    """
    Rotate the shape by the given Euler angles in radians

    Args:
        angle_xyz_radians (np.ndarray): 3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians
    """
    self._shape = rotate_shp_3_axis(
        self._shape,
        angles_xyz_radians[0],
        angles_xyz_radians[1],
        angles_xyz_radians[2],
        unity="rad",
    )

save_shapes_to_occ_native(filename, shapes, with_triangles=False, with_normals=False, format_version=None) staticmethod

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
@staticmethod
def save_shapes_to_occ_native(
        filename, 
        shapes,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
            is between one and two orders of magnitude faster 
            than loading from STEP, so it is recommended for 
            large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename

        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    new_api = False
    shapes_set = BRepTools_ShapeSet(with_triangles)
    # shapes_set.SetWithNormals(with_normals) # Not in OCC 7.5.0

    for shp in shapes:
        shapes_set.Add(shp.topods_shape())
    if format_version is not None:
        shapes_set.SetFormatNb(format_version)


    with open(filename, "w") as fp:
        s = shapes_set.WriteToString()
        fp.write(s)

save_to_occ_native(filename, verbosity=False, with_triangles=False, with_normals=False, format_version=None)

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def save_to_occ_native(
        self, 
        filename, 
        verbosity=False,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
           is between one and two orders of magnitude faster 
           than loading from STEP, so it is recommended for 
           large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename
        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    self.save_shapes_to_occ_native(
        filename, 
        [ self ],
        with_triangles=with_triangles,
        with_normals=with_normals,
        format_version=format_version
    )

scale(scale_vector)

Scale the shape by the given 3D vector

Parameters:

Name Type Description Default
scale_vector ndarray

3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively

required
Source code in src/occwl/shape.py
317
318
319
320
321
322
323
324
325
326
def scale(self, scale_vector):
    """
    Scale the shape by the given 3D vector

    Args:
        scale_vector (np.ndarray): 3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively
    """
    self._shape = scale_shape(
        self._shape, scale_vector[0], scale_vector[1], scale_vector[2]
    )

set_transform_to_identity()

When an assembly is loaded from a STEP file the solids will be transformed relative to their local coordinate system. i.e. they are placed in the assembly root components coordinate system.

When working with individual bodies you often want them to be axis aligned, in which case you want to remove the assembly transform. This function removes it for you.

If however you want to bake the transform into the bodies and suppress the asserts from parts of occwl which don't cope with transforms then use the transform() function below with copy=True

Source code in src/occwl/shape.py
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def set_transform_to_identity(self):
    """
    When an assembly is loaded from a STEP file
    the solids will be transformed relative to
    their local coordinate system.   i.e. they
    are placed in the assembly root components 
    coordinate system.

    When working with individual bodies you often
    want them to be axis aligned, in which case 
    you want to remove the assembly transform.
    This function removes it for you.

    If however you want to bake the transform
    into the bodies and suppress the asserts 
    from parts of occwl which don't cope with
    transforms then use the transform() function
    below with copy=True
    """
    identity = TopLoc_Location()
    self.topods_shape().Location(identity)
    self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)
    self.convert_geometric_identity_transforms_to_identity()

topods_shape()

Get the underlying OCC shape

Returns:

Type Description

OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*

Source code in src/occwl/shape.py
80
81
82
83
84
85
86
87
def topods_shape(self):
    """
    Get the underlying OCC shape

    Returns:
        OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*
    """
    return self._shape

transform(a, copy=True)

Apply the given 3x4 transform matrix to the solid.

 copy (bool)    True - Copy entities and apply the transform to
                       the underlying geometry
                False - Apply the transform to the topods Locator
                        if possible
Source code in src/occwl/shape.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
def transform(self, a: np.ndarray, copy=True):
    """
    Apply the given 3x4 transform matrix to the solid.

    Args: a (nd.array) - Homogeneous transform matrix
                         The transform that will be applied is

                         x' =  a[0,0]*x + a[0,1]*y + a[0,2]*z + a[0, 3]
                         y' =  a[1,0]*x + a[1,1]*y + a[1,2]*z + a[1, 3]
                         z' =  a[2,0]*x + a[2,1]*y + a[2,2]*z + a[2, 3]

         copy (bool)    True - Copy entities and apply the transform to
                               the underlying geometry
                        False - Apply the transform to the topods Locator
                                if possible 
    """
    assert (a.shape == (3, 4)), "Transform matrix must be 3x4"
    a = a.astype(np.float64)

    # Create an identity transform
    trsf = gp_Trsf()

    # If the matrix is an identity matrix then
    # we don't want to set the values as this
    # would give us a geometric identity without
    # the identity flag set
    if not np.allclose(a, np.eye(3, 4)):
        trsf.SetValues(
            a[0,0], a[0,1], a[0,2], a[0, 3],
            a[1,0], a[1,1], a[1,2], a[1, 3],
            a[2,0], a[2,1], a[2,2], a[2, 3]
        )
    return self._apply_transform(trsf, copy=copy)

translate(offset)

Translate the shape by an offset vector

Parameters:

Name Type Description Default
offset ndarray

Offset vector

required
Source code in src/occwl/shape.py
276
277
278
279
280
281
282
283
def translate(self, offset):
    """
    Translate the shape by an offset vector

    Args:
        offset (np.ndarray): Offset vector
    """
    self._shape = translate_shp(self._shape, geom_utils.numpy_to_gp_vec(offset))

valid(return_analyzer=False)

Check if the shape is valid

Parameters:

Name Type Description Default
return_analyzer bool

Whether to return the BRepCheck_Analyzer object for more inspection

False

Returns:

Name Type Description
bool

Whether the shape is valid

BRepCheck_Analyzer [optional]: if return_analyzer is True

Source code in src/occwl/shape.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def valid(self, return_analyzer=False):
    """
    Check if the shape is valid

    Args:
        return_analyzer (bool): Whether to return the BRepCheck_Analyzer object for more inspection

    Returns:
        bool: Whether the shape is valid
        BRepCheck_Analyzer [optional]: if return_analyzer is True
    """
    analyzer = BRepCheck_Analyzer(self.topods_shape())
    if return_analyzer:
        return analyzer.IsValid(), analyzer
    return analyzer.IsValid()

shell

Shell

Bases: BottomUpFaceIterator, BottomUpEdgeIterator, BoundingBoxMixin, VertexContainerMixin, EdgeContainerMixin, WireContainerMixin, FaceContainerMixin, SurfacePropertiesMixin, TriangulatorMixin, Shape

A shell is a sewed set of faces.

Source code in src/occwl/shell.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Shell(BottomUpFaceIterator, BottomUpEdgeIterator,
    BoundingBoxMixin,  VertexContainerMixin, EdgeContainerMixin, WireContainerMixin, FaceContainerMixin,
    SurfacePropertiesMixin, TriangulatorMixin, Shape):
    """
    A shell is a sewed set of faces.
    """
    def __init__(self, shape):
        assert isinstance(shape, TopoDS_Shell)
        super().__init__(shape)

    @staticmethod
    def make_by_sewing_faces(faces):
        """
        Make a shell by sewing a set of faces with overlapping edges

        Args:
            faces (List[occwl.face.Face]): List of faces

        Returns:
            Shell or None: Sewed shell or None if the output was not a Shell
        """
        sew = BRepBuilderAPI_Sewing()
        for f in faces:
            assert isinstance(f, Face)
            sew.Add(f.topods_shape())
        sew.Perform()
        sewed_shape = sew.SewedShape()
        if isinstance(sewed_shape, TopoDS_Shell):
            return Shell(sewed_shape)
        return None

__eq__(other)

Equality check for the shape

NOTE: This function only checks if the shape is the same. It doesn't check the edge orienation for example, so

edge1 == edge2

does not necessarily mean

edge1.reversed() == edge2.reversed()

Source code in src/occwl/shape.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def __eq__(self, other):
    """
    Equality check for the shape

    NOTE: This function only checks if the shape is the same.
    It doesn't check the edge orienation for example, so 

    edge1 == edge2

    does not necessarily mean 

    edge1.reversed() == edge2.reversed()
    """
    return self.topods_shape().__hash__() == other.topods_shape().__hash__()

__hash__()

Hash for the shape

Returns:

Name Type Description
int

Hash value

Source code in src/occwl/shape.py
135
136
137
138
139
140
141
142
def __hash__(self):
    """
    Hash for the shape

    Returns:
        int: Hash value
    """
    return self.topods_shape().__hash__()

area()

Compute the area of the Shape

Returns:

Name Type Description
float

Area

Source code in src/occwl/base.py
493
494
495
496
497
498
499
500
501
502
def area(self):
    """
    Compute the area of the Shape

    Returns:
        float: Area
    """
    geometry_properties = GProp_GProps()
    brepgprop_SurfaceProperties(self.topods_shape(), geometry_properties)
    return geometry_properties.Mass()

box()

Get a quick bounding box of the Shape

Returns:

Name Type Description
Box

Bounding box

Source code in src/occwl/base.py
565
566
567
568
569
570
571
572
573
574
575
def box(self):
    """
    Get a quick bounding box of the Shape

    Returns:
        Box: Bounding box
    """
    from occwl.geometry import geom_utils
    b = Bnd_Box()
    brepbndlib_Add(self.topods_shape(), b)
    return geom_utils.box_to_geometry(b)

convert_geometric_identity_transforms_to_identity()

Open Cascade models sometimes contain transforms which are "geometrically" identify transforms, but the identity flag is not set.

This function checks each transform and sets the flag if the appropriate.

Source code in src/occwl/shape.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def convert_geometric_identity_transforms_to_identity(self):
    """
    Open Cascade models sometimes contain transforms which
    are "geometrically" identify transforms, but the identity
    flag is not set.

    This function checks each transform and sets the flag if 
    the appropriate.
    """
    identity = TopLoc_Location()
    if geom_utils.is_geometric_identity(
        self.topods_shape().Location().Transformation()
    ):
        self.topods_shape().Location(identity)
        self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)

    for face in self._top_exp.faces():
        if geom_utils.is_geometric_identity(face.Location().Transformation()):
            face.Location(identity)

    for edge in self._top_exp.edges():
        if geom_utils.is_geometric_identity(edge.Location().Transformation()):
            edge.Location(identity)

    for vertex in self._top_exp.vertices():
        if geom_utils.is_geometric_identity(vertex.Location().Transformation()):
            vertex.Location(identity)

edge_continuity(edge)

Get the neighboring faces' continuity at given edge

Parameters:

Name Type Description Default
edge Edge

Edge

required

Returns:

Name Type Description
GeomAbs_Shape

enum describing the continuity order

Source code in src/occwl/base.py
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
def edge_continuity(self, edge):
    """
    Get the neighboring faces' continuity at given edge

    Args:
        edge (occwl.edge.Edge): Edge

    Returns:
        GeomAbs_Shape: enum describing the continuity order
    """
    faces = list(self.faces_from_edge(edge))
    # Handle seam edges which only have one face around them
    if len(faces) == 1:
        faces.append(faces[-1])
    return edge.continuity(faces[0], faces[1])

edges()

Get an iterator to go over all edges in the Shape

Returns:

Type Description

Iterator[occwl.edge.Edge]: Edge iterator

Source code in src/occwl/base.py
59
60
61
62
63
64
65
66
67
def edges(self):
    """
    Get an iterator to go over all edges in the Shape

    Returns:
        Iterator[occwl.edge.Edge]: Edge iterator
    """
    from occwl.edge import Edge
    return map(Edge, self._top_exp.edges())

edges_from_face(face)

Get an iterator to go over the edges in a face

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.edge.Edge]: Edge iterator

Source code in src/occwl/base.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def edges_from_face(self, face):
    """
    Get an iterator to go over the edges in a face

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.edge.Edge]: Edge iterator
    """
    from occwl.edge import Edge
    from occwl.face import Face
    assert isinstance(face, Face)
    return map(Edge, self._top_exp.edges_from_face(face.topods_shape()))

edges_from_vertex(vertex)

Get an iterator to go over the edges adjacent to a vertex

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.edge.Edge]: Edge iterator

Source code in src/occwl/base.py
386
387
388
389
390
391
392
393
394
395
396
397
398
399
def edges_from_vertex(self, vertex):
    """
    Get an iterator to go over the edges adjacent to a vertex

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.edge.Edge]: Edge iterator
    """
    from occwl.vertex import Vertex
    from occwl.edge import Edge
    assert isinstance(vertex, Vertex)
    return map(Edge, self._top_exp.edges_from_vertex(vertex.topods_shape()))

exact_box(use_shapetolerance=False)

Get a slow, but accurate box for the Shape.

Returns:

Name Type Description
Box

Bounding box

Source code in src/occwl/base.py
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
def exact_box(self, use_shapetolerance=False):
    """
    Get a slow, but accurate box for the Shape.

    Args:
        use_shapetolerance (bool, optional) Include the tolerance of edges
                                            and vertices in the box.

    Returns:
        Box: Bounding box
    """
    from occwl.geometry import geom_utils
    b = Bnd_Box()
    use_triangulation = True
    brepbndlib_AddOptimal(self.topods_shape(), b, use_triangulation, use_shapetolerance)
    return geom_utils.box_to_geometry(b)

faces()

Get an iterator to go over all faces in the Shape

Returns:

Type Description

Iterator[occwl.face.Face]: Face iterator

Source code in src/occwl/base.py
165
166
167
168
169
170
171
172
173
def faces(self):
    """
    Get an iterator to go over all faces in the Shape

    Returns:
        Iterator[occwl.face.Face]: Face iterator
    """
    from occwl.face import Face
    return map(Face, self._top_exp.faces())

faces_from_edge(edge)

Get an iterator to go over the faces adjacent to an edge

Parameters:

Name Type Description Default
edge Edge

Input edge

required

Returns:

Type Description

Iterator[occwl.face.Face]: Face iterator

Source code in src/occwl/base.py
317
318
319
320
321
322
323
324
325
326
327
328
329
330
def faces_from_edge(self, edge):
    """
    Get an iterator to go over the faces adjacent to an edge

    Args:
        edge (occwl.edge.Edge): Input edge

    Returns:
        Iterator[occwl.face.Face]: Face iterator
    """
    from occwl.edge import Edge
    from occwl.face import Face
    assert isinstance(edge, Edge)
    return map(Face, self._top_exp.faces_from_edge(edge.topods_shape()))

faces_from_vertex(vertex)

Get an iterator to go over the faces adjacent to a vertex

Parameters:

Name Type Description Default
edge Vertex

Input vertex

required

Returns:

Type Description

Iterator[occwl.face.Face]: Face iterator

Source code in src/occwl/base.py
332
333
334
335
336
337
338
339
340
341
342
343
344
345
def faces_from_vertex(self, vertex):
    """
    Get an iterator to go over the faces adjacent to a vertex

    Args:
        edge (occwl.vertex.Vertex): Input vertex

    Returns:
        Iterator[occwl.face.Face]: Face iterator
    """
    from occwl.vertex import Vertex
    from occwl.face import Face
    assert isinstance(vertex, Vertex)
    return map(Face, self._top_exp.faces_from_vertex(vertex.topods_shape()))

find_closest_edge_slow(datum)

Find the closest edge to the given datum point. The function is for testing only. It will be slow as it loops over all edges in the Shape. A quick way to find the closest entity is to call Shape.find_closest_point_data(), but then you may get a face, edge or vertex back.

Parameters:

Name Type Description Default
datum ndarray or tuple

3D datum point

required

Returns:

Name Type Description
Face

The closest face in the Shape

Source code in src/occwl/base.py
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def find_closest_edge_slow(self, datum):
    """
    Find the closest edge to the given datum point.
    The function is for testing only.  It will be slow 
    as it loops over all edges in the Shape.
    A quick way to find the closest entity is to call
    Shape.find_closest_point_data(), but then you
    may get a face, edge or vertex back.

    Args:
        datum (np.ndarray or tuple): 3D datum point

    Returns:
        Face: The closest face in the Shape
    """
    return _find_closest_shape_in_list(self.edges(), datum)

find_closest_face_slow(datum)

Find the closest face to the given datum point. The function is for testing only. It will be slow as it loops over all faces in the Shape. A quick way to find the closest entity is to call Shape.find_closest_point_data(), but then you may get a face, edge or vertex back.

Parameters:

Name Type Description Default
datum ndarray or tuple

3D datum point

required

Returns:

Name Type Description
Face

The closest face in the Shape

Source code in src/occwl/base.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def find_closest_face_slow(self, datum):
    """
    Find the closest face to the given datum point.
    The function is for testing only. It will be slow 
    as it loops over all faces in the Shape.
    A quick way to find the closest entity is to call
    Shape.find_closest_point_data(), but then you
    may get a face, edge or vertex back.

    Args:
        datum (np.ndarray or tuple): 3D datum point

    Returns:
        Face: The closest face in the Shape
    """
    return _find_closest_shape_in_list(self.faces(), datum)

find_closest_point_data(datum)

Find the information about the closest point on this shape

Parameters:

Name Type Description Default
datum ndarray

3D Point

required

Returns:

Name Type Description
ClosestPointData

Data about the closest point on this shape

None

if error

Source code in src/occwl/shape.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
def find_closest_point_data(self, datum):
    """
    Find the information about the closest point on this shape

    Args:
        datum (np.ndarray): 3D Point

    Returns:
        ClosestPointData: Data about the closest point on this shape
        None: if error
    """
    # Folowing https://dev.opencascade.org/content/how-retrieve-nearest-face-shape-given-gppnt
    # Create a vertex from the point
    occ_point = geom_utils.numpy_to_gp(datum)
    vertex_maker = BRepBuilderAPI_MakeVertex(occ_point)
    vertex = vertex_maker.Shape()
    dist_shape_shape = BRepExtrema_DistShapeShape(
        vertex, self.topods_shape(), Extrema_ExtFlag_MIN
    )
    ok = dist_shape_shape.Perform()
    if not ok:
        return None

    return ClosestPointData(dist_shape_shape)

get_triangles(triangle_face_tol=0.01, tol_relative_to_face=True, angle_tol_rads=0.1)

Compute and get the tessellation of the entire shape

Parameters:

Name Type Description Default
triangle_face_tol float

Toelrance between triangle and surface. Defaults to 0.01.

0.01
tol_relative_to_face bool

Whether tolerance is relative to face size

True
angle_tol_rads float

Angle tolerance in radians. Defaults to 0.1.

0.1

Returns:

Type Description

2D np.ndarray (float): Vertices or None if triangulation failed

2D np.ndarray (int): Faces or None if triangulation failed

Source code in src/occwl/base.py
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
def get_triangles(
    self,
    triangle_face_tol=0.01,  # Tolerance between triangle and surface
    tol_relative_to_face=True,  # The tolerance value is relative to the face size
    angle_tol_rads=0.1,  # Angle between normals/tangents at triangle vertices
):
    """
    Compute and get the tessellation of the entire shape

    Args:
        triangle_face_tol (float, optional): Toelrance between triangle and surface. Defaults to 0.01.
        tol_relative_to_face (bool): Whether tolerance is relative to face size
        angle_tol_rads (float, optional): Angle tolerance in radians. Defaults to 0.1.

    Returns:
        2D np.ndarray (float): Vertices or None if triangulation failed
        2D np.ndarray (int): Faces or None if triangulation failed
    """
    ok = self.triangulate_all_faces(
        triangle_face_tol, tol_relative_to_face, angle_tol_rads
    )
    if not ok:
        # Failed to triangulate
        return None, None
    verts = []
    tris = []
    faces = self.faces()
    last_vert_index = 0
    for face in faces:
        fverts, ftris = face.get_triangles()
        verts.extend(fverts)
        for tri in ftris:
            new_indices = [index + last_vert_index for index in tri]
            tris.append(new_indices)
        last_vert_index = len(verts)
    return np.asarray(verts, dtype=np.float32), np.asarray(tris, dtype=np.int32)

make_by_sewing_faces(faces) staticmethod

Make a shell by sewing a set of faces with overlapping edges

Parameters:

Name Type Description Default
faces List[Face]

List of faces

required

Returns:

Type Description

Shell or None: Sewed shell or None if the output was not a Shell

Source code in src/occwl/shell.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@staticmethod
def make_by_sewing_faces(faces):
    """
    Make a shell by sewing a set of faces with overlapping edges

    Args:
        faces (List[occwl.face.Face]): List of faces

    Returns:
        Shell or None: Sewed shell or None if the output was not a Shell
    """
    sew = BRepBuilderAPI_Sewing()
    for f in faces:
        assert isinstance(f, Face)
        sew.Add(f.topods_shape())
    sew.Perform()
    sewed_shape = sew.SewedShape()
    if isinstance(sewed_shape, TopoDS_Shell):
        return Shell(sewed_shape)
    return None

num_edges()

Number of edges in the Shape

Returns:

Name Type Description
int

Number of edges

Source code in src/occwl/base.py
50
51
52
53
54
55
56
57
def num_edges(self):
    """
    Number of edges in the Shape

    Returns:
        int: Number of edges
    """
    return self._top_exp.number_of_edges()

num_faces()

Number of faces in the Shape

Returns:

Name Type Description
int

Number of faces

Source code in src/occwl/base.py
156
157
158
159
160
161
162
163
def num_faces(self):
    """
    Number of faces in the Shape

    Returns:
        int: Number of faces
    """
    return self._top_exp.number_of_faces()

num_vertices()

Number of vertices in the Shape

Returns:

Name Type Description
int

Number of vertices

Source code in src/occwl/base.py
25
26
27
28
29
30
31
32
def num_vertices(self):
    """
    Number of vertices in the Shape

    Returns:
        int: Number of vertices
    """
    return self._top_exp.number_of_vertices()

num_wires()

Number of wires in the Shape

Returns:

Name Type Description
int

Number of wires

Source code in src/occwl/base.py
131
132
133
134
135
136
137
138
def num_wires(self):
    """
    Number of wires in the Shape

    Returns:
        int: Number of wires
    """
    return self._top_exp.number_of_wires()

occwl_shape(topods_shape) staticmethod

Static method to create an occwl shape of the appropriate class from the given topods_shape Args: topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

Returns:

Type Description

One of occwl.compound.Compound occwl.solid.Solid occwl.face.Face occwl.edge.Edge occwl.vertex.Vertex occwl.wire.Wire occwl.shell.Shell

Raises: Exception: [description]

Source code in src/occwl/shape.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@staticmethod
def occwl_shape(topods_shape):
    """
    Static method to create an occwl shape of the appropriate 
    class from the given topods_shape
    Args:
        topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

    Returns:
        One of
            occwl.compound.Compound
            occwl.solid.Solid
            occwl.face.Face
            occwl.edge.Edge
            occwl.vertex.Vertex
            occwl.wire.Wire
            occwl.shell.Shell
    Raises:
        Exception: [description]
    """
    from occwl.compound import Compound
    from occwl.solid import Solid
    from occwl.face import Face
    from occwl.edge import Edge
    from occwl.vertex import Vertex
    from occwl.wire import Wire
    from occwl.shell import Shell

    if isinstance(topods_shape, TopoDS_Vertex):
        return Vertex(topods_shape)
    if isinstance(topods_shape, TopoDS_Edge):
        return Edge(topods_shape)
    if isinstance(topods_shape, TopoDS_Face):
        return Face(topods_shape)
    if isinstance(topods_shape, TopoDS_Wire):
        return Wire(topods_shape)
    if isinstance(topods_shape, TopoDS_Shell):
        return Shell(topods_shape)
    if isinstance(topods_shape, TopoDS_Solid):
        return Solid(topods_shape)
    if isinstance(topods_shape, (TopoDS_Compound, TopoDS_CompSolid)):
        return Compound(topods_shape)
    raise Exception(
        "Shape must be one of TopoDS_Vertex, TopoDS_Edge, TopoDS_Face, TopoDS_Shell, TopoDS_Solid, TopoDS_Compound, TopoDS_CompSolid"
    )

reversed()

Whether this shape is reversed.

  • For an edge this is whether the edge is reversed with respect to the curve geometry
  • For a face this is whether the face is reversed with respect to the surface geometry
  • For a vertex this is whether the vertex is at the upper or lower parameter value on the edges curve

Returns:

Name Type Description
bool

If rational

Source code in src/occwl/shape.py
236
237
238
239
240
241
242
243
244
245
246
247
248
def reversed(self):
    """
    Whether this shape is reversed.

    - For an edge this is whether the edge is reversed with respect to the curve geometry
    - For a face this is whether the face is reversed with respect to the surface geometry
    - For a vertex this is whether the vertex is at the upper or lower parameter value on the
      edges curve

    Returns:
        bool: If rational
    """
    return self.topods_shape().Orientation() == TopAbs_REVERSED

rotate_axis_angle(axis, angle_radians, origin=np.zeros(3, dtype=(np.float32)))

Rotate the shape about the given axis by the given angle in radians

Parameters:

Name Type Description Default
axis ndarray

Rotation axis

required
angle_radians float

Angle in radians

required
Source code in src/occwl/shape.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
def rotate_axis_angle(
    self, axis, angle_radians, origin=np.zeros(3, dtype=np.float32)
):
    """
    Rotate the shape about the given axis by the given angle in radians

    Args:
        axis (np.ndarray): Rotation axis
        angle_radians (float): Angle in radians
    """
    self._shape = rotate_shape(
        self._shape,
        gp_Ax1(geom_utils.numpy_to_gp(origin), geom_utils.numpy_to_gp_dir(axis)),
        angle_radians,
        unite="rad",
    )

rotate_euler_angles(angles_xyz_radians)

Rotate the shape by the given Euler angles in radians

Parameters:

Name Type Description Default
angle_xyz_radians ndarray

3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians

required
Source code in src/occwl/shape.py
302
303
304
305
306
307
308
309
310
311
312
313
314
315
def rotate_euler_angles(self, angles_xyz_radians):
    """
    Rotate the shape by the given Euler angles in radians

    Args:
        angle_xyz_radians (np.ndarray): 3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians
    """
    self._shape = rotate_shp_3_axis(
        self._shape,
        angles_xyz_radians[0],
        angles_xyz_radians[1],
        angles_xyz_radians[2],
        unity="rad",
    )

save_shapes_to_occ_native(filename, shapes, with_triangles=False, with_normals=False, format_version=None) staticmethod

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
@staticmethod
def save_shapes_to_occ_native(
        filename, 
        shapes,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
            is between one and two orders of magnitude faster 
            than loading from STEP, so it is recommended for 
            large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename

        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    new_api = False
    shapes_set = BRepTools_ShapeSet(with_triangles)
    # shapes_set.SetWithNormals(with_normals) # Not in OCC 7.5.0

    for shp in shapes:
        shapes_set.Add(shp.topods_shape())
    if format_version is not None:
        shapes_set.SetFormatNb(format_version)


    with open(filename, "w") as fp:
        s = shapes_set.WriteToString()
        fp.write(s)

save_to_occ_native(filename, verbosity=False, with_triangles=False, with_normals=False, format_version=None)

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def save_to_occ_native(
        self, 
        filename, 
        verbosity=False,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
           is between one and two orders of magnitude faster 
           than loading from STEP, so it is recommended for 
           large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename
        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    self.save_shapes_to_occ_native(
        filename, 
        [ self ],
        with_triangles=with_triangles,
        with_normals=with_normals,
        format_version=format_version
    )

scale(scale_vector)

Scale the shape by the given 3D vector

Parameters:

Name Type Description Default
scale_vector ndarray

3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively

required
Source code in src/occwl/shape.py
317
318
319
320
321
322
323
324
325
326
def scale(self, scale_vector):
    """
    Scale the shape by the given 3D vector

    Args:
        scale_vector (np.ndarray): 3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively
    """
    self._shape = scale_shape(
        self._shape, scale_vector[0], scale_vector[1], scale_vector[2]
    )

scale_to_box(box_side, copy=True)

Translate and scale the Shape so it fits exactly into the [-box_side, box_side]^3 box

Returns:

Type Description

occwl..: The scaled version of this Shape

Source code in src/occwl/base.py
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
def scale_to_box(self, box_side, copy=True):
    """
    Translate and scale the Shape so it fits exactly 
    into the [-box_side, box_side]^3 box

    Args:
        box_side (float) The side length of the box
        copy (bool)      True - Copy entities and apply the transform to
                                the underlying geometry
                         False - Apply the transform to the topods Locator
                                 if possible 

    Returns:
        occwl.*.*: The scaled version of this Shape
    """
    from occwl.geometry import geom_utils
    # Get an exact box for the Shape
    box = self.exact_box()
    center = box.center()
    longest_length = box.max_box_length()

    orig = gp_Pnt(0.0, 0.0, 0.0)
    center = geom_utils.numpy_to_gp(center)
    vec_center_to_orig = gp_Vec(center, orig)
    move_to_center = gp_Trsf()
    move_to_center.SetTranslation(vec_center_to_orig)

    scale_trsf = gp_Trsf()
    scale_trsf.SetScale(orig, (2.0 * box_side) / longest_length)
    trsf_to_apply = scale_trsf.Multiplied(move_to_center)

    return self._apply_transform(trsf_to_apply, copy=copy)

scale_to_unit_box(copy=True)

Translate and scale the Shape so it fits exactly into the [-1, 1]^3 box

Returns: The scaled version of this shape

Source code in src/occwl/base.py
628
629
630
631
632
633
634
635
636
637
638
639
640
641
def scale_to_unit_box(self, copy=True):
    """
    Translate and scale the Shape so it fits exactly 
    into the [-1, 1]^3 box

    Args:
        copy (bool)      True - Copy entities and apply the transform to
                                    the underlying geometry
                            False - Apply the transform to the topods Locator
                                    if possible 
    Returns:
        The scaled version of this shape
    """
    return self.scale_to_box(1.0, copy=copy)

set_transform_to_identity()

When an assembly is loaded from a STEP file the solids will be transformed relative to their local coordinate system. i.e. they are placed in the assembly root components coordinate system.

When working with individual bodies you often want them to be axis aligned, in which case you want to remove the assembly transform. This function removes it for you.

If however you want to bake the transform into the bodies and suppress the asserts from parts of occwl which don't cope with transforms then use the transform() function below with copy=True

Source code in src/occwl/shape.py
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def set_transform_to_identity(self):
    """
    When an assembly is loaded from a STEP file
    the solids will be transformed relative to
    their local coordinate system.   i.e. they
    are placed in the assembly root components 
    coordinate system.

    When working with individual bodies you often
    want them to be axis aligned, in which case 
    you want to remove the assembly transform.
    This function removes it for you.

    If however you want to bake the transform
    into the bodies and suppress the asserts 
    from parts of occwl which don't cope with
    transforms then use the transform() function
    below with copy=True
    """
    identity = TopLoc_Location()
    self.topods_shape().Location(identity)
    self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)
    self.convert_geometric_identity_transforms_to_identity()

split_all_closed_edges(max_tol=0.01, precision=0.01, num_splits=1)

Split all the closed edges in this shape

Parameters:

Name Type Description Default
max_tol float

Maximum tolerance allowed. Defaults to 0.01.

0.01
precision float

Precision of the tool when splitting. Defaults to 0.01.

0.01
num_splits int

Number of splits to perform. Each split edge will result in num_splits + 1 edges. Defaults to 1.

1

Returns:

Type Description

occwl..: Shape with closed edges split

Source code in src/occwl/base.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def split_all_closed_edges(self, max_tol=0.01, precision=0.01, num_splits=1):
    """
    Split all the closed edges in this shape

    Args:
        max_tol (float, optional): Maximum tolerance allowed. Defaults to 0.01.
        precision (float, optional): Precision of the tool when splitting. Defaults to 0.01.
        num_splits (int, optional): Number of splits to perform. Each split edge will result in num_splits + 1 edges. Defaults to 1.

    Returns:
        occwl.*.*: Shape with closed edges split
    """
    divider = ShapeUpgrade_ShapeDivideClosedEdges(self.topods_shape())
    divider.SetPrecision(precision)
    divider.SetMinTolerance(0.1 * max_tol)
    divider.SetMaxTolerance(max_tol)
    divider.SetNbSplitPoints(num_splits)
    ok = divider.Perform()
    if not ok:
        # Splitting failed or there were no closed edges to split
        # Return the original shape
        return self
    return type(self)(divider.Result())

split_all_closed_faces(max_tol=0.01, precision=0.01, num_splits=1)

Split all the closed faces in this shape

Parameters:

Name Type Description Default
max_tol float

Maximum tolerance allowed. Defaults to 0.01.

0.01
precision float

Precision of the tool when splitting. Defaults to 0.01.

0.01
num_splits int

Number of splits to perform. Each split face will result in num_splits + 1 faces. Defaults to 1.

1

Returns:

Type Description

occwl..: Shape with closed faces split

Source code in src/occwl/base.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def split_all_closed_faces(self, max_tol=0.01, precision=0.01, num_splits=1):
    """
    Split all the closed faces in this shape

    Args:
        max_tol (float, optional): Maximum tolerance allowed. Defaults to 0.01.
        precision (float, optional): Precision of the tool when splitting. Defaults to 0.01.
        num_splits (int, optional): Number of splits to perform. Each split face will result in num_splits + 1 faces. Defaults to 1.

    Returns:
        occwl.*.*: Shape with closed faces split
    """
    divider = ShapeUpgrade_ShapeDivideClosed(self.topods_shape())
    divider.SetPrecision(precision)
    divider.SetMinTolerance(0.1 * max_tol)
    divider.SetMaxTolerance(max_tol)
    divider.SetNbSplitPoints(num_splits)
    ok = divider.Perform()
    if not ok:
        # Splitting failed or there were no closed faces to split
        # Return the original shape
        return self
    return type(self)(divider.Result())

topods_shape()

Get the underlying OCC shape

Returns:

Type Description

OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*

Source code in src/occwl/shape.py
80
81
82
83
84
85
86
87
def topods_shape(self):
    """
    Get the underlying OCC shape

    Returns:
        OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*
    """
    return self._shape

transform(a, copy=True)

Apply the given 3x4 transform matrix to the solid.

 copy (bool)    True - Copy entities and apply the transform to
                       the underlying geometry
                False - Apply the transform to the topods Locator
                        if possible
Source code in src/occwl/shape.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
def transform(self, a: np.ndarray, copy=True):
    """
    Apply the given 3x4 transform matrix to the solid.

    Args: a (nd.array) - Homogeneous transform matrix
                         The transform that will be applied is

                         x' =  a[0,0]*x + a[0,1]*y + a[0,2]*z + a[0, 3]
                         y' =  a[1,0]*x + a[1,1]*y + a[1,2]*z + a[1, 3]
                         z' =  a[2,0]*x + a[2,1]*y + a[2,2]*z + a[2, 3]

         copy (bool)    True - Copy entities and apply the transform to
                               the underlying geometry
                        False - Apply the transform to the topods Locator
                                if possible 
    """
    assert (a.shape == (3, 4)), "Transform matrix must be 3x4"
    a = a.astype(np.float64)

    # Create an identity transform
    trsf = gp_Trsf()

    # If the matrix is an identity matrix then
    # we don't want to set the values as this
    # would give us a geometric identity without
    # the identity flag set
    if not np.allclose(a, np.eye(3, 4)):
        trsf.SetValues(
            a[0,0], a[0,1], a[0,2], a[0, 3],
            a[1,0], a[1,1], a[1,2], a[1, 3],
            a[2,0], a[2,1], a[2,2], a[2, 3]
        )
    return self._apply_transform(trsf, copy=copy)

translate(offset)

Translate the shape by an offset vector

Parameters:

Name Type Description Default
offset ndarray

Offset vector

required
Source code in src/occwl/shape.py
276
277
278
279
280
281
282
283
def translate(self, offset):
    """
    Translate the shape by an offset vector

    Args:
        offset (np.ndarray): Offset vector
    """
    self._shape = translate_shp(self._shape, geom_utils.numpy_to_gp_vec(offset))

triangulate(triangle_face_tol=0.01, tol_relative_to_face=True, angle_tol_rads=0.1)

Triangulate all the faces in the shape. You can then get the triangles from each face separately using face.get_triangles(). If you wanted triangles for the entire shape then call shape.get_triangles() below. For more details see https://old.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11

Parameters:

Name Type Description Default
triangle_face_tol float

Tolerance between triangle and surface. Defaults to 0.01.

0.01
tol_relative_to_face bool

Whether tolerance is relative to face size

True
angle_tol_rads float

Angle tolerance in radians. Defaults to 0.1.

0.1

Returns:

Name Type Description
bool

Is successful

Source code in src/occwl/base.py
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
def triangulate(
    self,
    triangle_face_tol=0.01,  # Tolerance between triangle and surface
    tol_relative_to_face=True,  # The tolerance value is relative to the face size
    angle_tol_rads=0.1,  # Angle between normals/tangents at triangle vertices
):
    """
    Triangulate all the faces in the shape. You can then get the triangles 
    from each face separately using face.get_triangles().
    If you wanted triangles for the entire shape then call
    shape.get_triangles() below.
    For more details see 
    https://old.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11

    Args:
        triangle_face_tol (float, optional): Tolerance between triangle and surface. Defaults to 0.01.
        tol_relative_to_face (bool): Whether tolerance is relative to face size
        angle_tol_rads (float, optional): Angle tolerance in radians. Defaults to 0.1.

    Returns:
        bool: Is successful
    """
    mesh = BRepMesh_IncrementalMesh(
        self.topods_shape(),
        triangle_face_tol,
        tol_relative_to_face,
        angle_tol_rads,
        True,
    )
    mesh.Perform()
    return mesh.IsDone()

valid(return_analyzer=False)

Check if the shape is valid

Parameters:

Name Type Description Default
return_analyzer bool

Whether to return the BRepCheck_Analyzer object for more inspection

False

Returns:

Name Type Description
bool

Whether the shape is valid

BRepCheck_Analyzer [optional]: if return_analyzer is True

Source code in src/occwl/shape.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def valid(self, return_analyzer=False):
    """
    Check if the shape is valid

    Args:
        return_analyzer (bool): Whether to return the BRepCheck_Analyzer object for more inspection

    Returns:
        bool: Whether the shape is valid
        BRepCheck_Analyzer [optional]: if return_analyzer is True
    """
    analyzer = BRepCheck_Analyzer(self.topods_shape())
    if return_analyzer:
        return analyzer.IsValid(), analyzer
    return analyzer.IsValid()

vertices()

Get an iterator to go over all vertices in the Shape

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
34
35
36
37
38
39
40
41
42
def vertices(self):
    """
    Get an iterator to go over all vertices in the Shape

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    return map(Vertex, self._top_exp.vertices())

vertices_from_edge(edge)

Get an iterator to go over the vertices bounding an edge

Parameters:

Name Type Description Default
edge Edge

Input edge

required

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def vertices_from_edge(self, edge):
    """
    Get an iterator to go over the vertices bounding an edge

    Args:
        edge (occwl.edge.Edge): Input edge

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    from occwl.edge import Edge
    assert isinstance(edge, Edge)
    return map(Vertex, self._top_exp.vertices_from_edge(edge.topods_shape()))

vertices_from_face(face)

Get an iterator to go over the vertices in a face

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def vertices_from_face(self, face):
    """
    Get an iterator to go over the vertices in a face

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    from occwl.face import Face
    assert isinstance(face, Face)
    return map(Vertex, self._top_exp.vertices_from_face(face.topods_shape()))

wires()

Get an iterator to go over all wires in the Shape

Returns:

Type Description

Iterator[occwl.wire.Wire]: Wire iterator

Source code in src/occwl/base.py
140
141
142
143
144
145
146
147
148
def wires(self):
    """
    Get an iterator to go over all wires in the Shape

    Returns:
        Iterator[occwl.wire.Wire]: Wire iterator
    """
    from occwl.wire import Wire
    return map(Wire, self._top_exp.wires())

wires_from_face(face)

Get an iterator to go over the wires bounding a face

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.wire.Wire]: Wire iterator

Source code in src/occwl/base.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
def wires_from_face(self, face):
    """
    Get an iterator to go over the wires bounding a face

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.wire.Wire]: Wire iterator
    """
    from occwl.wire import Wire
    from occwl.face import Face
    assert isinstance(face, Face)
    return map(Wire, self._top_exp.wires_from_face(face.topods_shape()))

solid

Solid

Bases: Shape, VertexContainerMixin, EdgeContainerMixin, ShellContainerMixin, WireContainerMixin, FaceContainerMixin, BottomUpFaceIterator, BottomUpEdgeIterator, SurfacePropertiesMixin, VolumePropertiesMixin, BoundingBoxMixin, TriangulatorMixin

A solid model

Source code in src/occwl/solid.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
class Solid(Shape, VertexContainerMixin, EdgeContainerMixin, ShellContainerMixin, \
            WireContainerMixin, FaceContainerMixin, BottomUpFaceIterator, \
            BottomUpEdgeIterator, SurfacePropertiesMixin, VolumePropertiesMixin, \
            BoundingBoxMixin, TriangulatorMixin):
    """
    A solid model
    """

    def __init__(self, shape, allow_compound=False):
        if allow_compound:
            assert (isinstance(shape, TopoDS_Solid) or 
                isinstance(shape, TopoDS_Compound) or 
                isinstance(shape, TopoDS_CompSolid))
        else:
            assert isinstance(shape, TopoDS_Solid)
        super().__init__(shape)

    @staticmethod
    def make_box(width, height, depth):
        from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox

        return Solid(
            BRepPrimAPI_MakeBox(float(width), float(height), float(depth)).Shape()
        )

    @staticmethod
    def make_sphere(radius, center=(0, 0, 0)):
        from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeSphere

        return Solid(
            BRepPrimAPI_MakeSphere(geom_utils.to_gp_pnt(center), float(radius)).Shape()
        )

    @staticmethod
    def make_spherical_wedge(radius, center=(0, 0, 0), longitudinal_angle=2 * math.pi):
        from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeSphere

        return Solid(
            BRepPrimAPI_MakeSphere(
                geom_utils.to_gp_pnt(center), float(radius), float(longitudinal_angle)
            ).Shape()
        )

    @staticmethod
    def make_cone(
        radius_bottom,
        radius_top,
        height,
        apex_angle=2 * math.pi,
        base_point=(0, 0, 0),
        up_dir=(0, 0, 1),
    ):
        from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeCone

        return Solid(
            BRepPrimAPI_MakeCone(
                gp_Ax2(geom_utils.to_gp_pnt(base_point), geom_utils.to_gp_dir(up_dir)),
                float(radius_bottom),
                float(radius_top),
                float(height),
                float(apex_angle),
            ).Shape()
        )

    @staticmethod
    def make_cylinder(
        radius, height, angle=2 * math.pi, base_point=(0, 0, 0), up_dir=(0, 0, 1)
    ):
        from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeCylinder

        return Solid(
            BRepPrimAPI_MakeCylinder(
                gp_Ax2(geom_utils.to_gp_pnt(base_point), geom_utils.to_gp_dir(up_dir)),
                float(radius),
                float(height),
                float(angle),
            ).Shape()
        )

    def is_closed(self):
        """
        Checks and returns if the solid is closed (has no holes)

        Returns:
            bool: If closed
        """
        # In Open Cascade, unlinked (open) edges can be identified
        # as they appear in the edges iterator when ignore_orientation=False
        # but are not present in any wire
        ordered_edges = set()
        for wire in self.wires():
            for edge in wire.ordered_edges():
                ordered_edges.add(edge.topods_shape())
        unordered_edges = set([edge.topods_shape() for edge  in self.edges()])
        missing_edges = unordered_edges - ordered_edges
        return len(missing_edges) == 0

    def check_unique_oriented_edges(self):
        ordered_edges = set()
        for wire in self.wires():
            for oriented_edge in wire.ordered_edges():
                is_reversed = oriented_edge.reversed()
                tup = (oriented_edge, is_reversed)

                # We want to detect the case where the oriented
                # edges are not unique
                if tup in ordered_edges:
                    # Here we see the same oriented edges
                    # appears twice in the solid.  This is the 
                    # failure case we need to flag 
                    return False

                ordered_edges.add(tup)

        return True

__eq__(other)

Equality check for the shape

NOTE: This function only checks if the shape is the same. It doesn't check the edge orienation for example, so

edge1 == edge2

does not necessarily mean

edge1.reversed() == edge2.reversed()

Source code in src/occwl/shape.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def __eq__(self, other):
    """
    Equality check for the shape

    NOTE: This function only checks if the shape is the same.
    It doesn't check the edge orienation for example, so 

    edge1 == edge2

    does not necessarily mean 

    edge1.reversed() == edge2.reversed()
    """
    return self.topods_shape().__hash__() == other.topods_shape().__hash__()

__hash__()

Hash for the shape

Returns:

Name Type Description
int

Hash value

Source code in src/occwl/shape.py
135
136
137
138
139
140
141
142
def __hash__(self):
    """
    Hash for the shape

    Returns:
        int: Hash value
    """
    return self.topods_shape().__hash__()

area()

Compute the area of the Shape

Returns:

Name Type Description
float

Area

Source code in src/occwl/base.py
493
494
495
496
497
498
499
500
501
502
def area(self):
    """
    Compute the area of the Shape

    Returns:
        float: Area
    """
    geometry_properties = GProp_GProps()
    brepgprop_SurfaceProperties(self.topods_shape(), geometry_properties)
    return geometry_properties.Mass()

box()

Get a quick bounding box of the Shape

Returns:

Name Type Description
Box

Bounding box

Source code in src/occwl/base.py
565
566
567
568
569
570
571
572
573
574
575
def box(self):
    """
    Get a quick bounding box of the Shape

    Returns:
        Box: Bounding box
    """
    from occwl.geometry import geom_utils
    b = Bnd_Box()
    brepbndlib_Add(self.topods_shape(), b)
    return geom_utils.box_to_geometry(b)

center_of_mass(tolerance=1e-09)

Compute the center of mass of the Shape

Parameters:

Name Type Description Default
tolerance float

Tolerance. Defaults to 1e-9.

1e-09

Returns:

Type Description

np.ndarray: 3D point

Source code in src/occwl/base.py
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
def center_of_mass(self, tolerance=1e-9):
    """
    Compute the center of mass of the Shape

    Args:
        tolerance (float, optional): Tolerance. Defaults to 1e-9.

    Returns:
        np.ndarray: 3D point
    """
    from occwl.geometry import geom_utils
    props = GProp_GProps()
    brepgprop_VolumeProperties(self.topods_shape(), props, tolerance)
    com = props.CentreOfMass()
    return geom_utils.gp_to_numpy(com)

convert_geometric_identity_transforms_to_identity()

Open Cascade models sometimes contain transforms which are "geometrically" identify transforms, but the identity flag is not set.

This function checks each transform and sets the flag if the appropriate.

Source code in src/occwl/shape.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def convert_geometric_identity_transforms_to_identity(self):
    """
    Open Cascade models sometimes contain transforms which
    are "geometrically" identify transforms, but the identity
    flag is not set.

    This function checks each transform and sets the flag if 
    the appropriate.
    """
    identity = TopLoc_Location()
    if geom_utils.is_geometric_identity(
        self.topods_shape().Location().Transformation()
    ):
        self.topods_shape().Location(identity)
        self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)

    for face in self._top_exp.faces():
        if geom_utils.is_geometric_identity(face.Location().Transformation()):
            face.Location(identity)

    for edge in self._top_exp.edges():
        if geom_utils.is_geometric_identity(edge.Location().Transformation()):
            edge.Location(identity)

    for vertex in self._top_exp.vertices():
        if geom_utils.is_geometric_identity(vertex.Location().Transformation()):
            vertex.Location(identity)

edge_continuity(edge)

Get the neighboring faces' continuity at given edge

Parameters:

Name Type Description Default
edge Edge

Edge

required

Returns:

Name Type Description
GeomAbs_Shape

enum describing the continuity order

Source code in src/occwl/base.py
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
def edge_continuity(self, edge):
    """
    Get the neighboring faces' continuity at given edge

    Args:
        edge (occwl.edge.Edge): Edge

    Returns:
        GeomAbs_Shape: enum describing the continuity order
    """
    faces = list(self.faces_from_edge(edge))
    # Handle seam edges which only have one face around them
    if len(faces) == 1:
        faces.append(faces[-1])
    return edge.continuity(faces[0], faces[1])

edges()

Get an iterator to go over all edges in the Shape

Returns:

Type Description

Iterator[occwl.edge.Edge]: Edge iterator

Source code in src/occwl/base.py
59
60
61
62
63
64
65
66
67
def edges(self):
    """
    Get an iterator to go over all edges in the Shape

    Returns:
        Iterator[occwl.edge.Edge]: Edge iterator
    """
    from occwl.edge import Edge
    return map(Edge, self._top_exp.edges())

edges_from_face(face)

Get an iterator to go over the edges in a face

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.edge.Edge]: Edge iterator

Source code in src/occwl/base.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def edges_from_face(self, face):
    """
    Get an iterator to go over the edges in a face

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.edge.Edge]: Edge iterator
    """
    from occwl.edge import Edge
    from occwl.face import Face
    assert isinstance(face, Face)
    return map(Edge, self._top_exp.edges_from_face(face.topods_shape()))

edges_from_vertex(vertex)

Get an iterator to go over the edges adjacent to a vertex

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.edge.Edge]: Edge iterator

Source code in src/occwl/base.py
386
387
388
389
390
391
392
393
394
395
396
397
398
399
def edges_from_vertex(self, vertex):
    """
    Get an iterator to go over the edges adjacent to a vertex

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.edge.Edge]: Edge iterator
    """
    from occwl.vertex import Vertex
    from occwl.edge import Edge
    assert isinstance(vertex, Vertex)
    return map(Edge, self._top_exp.edges_from_vertex(vertex.topods_shape()))

exact_box(use_shapetolerance=False)

Get a slow, but accurate box for the Shape.

Returns:

Name Type Description
Box

Bounding box

Source code in src/occwl/base.py
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
def exact_box(self, use_shapetolerance=False):
    """
    Get a slow, but accurate box for the Shape.

    Args:
        use_shapetolerance (bool, optional) Include the tolerance of edges
                                            and vertices in the box.

    Returns:
        Box: Bounding box
    """
    from occwl.geometry import geom_utils
    b = Bnd_Box()
    use_triangulation = True
    brepbndlib_AddOptimal(self.topods_shape(), b, use_triangulation, use_shapetolerance)
    return geom_utils.box_to_geometry(b)

faces()

Get an iterator to go over all faces in the Shape

Returns:

Type Description

Iterator[occwl.face.Face]: Face iterator

Source code in src/occwl/base.py
165
166
167
168
169
170
171
172
173
def faces(self):
    """
    Get an iterator to go over all faces in the Shape

    Returns:
        Iterator[occwl.face.Face]: Face iterator
    """
    from occwl.face import Face
    return map(Face, self._top_exp.faces())

faces_from_edge(edge)

Get an iterator to go over the faces adjacent to an edge

Parameters:

Name Type Description Default
edge Edge

Input edge

required

Returns:

Type Description

Iterator[occwl.face.Face]: Face iterator

Source code in src/occwl/base.py
317
318
319
320
321
322
323
324
325
326
327
328
329
330
def faces_from_edge(self, edge):
    """
    Get an iterator to go over the faces adjacent to an edge

    Args:
        edge (occwl.edge.Edge): Input edge

    Returns:
        Iterator[occwl.face.Face]: Face iterator
    """
    from occwl.edge import Edge
    from occwl.face import Face
    assert isinstance(edge, Edge)
    return map(Face, self._top_exp.faces_from_edge(edge.topods_shape()))

faces_from_vertex(vertex)

Get an iterator to go over the faces adjacent to a vertex

Parameters:

Name Type Description Default
edge Vertex

Input vertex

required

Returns:

Type Description

Iterator[occwl.face.Face]: Face iterator

Source code in src/occwl/base.py
332
333
334
335
336
337
338
339
340
341
342
343
344
345
def faces_from_vertex(self, vertex):
    """
    Get an iterator to go over the faces adjacent to a vertex

    Args:
        edge (occwl.vertex.Vertex): Input vertex

    Returns:
        Iterator[occwl.face.Face]: Face iterator
    """
    from occwl.vertex import Vertex
    from occwl.face import Face
    assert isinstance(vertex, Vertex)
    return map(Face, self._top_exp.faces_from_vertex(vertex.topods_shape()))

find_closest_edge_slow(datum)

Find the closest edge to the given datum point. The function is for testing only. It will be slow as it loops over all edges in the Shape. A quick way to find the closest entity is to call Shape.find_closest_point_data(), but then you may get a face, edge or vertex back.

Parameters:

Name Type Description Default
datum ndarray or tuple

3D datum point

required

Returns:

Name Type Description
Face

The closest face in the Shape

Source code in src/occwl/base.py
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def find_closest_edge_slow(self, datum):
    """
    Find the closest edge to the given datum point.
    The function is for testing only.  It will be slow 
    as it loops over all edges in the Shape.
    A quick way to find the closest entity is to call
    Shape.find_closest_point_data(), but then you
    may get a face, edge or vertex back.

    Args:
        datum (np.ndarray or tuple): 3D datum point

    Returns:
        Face: The closest face in the Shape
    """
    return _find_closest_shape_in_list(self.edges(), datum)

find_closest_face_slow(datum)

Find the closest face to the given datum point. The function is for testing only. It will be slow as it loops over all faces in the Shape. A quick way to find the closest entity is to call Shape.find_closest_point_data(), but then you may get a face, edge or vertex back.

Parameters:

Name Type Description Default
datum ndarray or tuple

3D datum point

required

Returns:

Name Type Description
Face

The closest face in the Shape

Source code in src/occwl/base.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def find_closest_face_slow(self, datum):
    """
    Find the closest face to the given datum point.
    The function is for testing only. It will be slow 
    as it loops over all faces in the Shape.
    A quick way to find the closest entity is to call
    Shape.find_closest_point_data(), but then you
    may get a face, edge or vertex back.

    Args:
        datum (np.ndarray or tuple): 3D datum point

    Returns:
        Face: The closest face in the Shape
    """
    return _find_closest_shape_in_list(self.faces(), datum)

find_closest_point_data(datum)

Find the information about the closest point on this shape

Parameters:

Name Type Description Default
datum ndarray

3D Point

required

Returns:

Name Type Description
ClosestPointData

Data about the closest point on this shape

None

if error

Source code in src/occwl/shape.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
def find_closest_point_data(self, datum):
    """
    Find the information about the closest point on this shape

    Args:
        datum (np.ndarray): 3D Point

    Returns:
        ClosestPointData: Data about the closest point on this shape
        None: if error
    """
    # Folowing https://dev.opencascade.org/content/how-retrieve-nearest-face-shape-given-gppnt
    # Create a vertex from the point
    occ_point = geom_utils.numpy_to_gp(datum)
    vertex_maker = BRepBuilderAPI_MakeVertex(occ_point)
    vertex = vertex_maker.Shape()
    dist_shape_shape = BRepExtrema_DistShapeShape(
        vertex, self.topods_shape(), Extrema_ExtFlag_MIN
    )
    ok = dist_shape_shape.Perform()
    if not ok:
        return None

    return ClosestPointData(dist_shape_shape)

get_triangles(triangle_face_tol=0.01, tol_relative_to_face=True, angle_tol_rads=0.1)

Compute and get the tessellation of the entire shape

Parameters:

Name Type Description Default
triangle_face_tol float

Toelrance between triangle and surface. Defaults to 0.01.

0.01
tol_relative_to_face bool

Whether tolerance is relative to face size

True
angle_tol_rads float

Angle tolerance in radians. Defaults to 0.1.

0.1

Returns:

Type Description

2D np.ndarray (float): Vertices or None if triangulation failed

2D np.ndarray (int): Faces or None if triangulation failed

Source code in src/occwl/base.py
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
def get_triangles(
    self,
    triangle_face_tol=0.01,  # Tolerance between triangle and surface
    tol_relative_to_face=True,  # The tolerance value is relative to the face size
    angle_tol_rads=0.1,  # Angle between normals/tangents at triangle vertices
):
    """
    Compute and get the tessellation of the entire shape

    Args:
        triangle_face_tol (float, optional): Toelrance between triangle and surface. Defaults to 0.01.
        tol_relative_to_face (bool): Whether tolerance is relative to face size
        angle_tol_rads (float, optional): Angle tolerance in radians. Defaults to 0.1.

    Returns:
        2D np.ndarray (float): Vertices or None if triangulation failed
        2D np.ndarray (int): Faces or None if triangulation failed
    """
    ok = self.triangulate_all_faces(
        triangle_face_tol, tol_relative_to_face, angle_tol_rads
    )
    if not ok:
        # Failed to triangulate
        return None, None
    verts = []
    tris = []
    faces = self.faces()
    last_vert_index = 0
    for face in faces:
        fverts, ftris = face.get_triangles()
        verts.extend(fverts)
        for tri in ftris:
            new_indices = [index + last_vert_index for index in tri]
            tris.append(new_indices)
        last_vert_index = len(verts)
    return np.asarray(verts, dtype=np.float32), np.asarray(tris, dtype=np.int32)

is_closed()

Checks and returns if the solid is closed (has no holes)

Returns:

Name Type Description
bool

If closed

Source code in src/occwl/solid.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def is_closed(self):
    """
    Checks and returns if the solid is closed (has no holes)

    Returns:
        bool: If closed
    """
    # In Open Cascade, unlinked (open) edges can be identified
    # as they appear in the edges iterator when ignore_orientation=False
    # but are not present in any wire
    ordered_edges = set()
    for wire in self.wires():
        for edge in wire.ordered_edges():
            ordered_edges.add(edge.topods_shape())
    unordered_edges = set([edge.topods_shape() for edge  in self.edges()])
    missing_edges = unordered_edges - ordered_edges
    return len(missing_edges) == 0

moment_of_inertia(point, direction, tolerance=1e-09)

Compute the moment of inertia about an axis

Parameters:

Name Type Description Default
point ndarray

3D point (origin of the axis)

required
direction ndarray

3D direction of the axis

required
tolerance float

Tolerance. Defaults to 1e-9.

1e-09

Returns:

Name Type Description
float

Moment of inertia

Source code in src/occwl/base.py
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
def moment_of_inertia(self, point, direction, tolerance=1e-9):
    """
    Compute the moment of inertia about an axis

    Args:
        point (np.ndarray): 3D point (origin of the axis)
        direction (np.ndarray): 3D direction of the axis
        tolerance (float, optional): Tolerance. Defaults to 1e-9.

    Returns:
        float: Moment of inertia
    """
    from occwl.geometry import geom_utils
    props = GProp_GProps()
    brepgprop_VolumeProperties(self.topods_shape(), props, tolerance)
    axis = gp_Ax1(
        geom_utils.numpy_to_gp(point), geom_utils.numpy_to_gp_dir(direction)
    )
    return props.MomentOfInertia(axis)

num_edges()

Number of edges in the Shape

Returns:

Name Type Description
int

Number of edges

Source code in src/occwl/base.py
50
51
52
53
54
55
56
57
def num_edges(self):
    """
    Number of edges in the Shape

    Returns:
        int: Number of edges
    """
    return self._top_exp.number_of_edges()

num_faces()

Number of faces in the Shape

Returns:

Name Type Description
int

Number of faces

Source code in src/occwl/base.py
156
157
158
159
160
161
162
163
def num_faces(self):
    """
    Number of faces in the Shape

    Returns:
        int: Number of faces
    """
    return self._top_exp.number_of_faces()

num_shells()

Number of shells in the Shape

Returns:

Name Type Description
int

Number of shells

Source code in src/occwl/base.py
267
268
269
270
271
272
273
274
def num_shells(self):
    """
    Number of shells in the Shape

    Returns:
        int: Number of shells
    """
    return self._top_exp.number_of_shells()

num_vertices()

Number of vertices in the Shape

Returns:

Name Type Description
int

Number of vertices

Source code in src/occwl/base.py
25
26
27
28
29
30
31
32
def num_vertices(self):
    """
    Number of vertices in the Shape

    Returns:
        int: Number of vertices
    """
    return self._top_exp.number_of_vertices()

num_wires()

Number of wires in the Shape

Returns:

Name Type Description
int

Number of wires

Source code in src/occwl/base.py
131
132
133
134
135
136
137
138
def num_wires(self):
    """
    Number of wires in the Shape

    Returns:
        int: Number of wires
    """
    return self._top_exp.number_of_wires()

occwl_shape(topods_shape) staticmethod

Static method to create an occwl shape of the appropriate class from the given topods_shape Args: topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

Returns:

Type Description

One of occwl.compound.Compound occwl.solid.Solid occwl.face.Face occwl.edge.Edge occwl.vertex.Vertex occwl.wire.Wire occwl.shell.Shell

Raises: Exception: [description]

Source code in src/occwl/shape.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@staticmethod
def occwl_shape(topods_shape):
    """
    Static method to create an occwl shape of the appropriate 
    class from the given topods_shape
    Args:
        topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

    Returns:
        One of
            occwl.compound.Compound
            occwl.solid.Solid
            occwl.face.Face
            occwl.edge.Edge
            occwl.vertex.Vertex
            occwl.wire.Wire
            occwl.shell.Shell
    Raises:
        Exception: [description]
    """
    from occwl.compound import Compound
    from occwl.solid import Solid
    from occwl.face import Face
    from occwl.edge import Edge
    from occwl.vertex import Vertex
    from occwl.wire import Wire
    from occwl.shell import Shell

    if isinstance(topods_shape, TopoDS_Vertex):
        return Vertex(topods_shape)
    if isinstance(topods_shape, TopoDS_Edge):
        return Edge(topods_shape)
    if isinstance(topods_shape, TopoDS_Face):
        return Face(topods_shape)
    if isinstance(topods_shape, TopoDS_Wire):
        return Wire(topods_shape)
    if isinstance(topods_shape, TopoDS_Shell):
        return Shell(topods_shape)
    if isinstance(topods_shape, TopoDS_Solid):
        return Solid(topods_shape)
    if isinstance(topods_shape, (TopoDS_Compound, TopoDS_CompSolid)):
        return Compound(topods_shape)
    raise Exception(
        "Shape must be one of TopoDS_Vertex, TopoDS_Edge, TopoDS_Face, TopoDS_Shell, TopoDS_Solid, TopoDS_Compound, TopoDS_CompSolid"
    )

reversed()

Whether this shape is reversed.

  • For an edge this is whether the edge is reversed with respect to the curve geometry
  • For a face this is whether the face is reversed with respect to the surface geometry
  • For a vertex this is whether the vertex is at the upper or lower parameter value on the edges curve

Returns:

Name Type Description
bool

If rational

Source code in src/occwl/shape.py
236
237
238
239
240
241
242
243
244
245
246
247
248
def reversed(self):
    """
    Whether this shape is reversed.

    - For an edge this is whether the edge is reversed with respect to the curve geometry
    - For a face this is whether the face is reversed with respect to the surface geometry
    - For a vertex this is whether the vertex is at the upper or lower parameter value on the
      edges curve

    Returns:
        bool: If rational
    """
    return self.topods_shape().Orientation() == TopAbs_REVERSED

rotate_axis_angle(axis, angle_radians, origin=np.zeros(3, dtype=(np.float32)))

Rotate the shape about the given axis by the given angle in radians

Parameters:

Name Type Description Default
axis ndarray

Rotation axis

required
angle_radians float

Angle in radians

required
Source code in src/occwl/shape.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
def rotate_axis_angle(
    self, axis, angle_radians, origin=np.zeros(3, dtype=np.float32)
):
    """
    Rotate the shape about the given axis by the given angle in radians

    Args:
        axis (np.ndarray): Rotation axis
        angle_radians (float): Angle in radians
    """
    self._shape = rotate_shape(
        self._shape,
        gp_Ax1(geom_utils.numpy_to_gp(origin), geom_utils.numpy_to_gp_dir(axis)),
        angle_radians,
        unite="rad",
    )

rotate_euler_angles(angles_xyz_radians)

Rotate the shape by the given Euler angles in radians

Parameters:

Name Type Description Default
angle_xyz_radians ndarray

3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians

required
Source code in src/occwl/shape.py
302
303
304
305
306
307
308
309
310
311
312
313
314
315
def rotate_euler_angles(self, angles_xyz_radians):
    """
    Rotate the shape by the given Euler angles in radians

    Args:
        angle_xyz_radians (np.ndarray): 3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians
    """
    self._shape = rotate_shp_3_axis(
        self._shape,
        angles_xyz_radians[0],
        angles_xyz_radians[1],
        angles_xyz_radians[2],
        unity="rad",
    )

save_shapes_to_occ_native(filename, shapes, with_triangles=False, with_normals=False, format_version=None) staticmethod

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
@staticmethod
def save_shapes_to_occ_native(
        filename, 
        shapes,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
            is between one and two orders of magnitude faster 
            than loading from STEP, so it is recommended for 
            large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename

        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    new_api = False
    shapes_set = BRepTools_ShapeSet(with_triangles)
    # shapes_set.SetWithNormals(with_normals) # Not in OCC 7.5.0

    for shp in shapes:
        shapes_set.Add(shp.topods_shape())
    if format_version is not None:
        shapes_set.SetFormatNb(format_version)


    with open(filename, "w") as fp:
        s = shapes_set.WriteToString()
        fp.write(s)

save_to_occ_native(filename, verbosity=False, with_triangles=False, with_normals=False, format_version=None)

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def save_to_occ_native(
        self, 
        filename, 
        verbosity=False,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
           is between one and two orders of magnitude faster 
           than loading from STEP, so it is recommended for 
           large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename
        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    self.save_shapes_to_occ_native(
        filename, 
        [ self ],
        with_triangles=with_triangles,
        with_normals=with_normals,
        format_version=format_version
    )

scale(scale_vector)

Scale the shape by the given 3D vector

Parameters:

Name Type Description Default
scale_vector ndarray

3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively

required
Source code in src/occwl/shape.py
317
318
319
320
321
322
323
324
325
326
def scale(self, scale_vector):
    """
    Scale the shape by the given 3D vector

    Args:
        scale_vector (np.ndarray): 3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively
    """
    self._shape = scale_shape(
        self._shape, scale_vector[0], scale_vector[1], scale_vector[2]
    )

scale_to_box(box_side, copy=True)

Translate and scale the Shape so it fits exactly into the [-box_side, box_side]^3 box

Returns:

Type Description

occwl..: The scaled version of this Shape

Source code in src/occwl/base.py
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
def scale_to_box(self, box_side, copy=True):
    """
    Translate and scale the Shape so it fits exactly 
    into the [-box_side, box_side]^3 box

    Args:
        box_side (float) The side length of the box
        copy (bool)      True - Copy entities and apply the transform to
                                the underlying geometry
                         False - Apply the transform to the topods Locator
                                 if possible 

    Returns:
        occwl.*.*: The scaled version of this Shape
    """
    from occwl.geometry import geom_utils
    # Get an exact box for the Shape
    box = self.exact_box()
    center = box.center()
    longest_length = box.max_box_length()

    orig = gp_Pnt(0.0, 0.0, 0.0)
    center = geom_utils.numpy_to_gp(center)
    vec_center_to_orig = gp_Vec(center, orig)
    move_to_center = gp_Trsf()
    move_to_center.SetTranslation(vec_center_to_orig)

    scale_trsf = gp_Trsf()
    scale_trsf.SetScale(orig, (2.0 * box_side) / longest_length)
    trsf_to_apply = scale_trsf.Multiplied(move_to_center)

    return self._apply_transform(trsf_to_apply, copy=copy)

scale_to_unit_box(copy=True)

Translate and scale the Shape so it fits exactly into the [-1, 1]^3 box

Returns: The scaled version of this shape

Source code in src/occwl/base.py
628
629
630
631
632
633
634
635
636
637
638
639
640
641
def scale_to_unit_box(self, copy=True):
    """
    Translate and scale the Shape so it fits exactly 
    into the [-1, 1]^3 box

    Args:
        copy (bool)      True - Copy entities and apply the transform to
                                    the underlying geometry
                            False - Apply the transform to the topods Locator
                                    if possible 
    Returns:
        The scaled version of this shape
    """
    return self.scale_to_box(1.0, copy=copy)

set_transform_to_identity()

When an assembly is loaded from a STEP file the solids will be transformed relative to their local coordinate system. i.e. they are placed in the assembly root components coordinate system.

When working with individual bodies you often want them to be axis aligned, in which case you want to remove the assembly transform. This function removes it for you.

If however you want to bake the transform into the bodies and suppress the asserts from parts of occwl which don't cope with transforms then use the transform() function below with copy=True

Source code in src/occwl/shape.py
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def set_transform_to_identity(self):
    """
    When an assembly is loaded from a STEP file
    the solids will be transformed relative to
    their local coordinate system.   i.e. they
    are placed in the assembly root components 
    coordinate system.

    When working with individual bodies you often
    want them to be axis aligned, in which case 
    you want to remove the assembly transform.
    This function removes it for you.

    If however you want to bake the transform
    into the bodies and suppress the asserts 
    from parts of occwl which don't cope with
    transforms then use the transform() function
    below with copy=True
    """
    identity = TopLoc_Location()
    self.topods_shape().Location(identity)
    self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)
    self.convert_geometric_identity_transforms_to_identity()

shells()

Get an iterator to go over all shells in the Shape

Returns:

Type Description

Iterator[occwl.shell.Shell]: Shell iterator

Source code in src/occwl/base.py
276
277
278
279
280
281
282
283
284
def shells(self):
    """
    Get an iterator to go over all shells in the Shape

    Returns:
        Iterator[occwl.shell.Shell]: Shell iterator
    """
    from occwl.shell import Shell
    return map(Shell, self._top_exp.shells())

split_all_closed_edges(max_tol=0.01, precision=0.01, num_splits=1)

Split all the closed edges in this shape

Parameters:

Name Type Description Default
max_tol float

Maximum tolerance allowed. Defaults to 0.01.

0.01
precision float

Precision of the tool when splitting. Defaults to 0.01.

0.01
num_splits int

Number of splits to perform. Each split edge will result in num_splits + 1 edges. Defaults to 1.

1

Returns:

Type Description

occwl..: Shape with closed edges split

Source code in src/occwl/base.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def split_all_closed_edges(self, max_tol=0.01, precision=0.01, num_splits=1):
    """
    Split all the closed edges in this shape

    Args:
        max_tol (float, optional): Maximum tolerance allowed. Defaults to 0.01.
        precision (float, optional): Precision of the tool when splitting. Defaults to 0.01.
        num_splits (int, optional): Number of splits to perform. Each split edge will result in num_splits + 1 edges. Defaults to 1.

    Returns:
        occwl.*.*: Shape with closed edges split
    """
    divider = ShapeUpgrade_ShapeDivideClosedEdges(self.topods_shape())
    divider.SetPrecision(precision)
    divider.SetMinTolerance(0.1 * max_tol)
    divider.SetMaxTolerance(max_tol)
    divider.SetNbSplitPoints(num_splits)
    ok = divider.Perform()
    if not ok:
        # Splitting failed or there were no closed edges to split
        # Return the original shape
        return self
    return type(self)(divider.Result())

split_all_closed_faces(max_tol=0.01, precision=0.01, num_splits=1)

Split all the closed faces in this shape

Parameters:

Name Type Description Default
max_tol float

Maximum tolerance allowed. Defaults to 0.01.

0.01
precision float

Precision of the tool when splitting. Defaults to 0.01.

0.01
num_splits int

Number of splits to perform. Each split face will result in num_splits + 1 faces. Defaults to 1.

1

Returns:

Type Description

occwl..: Shape with closed faces split

Source code in src/occwl/base.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def split_all_closed_faces(self, max_tol=0.01, precision=0.01, num_splits=1):
    """
    Split all the closed faces in this shape

    Args:
        max_tol (float, optional): Maximum tolerance allowed. Defaults to 0.01.
        precision (float, optional): Precision of the tool when splitting. Defaults to 0.01.
        num_splits (int, optional): Number of splits to perform. Each split face will result in num_splits + 1 faces. Defaults to 1.

    Returns:
        occwl.*.*: Shape with closed faces split
    """
    divider = ShapeUpgrade_ShapeDivideClosed(self.topods_shape())
    divider.SetPrecision(precision)
    divider.SetMinTolerance(0.1 * max_tol)
    divider.SetMaxTolerance(max_tol)
    divider.SetNbSplitPoints(num_splits)
    ok = divider.Perform()
    if not ok:
        # Splitting failed or there were no closed faces to split
        # Return the original shape
        return self
    return type(self)(divider.Result())

topods_shape()

Get the underlying OCC shape

Returns:

Type Description

OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*

Source code in src/occwl/shape.py
80
81
82
83
84
85
86
87
def topods_shape(self):
    """
    Get the underlying OCC shape

    Returns:
        OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*
    """
    return self._shape

transform(a, copy=True)

Apply the given 3x4 transform matrix to the solid.

 copy (bool)    True - Copy entities and apply the transform to
                       the underlying geometry
                False - Apply the transform to the topods Locator
                        if possible
Source code in src/occwl/shape.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
def transform(self, a: np.ndarray, copy=True):
    """
    Apply the given 3x4 transform matrix to the solid.

    Args: a (nd.array) - Homogeneous transform matrix
                         The transform that will be applied is

                         x' =  a[0,0]*x + a[0,1]*y + a[0,2]*z + a[0, 3]
                         y' =  a[1,0]*x + a[1,1]*y + a[1,2]*z + a[1, 3]
                         z' =  a[2,0]*x + a[2,1]*y + a[2,2]*z + a[2, 3]

         copy (bool)    True - Copy entities and apply the transform to
                               the underlying geometry
                        False - Apply the transform to the topods Locator
                                if possible 
    """
    assert (a.shape == (3, 4)), "Transform matrix must be 3x4"
    a = a.astype(np.float64)

    # Create an identity transform
    trsf = gp_Trsf()

    # If the matrix is an identity matrix then
    # we don't want to set the values as this
    # would give us a geometric identity without
    # the identity flag set
    if not np.allclose(a, np.eye(3, 4)):
        trsf.SetValues(
            a[0,0], a[0,1], a[0,2], a[0, 3],
            a[1,0], a[1,1], a[1,2], a[1, 3],
            a[2,0], a[2,1], a[2,2], a[2, 3]
        )
    return self._apply_transform(trsf, copy=copy)

translate(offset)

Translate the shape by an offset vector

Parameters:

Name Type Description Default
offset ndarray

Offset vector

required
Source code in src/occwl/shape.py
276
277
278
279
280
281
282
283
def translate(self, offset):
    """
    Translate the shape by an offset vector

    Args:
        offset (np.ndarray): Offset vector
    """
    self._shape = translate_shp(self._shape, geom_utils.numpy_to_gp_vec(offset))

triangulate(triangle_face_tol=0.01, tol_relative_to_face=True, angle_tol_rads=0.1)

Triangulate all the faces in the shape. You can then get the triangles from each face separately using face.get_triangles(). If you wanted triangles for the entire shape then call shape.get_triangles() below. For more details see https://old.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11

Parameters:

Name Type Description Default
triangle_face_tol float

Tolerance between triangle and surface. Defaults to 0.01.

0.01
tol_relative_to_face bool

Whether tolerance is relative to face size

True
angle_tol_rads float

Angle tolerance in radians. Defaults to 0.1.

0.1

Returns:

Name Type Description
bool

Is successful

Source code in src/occwl/base.py
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
def triangulate(
    self,
    triangle_face_tol=0.01,  # Tolerance between triangle and surface
    tol_relative_to_face=True,  # The tolerance value is relative to the face size
    angle_tol_rads=0.1,  # Angle between normals/tangents at triangle vertices
):
    """
    Triangulate all the faces in the shape. You can then get the triangles 
    from each face separately using face.get_triangles().
    If you wanted triangles for the entire shape then call
    shape.get_triangles() below.
    For more details see 
    https://old.opencascade.com/doc/occt-7.1.0/overview/html/occt_user_guides__modeling_algos.html#occt_modalg_11

    Args:
        triangle_face_tol (float, optional): Tolerance between triangle and surface. Defaults to 0.01.
        tol_relative_to_face (bool): Whether tolerance is relative to face size
        angle_tol_rads (float, optional): Angle tolerance in radians. Defaults to 0.1.

    Returns:
        bool: Is successful
    """
    mesh = BRepMesh_IncrementalMesh(
        self.topods_shape(),
        triangle_face_tol,
        tol_relative_to_face,
        angle_tol_rads,
        True,
    )
    mesh.Perform()
    return mesh.IsDone()

valid(return_analyzer=False)

Check if the shape is valid

Parameters:

Name Type Description Default
return_analyzer bool

Whether to return the BRepCheck_Analyzer object for more inspection

False

Returns:

Name Type Description
bool

Whether the shape is valid

BRepCheck_Analyzer [optional]: if return_analyzer is True

Source code in src/occwl/shape.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def valid(self, return_analyzer=False):
    """
    Check if the shape is valid

    Args:
        return_analyzer (bool): Whether to return the BRepCheck_Analyzer object for more inspection

    Returns:
        bool: Whether the shape is valid
        BRepCheck_Analyzer [optional]: if return_analyzer is True
    """
    analyzer = BRepCheck_Analyzer(self.topods_shape())
    if return_analyzer:
        return analyzer.IsValid(), analyzer
    return analyzer.IsValid()

vertices()

Get an iterator to go over all vertices in the Shape

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
34
35
36
37
38
39
40
41
42
def vertices(self):
    """
    Get an iterator to go over all vertices in the Shape

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    return map(Vertex, self._top_exp.vertices())

vertices_from_edge(edge)

Get an iterator to go over the vertices bounding an edge

Parameters:

Name Type Description Default
edge Edge

Input edge

required

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def vertices_from_edge(self, edge):
    """
    Get an iterator to go over the vertices bounding an edge

    Args:
        edge (occwl.edge.Edge): Input edge

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    from occwl.edge import Edge
    assert isinstance(edge, Edge)
    return map(Vertex, self._top_exp.vertices_from_edge(edge.topods_shape()))

vertices_from_face(face)

Get an iterator to go over the vertices in a face

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: Vertex iterator

Source code in src/occwl/base.py
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def vertices_from_face(self, face):
    """
    Get an iterator to go over the vertices in a face

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.vertex.Vertex]: Vertex iterator
    """
    from occwl.vertex import Vertex
    from occwl.face import Face
    assert isinstance(face, Face)
    return map(Vertex, self._top_exp.vertices_from_face(face.topods_shape()))

volume(tolerance=1e-09)

Compute the volume of the Shape

Parameters:

Name Type Description Default
tolerance float

Tolerance. Defaults to 1e-9.

1e-09

Returns:

Name Type Description
float

Volume

Source code in src/occwl/base.py
509
510
511
512
513
514
515
516
517
518
519
520
521
def volume(self, tolerance=1e-9):
    """
    Compute the volume of the Shape

    Args:
        tolerance (float, optional): Tolerance. Defaults to 1e-9.

    Returns:
        float: Volume
    """
    props = GProp_GProps()
    brepgprop_VolumeProperties(self.topods_shape(), props, tolerance)
    return props.Mass()

wires()

Get an iterator to go over all wires in the Shape

Returns:

Type Description

Iterator[occwl.wire.Wire]: Wire iterator

Source code in src/occwl/base.py
140
141
142
143
144
145
146
147
148
def wires(self):
    """
    Get an iterator to go over all wires in the Shape

    Returns:
        Iterator[occwl.wire.Wire]: Wire iterator
    """
    from occwl.wire import Wire
    return map(Wire, self._top_exp.wires())

wires_from_face(face)

Get an iterator to go over the wires bounding a face

Parameters:

Name Type Description Default
face Face

Input face

required

Returns:

Type Description

Iterator[occwl.wire.Wire]: Wire iterator

Source code in src/occwl/base.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
def wires_from_face(self, face):
    """
    Get an iterator to go over the wires bounding a face

    Args:
        face (occwl.face.Face): Input face

    Returns:
        Iterator[occwl.wire.Wire]: Wire iterator
    """
    from occwl.wire import Wire
    from occwl.face import Face
    assert isinstance(face, Face)
    return map(Wire, self._top_exp.wires_from_face(face.topods_shape()))

uvgrid

ugrid(edge, num_u=10, us=False, method='point', reverse_order_with_edge=True)

Creates a 1D UV-grid of samples from the given edge edge (occwl.edge.Edge): A B-rep edge num_u (int): Number of samples along the curve. Defaults to 10/ us (bool): Return the u values at which the quantity were evaluated method (str): Name of the method in the occwl.edge.Edge object to be called (the method has to accept the u value as argument). Defaults to "point". Returns: np.ndarray: 1D array of quantity evaluated on the edge geometry np.ndarray (optional): 1D array of u-values where evaluation was done

Source code in src/occwl/uvgrid.py
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
def ugrid(edge, num_u: int = 10, us=False, method="point", reverse_order_with_edge=True):
    """ 
    Creates a 1D UV-grid of samples from the given edge
        edge (occwl.edge.Edge): A B-rep edge
        num_u (int): Number of samples along the curve. Defaults to 10/
        us (bool): Return the u values at which the quantity were evaluated
        method (str): Name of the method in the occwl.edge.Edge object to be called 
                      (the method has to accept the u value as argument). Defaults to "point".
    Returns:
        np.ndarray: 1D array of quantity evaluated on the edge geometry
        np.ndarray (optional): 1D array of u-values where evaluation was done
    """
    assert num_u >= 2
    ugrid = []
    u_values = np.zeros((num_u), dtype=np.float32)

    if type(edge.curve()) is float:
        # Can't get an curve for this edge.
        if us:
            return None, u_values
        return None

    bound = edge.u_bounds()
    fn = getattr(edge, method)

    for i in range(num_u):
        u = bound.interpolate(float(i) / (num_u - 1))
        u_values[i] = u
        val = fn(u)
        ugrid.append(val)

    ugrid = np.asarray(ugrid).reshape((num_u, -1))
    if reverse_order_with_edge:
        if edge.reversed():
            ugrid = _ugrid_reverse_u(ugrid)
            u_values = u_values[::-1]
    if us:
        return ugrid, u_values
    return ugrid

uvgrid(face, num_u=10, num_v=10, uvs=False, method='point', reverse_order_with_face=True)

Creates a 2D UV-grid of samples from the given face

Parameters:

Name Type Description Default
face Face

A B-rep face

required
num_u int

Number of samples along u-direction. Defaults to 10.

10
num_v int

Number of samples along v-direction. Defaults to 10.

10
uvs bool

Return the surface UVs where quantities are evaluated. Defaults to False.

False
method str

Name of the method in the occwl.face.Face object to be called (the method has to accept the uv value as argument). Defaults to "point".

'point'

Returns:

Type Description

np.ndarray: 2D array of quantity evaluated on the face geometry

np.ndarray (optional): 2D array of uv-values where evaluation was done

Source code in src/occwl/uvgrid.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def uvgrid(face, num_u=10, num_v=10, uvs=False, method="point", reverse_order_with_face=True):
    """ 
    Creates a 2D UV-grid of samples from the given face

    Args:
        face (occwl.face.Face): A B-rep face
        num_u (int): Number of samples along u-direction. Defaults to 10.
        num_v (int): Number of samples along v-direction. Defaults to 10.
        uvs (bool): Return the surface UVs where quantities are evaluated. Defaults to False.
        method (str): Name of the method in the occwl.face.Face object to be called 
                      (the method has to accept the uv value as argument). Defaults to "point".

    Returns:
        np.ndarray: 2D array of quantity evaluated on the face geometry
        np.ndarray (optional): 2D array of uv-values where evaluation was done
    """
    assert num_u >= 2
    assert num_v >= 2
    uv_box = face.uv_bounds()

    fn = getattr(face, method)

    uvgrid = []
    uv_values = np.zeros((num_u, num_v, 2), dtype=np.float32)

    if type(face.surface()) is float:
        # Can't get an curve for this face.
        if uvs:
            return None, uv_values
        return None

    for i in range(num_u):
        u = uv_box.intervals[0].interpolate(float(i) / (num_u - 1))
        for j in range(num_v):
            v = uv_box.intervals[1].interpolate(float(j) / (num_v - 1))
            uv = np.array([u, v])
            uv_values[i, j] = uv
            val = fn(uv)
            uvgrid.append(val)
    uvgrid = np.asarray(uvgrid).reshape((num_u, num_v, -1))

    if reverse_order_with_face:
        if face.reversed():
            uvgrid = _uvgrid_reverse_u(uvgrid)
            uv_values = _uvgrid_reverse_u(uv_values)

    if uvs:
        return uvgrid, uv_values
    return uvgrid

vertex

Vertex

Bases: Shape

A topological vertex in a solid model Represents a 3D geometric point

Source code in src/occwl/vertex.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Vertex(Shape):
    """
    A topological vertex in a solid model
    Represents a 3D geometric point
    """

    def __init__(self, topods_vertex):
        """
        Constructor to initialize a vertex from a TodoDS_Vertex

        Args:
            topods_vertex (OCC.Core.TopoDS.TopoDS_Vertex): OCC Vertex
        """
        assert isinstance(topods_vertex, TopoDS_Vertex)
        super().__init__(topods_vertex)

    @staticmethod
    def make_vertex(point):
        """
        Create a vertex from a 3D point

        Args:
            point (np.ndarray): 3D Point

        Returns:
            occwl.Vertex: Vertex representing the 3D point
        """
        occ_point = geom_utils.numpy_to_gp(point)
        vertex_maker = BRepBuilderAPI_MakeVertex(occ_point)
        vertex = vertex_maker.Shape()
        return Vertex(vertex)

    def point(self):
        """
        3D point stored in this vertex

        Returns:
            np.ndarray: 3D Point
        """
        pt = BRep_Tool.Pnt(self.topods_shape())
        return geom_utils.gp_to_numpy(pt)

__eq__(other)

Equality check for the shape

NOTE: This function only checks if the shape is the same. It doesn't check the edge orienation for example, so

edge1 == edge2

does not necessarily mean

edge1.reversed() == edge2.reversed()

Source code in src/occwl/shape.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def __eq__(self, other):
    """
    Equality check for the shape

    NOTE: This function only checks if the shape is the same.
    It doesn't check the edge orienation for example, so 

    edge1 == edge2

    does not necessarily mean 

    edge1.reversed() == edge2.reversed()
    """
    return self.topods_shape().__hash__() == other.topods_shape().__hash__()

__hash__()

Hash for the shape

Returns:

Name Type Description
int

Hash value

Source code in src/occwl/shape.py
135
136
137
138
139
140
141
142
def __hash__(self):
    """
    Hash for the shape

    Returns:
        int: Hash value
    """
    return self.topods_shape().__hash__()

__init__(topods_vertex)

Constructor to initialize a vertex from a TodoDS_Vertex

Parameters:

Name Type Description Default
topods_vertex TopoDS_Vertex

OCC Vertex

required
Source code in src/occwl/vertex.py
18
19
20
21
22
23
24
25
26
def __init__(self, topods_vertex):
    """
    Constructor to initialize a vertex from a TodoDS_Vertex

    Args:
        topods_vertex (OCC.Core.TopoDS.TopoDS_Vertex): OCC Vertex
    """
    assert isinstance(topods_vertex, TopoDS_Vertex)
    super().__init__(topods_vertex)

convert_geometric_identity_transforms_to_identity()

Open Cascade models sometimes contain transforms which are "geometrically" identify transforms, but the identity flag is not set.

This function checks each transform and sets the flag if the appropriate.

Source code in src/occwl/shape.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def convert_geometric_identity_transforms_to_identity(self):
    """
    Open Cascade models sometimes contain transforms which
    are "geometrically" identify transforms, but the identity
    flag is not set.

    This function checks each transform and sets the flag if 
    the appropriate.
    """
    identity = TopLoc_Location()
    if geom_utils.is_geometric_identity(
        self.topods_shape().Location().Transformation()
    ):
        self.topods_shape().Location(identity)
        self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)

    for face in self._top_exp.faces():
        if geom_utils.is_geometric_identity(face.Location().Transformation()):
            face.Location(identity)

    for edge in self._top_exp.edges():
        if geom_utils.is_geometric_identity(edge.Location().Transformation()):
            edge.Location(identity)

    for vertex in self._top_exp.vertices():
        if geom_utils.is_geometric_identity(vertex.Location().Transformation()):
            vertex.Location(identity)

find_closest_point_data(datum)

Find the information about the closest point on this shape

Parameters:

Name Type Description Default
datum ndarray

3D Point

required

Returns:

Name Type Description
ClosestPointData

Data about the closest point on this shape

None

if error

Source code in src/occwl/shape.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
def find_closest_point_data(self, datum):
    """
    Find the information about the closest point on this shape

    Args:
        datum (np.ndarray): 3D Point

    Returns:
        ClosestPointData: Data about the closest point on this shape
        None: if error
    """
    # Folowing https://dev.opencascade.org/content/how-retrieve-nearest-face-shape-given-gppnt
    # Create a vertex from the point
    occ_point = geom_utils.numpy_to_gp(datum)
    vertex_maker = BRepBuilderAPI_MakeVertex(occ_point)
    vertex = vertex_maker.Shape()
    dist_shape_shape = BRepExtrema_DistShapeShape(
        vertex, self.topods_shape(), Extrema_ExtFlag_MIN
    )
    ok = dist_shape_shape.Perform()
    if not ok:
        return None

    return ClosestPointData(dist_shape_shape)

make_vertex(point) staticmethod

Create a vertex from a 3D point

Parameters:

Name Type Description Default
point ndarray

3D Point

required

Returns:

Type Description

occwl.Vertex: Vertex representing the 3D point

Source code in src/occwl/vertex.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@staticmethod
def make_vertex(point):
    """
    Create a vertex from a 3D point

    Args:
        point (np.ndarray): 3D Point

    Returns:
        occwl.Vertex: Vertex representing the 3D point
    """
    occ_point = geom_utils.numpy_to_gp(point)
    vertex_maker = BRepBuilderAPI_MakeVertex(occ_point)
    vertex = vertex_maker.Shape()
    return Vertex(vertex)

occwl_shape(topods_shape) staticmethod

Static method to create an occwl shape of the appropriate class from the given topods_shape Args: topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

Returns:

Type Description

One of occwl.compound.Compound occwl.solid.Solid occwl.face.Face occwl.edge.Edge occwl.vertex.Vertex occwl.wire.Wire occwl.shell.Shell

Raises: Exception: [description]

Source code in src/occwl/shape.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@staticmethod
def occwl_shape(topods_shape):
    """
    Static method to create an occwl shape of the appropriate 
    class from the given topods_shape
    Args:
        topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

    Returns:
        One of
            occwl.compound.Compound
            occwl.solid.Solid
            occwl.face.Face
            occwl.edge.Edge
            occwl.vertex.Vertex
            occwl.wire.Wire
            occwl.shell.Shell
    Raises:
        Exception: [description]
    """
    from occwl.compound import Compound
    from occwl.solid import Solid
    from occwl.face import Face
    from occwl.edge import Edge
    from occwl.vertex import Vertex
    from occwl.wire import Wire
    from occwl.shell import Shell

    if isinstance(topods_shape, TopoDS_Vertex):
        return Vertex(topods_shape)
    if isinstance(topods_shape, TopoDS_Edge):
        return Edge(topods_shape)
    if isinstance(topods_shape, TopoDS_Face):
        return Face(topods_shape)
    if isinstance(topods_shape, TopoDS_Wire):
        return Wire(topods_shape)
    if isinstance(topods_shape, TopoDS_Shell):
        return Shell(topods_shape)
    if isinstance(topods_shape, TopoDS_Solid):
        return Solid(topods_shape)
    if isinstance(topods_shape, (TopoDS_Compound, TopoDS_CompSolid)):
        return Compound(topods_shape)
    raise Exception(
        "Shape must be one of TopoDS_Vertex, TopoDS_Edge, TopoDS_Face, TopoDS_Shell, TopoDS_Solid, TopoDS_Compound, TopoDS_CompSolid"
    )

point()

3D point stored in this vertex

Returns:

Type Description

np.ndarray: 3D Point

Source code in src/occwl/vertex.py
44
45
46
47
48
49
50
51
52
def point(self):
    """
    3D point stored in this vertex

    Returns:
        np.ndarray: 3D Point
    """
    pt = BRep_Tool.Pnt(self.topods_shape())
    return geom_utils.gp_to_numpy(pt)

reversed()

Whether this shape is reversed.

  • For an edge this is whether the edge is reversed with respect to the curve geometry
  • For a face this is whether the face is reversed with respect to the surface geometry
  • For a vertex this is whether the vertex is at the upper or lower parameter value on the edges curve

Returns:

Name Type Description
bool

If rational

Source code in src/occwl/shape.py
236
237
238
239
240
241
242
243
244
245
246
247
248
def reversed(self):
    """
    Whether this shape is reversed.

    - For an edge this is whether the edge is reversed with respect to the curve geometry
    - For a face this is whether the face is reversed with respect to the surface geometry
    - For a vertex this is whether the vertex is at the upper or lower parameter value on the
      edges curve

    Returns:
        bool: If rational
    """
    return self.topods_shape().Orientation() == TopAbs_REVERSED

rotate_axis_angle(axis, angle_radians, origin=np.zeros(3, dtype=(np.float32)))

Rotate the shape about the given axis by the given angle in radians

Parameters:

Name Type Description Default
axis ndarray

Rotation axis

required
angle_radians float

Angle in radians

required
Source code in src/occwl/shape.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
def rotate_axis_angle(
    self, axis, angle_radians, origin=np.zeros(3, dtype=np.float32)
):
    """
    Rotate the shape about the given axis by the given angle in radians

    Args:
        axis (np.ndarray): Rotation axis
        angle_radians (float): Angle in radians
    """
    self._shape = rotate_shape(
        self._shape,
        gp_Ax1(geom_utils.numpy_to_gp(origin), geom_utils.numpy_to_gp_dir(axis)),
        angle_radians,
        unite="rad",
    )

rotate_euler_angles(angles_xyz_radians)

Rotate the shape by the given Euler angles in radians

Parameters:

Name Type Description Default
angle_xyz_radians ndarray

3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians

required
Source code in src/occwl/shape.py
302
303
304
305
306
307
308
309
310
311
312
313
314
315
def rotate_euler_angles(self, angles_xyz_radians):
    """
    Rotate the shape by the given Euler angles in radians

    Args:
        angle_xyz_radians (np.ndarray): 3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians
    """
    self._shape = rotate_shp_3_axis(
        self._shape,
        angles_xyz_radians[0],
        angles_xyz_radians[1],
        angles_xyz_radians[2],
        unity="rad",
    )

save_shapes_to_occ_native(filename, shapes, with_triangles=False, with_normals=False, format_version=None) staticmethod

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
@staticmethod
def save_shapes_to_occ_native(
        filename, 
        shapes,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
            is between one and two orders of magnitude faster 
            than loading from STEP, so it is recommended for 
            large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename

        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    new_api = False
    shapes_set = BRepTools_ShapeSet(with_triangles)
    # shapes_set.SetWithNormals(with_normals) # Not in OCC 7.5.0

    for shp in shapes:
        shapes_set.Add(shp.topods_shape())
    if format_version is not None:
        shapes_set.SetFormatNb(format_version)


    with open(filename, "w") as fp:
        s = shapes_set.WriteToString()
        fp.write(s)

save_to_occ_native(filename, verbosity=False, with_triangles=False, with_normals=False, format_version=None)

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def save_to_occ_native(
        self, 
        filename, 
        verbosity=False,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
           is between one and two orders of magnitude faster 
           than loading from STEP, so it is recommended for 
           large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename
        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    self.save_shapes_to_occ_native(
        filename, 
        [ self ],
        with_triangles=with_triangles,
        with_normals=with_normals,
        format_version=format_version
    )

scale(scale_vector)

Scale the shape by the given 3D vector

Parameters:

Name Type Description Default
scale_vector ndarray

3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively

required
Source code in src/occwl/shape.py
317
318
319
320
321
322
323
324
325
326
def scale(self, scale_vector):
    """
    Scale the shape by the given 3D vector

    Args:
        scale_vector (np.ndarray): 3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively
    """
    self._shape = scale_shape(
        self._shape, scale_vector[0], scale_vector[1], scale_vector[2]
    )

set_transform_to_identity()

When an assembly is loaded from a STEP file the solids will be transformed relative to their local coordinate system. i.e. they are placed in the assembly root components coordinate system.

When working with individual bodies you often want them to be axis aligned, in which case you want to remove the assembly transform. This function removes it for you.

If however you want to bake the transform into the bodies and suppress the asserts from parts of occwl which don't cope with transforms then use the transform() function below with copy=True

Source code in src/occwl/shape.py
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def set_transform_to_identity(self):
    """
    When an assembly is loaded from a STEP file
    the solids will be transformed relative to
    their local coordinate system.   i.e. they
    are placed in the assembly root components 
    coordinate system.

    When working with individual bodies you often
    want them to be axis aligned, in which case 
    you want to remove the assembly transform.
    This function removes it for you.

    If however you want to bake the transform
    into the bodies and suppress the asserts 
    from parts of occwl which don't cope with
    transforms then use the transform() function
    below with copy=True
    """
    identity = TopLoc_Location()
    self.topods_shape().Location(identity)
    self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)
    self.convert_geometric_identity_transforms_to_identity()

topods_shape()

Get the underlying OCC shape

Returns:

Type Description

OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*

Source code in src/occwl/shape.py
80
81
82
83
84
85
86
87
def topods_shape(self):
    """
    Get the underlying OCC shape

    Returns:
        OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*
    """
    return self._shape

transform(a, copy=True)

Apply the given 3x4 transform matrix to the solid.

 copy (bool)    True - Copy entities and apply the transform to
                       the underlying geometry
                False - Apply the transform to the topods Locator
                        if possible
Source code in src/occwl/shape.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
def transform(self, a: np.ndarray, copy=True):
    """
    Apply the given 3x4 transform matrix to the solid.

    Args: a (nd.array) - Homogeneous transform matrix
                         The transform that will be applied is

                         x' =  a[0,0]*x + a[0,1]*y + a[0,2]*z + a[0, 3]
                         y' =  a[1,0]*x + a[1,1]*y + a[1,2]*z + a[1, 3]
                         z' =  a[2,0]*x + a[2,1]*y + a[2,2]*z + a[2, 3]

         copy (bool)    True - Copy entities and apply the transform to
                               the underlying geometry
                        False - Apply the transform to the topods Locator
                                if possible 
    """
    assert (a.shape == (3, 4)), "Transform matrix must be 3x4"
    a = a.astype(np.float64)

    # Create an identity transform
    trsf = gp_Trsf()

    # If the matrix is an identity matrix then
    # we don't want to set the values as this
    # would give us a geometric identity without
    # the identity flag set
    if not np.allclose(a, np.eye(3, 4)):
        trsf.SetValues(
            a[0,0], a[0,1], a[0,2], a[0, 3],
            a[1,0], a[1,1], a[1,2], a[1, 3],
            a[2,0], a[2,1], a[2,2], a[2, 3]
        )
    return self._apply_transform(trsf, copy=copy)

translate(offset)

Translate the shape by an offset vector

Parameters:

Name Type Description Default
offset ndarray

Offset vector

required
Source code in src/occwl/shape.py
276
277
278
279
280
281
282
283
def translate(self, offset):
    """
    Translate the shape by an offset vector

    Args:
        offset (np.ndarray): Offset vector
    """
    self._shape = translate_shp(self._shape, geom_utils.numpy_to_gp_vec(offset))

valid(return_analyzer=False)

Check if the shape is valid

Parameters:

Name Type Description Default
return_analyzer bool

Whether to return the BRepCheck_Analyzer object for more inspection

False

Returns:

Name Type Description
bool

Whether the shape is valid

BRepCheck_Analyzer [optional]: if return_analyzer is True

Source code in src/occwl/shape.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def valid(self, return_analyzer=False):
    """
    Check if the shape is valid

    Args:
        return_analyzer (bool): Whether to return the BRepCheck_Analyzer object for more inspection

    Returns:
        bool: Whether the shape is valid
        BRepCheck_Analyzer [optional]: if return_analyzer is True
    """
    analyzer = BRepCheck_Analyzer(self.topods_shape())
    if return_analyzer:
        return analyzer.IsValid(), analyzer
    return analyzer.IsValid()

viewer

OffscreenRenderer

Bases: _BaseViewer

Offscreen renderer that doesn't create a window. Useful for batch rendering.

Source code in src/occwl/viewer.py
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
class OffscreenRenderer(_BaseViewer):
    """
    Offscreen renderer that doesn't create a window. Useful for batch rendering.
    """
    def __init__(self, size: Optional[Tuple[int, int]] = (1024, 768),
        axes: Optional[bool] = True,
        background_top_color: Optional[List[int]] = [206, 215, 222],
        background_bottom_color: Optional[List[int]] = [128, 128, 128]
    ):
        """        
        Construct the OffscreenRenderer

        Args:
            size (Optional[Tuple[int, int]], optional): Size of the viewer window. Defaults to (1024, 768).
            axes (Optional[bool], optional): Show arrows for coordinate axes. Defaults to True.
            background_top_color (Optional[List[int]], optional): Background color at the top. Defaults to [206, 215, 222].
            background_bottom_color (Optional[List[int]], optional): Background color at the bottom. Defaults to [128, 128, 128].
        """
        super().__init__()
        self._display = Viewer3d()
        self._display.Create()
        if axes:
            self.show_axes()
        else:
            self.hide_axes()
        self.set_size(*size)
        self.set_background_color(background_top_color, background_bottom_color)
        self.shaded()

__init__(size=(1024, 768), axes=True, background_top_color=[206, 215, 222], background_bottom_color=[128, 128, 128])

Construct the OffscreenRenderer

Parameters:

Name Type Description Default
size Optional[Tuple[int, int]]

Size of the viewer window. Defaults to (1024, 768).

(1024, 768)
axes Optional[bool]

Show arrows for coordinate axes. Defaults to True.

True
background_top_color Optional[List[int]]

Background color at the top. Defaults to [206, 215, 222].

[206, 215, 222]
background_bottom_color Optional[List[int]]

Background color at the bottom. Defaults to [128, 128, 128].

[128, 128, 128]
Source code in src/occwl/viewer.py
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
def __init__(self, size: Optional[Tuple[int, int]] = (1024, 768),
    axes: Optional[bool] = True,
    background_top_color: Optional[List[int]] = [206, 215, 222],
    background_bottom_color: Optional[List[int]] = [128, 128, 128]
):
    """        
    Construct the OffscreenRenderer

    Args:
        size (Optional[Tuple[int, int]], optional): Size of the viewer window. Defaults to (1024, 768).
        axes (Optional[bool], optional): Show arrows for coordinate axes. Defaults to True.
        background_top_color (Optional[List[int]], optional): Background color at the top. Defaults to [206, 215, 222].
        background_bottom_color (Optional[List[int]], optional): Background color at the bottom. Defaults to [128, 128, 128].
    """
    super().__init__()
    self._display = Viewer3d()
    self._display.Create()
    if axes:
        self.show_axes()
    else:
        self.hide_axes()
    self.set_size(*size)
    self.set_background_color(background_top_color, background_bottom_color)
    self.shaded()

clear()

Clear all shapes from the viewer

Source code in src/occwl/viewer.py
246
247
248
249
250
def clear(self):
    """
    Clear all shapes from the viewer
    """
    self._display.EraseAll()

disable_antialiasing()

Disable antialiasing

Source code in src/occwl/viewer.py
333
334
335
336
337
def disable_antialiasing(self):
    """
    Disable antialiasing
    """
    self._display.DisableAntiAliasing()

display(shape, update=False, color=None, transparency=0.0)

Display a shape (must be a Solid, Face, or Edge)

Parameters:

Name Type Description Default
shape Solid, Face, or Edge

Shape to display

required
update bool

Whether to update and repaint. Defaults to False.

False
color str or tuple

Color of the shape. If str, can be 'WHITE', 'BLUE', 'RED', 'GREEN', 'YELLOW', 'CYAN', 'BLACK', or 'ORANGE'. Defaults to None.

None
transparency float

How transparent the shape is. Defaults to 0.0.

0.0
Source code in src/occwl/viewer.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def display(self, shape, update=False, color=None, transparency=0.0):
    """
    Display a shape (must be a Solid, Face, or Edge)

    Args:
        shape (Solid, Face, or Edge): Shape to display
        update (bool, optional): Whether to update and repaint. Defaults to False.
        color (str or tuple, optional): Color of the shape.
                                        If str, can be 'WHITE', 'BLUE', 'RED', 'GREEN', 'YELLOW',
                                       'CYAN', 'BLACK', or 'ORANGE'. Defaults to None.
        transparency (float, optional): How transparent the shape is. Defaults to 0.0.
    """
    if isinstance(shape, (Compound, Solid, Shell, Face, Edge, Wire, Vertex)):
        shape = shape.topods_shape()
    if color and not isinstance(color, (str, tuple)):
        color = "BLACK"
    if isinstance(color, (tuple, list)):
        assert len(color) == 3, "Expected a 3-tuple/list when color is specified as RGB"
        color = Quantity_Color(
            float(color[0]), float(color[1]), float(color[2]), Quantity_TOC_RGB
        )
    return self._display.DisplayShape(
        shape, update=update, color=color, transparency=transparency
    )

display_lines(origins, directions, color=None, thickness=1, style='solid')

Display a set of lines

Parameters:

Name Type Description Default
origins 2D np.ndarray of size #points x 3

Origin points of the arrows

required
directions 2D np.ndarray of size #points x 3

Unit vectors for directions of the arrows

required
color tuple of 3 floats or 2D np.ndarray of size #points x 3 or str

RGB color (can be a single color or per-point colors). Defaults to None.

None
thickness float

Thickness of the lines

1
style str

Style for the lines. Must be one of ('solid', 'dash', 'dot', 'dotdash'). Defaults to 'solid'.

'solid'
Source code in src/occwl/viewer.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def display_lines(
    self, origins, directions, color=None, thickness=1, style="solid",
):
    """
    Display a set of lines

    Args:
        origins (2D np.ndarray of size #points x 3): Origin points of the arrows
        directions (2D np.ndarray of size #points x 3): Unit vectors for directions of the arrows
        color (tuple of 3 floats or 2D np.ndarray of size #points x 3 or str, optional): RGB color (can be a single color or per-point colors). Defaults to None.
        thickness (float, optional): Thickness of the lines
        style (str, optional): Style for the lines. Must be one of ('solid', 'dash', 'dot', 'dotdash'). Defaults to 'solid'.
    """
    assert (
        origins.shape[0] == directions.shape[0]
    ), "origins and directions must match in size (#points x 3)"
    if color is None:
        color = (0, 0, 0)

    if style == "solid":
        type_of_line = Aspect_TOL_SOLID
    elif style == "dash":
        type_of_line = Aspect_TOL_DASH
    elif style == "dot":
        type_of_line = Aspect_TOL_DOT
    elif style == "dotdash":
        type_of_line = Aspect_TOL_DOTDASH
    else:
        type_of_line = Aspect_TOL_SOLID
        print(
            f"Unknown style {style}. Expected one of ('solid', 'dash', 'dot', 'dotdash'). Setting to 'solid'."
        )

    line_entities = []
    for idx in range(origins.shape[0]):
        if isinstance(color, tuple):
            quantity_color = Quantity_Color(color[0], color[1], color[2], Quantity_TOC_RGB)
        elif isinstance(color, np.ndarray):
            assert (
                origins.shape[0] == color.shape[0]
            ), "pts and color must match in size (#points x 3)"
            quantity_color = Quantity_Color(
                color[idx, 0], color[idx, 1], color[idx, 2], Quantity_TOC_RGB
            )
        elif isinstance(color, str):
            quantity_color = get_color_from_name(color)

        line = Geom_Line(
            geom_utils.to_gp_pnt(origins[idx, :]),
            geom_utils.to_gp_dir(directions[idx, :]),
        )
        ais_line = AIS_Line(line)
        attr = ais_line.Attributes()
        asp = Prs3d_LineAspect(quantity_color, type_of_line, thickness)
        attr.SetLineAspect(asp)
        ais_line.SetAttributes(attr)
        self._display.Context.Display(ais_line, False)
        line_entities.append(ais_line)
    return line_entities

display_points(pts, color=None, scale=10, marker='ball')

Display a set of points

Parameters:

Name Type Description Default
points ndarray

Points to display

required
color tuple of 3 floats or np.ndarray of size #points x 3 or str

RGB color (can be a single color or per-point colors). Defaults to None.

None
scale float

Scale of the points

10
marker str

Marker type for the point. Must be one of ('point', 'star', 'ball', 'x', 'o'). Defaults to 'ball'.

'ball'
Source code in src/occwl/viewer.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def display_points(self, pts, color=None, scale=10, marker="ball"):
    """
    Display a set of points

    Args:
        points (np.ndarray #points x 3): Points to display
        color (tuple of 3 floats or np.ndarray of size #points x 3 or str, optional): RGB color (can be a single color or per-point colors). Defaults to None.
        scale (float, optional): Scale of the points
        marker (str, optional): Marker type for the point. Must be one of ('point', 'star', 'ball', 'x', 'o'). Defaults to 'ball'.
    """
    if color is None:
        color = (0, 0, 0)
    if marker == "point":
        marker_type = Aspect_TOM_POINT
    elif marker == "o":
        marker_type = Aspect_TOM_O
    elif marker == "star":
        marker_type = Aspect_TOM_STAR
    elif marker == "x":
        marker_type = Aspect_TOM_X
    elif marker == "ball":
        marker_type = Aspect_TOM_BALL
    else:
        marker_type = Aspect_TOM_POINT
        print(
            "Unknown marker type {}. Expected one of ('point', 'star', 'ball', 'o', 'x'). Setting to 'point'."
        )
    point_entities = []
    for idx in range(pts.shape[0]):
        if isinstance(color, tuple):
            quantity_color = Quantity_Color(color[0], color[1], color[2], Quantity_TOC_RGB)
        elif isinstance(color, np.ndarray):
            assert (
                pts.shape[0] == color.shape[0]
            ), "pts and color must match in size (#points x 3)"
            quantity_color = Quantity_Color(
                color[idx, 0], color[idx, 1], color[idx, 2], Quantity_TOC_RGB
            )
        elif isinstance(color, str):
            quantity_color = get_color_from_name(color)
        p = Geom_CartesianPoint(geom_utils.to_gp_pnt(pts[idx, :]))
        ais_point = AIS_Point(p)
        attr = ais_point.Attributes()
        asp = Prs3d_PointAspect(marker_type, quantity_color, float(scale))
        attr.SetPointAspect(asp)
        ais_point.SetAttributes(attr)
        self._display.Context.Display(ais_point, False)
        point_entities.append(ais_point)
    return point_entities

display_text(xyz, text, height=None, color=None)

Display a text

Parameters:

Name Type Description Default
xyz tuple of floats or 1D np.ndarray of 2 or 3

Coordinate in model space where text would appear

required
text str

Text to display

required
height float

Height of the text font. Defaults to None.

None
color tuple of 3 floats

RGB color. Defaults to None.

None
Source code in src/occwl/viewer.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
def display_text(self, xyz, text, height=None, color=None):
    """
    Display a text

    Args:
        xyz (tuple of floats or 1D np.ndarray of 2 or 3): Coordinate in model space where text would appear
        text (str): Text to display
        height (float, optional): Height of the text font. Defaults to None.
        color (tuple of 3 floats, optional): RGB color. Defaults to None.
    """
    return self._display.DisplayMessage(
        geom_utils.to_gp_pnt(xyz), text, height=height, message_color=color
    )

enable_antialiasing()

Enable antialiasing

Source code in src/occwl/viewer.py
327
328
329
330
331
def enable_antialiasing(self):
    """
    Enable antialiasing
    """
    self._display.EnableAntiAliasing()

fit()

Fit the camera to the scene

Source code in src/occwl/viewer.py
252
253
254
255
256
def fit(self):
    """
    Fit the camera to the scene
    """
    self._display.FitAll()

hide_axes()

Hide the XYZ-axes widget

Source code in src/occwl/viewer.py
321
322
323
324
325
def hide_axes(self):
    """
    Hide the XYZ-axes widget
    """
    self._display.hide_triedron()

hide_face_boundary()

Hide the edges bounding each face

Source code in src/occwl/viewer.py
357
358
359
360
361
def hide_face_boundary(self):
    """
    Hide the edges bounding each face
    """
    self._display.default_drawer.SetFaceBoundaryDraw(False)

orthographic()

Set orthographic camera projection

Source code in src/occwl/viewer.py
265
266
267
268
269
270
def orthographic(self):
    """
    Set orthographic camera projection
    """
    self._display.SetOrthographicProjection()
    self._display.FitAll()

perspective()

Set perspective camera projection

Source code in src/occwl/viewer.py
258
259
260
261
262
263
def perspective(self):
    """
    Set perspective camera projection
    """
    self._display.SetPerspectiveProjection()
    self._display.FitAll()

save_image(filename=None)

Save a screenshot of the viewer

Parameters:

Name Type Description Default
filename str or Path

Image file to save the screenshot. Defaults to None. If None, writes a PNG file named with the current timestamp

None
Source code in src/occwl/viewer.py
286
287
288
289
290
291
292
293
294
295
296
297
298
def save_image(self, filename=None):
    """
    Save a screenshot of the viewer

    Args:
        filename (str or pathlib.Path, optional): Image file to save the screenshot. Defaults to None.
                                                  If None, writes a PNG file named with the current timestamp
    """
    if filename is None:
        now = datetime.now()
        current_time = str(now)
        filename = current_time + ".png"
    self._display.View.Dump(str(filename))

selected_shapes()

Get the selected shapes

Returns:

Type Description

List[TopoDS_Shape]: List of selected shapes

Source code in src/occwl/viewer.py
235
236
237
238
239
240
241
242
243
244
def selected_shapes(self):
    """
    Get the selected shapes

    Returns:
        List[TopoDS_Shape]: List of selected shapes
    """
    shapes = self._display.GetSelectedShapes()
    shapes = self._convert_to_occwl_types(shapes)
    return shapes

set_background_color(top_color, bottom_color)

Set the background gradient color

Parameters:

Name Type Description Default
top_color List / Tuple[int, int, int]

Top color

required
bottom_color List / Tuple[int, int, int]

Bottom color

required
Source code in src/occwl/viewer.py
339
340
341
342
343
344
345
346
347
348
349
def set_background_color(self, top_color, bottom_color):
    """
    Set the background gradient color

    Args:
        top_color (List/Tuple[int, int, int]): Top color
        bottom_color (List/Tuple[int, int, int]): Bottom color
    """
    assert isinstance(top_color, (tuple, list))
    assert isinstance(bottom_color, (tuple, list))
    self._display.set_bg_gradient_color(top_color, bottom_color)

set_size(width, height)

Set the size of the framebuffer

Parameters:

Name Type Description Default
width int

Width of the framebuffer

required
height int

Height of the framebuffer

required
Source code in src/occwl/viewer.py
363
364
365
366
367
368
369
370
371
def set_size(self, width, height):
    """
    Set the size of the framebuffer

    Args:
        width (int): Width of the framebuffer
        height (int): Height of the framebuffer
    """
    self._display.SetSize(width, height)

shaded()

Shade all shapes

Source code in src/occwl/viewer.py
279
280
281
282
283
284
def shaded(self):
    """
    Shade all shapes
    """
    self._display.View.SetComputedMode(False)
    self._display.Context.SetDisplayMode(AIS_Shaded, True)

show_axes()

Show the XYZ-axes widget

Source code in src/occwl/viewer.py
315
316
317
318
319
def show_axes(self):
    """
    Show the XYZ-axes widget
    """
    self._display.display_triedron()

show_face_boundary()

Show the edges bounding each face

Source code in src/occwl/viewer.py
351
352
353
354
355
def show_face_boundary(self):
    """
    Show the edges bounding each face
    """
    self._display.default_drawer.SetFaceBoundaryDraw(True)

use_flat_shading()

Use no interpolation when computing color for fragments in a triangle

Source code in src/occwl/viewer.py
379
380
381
382
383
def use_flat_shading(self):
    """
    Use no interpolation when computing color for fragments in a triangle
    """
    self._display.View.SetShadingModel(Graphic3d_TOSM_FACET)

use_gouraud_shading()

Compute colors per vertex and interpolate

Source code in src/occwl/viewer.py
373
374
375
376
377
def use_gouraud_shading(self):
    """
    Compute colors per vertex and interpolate
    """
    self._display.View.SetShadingModel(Graphic3d_TOSM_VERTEX)

use_phong_shading()

Compute colors per fragment

Source code in src/occwl/viewer.py
385
386
387
388
389
def use_phong_shading(self):
    """
    Compute colors per fragment
    """
    self._display.View.SetShadingModel(Graphic3d_TOSM_FRAGMENT)

use_rasterization()

Render using rasterization

Source code in src/occwl/viewer.py
300
301
302
303
304
def use_rasterization(self):
    """
    Render using rasterization
    """
    self._display.SetRasterizationMode()

use_raytracing(depth=3)

Render using raytracing

Parameters:

Name Type Description Default
depth int

Number of bounces for rays Defaults to 3.

3
Source code in src/occwl/viewer.py
306
307
308
309
310
311
312
313
def use_raytracing(self, depth=3):
    """
    Render using raytracing

    Args:
        depth (int, optional): Number of bounces for rays Defaults to 3.
    """
    self._display.SetRaytracingMode(depth=depth)

wireframe()

Set all shapes to appear as wireframes

Source code in src/occwl/viewer.py
272
273
274
275
276
277
def wireframe(self):
    """
    Set all shapes to appear as wireframes
    """
    self._display.View.SetComputedMode(False)
    self._display.Context.SetDisplayMode(AIS_WireFrame, True)

Viewer

Bases: _BaseViewer

A Viewer for topological entities

Source code in src/occwl/viewer.py
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
class Viewer(_BaseViewer):
    """
    A Viewer for topological entities
    """

    def __init__(
        self,
        backend: str = None,
        size: Optional[Tuple[int, int]] = (1024, 768),
        axes: Optional[bool] = True,
        background_gradient_color1: Optional[List[int]] = [206, 215, 222],
        background_gradient_color2: Optional[List[int]] = [128, 128, 128],
    ):
        """
        Construct the Viewer

        Args:
            backend (str, optional): Backend use to create the viewer. Must be one of wx, pyqt4, pyqt5 or pyside. Defaults to None.
            size (Optional[Tuple[int, int]], optional): Size of the viewer window. Defaults to (1024, 768).
            axes (Optional[bool], optional): Show arrows for coordinate axes. Defaults to True.
            background_gradient_color1 (Optional[List[int]], optional): Background color at the top. Defaults to [206, 215, 222].
            background_gradient_color2 (Optional[List[int]], optional): Background color at the bottom. Defaults to [128, 128, 128].
        """
        (
            self._display,
            self._start_display,
            self._add_menu,
            self._add_function_to_menu,
        ) = init_display(
            backend_str=backend,
            size=size,
            display_triedron=axes,
            background_gradient_color1=background_gradient_color1,
            background_gradient_color2=background_gradient_color2,
        )

    def on_select(self, callback):
        """
        Callback to execute when a selection is made

        Args:
            callback (function): Called when a selection is made. Must have signature:
                                 def callback(selected_shapes, mouse_x, mouse_y)
        """

        def wrapped_callback(selected_shapes, x, y):
            selected_shapes = self._convert_to_occwl_types(selected_shapes)
            return callback(selected_shapes, x, y)

        self._display.register_select_callback(wrapped_callback)

    def _convert_to_occwl_types(self, shapes):
        for i in range(len(shapes)):
            if type(shapes[i]) == TopoDS_Vertex:
                shapes[i] = Vertex(shapes[i])
            elif type(shapes[i]) == TopoDS_Edge:
                shapes[i] = Edge(shapes[i])
            elif type(shapes[i]) == TopoDS_Face:
                shapes[i] = Face(shapes[i])
            elif type(shapes[i]) == TopoDS_Shell:
                shapes[i] = Shell(shapes[i])
            elif type(shapes[i]) == TopoDS_Solid:
                shapes[i] = Solid(shapes[i])
            elif type(shapes[i]) == TopoDS_Compound:
                shapes[i] = Compound(shapes[i])
        return shapes

    def selected_shapes(self):
        """
        Get the selected shapes

        Returns:
            List[TopoDS_Shape]: List of selected shapes
        """
        shapes = self._display.GetSelectedShapes()
        shapes = self._convert_to_occwl_types(shapes)
        return shapes

    def show(self):
        """
        Show the viewer
        """
        self._start_display()

    def add_menu(self, name):
        """
        Add a custom menu to the viewer

        Args:
            name (str): Name of the menu
        """
        self._add_menu(name)

    def add_submenu(self, menu, callback):
        """
        Add a sub-menu to an existing menu

        Args:
            menu (str): Name of the menu
            callback (function): Function to be added as a sub-menu. The name of the function will appear under menu.
        """
        self._add_function_to_menu(menu, callback)

    def exit(self):
        """
        Exit the viewer
        """
        import sys

        sys.exit()

    def selection_mode_vertex(self):
        """
        Allow vertices to be selected
        """
        self._display.SetSelectionMode(TopAbs_VERTEX)

    def selection_mode_edge(self):
        """
        Allow edges to be selected
        """
        self._display.SetSelectionMode(TopAbs_EDGE)

    def selection_mode_face(self):
        """
        Allow faces to be selected
        """
        self._display.SetSelectionMode(TopAbs_FACE)

    def selection_mode_shell(self):
        """
        Allow all shapes to be selected
        """
        self._display.SetSelectionMode(TopAbs_SHELL)

    def selection_mode_solid(self):
        """
        Allow no shapes to be selected
        """
        self._display.SetSelectionMode(TopAbs_SOLID)

    def selection_mode_none(self):
        """
        Allow no shapes to be selected
        """
        self._display.SetSelectionModeShape()

__init__(backend=None, size=(1024, 768), axes=True, background_gradient_color1=[206, 215, 222], background_gradient_color2=[128, 128, 128])

Construct the Viewer

Parameters:

Name Type Description Default
backend str

Backend use to create the viewer. Must be one of wx, pyqt4, pyqt5 or pyside. Defaults to None.

None
size Optional[Tuple[int, int]]

Size of the viewer window. Defaults to (1024, 768).

(1024, 768)
axes Optional[bool]

Show arrows for coordinate axes. Defaults to True.

True
background_gradient_color1 Optional[List[int]]

Background color at the top. Defaults to [206, 215, 222].

[206, 215, 222]
background_gradient_color2 Optional[List[int]]

Background color at the bottom. Defaults to [128, 128, 128].

[128, 128, 128]
Source code in src/occwl/viewer.py
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
def __init__(
    self,
    backend: str = None,
    size: Optional[Tuple[int, int]] = (1024, 768),
    axes: Optional[bool] = True,
    background_gradient_color1: Optional[List[int]] = [206, 215, 222],
    background_gradient_color2: Optional[List[int]] = [128, 128, 128],
):
    """
    Construct the Viewer

    Args:
        backend (str, optional): Backend use to create the viewer. Must be one of wx, pyqt4, pyqt5 or pyside. Defaults to None.
        size (Optional[Tuple[int, int]], optional): Size of the viewer window. Defaults to (1024, 768).
        axes (Optional[bool], optional): Show arrows for coordinate axes. Defaults to True.
        background_gradient_color1 (Optional[List[int]], optional): Background color at the top. Defaults to [206, 215, 222].
        background_gradient_color2 (Optional[List[int]], optional): Background color at the bottom. Defaults to [128, 128, 128].
    """
    (
        self._display,
        self._start_display,
        self._add_menu,
        self._add_function_to_menu,
    ) = init_display(
        backend_str=backend,
        size=size,
        display_triedron=axes,
        background_gradient_color1=background_gradient_color1,
        background_gradient_color2=background_gradient_color2,
    )

add_menu(name)

Add a custom menu to the viewer

Parameters:

Name Type Description Default
name str

Name of the menu

required
Source code in src/occwl/viewer.py
486
487
488
489
490
491
492
493
def add_menu(self, name):
    """
    Add a custom menu to the viewer

    Args:
        name (str): Name of the menu
    """
    self._add_menu(name)

add_submenu(menu, callback)

Add a sub-menu to an existing menu

Parameters:

Name Type Description Default
menu str

Name of the menu

required
callback function

Function to be added as a sub-menu. The name of the function will appear under menu.

required
Source code in src/occwl/viewer.py
495
496
497
498
499
500
501
502
503
def add_submenu(self, menu, callback):
    """
    Add a sub-menu to an existing menu

    Args:
        menu (str): Name of the menu
        callback (function): Function to be added as a sub-menu. The name of the function will appear under menu.
    """
    self._add_function_to_menu(menu, callback)

clear()

Clear all shapes from the viewer

Source code in src/occwl/viewer.py
246
247
248
249
250
def clear(self):
    """
    Clear all shapes from the viewer
    """
    self._display.EraseAll()

disable_antialiasing()

Disable antialiasing

Source code in src/occwl/viewer.py
333
334
335
336
337
def disable_antialiasing(self):
    """
    Disable antialiasing
    """
    self._display.DisableAntiAliasing()

display(shape, update=False, color=None, transparency=0.0)

Display a shape (must be a Solid, Face, or Edge)

Parameters:

Name Type Description Default
shape Solid, Face, or Edge

Shape to display

required
update bool

Whether to update and repaint. Defaults to False.

False
color str or tuple

Color of the shape. If str, can be 'WHITE', 'BLUE', 'RED', 'GREEN', 'YELLOW', 'CYAN', 'BLACK', or 'ORANGE'. Defaults to None.

None
transparency float

How transparent the shape is. Defaults to 0.0.

0.0
Source code in src/occwl/viewer.py
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def display(self, shape, update=False, color=None, transparency=0.0):
    """
    Display a shape (must be a Solid, Face, or Edge)

    Args:
        shape (Solid, Face, or Edge): Shape to display
        update (bool, optional): Whether to update and repaint. Defaults to False.
        color (str or tuple, optional): Color of the shape.
                                        If str, can be 'WHITE', 'BLUE', 'RED', 'GREEN', 'YELLOW',
                                       'CYAN', 'BLACK', or 'ORANGE'. Defaults to None.
        transparency (float, optional): How transparent the shape is. Defaults to 0.0.
    """
    if isinstance(shape, (Compound, Solid, Shell, Face, Edge, Wire, Vertex)):
        shape = shape.topods_shape()
    if color and not isinstance(color, (str, tuple)):
        color = "BLACK"
    if isinstance(color, (tuple, list)):
        assert len(color) == 3, "Expected a 3-tuple/list when color is specified as RGB"
        color = Quantity_Color(
            float(color[0]), float(color[1]), float(color[2]), Quantity_TOC_RGB
        )
    return self._display.DisplayShape(
        shape, update=update, color=color, transparency=transparency
    )

display_lines(origins, directions, color=None, thickness=1, style='solid')

Display a set of lines

Parameters:

Name Type Description Default
origins 2D np.ndarray of size #points x 3

Origin points of the arrows

required
directions 2D np.ndarray of size #points x 3

Unit vectors for directions of the arrows

required
color tuple of 3 floats or 2D np.ndarray of size #points x 3 or str

RGB color (can be a single color or per-point colors). Defaults to None.

None
thickness float

Thickness of the lines

1
style str

Style for the lines. Must be one of ('solid', 'dash', 'dot', 'dotdash'). Defaults to 'solid'.

'solid'
Source code in src/occwl/viewer.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def display_lines(
    self, origins, directions, color=None, thickness=1, style="solid",
):
    """
    Display a set of lines

    Args:
        origins (2D np.ndarray of size #points x 3): Origin points of the arrows
        directions (2D np.ndarray of size #points x 3): Unit vectors for directions of the arrows
        color (tuple of 3 floats or 2D np.ndarray of size #points x 3 or str, optional): RGB color (can be a single color or per-point colors). Defaults to None.
        thickness (float, optional): Thickness of the lines
        style (str, optional): Style for the lines. Must be one of ('solid', 'dash', 'dot', 'dotdash'). Defaults to 'solid'.
    """
    assert (
        origins.shape[0] == directions.shape[0]
    ), "origins and directions must match in size (#points x 3)"
    if color is None:
        color = (0, 0, 0)

    if style == "solid":
        type_of_line = Aspect_TOL_SOLID
    elif style == "dash":
        type_of_line = Aspect_TOL_DASH
    elif style == "dot":
        type_of_line = Aspect_TOL_DOT
    elif style == "dotdash":
        type_of_line = Aspect_TOL_DOTDASH
    else:
        type_of_line = Aspect_TOL_SOLID
        print(
            f"Unknown style {style}. Expected one of ('solid', 'dash', 'dot', 'dotdash'). Setting to 'solid'."
        )

    line_entities = []
    for idx in range(origins.shape[0]):
        if isinstance(color, tuple):
            quantity_color = Quantity_Color(color[0], color[1], color[2], Quantity_TOC_RGB)
        elif isinstance(color, np.ndarray):
            assert (
                origins.shape[0] == color.shape[0]
            ), "pts and color must match in size (#points x 3)"
            quantity_color = Quantity_Color(
                color[idx, 0], color[idx, 1], color[idx, 2], Quantity_TOC_RGB
            )
        elif isinstance(color, str):
            quantity_color = get_color_from_name(color)

        line = Geom_Line(
            geom_utils.to_gp_pnt(origins[idx, :]),
            geom_utils.to_gp_dir(directions[idx, :]),
        )
        ais_line = AIS_Line(line)
        attr = ais_line.Attributes()
        asp = Prs3d_LineAspect(quantity_color, type_of_line, thickness)
        attr.SetLineAspect(asp)
        ais_line.SetAttributes(attr)
        self._display.Context.Display(ais_line, False)
        line_entities.append(ais_line)
    return line_entities

display_points(pts, color=None, scale=10, marker='ball')

Display a set of points

Parameters:

Name Type Description Default
points ndarray

Points to display

required
color tuple of 3 floats or np.ndarray of size #points x 3 or str

RGB color (can be a single color or per-point colors). Defaults to None.

None
scale float

Scale of the points

10
marker str

Marker type for the point. Must be one of ('point', 'star', 'ball', 'x', 'o'). Defaults to 'ball'.

'ball'
Source code in src/occwl/viewer.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def display_points(self, pts, color=None, scale=10, marker="ball"):
    """
    Display a set of points

    Args:
        points (np.ndarray #points x 3): Points to display
        color (tuple of 3 floats or np.ndarray of size #points x 3 or str, optional): RGB color (can be a single color or per-point colors). Defaults to None.
        scale (float, optional): Scale of the points
        marker (str, optional): Marker type for the point. Must be one of ('point', 'star', 'ball', 'x', 'o'). Defaults to 'ball'.
    """
    if color is None:
        color = (0, 0, 0)
    if marker == "point":
        marker_type = Aspect_TOM_POINT
    elif marker == "o":
        marker_type = Aspect_TOM_O
    elif marker == "star":
        marker_type = Aspect_TOM_STAR
    elif marker == "x":
        marker_type = Aspect_TOM_X
    elif marker == "ball":
        marker_type = Aspect_TOM_BALL
    else:
        marker_type = Aspect_TOM_POINT
        print(
            "Unknown marker type {}. Expected one of ('point', 'star', 'ball', 'o', 'x'). Setting to 'point'."
        )
    point_entities = []
    for idx in range(pts.shape[0]):
        if isinstance(color, tuple):
            quantity_color = Quantity_Color(color[0], color[1], color[2], Quantity_TOC_RGB)
        elif isinstance(color, np.ndarray):
            assert (
                pts.shape[0] == color.shape[0]
            ), "pts and color must match in size (#points x 3)"
            quantity_color = Quantity_Color(
                color[idx, 0], color[idx, 1], color[idx, 2], Quantity_TOC_RGB
            )
        elif isinstance(color, str):
            quantity_color = get_color_from_name(color)
        p = Geom_CartesianPoint(geom_utils.to_gp_pnt(pts[idx, :]))
        ais_point = AIS_Point(p)
        attr = ais_point.Attributes()
        asp = Prs3d_PointAspect(marker_type, quantity_color, float(scale))
        attr.SetPointAspect(asp)
        ais_point.SetAttributes(attr)
        self._display.Context.Display(ais_point, False)
        point_entities.append(ais_point)
    return point_entities

display_text(xyz, text, height=None, color=None)

Display a text

Parameters:

Name Type Description Default
xyz tuple of floats or 1D np.ndarray of 2 or 3

Coordinate in model space where text would appear

required
text str

Text to display

required
height float

Height of the text font. Defaults to None.

None
color tuple of 3 floats

RGB color. Defaults to None.

None
Source code in src/occwl/viewer.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
def display_text(self, xyz, text, height=None, color=None):
    """
    Display a text

    Args:
        xyz (tuple of floats or 1D np.ndarray of 2 or 3): Coordinate in model space where text would appear
        text (str): Text to display
        height (float, optional): Height of the text font. Defaults to None.
        color (tuple of 3 floats, optional): RGB color. Defaults to None.
    """
    return self._display.DisplayMessage(
        geom_utils.to_gp_pnt(xyz), text, height=height, message_color=color
    )

enable_antialiasing()

Enable antialiasing

Source code in src/occwl/viewer.py
327
328
329
330
331
def enable_antialiasing(self):
    """
    Enable antialiasing
    """
    self._display.EnableAntiAliasing()

exit()

Exit the viewer

Source code in src/occwl/viewer.py
505
506
507
508
509
510
511
def exit(self):
    """
    Exit the viewer
    """
    import sys

    sys.exit()

fit()

Fit the camera to the scene

Source code in src/occwl/viewer.py
252
253
254
255
256
def fit(self):
    """
    Fit the camera to the scene
    """
    self._display.FitAll()

hide_axes()

Hide the XYZ-axes widget

Source code in src/occwl/viewer.py
321
322
323
324
325
def hide_axes(self):
    """
    Hide the XYZ-axes widget
    """
    self._display.hide_triedron()

hide_face_boundary()

Hide the edges bounding each face

Source code in src/occwl/viewer.py
357
358
359
360
361
def hide_face_boundary(self):
    """
    Hide the edges bounding each face
    """
    self._display.default_drawer.SetFaceBoundaryDraw(False)

on_select(callback)

Callback to execute when a selection is made

Parameters:

Name Type Description Default
callback function

Called when a selection is made. Must have signature: def callback(selected_shapes, mouse_x, mouse_y)

required
Source code in src/occwl/viewer.py
438
439
440
441
442
443
444
445
446
447
448
449
450
451
def on_select(self, callback):
    """
    Callback to execute when a selection is made

    Args:
        callback (function): Called when a selection is made. Must have signature:
                             def callback(selected_shapes, mouse_x, mouse_y)
    """

    def wrapped_callback(selected_shapes, x, y):
        selected_shapes = self._convert_to_occwl_types(selected_shapes)
        return callback(selected_shapes, x, y)

    self._display.register_select_callback(wrapped_callback)

orthographic()

Set orthographic camera projection

Source code in src/occwl/viewer.py
265
266
267
268
269
270
def orthographic(self):
    """
    Set orthographic camera projection
    """
    self._display.SetOrthographicProjection()
    self._display.FitAll()

perspective()

Set perspective camera projection

Source code in src/occwl/viewer.py
258
259
260
261
262
263
def perspective(self):
    """
    Set perspective camera projection
    """
    self._display.SetPerspectiveProjection()
    self._display.FitAll()

save_image(filename=None)

Save a screenshot of the viewer

Parameters:

Name Type Description Default
filename str or Path

Image file to save the screenshot. Defaults to None. If None, writes a PNG file named with the current timestamp

None
Source code in src/occwl/viewer.py
286
287
288
289
290
291
292
293
294
295
296
297
298
def save_image(self, filename=None):
    """
    Save a screenshot of the viewer

    Args:
        filename (str or pathlib.Path, optional): Image file to save the screenshot. Defaults to None.
                                                  If None, writes a PNG file named with the current timestamp
    """
    if filename is None:
        now = datetime.now()
        current_time = str(now)
        filename = current_time + ".png"
    self._display.View.Dump(str(filename))

selected_shapes()

Get the selected shapes

Returns:

Type Description

List[TopoDS_Shape]: List of selected shapes

Source code in src/occwl/viewer.py
469
470
471
472
473
474
475
476
477
478
def selected_shapes(self):
    """
    Get the selected shapes

    Returns:
        List[TopoDS_Shape]: List of selected shapes
    """
    shapes = self._display.GetSelectedShapes()
    shapes = self._convert_to_occwl_types(shapes)
    return shapes

selection_mode_edge()

Allow edges to be selected

Source code in src/occwl/viewer.py
519
520
521
522
523
def selection_mode_edge(self):
    """
    Allow edges to be selected
    """
    self._display.SetSelectionMode(TopAbs_EDGE)

selection_mode_face()

Allow faces to be selected

Source code in src/occwl/viewer.py
525
526
527
528
529
def selection_mode_face(self):
    """
    Allow faces to be selected
    """
    self._display.SetSelectionMode(TopAbs_FACE)

selection_mode_none()

Allow no shapes to be selected

Source code in src/occwl/viewer.py
543
544
545
546
547
def selection_mode_none(self):
    """
    Allow no shapes to be selected
    """
    self._display.SetSelectionModeShape()

selection_mode_shell()

Allow all shapes to be selected

Source code in src/occwl/viewer.py
531
532
533
534
535
def selection_mode_shell(self):
    """
    Allow all shapes to be selected
    """
    self._display.SetSelectionMode(TopAbs_SHELL)

selection_mode_solid()

Allow no shapes to be selected

Source code in src/occwl/viewer.py
537
538
539
540
541
def selection_mode_solid(self):
    """
    Allow no shapes to be selected
    """
    self._display.SetSelectionMode(TopAbs_SOLID)

selection_mode_vertex()

Allow vertices to be selected

Source code in src/occwl/viewer.py
513
514
515
516
517
def selection_mode_vertex(self):
    """
    Allow vertices to be selected
    """
    self._display.SetSelectionMode(TopAbs_VERTEX)

set_background_color(top_color, bottom_color)

Set the background gradient color

Parameters:

Name Type Description Default
top_color List / Tuple[int, int, int]

Top color

required
bottom_color List / Tuple[int, int, int]

Bottom color

required
Source code in src/occwl/viewer.py
339
340
341
342
343
344
345
346
347
348
349
def set_background_color(self, top_color, bottom_color):
    """
    Set the background gradient color

    Args:
        top_color (List/Tuple[int, int, int]): Top color
        bottom_color (List/Tuple[int, int, int]): Bottom color
    """
    assert isinstance(top_color, (tuple, list))
    assert isinstance(bottom_color, (tuple, list))
    self._display.set_bg_gradient_color(top_color, bottom_color)

set_size(width, height)

Set the size of the framebuffer

Parameters:

Name Type Description Default
width int

Width of the framebuffer

required
height int

Height of the framebuffer

required
Source code in src/occwl/viewer.py
363
364
365
366
367
368
369
370
371
def set_size(self, width, height):
    """
    Set the size of the framebuffer

    Args:
        width (int): Width of the framebuffer
        height (int): Height of the framebuffer
    """
    self._display.SetSize(width, height)

shaded()

Shade all shapes

Source code in src/occwl/viewer.py
279
280
281
282
283
284
def shaded(self):
    """
    Shade all shapes
    """
    self._display.View.SetComputedMode(False)
    self._display.Context.SetDisplayMode(AIS_Shaded, True)

show()

Show the viewer

Source code in src/occwl/viewer.py
480
481
482
483
484
def show(self):
    """
    Show the viewer
    """
    self._start_display()

show_axes()

Show the XYZ-axes widget

Source code in src/occwl/viewer.py
315
316
317
318
319
def show_axes(self):
    """
    Show the XYZ-axes widget
    """
    self._display.display_triedron()

show_face_boundary()

Show the edges bounding each face

Source code in src/occwl/viewer.py
351
352
353
354
355
def show_face_boundary(self):
    """
    Show the edges bounding each face
    """
    self._display.default_drawer.SetFaceBoundaryDraw(True)

use_flat_shading()

Use no interpolation when computing color for fragments in a triangle

Source code in src/occwl/viewer.py
379
380
381
382
383
def use_flat_shading(self):
    """
    Use no interpolation when computing color for fragments in a triangle
    """
    self._display.View.SetShadingModel(Graphic3d_TOSM_FACET)

use_gouraud_shading()

Compute colors per vertex and interpolate

Source code in src/occwl/viewer.py
373
374
375
376
377
def use_gouraud_shading(self):
    """
    Compute colors per vertex and interpolate
    """
    self._display.View.SetShadingModel(Graphic3d_TOSM_VERTEX)

use_phong_shading()

Compute colors per fragment

Source code in src/occwl/viewer.py
385
386
387
388
389
def use_phong_shading(self):
    """
    Compute colors per fragment
    """
    self._display.View.SetShadingModel(Graphic3d_TOSM_FRAGMENT)

use_rasterization()

Render using rasterization

Source code in src/occwl/viewer.py
300
301
302
303
304
def use_rasterization(self):
    """
    Render using rasterization
    """
    self._display.SetRasterizationMode()

use_raytracing(depth=3)

Render using raytracing

Parameters:

Name Type Description Default
depth int

Number of bounces for rays Defaults to 3.

3
Source code in src/occwl/viewer.py
306
307
308
309
310
311
312
313
def use_raytracing(self, depth=3):
    """
    Render using raytracing

    Args:
        depth (int, optional): Number of bounces for rays Defaults to 3.
    """
    self._display.SetRaytracingMode(depth=depth)

wireframe()

Set all shapes to appear as wireframes

Source code in src/occwl/viewer.py
272
273
274
275
276
277
def wireframe(self):
    """
    Set all shapes to appear as wireframes
    """
    self._display.View.SetComputedMode(False)
    self._display.Context.SetDisplayMode(AIS_WireFrame, True)

wire

Wire

Bases: Shape

A topological wire in a solid model Represents a closed loop of edges

Source code in src/occwl/wire.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Wire(Shape):
    """
    A topological wire in a solid model
    Represents a closed loop of edges
    """

    def __init__(self, topods_wire):
        """
        Construct a Wire

        Args:
            topods_wire (OCC.Core.TopoDS_Wire): OCC wire type
        """
        assert isinstance(topods_wire, TopoDS_Wire)
        super().__init__(topods_wire)
        self._wire_exp = TopologyUtils.WireExplorer(self.topods_shape())

    @staticmethod
    def make_from_edges(edges):
        """
        Make a wire from connected edges

        Args:
            edges (List[occwl.edge.Edge]): List of edges

        Returns:
            occwl.wire.Wire or None: Returns a Wire or None if the operation failed
        """
        wire_builder = BRepBuilderAPI_MakeWire()
        for edge in edges:
            wire_builder.Add(edge.topods_shape())
        wire_builder.Build()
        if not wire_builder.IsDone():
            return None
        return Wire(wire_builder.Wire())

    def ordered_edges(self):
        """
        Get an iterator to go over the edges while respecting the wire ordering

        Returns:
            Iterator[occwl.edge.Edge]: An iterator to edges
        """
        return map(Edge, self._wire_exp.ordered_edges())

    def ordered_vertices(self):
        """
        Get an iterator to go over the vertices while respecting the wire ordering

        Returns:
            Iterator[occwl.vertex.Vertex]: An iterator to vertices
        """
        return map(Vertex, self._wire_exp.ordered_vertices())

__eq__(other)

Equality check for the shape

NOTE: This function only checks if the shape is the same. It doesn't check the edge orienation for example, so

edge1 == edge2

does not necessarily mean

edge1.reversed() == edge2.reversed()

Source code in src/occwl/shape.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def __eq__(self, other):
    """
    Equality check for the shape

    NOTE: This function only checks if the shape is the same.
    It doesn't check the edge orienation for example, so 

    edge1 == edge2

    does not necessarily mean 

    edge1.reversed() == edge2.reversed()
    """
    return self.topods_shape().__hash__() == other.topods_shape().__hash__()

__hash__()

Hash for the shape

Returns:

Name Type Description
int

Hash value

Source code in src/occwl/shape.py
135
136
137
138
139
140
141
142
def __hash__(self):
    """
    Hash for the shape

    Returns:
        int: Hash value
    """
    return self.topods_shape().__hash__()

__init__(topods_wire)

Construct a Wire

Parameters:

Name Type Description Default
topods_wire TopoDS_Wire

OCC wire type

required
Source code in src/occwl/wire.py
18
19
20
21
22
23
24
25
26
27
def __init__(self, topods_wire):
    """
    Construct a Wire

    Args:
        topods_wire (OCC.Core.TopoDS_Wire): OCC wire type
    """
    assert isinstance(topods_wire, TopoDS_Wire)
    super().__init__(topods_wire)
    self._wire_exp = TopologyUtils.WireExplorer(self.topods_shape())

convert_geometric_identity_transforms_to_identity()

Open Cascade models sometimes contain transforms which are "geometrically" identify transforms, but the identity flag is not set.

This function checks each transform and sets the flag if the appropriate.

Source code in src/occwl/shape.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def convert_geometric_identity_transforms_to_identity(self):
    """
    Open Cascade models sometimes contain transforms which
    are "geometrically" identify transforms, but the identity
    flag is not set.

    This function checks each transform and sets the flag if 
    the appropriate.
    """
    identity = TopLoc_Location()
    if geom_utils.is_geometric_identity(
        self.topods_shape().Location().Transformation()
    ):
        self.topods_shape().Location(identity)
        self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)

    for face in self._top_exp.faces():
        if geom_utils.is_geometric_identity(face.Location().Transformation()):
            face.Location(identity)

    for edge in self._top_exp.edges():
        if geom_utils.is_geometric_identity(edge.Location().Transformation()):
            edge.Location(identity)

    for vertex in self._top_exp.vertices():
        if geom_utils.is_geometric_identity(vertex.Location().Transformation()):
            vertex.Location(identity)

find_closest_point_data(datum)

Find the information about the closest point on this shape

Parameters:

Name Type Description Default
datum ndarray

3D Point

required

Returns:

Name Type Description
ClosestPointData

Data about the closest point on this shape

None

if error

Source code in src/occwl/shape.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
def find_closest_point_data(self, datum):
    """
    Find the information about the closest point on this shape

    Args:
        datum (np.ndarray): 3D Point

    Returns:
        ClosestPointData: Data about the closest point on this shape
        None: if error
    """
    # Folowing https://dev.opencascade.org/content/how-retrieve-nearest-face-shape-given-gppnt
    # Create a vertex from the point
    occ_point = geom_utils.numpy_to_gp(datum)
    vertex_maker = BRepBuilderAPI_MakeVertex(occ_point)
    vertex = vertex_maker.Shape()
    dist_shape_shape = BRepExtrema_DistShapeShape(
        vertex, self.topods_shape(), Extrema_ExtFlag_MIN
    )
    ok = dist_shape_shape.Perform()
    if not ok:
        return None

    return ClosestPointData(dist_shape_shape)

make_from_edges(edges) staticmethod

Make a wire from connected edges

Parameters:

Name Type Description Default
edges List[Edge]

List of edges

required

Returns:

Type Description

occwl.wire.Wire or None: Returns a Wire or None if the operation failed

Source code in src/occwl/wire.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@staticmethod
def make_from_edges(edges):
    """
    Make a wire from connected edges

    Args:
        edges (List[occwl.edge.Edge]): List of edges

    Returns:
        occwl.wire.Wire or None: Returns a Wire or None if the operation failed
    """
    wire_builder = BRepBuilderAPI_MakeWire()
    for edge in edges:
        wire_builder.Add(edge.topods_shape())
    wire_builder.Build()
    if not wire_builder.IsDone():
        return None
    return Wire(wire_builder.Wire())

occwl_shape(topods_shape) staticmethod

Static method to create an occwl shape of the appropriate class from the given topods_shape Args: topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

Returns:

Type Description

One of occwl.compound.Compound occwl.solid.Solid occwl.face.Face occwl.edge.Edge occwl.vertex.Vertex occwl.wire.Wire occwl.shell.Shell

Raises: Exception: [description]

Source code in src/occwl/shape.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@staticmethod
def occwl_shape(topods_shape):
    """
    Static method to create an occwl shape of the appropriate 
    class from the given topods_shape
    Args:
        topods_shape (OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid): TopoDS shape

    Returns:
        One of
            occwl.compound.Compound
            occwl.solid.Solid
            occwl.face.Face
            occwl.edge.Edge
            occwl.vertex.Vertex
            occwl.wire.Wire
            occwl.shell.Shell
    Raises:
        Exception: [description]
    """
    from occwl.compound import Compound
    from occwl.solid import Solid
    from occwl.face import Face
    from occwl.edge import Edge
    from occwl.vertex import Vertex
    from occwl.wire import Wire
    from occwl.shell import Shell

    if isinstance(topods_shape, TopoDS_Vertex):
        return Vertex(topods_shape)
    if isinstance(topods_shape, TopoDS_Edge):
        return Edge(topods_shape)
    if isinstance(topods_shape, TopoDS_Face):
        return Face(topods_shape)
    if isinstance(topods_shape, TopoDS_Wire):
        return Wire(topods_shape)
    if isinstance(topods_shape, TopoDS_Shell):
        return Shell(topods_shape)
    if isinstance(topods_shape, TopoDS_Solid):
        return Solid(topods_shape)
    if isinstance(topods_shape, (TopoDS_Compound, TopoDS_CompSolid)):
        return Compound(topods_shape)
    raise Exception(
        "Shape must be one of TopoDS_Vertex, TopoDS_Edge, TopoDS_Face, TopoDS_Shell, TopoDS_Solid, TopoDS_Compound, TopoDS_CompSolid"
    )

ordered_edges()

Get an iterator to go over the edges while respecting the wire ordering

Returns:

Type Description

Iterator[occwl.edge.Edge]: An iterator to edges

Source code in src/occwl/wire.py
48
49
50
51
52
53
54
55
def ordered_edges(self):
    """
    Get an iterator to go over the edges while respecting the wire ordering

    Returns:
        Iterator[occwl.edge.Edge]: An iterator to edges
    """
    return map(Edge, self._wire_exp.ordered_edges())

ordered_vertices()

Get an iterator to go over the vertices while respecting the wire ordering

Returns:

Type Description

Iterator[occwl.vertex.Vertex]: An iterator to vertices

Source code in src/occwl/wire.py
57
58
59
60
61
62
63
64
def ordered_vertices(self):
    """
    Get an iterator to go over the vertices while respecting the wire ordering

    Returns:
        Iterator[occwl.vertex.Vertex]: An iterator to vertices
    """
    return map(Vertex, self._wire_exp.ordered_vertices())

reversed()

Whether this shape is reversed.

  • For an edge this is whether the edge is reversed with respect to the curve geometry
  • For a face this is whether the face is reversed with respect to the surface geometry
  • For a vertex this is whether the vertex is at the upper or lower parameter value on the edges curve

Returns:

Name Type Description
bool

If rational

Source code in src/occwl/shape.py
236
237
238
239
240
241
242
243
244
245
246
247
248
def reversed(self):
    """
    Whether this shape is reversed.

    - For an edge this is whether the edge is reversed with respect to the curve geometry
    - For a face this is whether the face is reversed with respect to the surface geometry
    - For a vertex this is whether the vertex is at the upper or lower parameter value on the
      edges curve

    Returns:
        bool: If rational
    """
    return self.topods_shape().Orientation() == TopAbs_REVERSED

rotate_axis_angle(axis, angle_radians, origin=np.zeros(3, dtype=(np.float32)))

Rotate the shape about the given axis by the given angle in radians

Parameters:

Name Type Description Default
axis ndarray

Rotation axis

required
angle_radians float

Angle in radians

required
Source code in src/occwl/shape.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
def rotate_axis_angle(
    self, axis, angle_radians, origin=np.zeros(3, dtype=np.float32)
):
    """
    Rotate the shape about the given axis by the given angle in radians

    Args:
        axis (np.ndarray): Rotation axis
        angle_radians (float): Angle in radians
    """
    self._shape = rotate_shape(
        self._shape,
        gp_Ax1(geom_utils.numpy_to_gp(origin), geom_utils.numpy_to_gp_dir(axis)),
        angle_radians,
        unite="rad",
    )

rotate_euler_angles(angles_xyz_radians)

Rotate the shape by the given Euler angles in radians

Parameters:

Name Type Description Default
angle_xyz_radians ndarray

3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians

required
Source code in src/occwl/shape.py
302
303
304
305
306
307
308
309
310
311
312
313
314
315
def rotate_euler_angles(self, angles_xyz_radians):
    """
    Rotate the shape by the given Euler angles in radians

    Args:
        angle_xyz_radians (np.ndarray): 3D array with angles to rotate about x-axis, y-axis and z-axis respectively in radians
    """
    self._shape = rotate_shp_3_axis(
        self._shape,
        angles_xyz_radians[0],
        angles_xyz_radians[1],
        angles_xyz_radians[2],
        unity="rad",
    )

save_shapes_to_occ_native(filename, shapes, with_triangles=False, with_normals=False, format_version=None) staticmethod

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
@staticmethod
def save_shapes_to_occ_native(
        filename, 
        shapes,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
            is between one and two orders of magnitude faster 
            than loading from STEP, so it is recommended for 
            large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename

        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    new_api = False
    shapes_set = BRepTools_ShapeSet(with_triangles)
    # shapes_set.SetWithNormals(with_normals) # Not in OCC 7.5.0

    for shp in shapes:
        shapes_set.Add(shp.topods_shape())
    if format_version is not None:
        shapes_set.SetFormatNb(format_version)


    with open(filename, "w") as fp:
        s = shapes_set.WriteToString()
        fp.write(s)

save_to_occ_native(filename, verbosity=False, with_triangles=False, with_normals=False, format_version=None)

Save this shape into a native OCC binary .brep file.

Saving to and loading from the native file format

is between one and two orders of magnitude faster than loading from STEP, so it is recommended for large scale data processing

Parameters:

Name Type Description Default
filename str or Path

.brep filename

required
with_triangles bool

Whether to save triangle data cached in the shape.

False
with_normals bool

Whether to save vertex normals cached in the shape

False
format_version int

Use None to save to the latest version 1 - first revision 2 - added storing of CurveOnSurface UV Points 3 - [OCCT 7.6] added storing of per-vertex normal information and dropped storing of CurveOnSurface UV Points

None
Source code in src/occwl/shape.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def save_to_occ_native(
        self, 
        filename, 
        verbosity=False,
        with_triangles=False,
        with_normals=False,
        format_version=None
    ):
    """
    Save this shape into a native OCC binary .brep file.

    Note:  Saving to and loading from the native file format 
           is between one and two orders of magnitude faster 
           than loading from STEP, so it is recommended for 
           large scale data processing

    Args:
        filename (str or pathlib.Path): .brep filename
        with_triangles (bool): Whether to save triangle data cached in the shape.
        with_normals (bool): Whether to save vertex normals cached in the shape
        format_version (int):  Use None to save to the latest version
            1 - first revision
            2 - added storing of CurveOnSurface UV Points
            3 - [OCCT 7.6] added storing of per-vertex normal information
                           and dropped storing of CurveOnSurface UV Points
    """
    self.save_shapes_to_occ_native(
        filename, 
        [ self ],
        with_triangles=with_triangles,
        with_normals=with_normals,
        format_version=format_version
    )

scale(scale_vector)

Scale the shape by the given 3D vector

Parameters:

Name Type Description Default
scale_vector ndarray

3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively

required
Source code in src/occwl/shape.py
317
318
319
320
321
322
323
324
325
326
def scale(self, scale_vector):
    """
    Scale the shape by the given 3D vector

    Args:
        scale_vector (np.ndarray): 3D array with scales to resize the shape along the x-axis, y-axis and z-axis respectively
    """
    self._shape = scale_shape(
        self._shape, scale_vector[0], scale_vector[1], scale_vector[2]
    )

set_transform_to_identity()

When an assembly is loaded from a STEP file the solids will be transformed relative to their local coordinate system. i.e. they are placed in the assembly root components coordinate system.

When working with individual bodies you often want them to be axis aligned, in which case you want to remove the assembly transform. This function removes it for you.

If however you want to bake the transform into the bodies and suppress the asserts from parts of occwl which don't cope with transforms then use the transform() function below with copy=True

Source code in src/occwl/shape.py
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def set_transform_to_identity(self):
    """
    When an assembly is loaded from a STEP file
    the solids will be transformed relative to
    their local coordinate system.   i.e. they
    are placed in the assembly root components 
    coordinate system.

    When working with individual bodies you often
    want them to be axis aligned, in which case 
    you want to remove the assembly transform.
    This function removes it for you.

    If however you want to bake the transform
    into the bodies and suppress the asserts 
    from parts of occwl which don't cope with
    transforms then use the transform() function
    below with copy=True
    """
    identity = TopLoc_Location()
    self.topods_shape().Location(identity)
    self._top_exp = TopologyUtils.TopologyExplorer(self.topods_shape(), True)
    self.convert_geometric_identity_transforms_to_identity()

topods_shape()

Get the underlying OCC shape

Returns:

Type Description

OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*

Source code in src/occwl/shape.py
80
81
82
83
84
85
86
87
def topods_shape(self):
    """
    Get the underlying OCC shape

    Returns:
        OCC.Core.TopoDS.TopoDS_Vertex/Edge/Face/Wire/Shell/Solid: OCC TopoDS_*
    """
    return self._shape

transform(a, copy=True)

Apply the given 3x4 transform matrix to the solid.

 copy (bool)    True - Copy entities and apply the transform to
                       the underlying geometry
                False - Apply the transform to the topods Locator
                        if possible
Source code in src/occwl/shape.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
def transform(self, a: np.ndarray, copy=True):
    """
    Apply the given 3x4 transform matrix to the solid.

    Args: a (nd.array) - Homogeneous transform matrix
                         The transform that will be applied is

                         x' =  a[0,0]*x + a[0,1]*y + a[0,2]*z + a[0, 3]
                         y' =  a[1,0]*x + a[1,1]*y + a[1,2]*z + a[1, 3]
                         z' =  a[2,0]*x + a[2,1]*y + a[2,2]*z + a[2, 3]

         copy (bool)    True - Copy entities and apply the transform to
                               the underlying geometry
                        False - Apply the transform to the topods Locator
                                if possible 
    """
    assert (a.shape == (3, 4)), "Transform matrix must be 3x4"
    a = a.astype(np.float64)

    # Create an identity transform
    trsf = gp_Trsf()

    # If the matrix is an identity matrix then
    # we don't want to set the values as this
    # would give us a geometric identity without
    # the identity flag set
    if not np.allclose(a, np.eye(3, 4)):
        trsf.SetValues(
            a[0,0], a[0,1], a[0,2], a[0, 3],
            a[1,0], a[1,1], a[1,2], a[1, 3],
            a[2,0], a[2,1], a[2,2], a[2, 3]
        )
    return self._apply_transform(trsf, copy=copy)

translate(offset)

Translate the shape by an offset vector

Parameters:

Name Type Description Default
offset ndarray

Offset vector

required
Source code in src/occwl/shape.py
276
277
278
279
280
281
282
283
def translate(self, offset):
    """
    Translate the shape by an offset vector

    Args:
        offset (np.ndarray): Offset vector
    """
    self._shape = translate_shp(self._shape, geom_utils.numpy_to_gp_vec(offset))

valid(return_analyzer=False)

Check if the shape is valid

Parameters:

Name Type Description Default
return_analyzer bool

Whether to return the BRepCheck_Analyzer object for more inspection

False

Returns:

Name Type Description
bool

Whether the shape is valid

BRepCheck_Analyzer [optional]: if return_analyzer is True

Source code in src/occwl/shape.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def valid(self, return_analyzer=False):
    """
    Check if the shape is valid

    Args:
        return_analyzer (bool): Whether to return the BRepCheck_Analyzer object for more inspection

    Returns:
        bool: Whether the shape is valid
        BRepCheck_Analyzer [optional]: if return_analyzer is True
    """
    analyzer = BRepCheck_Analyzer(self.topods_shape())
    if return_analyzer:
        return analyzer.IsValid(), analyzer
    return analyzer.IsValid()