resqpy 4.18.11__py3-none-any.whl → 5.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- resqpy/__init__.py +1 -1
 - resqpy/grid/__init__.py +2 -3
 - resqpy/grid/_grid.py +1 -7
 - resqpy/grid_surface/_find_faces.py +98 -29
 - resqpy/lines/_polyline.py +24 -33
 - resqpy/model/_model.py +9 -9
 - resqpy/multi_processing/wrappers/grid_surface_mp.py +91 -44
 - resqpy/olio/triangulation.py +19 -17
 - resqpy/olio/volume.py +0 -20
 - resqpy/property/__init__.py +3 -2
 - resqpy/property/_collection_get_attributes.py +2 -0
 - resqpy/rq_import/_grid_from_cp.py +2 -2
 - resqpy/surface/_surface.py +377 -53
 - resqpy/surface/_tri_mesh.py +3 -1
 - resqpy/time_series/_any_time_series.py +5 -4
 - resqpy/well/_blocked_well.py +1916 -1910
 - resqpy/well/_md_datum.py +11 -21
 - resqpy/well/_wellbore_frame.py +10 -2
 - resqpy/well/well_utils.py +33 -0
 - {resqpy-4.18.11.dist-info → resqpy-5.1.0.dist-info}/METADATA +8 -8
 - {resqpy-4.18.11.dist-info → resqpy-5.1.0.dist-info}/RECORD +23 -24
 - {resqpy-4.18.11.dist-info → resqpy-5.1.0.dist-info}/WHEEL +1 -1
 - resqpy/grid/_moved_functions.py +0 -15
 - {resqpy-4.18.11.dist-info → resqpy-5.1.0.dist-info}/LICENSE +0 -0
 
    
        resqpy/surface/_surface.py
    CHANGED
    
    | 
         @@ -8,6 +8,7 @@ import logging 
     | 
|
| 
       8 
8 
     | 
    
         
             
            log = logging.getLogger(__name__)
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
            import numpy as np
         
     | 
| 
      
 11 
     | 
    
         
            +
            import math as maths
         
     | 
| 
       11 
12 
     | 
    
         | 
| 
       12 
13 
     | 
    
         
             
            import resqpy.crs as rqc
         
     | 
| 
       13 
14 
     | 
    
         
             
            import resqpy.lines as rql
         
     | 
| 
         @@ -47,7 +48,7 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       47 
48 
     | 
    
         
             
                             originator = None,
         
     | 
| 
       48 
49 
     | 
    
         
             
                             extra_metadata = {}):
         
     | 
| 
       49 
50 
     | 
    
         
             
                    """Create an empty Surface object (RESQML TriangulatedSetRepresentation).
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
       51 
52 
     | 
    
         
             
                    Optionally populates from xml, point set or mesh.
         
     | 
| 
       52 
53 
     | 
    
         | 
| 
       53 
54 
     | 
    
         
             
                    arguments:
         
     | 
| 
         @@ -124,6 +125,75 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       124 
125 
     | 
    
         
             
                        self.set_from_tsurf_file(tsurf_file)
         
     | 
| 
       125 
126 
     | 
    
         
             
                    self._load_normal_vector_from_extra_metadata()
         
     | 
| 
       126 
127 
     | 
    
         | 
| 
      
 128 
     | 
    
         
            +
                @classmethod
         
     | 
| 
      
 129 
     | 
    
         
            +
                def from_list_of_patches(cls, model, patch_list, title, crs_uuid = None, extra_metadata = None):
         
     | 
| 
      
 130 
     | 
    
         
            +
                    """Create a Surface from a prepared list of TriangulatedPatch objects.
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                    arguments:
         
     | 
| 
      
 133 
     | 
    
         
            +
                        - model (Model): the model to which the surface will be associated
         
     | 
| 
      
 134 
     | 
    
         
            +
                        - patch_list (list of TriangulatedPatch): the list of patches to be combined to form the surface
         
     | 
| 
      
 135 
     | 
    
         
            +
                        - title (str): the citation title for the new surface
         
     | 
| 
      
 136 
     | 
    
         
            +
                        - crs_uuid (uuid, optional): the uuid of a crs in model which the points are deemed to be in
         
     | 
| 
      
 137 
     | 
    
         
            +
                        - extra_metadata (dict of (str: str), optional): extra metadata to add to the new surface
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                    returns:
         
     | 
| 
      
 140 
     | 
    
         
            +
                        - new Surface comprised of a patch for each entry in the patch list
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                    notes:
         
     | 
| 
      
 143 
     | 
    
         
            +
                        - the triangulated patch objects are used directly in the surface
         
     | 
| 
      
 144 
     | 
    
         
            +
                        - the patches should not have had their hdf5 data written yet
         
     | 
| 
      
 145 
     | 
    
         
            +
                        - the patch index values will be set, with any previous values ignored
         
     | 
| 
      
 146 
     | 
    
         
            +
                        - the patches will be hijacked to the target model if their model is different
         
     | 
| 
      
 147 
     | 
    
         
            +
                        - each patch will have its points converted in situ into the surface crs
         
     | 
| 
      
 148 
     | 
    
         
            +
                        - if the crs_uuid argument is None, the crs_uuid is taken from the first patch
         
     | 
| 
      
 149 
     | 
    
         
            +
                    """
         
     | 
| 
      
 150 
     | 
    
         
            +
                    assert len(patch_list) > 0, 'attempting to create Surface from empty patch list'
         
     | 
| 
      
 151 
     | 
    
         
            +
                    if crs_uuid is None:
         
     | 
| 
      
 152 
     | 
    
         
            +
                        crs_uuid = patch_list[0].crs_uuid
         
     | 
| 
      
 153 
     | 
    
         
            +
                        if model.uuid(uuid = crs_uuid) is None:
         
     | 
| 
      
 154 
     | 
    
         
            +
                            model.copy_uuid_from_other_model(patch_list[0].model, crs_uuid)
         
     | 
| 
      
 155 
     | 
    
         
            +
                    surf = cls(model, title = title, crs_uuid = crs_uuid, extra_metadata = extra_metadata)
         
     | 
| 
      
 156 
     | 
    
         
            +
                    surf.patch_list = patch_list
         
     | 
| 
      
 157 
     | 
    
         
            +
                    surf.crs_uuid = crs_uuid
         
     | 
| 
      
 158 
     | 
    
         
            +
                    crs = rqc.Crs(model, uuid = crs_uuid)
         
     | 
| 
      
 159 
     | 
    
         
            +
                    for i, patch in enumerate(surf.patch_list):
         
     | 
| 
      
 160 
     | 
    
         
            +
                        assert patch.points is not None, f'points missing in patch {i} when making surface {title}'
         
     | 
| 
      
 161 
     | 
    
         
            +
                        patch.index = i
         
     | 
| 
      
 162 
     | 
    
         
            +
                        patch._set_t_type()
         
     | 
| 
      
 163 
     | 
    
         
            +
                        if not bu.matching_uuids(patch.crs_uuid, crs_uuid):
         
     | 
| 
      
 164 
     | 
    
         
            +
                            p_crs = rqc.Crs(patch.model, uuid = patch.crs_uuid)
         
     | 
| 
      
 165 
     | 
    
         
            +
                            p_crs.convert_array_to(crs, patch.points)
         
     | 
| 
      
 166 
     | 
    
         
            +
                        patch.model = model
         
     | 
| 
      
 167 
     | 
    
         
            +
                    return surf
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                @classmethod
         
     | 
| 
      
 170 
     | 
    
         
            +
                def from_list_of_patches_of_triangles_and_points(cls, model, t_p_list, title, crs_uuid, extra_metadata = None):
         
     | 
| 
      
 171 
     | 
    
         
            +
                    """Create a Surface from a prepared list of pairs of (triangles, points).
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                    arguments:
         
     | 
| 
      
 174 
     | 
    
         
            +
                        - model (Model): the model to which the surface will be associated
         
     | 
| 
      
 175 
     | 
    
         
            +
                        - t_p_list (list of (numpy int array, numpy float array)): the list of patches of triangles and points;
         
     | 
| 
      
 176 
     | 
    
         
            +
                          the int arrays have shape (N, 3) being the triangle vertex indices of points; the float array has
         
     | 
| 
      
 177 
     | 
    
         
            +
                          shape (M, 3) being the xyx values for the points, in the crs identified by crs_uuid
         
     | 
| 
      
 178 
     | 
    
         
            +
                        - title (str): the citation title for the new surface
         
     | 
| 
      
 179 
     | 
    
         
            +
                        - crs_uuid (uuid): the uuid of a crs in model which the points are deemed to be in
         
     | 
| 
      
 180 
     | 
    
         
            +
                        - extra_metadata (dict of (str: str), optional): extra metadata to add to the new surface
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
                    returns:
         
     | 
| 
      
 183 
     | 
    
         
            +
                        - new Surface comprised of a patch for each entry in the list of pairs of triangles and points data
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                    note:
         
     | 
| 
      
 186 
     | 
    
         
            +
                        - each entry in the t_p_list will have its own patch in the resulting surface, indexed in order of list
         
     | 
| 
      
 187 
     | 
    
         
            +
                    """
         
     | 
| 
      
 188 
     | 
    
         
            +
                    assert t_p_list, f'no triangles and points pairs in list when generating surface: {title}'
         
     | 
| 
      
 189 
     | 
    
         
            +
                    assert crs_uuid is not None
         
     | 
| 
      
 190 
     | 
    
         
            +
                    patch_list = []
         
     | 
| 
      
 191 
     | 
    
         
            +
                    for i, (t, p) in enumerate(t_p_list):
         
     | 
| 
      
 192 
     | 
    
         
            +
                        patch = rqs.TriangulatedPatch(model, patch_index = i, crs_uuid = crs_uuid)
         
     | 
| 
      
 193 
     | 
    
         
            +
                        patch.set_from_triangles_and_points(t, p)
         
     | 
| 
      
 194 
     | 
    
         
            +
                        patch_list.append(patch)
         
     | 
| 
      
 195 
     | 
    
         
            +
                    return cls.from_list_of_patches(model, patch_list, title, crs_uuid = crs_uuid, extra_metadata = extra_metadata)
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
       127 
197 
     | 
    
         
             
                @classmethod
         
     | 
| 
       128 
198 
     | 
    
         
             
                def from_tri_mesh(cls, tri_mesh, exclude_nans = False):
         
     | 
| 
       129 
199 
     | 
    
         
             
                    """Create a Surface from a TriMesh.
         
     | 
| 
         @@ -318,6 +388,39 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       318 
388 
     | 
    
         
             
                        ValueError(f'patch index {patch} out of range for surface with {len(self.patch_list)} patches')
         
     | 
| 
       319 
389 
     | 
    
         
             
                    return self.patch_list[patch].triangles_and_points(copy = copy)
         
     | 
| 
       320 
390 
     | 
    
         | 
| 
      
 391 
     | 
    
         
            +
                def patch_index_for_triangle_index(self, triangle_index):
         
     | 
| 
      
 392 
     | 
    
         
            +
                    """Returns the patch index for a triangle index (as applicable to triangles_and_points() triangles)."""
         
     | 
| 
      
 393 
     | 
    
         
            +
                    if triangle_index is None or triangle_index < 0:
         
     | 
| 
      
 394 
     | 
    
         
            +
                        return None
         
     | 
| 
      
 395 
     | 
    
         
            +
                    self.extract_patches(self.root)
         
     | 
| 
      
 396 
     | 
    
         
            +
                    if not self.patch_list:
         
     | 
| 
      
 397 
     | 
    
         
            +
                        return None
         
     | 
| 
      
 398 
     | 
    
         
            +
                    for i, patch in enumerate(self.patch_list):
         
     | 
| 
      
 399 
     | 
    
         
            +
                        triangle_index -= patch.triangle_count
         
     | 
| 
      
 400 
     | 
    
         
            +
                        if triangle_index < 0:
         
     | 
| 
      
 401 
     | 
    
         
            +
                            return i
         
     | 
| 
      
 402 
     | 
    
         
            +
                    return None
         
     | 
| 
      
 403 
     | 
    
         
            +
             
     | 
| 
      
 404 
     | 
    
         
            +
                def patch_indices_for_triangle_indices(self, triangle_indices, lazy = True):
         
     | 
| 
      
 405 
     | 
    
         
            +
                    """Returns array of patch indices for array of triangle indices (as applicable to triangles_and_points() triangles)."""
         
     | 
| 
      
 406 
     | 
    
         
            +
                    self.extract_patches(self.root)
         
     | 
| 
      
 407 
     | 
    
         
            +
                    if not self.patch_list:
         
     | 
| 
      
 408 
     | 
    
         
            +
                        return np.full(triangle_indices.shape, -1, dtype = np.int8)
         
     | 
| 
      
 409 
     | 
    
         
            +
                    patch_count = len(self.patch_list)
         
     | 
| 
      
 410 
     | 
    
         
            +
                    dtype = (np.int8 if patch_count < 127 else np.int32)
         
     | 
| 
      
 411 
     | 
    
         
            +
                    if lazy and patch_count == 1:
         
     | 
| 
      
 412 
     | 
    
         
            +
                        return np.zeros(triangle_indices.shape, dtype = np.int8)
         
     | 
| 
      
 413 
     | 
    
         
            +
                    patch_limits = np.zeros(patch_count, dtype = np.int32)
         
     | 
| 
      
 414 
     | 
    
         
            +
                    t_count = 0
         
     | 
| 
      
 415 
     | 
    
         
            +
                    for p_i in range(patch_count):
         
     | 
| 
      
 416 
     | 
    
         
            +
                        t_count += self.patch_list[p_i].triangle_count
         
     | 
| 
      
 417 
     | 
    
         
            +
                        patch_limits[p_i] = t_count
         
     | 
| 
      
 418 
     | 
    
         
            +
                    patches = np.empty(triangle_indices.shape, dtype = dtype)
         
     | 
| 
      
 419 
     | 
    
         
            +
                    patches[:] = np.digitize(triangle_indices, patch_limits, right = False)
         
     | 
| 
      
 420 
     | 
    
         
            +
                    if not lazy:
         
     | 
| 
      
 421 
     | 
    
         
            +
                        patches[np.logical_or(triangle_indices < 0, patches == patch_count)] = -1
         
     | 
| 
      
 422 
     | 
    
         
            +
                    return patches
         
     | 
| 
      
 423 
     | 
    
         
            +
             
     | 
| 
       321 
424 
     | 
    
         
             
                def decache_triangles_and_points(self):
         
     | 
| 
       322 
425 
     | 
    
         
             
                    """Removes the cached composite triangles and points arrays."""
         
     | 
| 
       323 
426 
     | 
    
         
             
                    self.points = None
         
     | 
| 
         @@ -368,9 +471,11 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       368 
471 
     | 
    
         
             
                def change_crs(self, required_crs):
         
     | 
| 
       369 
472 
     | 
    
         
             
                    """Changes the crs of the surface, also sets a new uuid if crs changed.
         
     | 
| 
       370 
473 
     | 
    
         | 
| 
       371 
     | 
    
         
            -
                     
     | 
| 
      
 474 
     | 
    
         
            +
                    notes:
         
     | 
| 
       372 
475 
     | 
    
         
             
                       this method is usually used to change the coordinate system for a temporary resqpy object;
         
     | 
| 
       373 
     | 
    
         
            -
                       to add as a new part, call write_hdf5() and create_xml() methods
         
     | 
| 
      
 476 
     | 
    
         
            +
                       to add as a new part, call write_hdf5() and create_xml() methods;
         
     | 
| 
      
 477 
     | 
    
         
            +
                       patches are maintained by this method;
         
     | 
| 
      
 478 
     | 
    
         
            +
                       normal vector extra metadata item is updated if present; rotation matrix is removed
         
     | 
| 
       374 
479 
     | 
    
         
             
                    """
         
     | 
| 
       375 
480 
     | 
    
         | 
| 
       376 
481 
     | 
    
         
             
                    old_crs = rqc.Crs(self.model, uuid = self.crs_uuid)
         
     | 
| 
         @@ -385,6 +490,18 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       385 
490 
     | 
    
         
             
                        patch.crs_uuid = self.crs_uuid
         
     | 
| 
       386 
491 
     | 
    
         
             
                    self.triangles = None  # clear cached arrays for surface
         
     | 
| 
       387 
492 
     | 
    
         
             
                    self.points = None
         
     | 
| 
      
 493 
     | 
    
         
            +
                    if self.extra_metadata.pop('rotation matrix', None) is not None:
         
     | 
| 
      
 494 
     | 
    
         
            +
                        log.warning(f'discarding rotation matrix extra metadata during crs change of: {self.title}')
         
     | 
| 
      
 495 
     | 
    
         
            +
                    self._load_normal_vector_from_extra_metadata()
         
     | 
| 
      
 496 
     | 
    
         
            +
                    if self.normal_vector is not None:
         
     | 
| 
      
 497 
     | 
    
         
            +
                        if required_crs.z_inc_down != old_crs.z_inc_down:
         
     | 
| 
      
 498 
     | 
    
         
            +
                            self.normal_vector[2] = -self.normal_vector[2]
         
     | 
| 
      
 499 
     | 
    
         
            +
                        theta = (wam.convert(required_crs.rotation, required_crs.rotation_units, 'dega') -
         
     | 
| 
      
 500 
     | 
    
         
            +
                                 wam.convert(old_crs.rotation, old_crs.rotation_units, 'dega'))
         
     | 
| 
      
 501 
     | 
    
         
            +
                        if not maths.isclose(theta, 0.0):
         
     | 
| 
      
 502 
     | 
    
         
            +
                            self.normal_vector = vec.rotate_vector(vec.rotation_matrix_3d_axial(2, theta), self.normal_vector)
         
     | 
| 
      
 503 
     | 
    
         
            +
                        self.extra_metadata['normal vector'] = str(
         
     | 
| 
      
 504 
     | 
    
         
            +
                            f'{self.normal_vector[0]},{self.normal_vector[1]},{self.normal_vector[2]}')
         
     | 
| 
       388 
505 
     | 
    
         
             
                    self.uuid = bu.new_uuid()  # hope this doesn't cause problems
         
     | 
| 
       389 
506 
     | 
    
         
             
                    assert self.root is None
         
     | 
| 
       390 
507 
     | 
    
         | 
| 
         @@ -579,7 +696,8 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       579 
696 
     | 
    
         
             
                                       flange_radial_distance = None,
         
     | 
| 
       580 
697 
     | 
    
         
             
                                       flange_inner_ring = False,
         
     | 
| 
       581 
698 
     | 
    
         
             
                                       saucer_parameter = None,
         
     | 
| 
       582 
     | 
    
         
            -
                                       make_clockwise = False 
     | 
| 
      
 699 
     | 
    
         
            +
                                       make_clockwise = False,
         
     | 
| 
      
 700 
     | 
    
         
            +
                                       normal_vector = None):
         
     | 
| 
       583 
701 
     | 
    
         
             
                    """Populate this (empty) Surface object with a Delaunay triangulation of points in a PointSet object.
         
     | 
| 
       584 
702 
     | 
    
         | 
| 
       585 
703 
     | 
    
         
             
                    arguments:
         
     | 
| 
         @@ -588,9 +706,10 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       588 
706 
     | 
    
         
             
                          convex; reduce to 1.0 to allow slightly more concavities; increase to 100.0 or more for very little
         
     | 
| 
       589 
707 
     | 
    
         
             
                          chance of even a slight concavity
         
     | 
| 
       590 
708 
     | 
    
         
             
                       reorient (bool, default False): if True, a copy of the points is made and reoriented to minimise the
         
     | 
| 
       591 
     | 
    
         
            -
                          z range (ie. z axis is approximate normal to plane of points), to enhace the triangulation
         
     | 
| 
      
 709 
     | 
    
         
            +
                          z range (ie. z axis is approximate normal to plane of points), to enhace the triangulation; if a
         
     | 
| 
      
 710 
     | 
    
         
            +
                          normal_vector is supplied, the reorientation is based on that instead of minimising z
         
     | 
| 
       592 
711 
     | 
    
         
             
                       reorient_max_dip (float, optional): if present, the reorientation of perspective off vertical is
         
     | 
| 
       593 
     | 
    
         
            -
                          limited to this angle in degrees
         
     | 
| 
      
 712 
     | 
    
         
            +
                          limited to this angle in degrees; ignored if normal_vector is specified
         
     | 
| 
       594 
713 
     | 
    
         
             
                       extend_with_flange (bool, default False): if True, a ring of points is added around the outside of the
         
     | 
| 
       595 
714 
     | 
    
         
             
                          points before the triangulation, effectively extending the surface with a flange
         
     | 
| 
       596 
715 
     | 
    
         
             
                       flange_point_count (int, default 11): the number of points to generate in the flange ring; ignored if
         
     | 
| 
         @@ -608,10 +727,12 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       608 
727 
     | 
    
         
             
                       make_clockwise (bool, default False): if True, the returned triangles will all be clockwise when
         
     | 
| 
       609 
728 
     | 
    
         
             
                          viewed in the direction -ve to +ve z axis; if reorient is also True, the clockwise aspect is
         
     | 
| 
       610 
729 
     | 
    
         
             
                          enforced in the reoriented space
         
     | 
| 
      
 730 
     | 
    
         
            +
                       normal_vector (triple float, optional): if present and reorienting, the normal vector to use for reorientation;
         
     | 
| 
      
 731 
     | 
    
         
            +
                          if None, the reorientation is made so as to minimise the z range
         
     | 
| 
       611 
732 
     | 
    
         | 
| 
       612 
733 
     | 
    
         
             
                    returns:
         
     | 
| 
       613 
734 
     | 
    
         
             
                       if extend_with_flange is True, numpy bool array with a value per triangle indicating flange triangles;
         
     | 
| 
       614 
     | 
    
         
            -
                       if  
     | 
| 
      
 735 
     | 
    
         
            +
                       if extend_with_flange is False, None
         
     | 
| 
       615 
736 
     | 
    
         | 
| 
       616 
737 
     | 
    
         
             
                    notes:
         
     | 
| 
       617 
738 
     | 
    
         
             
                       if extend_with_flange is True, then a boolean array is created for the surface, with a value per triangle,
         
     | 
| 
         @@ -619,23 +740,18 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       619 
740 
     | 
    
         
             
                       suitable for adding as a property for the surface, with indexable element 'faces';
         
     | 
| 
       620 
741 
     | 
    
         
             
                       when flange extension occurs, the radius is the greater of the values determined from the radial factor
         
     | 
| 
       621 
742 
     | 
    
         
             
                       and radial distance arguments;
         
     | 
| 
       622 
     | 
    
         
            -
                       the saucer_parameter  
     | 
| 
       623 
     | 
    
         
            -
                        
     | 
| 
       624 
     | 
    
         
            -
                        
     | 
| 
       625 
     | 
    
         
            -
                        
     | 
| 
       626 
     | 
    
         
            -
                        
     | 
| 
       627 
     | 
    
         
            -
                       the plane of the original points, to give a simple (and less computationally demanding) saucer shape;
         
     | 
| 
       628 
     | 
    
         
            -
                       +ve angles result in the shift being in the direction of the -ve z hemisphere; -ve angles result in
         
     | 
| 
       629 
     | 
    
         
            -
                       the shift being in the +ve z hemisphere; in either case the direction of the shift is perpendicular
         
     | 
| 
       630 
     | 
    
         
            -
                       to the average plane of the original points
         
     | 
| 
      
 743 
     | 
    
         
            +
                       the saucer_parameter must be between -90.0 and 90.0, and is interpreted as an angle to apply out of
         
     | 
| 
      
 744 
     | 
    
         
            +
                       the plane of the original points, to give a simple saucer shape; +ve angles result in the shift being in 
         
     | 
| 
      
 745 
     | 
    
         
            +
                       the direction of the -ve z hemisphere; -ve angles result in the shift being in the +ve z hemisphere; in 
         
     | 
| 
      
 746 
     | 
    
         
            +
                       either case the direction of the shift is perpendicular to the average plane of the original points;
         
     | 
| 
      
 747 
     | 
    
         
            +
                       normal_vector, if supplied, should be in the crs of the point set
         
     | 
| 
       631 
748 
     | 
    
         
             
                    """
         
     | 
| 
       632 
749 
     | 
    
         | 
| 
       633 
750 
     | 
    
         
             
                    simple_saucer_angle = None
         
     | 
| 
       634 
     | 
    
         
            -
                    if saucer_parameter is not None 
     | 
| 
      
 751 
     | 
    
         
            +
                    if saucer_parameter is not None:
         
     | 
| 
       635 
752 
     | 
    
         
             
                        assert -90.0 < saucer_parameter < 90.0, f'simple saucer angle parameter must be less than 90 degrees; too big: {saucer_parameter}'
         
     | 
| 
       636 
753 
     | 
    
         
             
                        simple_saucer_angle = saucer_parameter
         
     | 
| 
       637 
754 
     | 
    
         
             
                        saucer_parameter = None
         
     | 
| 
       638 
     | 
    
         
            -
                    assert saucer_parameter is None or 0.0 <= saucer_parameter < 1.0
         
     | 
| 
       639 
755 
     | 
    
         
             
                    crs = rqc.Crs(self.model, uuid = point_set.crs_uuid)
         
     | 
| 
       640 
756 
     | 
    
         
             
                    p = point_set.full_array_ref()
         
     | 
| 
       641 
757 
     | 
    
         
             
                    assert p.ndim >= 2
         
     | 
| 
         @@ -648,37 +764,44 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       648 
764 
     | 
    
         
             
                            f'removing {len(p) - np.count_nonzero(row_mask)} NaN points from point set {point_set.title} prior to surface triangulation'
         
     | 
| 
       649 
765 
     | 
    
         
             
                        )
         
     | 
| 
       650 
766 
     | 
    
         
             
                        p = p[row_mask, :]
         
     | 
| 
       651 
     | 
    
         
            -
                    if crs.xy_units == crs.z_units 
     | 
| 
      
 767 
     | 
    
         
            +
                    if crs.xy_units == crs.z_units:
         
     | 
| 
       652 
768 
     | 
    
         
             
                        unit_adjusted_p = p
         
     | 
| 
       653 
769 
     | 
    
         
             
                    else:
         
     | 
| 
       654 
770 
     | 
    
         
             
                        unit_adjusted_p = p.copy()
         
     | 
| 
       655 
771 
     | 
    
         
             
                        wam.convert_lengths(unit_adjusted_p[:, 2], crs.z_units, crs.xy_units)
         
     | 
| 
       656 
     | 
    
         
            -
             
     | 
| 
      
 772 
     | 
    
         
            +
                        # note: normal vector should already be for a crs with common xy  & z units
         
     | 
| 
      
 773 
     | 
    
         
            +
                    # reorient the points to the fault normal vector
         
     | 
| 
      
 774 
     | 
    
         
            +
                    if normal_vector is None:
         
     | 
| 
       657 
775 
     | 
    
         
             
                        p_xy, self.normal_vector, reorient_matrix = triangulate.reorient(unit_adjusted_p,
         
     | 
| 
       658 
776 
     | 
    
         
             
                                                                                         max_dip = reorient_max_dip)
         
     | 
| 
       659 
777 
     | 
    
         
             
                    else:
         
     | 
| 
       660 
     | 
    
         
            -
                         
     | 
| 
      
 778 
     | 
    
         
            +
                        assert len(normal_vector) == 3
         
     | 
| 
      
 779 
     | 
    
         
            +
                        self.normal_vector = np.array(normal_vector, dtype = np.float64)
         
     | 
| 
      
 780 
     | 
    
         
            +
                        if self.normal_vector[2] < 0.0:
         
     | 
| 
      
 781 
     | 
    
         
            +
                            self.normal_vector = -self.normal_vector
         
     | 
| 
      
 782 
     | 
    
         
            +
                        incl = vec.inclination(normal_vector)
         
     | 
| 
      
 783 
     | 
    
         
            +
                        if maths.isclose(incl, 0.0):
         
     | 
| 
      
 784 
     | 
    
         
            +
                            reorient_matrix = vec.no_rotation_matrix()
         
     | 
| 
      
 785 
     | 
    
         
            +
                            p_xy = unit_adjusted_p
         
     | 
| 
      
 786 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 787 
     | 
    
         
            +
                            azi = vec.azimuth(normal_vector)
         
     | 
| 
      
 788 
     | 
    
         
            +
                            reorient_matrix = vec.tilt_3d_matrix(azi, incl)
         
     | 
| 
      
 789 
     | 
    
         
            +
                            p_xy = vec.rotate_array(reorient_matrix, unit_adjusted_p)
         
     | 
| 
       661 
790 
     | 
    
         
             
                    if extend_with_flange:
         
     | 
| 
       662 
     | 
    
         
            -
                         
     | 
| 
       663 
     | 
    
         
            -
             
     | 
| 
       664 
     | 
    
         
            -
             
     | 
| 
       665 
     | 
    
         
            -
             
     | 
| 
       666 
     | 
    
         
            -
             
     | 
| 
       667 
     | 
    
         
            -
             
     | 
| 
       668 
     | 
    
         
            -
             
     | 
| 
       669 
     | 
    
         
            -
                                                                        radial_distance = flange_radial_distance,
         
     | 
| 
       670 
     | 
    
         
            -
                                                                        inner_ring = flange_inner_ring,
         
     | 
| 
       671 
     | 
    
         
            -
                                                                        saucer_angle = simple_saucer_angle)
         
     | 
| 
       672 
     | 
    
         
            -
                        p_xy_e = np.concatenate((p_xy, flange_points), axis = 0)
         
     | 
| 
      
 791 
     | 
    
         
            +
                        flange_points, radius = triangulate.surrounding_xy_ring(p_xy,
         
     | 
| 
      
 792 
     | 
    
         
            +
                                                                                count = flange_point_count,
         
     | 
| 
      
 793 
     | 
    
         
            +
                                                                                radial_factor = flange_radial_factor,
         
     | 
| 
      
 794 
     | 
    
         
            +
                                                                                radial_distance = flange_radial_distance,
         
     | 
| 
      
 795 
     | 
    
         
            +
                                                                                inner_ring = flange_inner_ring,
         
     | 
| 
      
 796 
     | 
    
         
            +
                                                                                saucer_angle = 0.0)
         
     | 
| 
      
 797 
     | 
    
         
            +
                        flange_points_reverse_oriented = vec.rotate_array(reorient_matrix.T, flange_points)
         
     | 
| 
       673 
798 
     | 
    
         
             
                        if reorient:
         
     | 
| 
       674 
     | 
    
         
            -
                             
     | 
| 
       675 
     | 
    
         
            -
                            flange_points_reverse_oriented = vec.rotate_array(reorient_matrix.T, flange_points)
         
     | 
| 
       676 
     | 
    
         
            -
                            p_e = np.concatenate((unit_adjusted_p, flange_points_reverse_oriented), axis = 0)
         
     | 
| 
      
 799 
     | 
    
         
            +
                            p_xy_e = np.concatenate((p_xy, flange_points), axis = 0)
         
     | 
| 
       677 
800 
     | 
    
         
             
                        else:
         
     | 
| 
       678 
     | 
    
         
            -
                             
     | 
| 
      
 801 
     | 
    
         
            +
                            p_xy_e = np.concatenate((unit_adjusted_p, flange_points_reverse_oriented), axis = 0)
         
     | 
| 
      
 802 
     | 
    
         
            +
             
     | 
| 
       679 
803 
     | 
    
         
             
                    else:
         
     | 
| 
       680 
     | 
    
         
            -
                        p_xy_e =  
     | 
| 
       681 
     | 
    
         
            -
                        p_e = unit_adjusted_p
         
     | 
| 
      
 804 
     | 
    
         
            +
                        p_xy_e = unit_adjusted_p
         
     | 
| 
       682 
805 
     | 
    
         
             
                        flange_array = None
         
     | 
| 
       683 
806 
     | 
    
         
             
                    log.debug('number of points going into dt: ' + str(len(p_xy_e)))
         
     | 
| 
       684 
807 
     | 
    
         
             
                    success = False
         
     | 
| 
         @@ -693,19 +816,214 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       693 
816 
     | 
    
         
             
                        t = triangulate.dt(p_xy_e[:, :2], container_size_factor = convexity_parameter * 1.1)
         
     | 
| 
       694 
817 
     | 
    
         
             
                    log.debug('number of triangles: ' + str(len(t)))
         
     | 
| 
       695 
818 
     | 
    
         
             
                    if make_clockwise:
         
     | 
| 
       696 
     | 
    
         
            -
                        triangulate.make_all_clockwise_xy(t,  
     | 
| 
      
 819 
     | 
    
         
            +
                        triangulate.make_all_clockwise_xy(t, p_xy_e)  # modifies t in situ
         
     | 
| 
       697 
820 
     | 
    
         
             
                    if extend_with_flange:
         
     | 
| 
       698 
821 
     | 
    
         
             
                        flange_array = np.zeros(len(t), dtype = bool)
         
     | 
| 
       699 
822 
     | 
    
         
             
                        flange_array[:] = np.where(np.any(t >= len(p), axis = 1), True, False)
         
     | 
| 
       700 
     | 
    
         
            -
                        if  
     | 
| 
       701 
     | 
    
         
            -
                             
     | 
| 
       702 
     | 
    
         
            -
                             
     | 
| 
       703 
     | 
    
         
            -
             
     | 
| 
       704 
     | 
    
         
            -
             
     | 
| 
      
 823 
     | 
    
         
            +
                        if simple_saucer_angle is not None:
         
     | 
| 
      
 824 
     | 
    
         
            +
                            assert abs(simple_saucer_angle) < 90.0
         
     | 
| 
      
 825 
     | 
    
         
            +
                            z_shift = radius * maths.tan(vec.radians_from_degrees(simple_saucer_angle))
         
     | 
| 
      
 826 
     | 
    
         
            +
                            flange_points[:, 2] -= z_shift
         
     | 
| 
      
 827 
     | 
    
         
            +
                            flange_points_reverse_oriented = vec.rotate_array(reorient_matrix.T, flange_points)
         
     | 
| 
      
 828 
     | 
    
         
            +
                        if crs.xy_units != crs.z_units:
         
     | 
| 
      
 829 
     | 
    
         
            +
                            wam.convert_lengths(flange_points_reverse_oriented[:, 2], crs.xy_units, crs.z_units)
         
     | 
| 
      
 830 
     | 
    
         
            +
                        p_e = np.concatenate((p, flange_points_reverse_oriented))
         
     | 
| 
      
 831 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 832 
     | 
    
         
            +
                        p_e = p
         
     | 
| 
       705 
833 
     | 
    
         
             
                    self.crs_uuid = point_set.crs_uuid
         
     | 
| 
       706 
834 
     | 
    
         
             
                    self.set_from_triangles_and_points(t, p_e)
         
     | 
| 
       707 
835 
     | 
    
         
             
                    return flange_array
         
     | 
| 
       708 
836 
     | 
    
         | 
| 
      
 837 
     | 
    
         
            +
                def extend_surface_with_flange(self,
         
     | 
| 
      
 838 
     | 
    
         
            +
                                               convexity_parameter = 5.0,
         
     | 
| 
      
 839 
     | 
    
         
            +
                                               reorient = False,
         
     | 
| 
      
 840 
     | 
    
         
            +
                                               reorient_max_dip = None,
         
     | 
| 
      
 841 
     | 
    
         
            +
                                               flange_point_count = 11,
         
     | 
| 
      
 842 
     | 
    
         
            +
                                               flange_radial_factor = 10.0,
         
     | 
| 
      
 843 
     | 
    
         
            +
                                               flange_radial_distance = None,
         
     | 
| 
      
 844 
     | 
    
         
            +
                                               flange_inner_ring = False,
         
     | 
| 
      
 845 
     | 
    
         
            +
                                               saucer_parameter = None,
         
     | 
| 
      
 846 
     | 
    
         
            +
                                               make_clockwise = False,
         
     | 
| 
      
 847 
     | 
    
         
            +
                                               retriangulate = False,
         
     | 
| 
      
 848 
     | 
    
         
            +
                                               normal_vector = None):
         
     | 
| 
      
 849 
     | 
    
         
            +
                    """Returns a new Surface object where the original surface has been extended with a flange with a Delaunay triangulation of points in a PointSet object.
         
     | 
| 
      
 850 
     | 
    
         
            +
             
     | 
| 
      
 851 
     | 
    
         
            +
                    arguments:
         
     | 
| 
      
 852 
     | 
    
         
            +
                        convexity_parameter (float, default 5.0): controls how likely the resulting triangulation is to be
         
     | 
| 
      
 853 
     | 
    
         
            +
                            convex; reduce to 1.0 to allow slightly more concavities; increase to 100.0 or more for very little
         
     | 
| 
      
 854 
     | 
    
         
            +
                            chance of even a slight concavity
         
     | 
| 
      
 855 
     | 
    
         
            +
                        reorient (bool, default False): if True, a copy of the points is made and reoriented to minimise the
         
     | 
| 
      
 856 
     | 
    
         
            +
                            z range (ie. z axis is approximate normal to plane of points), to enhace the triangulation; if
         
     | 
| 
      
 857 
     | 
    
         
            +
                            normal_vector is supplied that is used to determine the reorientation instead of minimising z
         
     | 
| 
      
 858 
     | 
    
         
            +
                        reorient_max_dip (float, optional): if present, the reorientation of perspective off vertical is
         
     | 
| 
      
 859 
     | 
    
         
            +
                            limited to this angle in degrees; ignored if normal_vector is specified
         
     | 
| 
      
 860 
     | 
    
         
            +
                        flange_point_count (int, default 11): the number of points to generate in the flange ring; ignored if
         
     | 
| 
      
 861 
     | 
    
         
            +
                            retriangulate is False
         
     | 
| 
      
 862 
     | 
    
         
            +
                        flange_radial_factor (float, default 10.0): distance of flange points from centre of points, as a
         
     | 
| 
      
 863 
     | 
    
         
            +
                            factor of the maximum radial distance of the points themselves; ignored if extend_with_flange is False
         
     | 
| 
      
 864 
     | 
    
         
            +
                        flange_radial_distance (float, optional): if present, the minimum absolute distance of flange points from
         
     | 
| 
      
 865 
     | 
    
         
            +
                            centre of points; units are those of the crs
         
     | 
| 
      
 866 
     | 
    
         
            +
                        flange_inner_ring (bool, default False): if True, an inner ring of points, with double flange point counr,
         
     | 
| 
      
 867 
     | 
    
         
            +
                            is created at a radius just outside that of the furthest flung original point; this improves
         
     | 
| 
      
 868 
     | 
    
         
            +
                            triangulation of the extended point set when the original has a non-convex hull. Ignored if retriangulate
         
     | 
| 
      
 869 
     | 
    
         
            +
                            is False
         
     | 
| 
      
 870 
     | 
    
         
            +
                        saucer_parameter (float, optional): if present, and extend_with_flange is True, then a parameter
         
     | 
| 
      
 871 
     | 
    
         
            +
                            controlling the shift of flange points in a perpendicular direction away from the fault plane;
         
     | 
| 
      
 872 
     | 
    
         
            +
                            see notes for how this parameter is interpreted
         
     | 
| 
      
 873 
     | 
    
         
            +
                        make_clockwise (bool, default False): if True, the returned triangles will all be clockwise when
         
     | 
| 
      
 874 
     | 
    
         
            +
                            viewed in the direction -ve to +ve z axis; if reorient is also True, the clockwise aspect is
         
     | 
| 
      
 875 
     | 
    
         
            +
                            enforced in the reoriented space
         
     | 
| 
      
 876 
     | 
    
         
            +
                        retriangulate (bool, default False): if True, the surface will be generated with a retriangulation of
         
     | 
| 
      
 877 
     | 
    
         
            +
                            the existing points. If False, the surface will be generated by adding flange points and triangles directly
         
     | 
| 
      
 878 
     | 
    
         
            +
                            from the original surface edges, and will no retriangulate the input surface. If False the surface must not
         
     | 
| 
      
 879 
     | 
    
         
            +
                            contain tears
         
     | 
| 
      
 880 
     | 
    
         
            +
                        normal_vector (triple float, optional): if present and reorienting, the normal vector to use for reorientation;
         
     | 
| 
      
 881 
     | 
    
         
            +
                            if None, the reorientation is made so as to minimise the z range
         
     | 
| 
      
 882 
     | 
    
         
            +
             
     | 
| 
      
 883 
     | 
    
         
            +
                    returns:
         
     | 
| 
      
 884 
     | 
    
         
            +
                        a new surface, and a boolean array of length N, where N is the number of triangles on the surface. This boolean
         
     | 
| 
      
 885 
     | 
    
         
            +
                        array is False on original triangle points, and True for extended flange triangles
         
     | 
| 
      
 886 
     | 
    
         
            +
             
     | 
| 
      
 887 
     | 
    
         
            +
                    notes:
         
     | 
| 
      
 888 
     | 
    
         
            +
                        a boolean array is created for the surface, with a value per triangle, set to False (zero) for non-flange
         
     | 
| 
      
 889 
     | 
    
         
            +
                        triangles and True (one) for flange triangles; this array is suitable for adding as a property for the
         
     | 
| 
      
 890 
     | 
    
         
            +
                        surface, with indexable element 'faces';
         
     | 
| 
      
 891 
     | 
    
         
            +
                        when flange extension occurs, the radius is the greater of the values determined from the radial factor
         
     | 
| 
      
 892 
     | 
    
         
            +
                        and radial distance arguments;
         
     | 
| 
      
 893 
     | 
    
         
            +
                        the saucer_parameter is interpreted in one of two ways: (1) +ve fractoinal values between zero and one
         
     | 
| 
      
 894 
     | 
    
         
            +
                        are the fractional distance from the centre of the points to its rim at which to sample the surface for
         
     | 
| 
      
 895 
     | 
    
         
            +
                        extrapolation and thereby modify the recumbent z of flange points; 0 will usually give shallower and
         
     | 
| 
      
 896 
     | 
    
         
            +
                        smoother saucer; larger values (must be less than one) will lead to stronger and more erratic saucer
         
     | 
| 
      
 897 
     | 
    
         
            +
                        shape in flange; (2) other values between -90.0 and 90.0 are interpreted as an angle to apply out of
         
     | 
| 
      
 898 
     | 
    
         
            +
                        the plane of the original points, to give a simple (and less computationally demanding) saucer shape;
         
     | 
| 
      
 899 
     | 
    
         
            +
                        +ve angles result in the shift being in the direction of the -ve z hemisphere; -ve angles result in
         
     | 
| 
      
 900 
     | 
    
         
            +
                        the shift being in the +ve z hemisphere; in either case the direction of the shift is perpendicular
         
     | 
| 
      
 901 
     | 
    
         
            +
                        to the average plane of the original points;
         
     | 
| 
      
 902 
     | 
    
         
            +
                        normal_vector, if supplied, should be in the crs of this surface
         
     | 
| 
      
 903 
     | 
    
         
            +
                    """
         
     | 
| 
      
 904 
     | 
    
         
            +
                    prev_t, prev_p = self.triangles_and_points()
         
     | 
| 
      
 905 
     | 
    
         
            +
                    point_set = rqs.PointSet(self.model, crs_uuid = self.crs_uuid, title = self.title, points_array = prev_p)
         
     | 
| 
      
 906 
     | 
    
         
            +
                    if retriangulate:
         
     | 
| 
      
 907 
     | 
    
         
            +
                        out_surf = Surface(self.model, crs_uuid = self.crs_uuid, title = self.title)
         
     | 
| 
      
 908 
     | 
    
         
            +
                        return out_surf, out_surf.set_from_point_set(point_set, convexity_parameter, reorient, reorient_max_dip,
         
     | 
| 
      
 909 
     | 
    
         
            +
                                                                     True, flange_point_count, flange_radial_factor,
         
     | 
| 
      
 910 
     | 
    
         
            +
                                                                     flange_radial_distance, flange_inner_ring, saucer_parameter,
         
     | 
| 
      
 911 
     | 
    
         
            +
                                                                     make_clockwise, normal_vector)
         
     | 
| 
      
 912 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 913 
     | 
    
         
            +
                        simple_saucer_angle = None
         
     | 
| 
      
 914 
     | 
    
         
            +
                        if saucer_parameter is not None and (saucer_parameter > 1.0 or saucer_parameter < 0.0):
         
     | 
| 
      
 915 
     | 
    
         
            +
                            assert -90.0 < saucer_parameter < 90.0, f'simple saucer angle parameter must be less than 90 degrees; too big: {saucer_parameter}'
         
     | 
| 
      
 916 
     | 
    
         
            +
                            simple_saucer_angle = saucer_parameter
         
     | 
| 
      
 917 
     | 
    
         
            +
                            saucer_parameter = None
         
     | 
| 
      
 918 
     | 
    
         
            +
                        assert saucer_parameter is None or 0.0 <= saucer_parameter < 1.0
         
     | 
| 
      
 919 
     | 
    
         
            +
                        crs = rqc.Crs(self.model, uuid = point_set.crs_uuid)
         
     | 
| 
      
 920 
     | 
    
         
            +
                        assert prev_p.ndim >= 2
         
     | 
| 
      
 921 
     | 
    
         
            +
                        assert prev_p.shape[-1] == 3
         
     | 
| 
      
 922 
     | 
    
         
            +
                        p = prev_p.reshape((-1, 3))
         
     | 
| 
      
 923 
     | 
    
         
            +
                        if crs.xy_units == crs.z_units or not reorient:
         
     | 
| 
      
 924 
     | 
    
         
            +
                            unit_adjusted_p = p
         
     | 
| 
      
 925 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 926 
     | 
    
         
            +
                            unit_adjusted_p = p.copy()
         
     | 
| 
      
 927 
     | 
    
         
            +
                            wam.convert_lengths(unit_adjusted_p[:, 2], crs.z_units, crs.xy_units)
         
     | 
| 
      
 928 
     | 
    
         
            +
                        if reorient:
         
     | 
| 
      
 929 
     | 
    
         
            +
                            p_xy, normal, reorient_matrix = triangulate.reorient(unit_adjusted_p, max_dip = reorient_max_dip)
         
     | 
| 
      
 930 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 931 
     | 
    
         
            +
                            p_xy = unit_adjusted_p
         
     | 
| 
      
 932 
     | 
    
         
            +
                            normal = self.normal()
         
     | 
| 
      
 933 
     | 
    
         
            +
                            reorient_matrix = None
         
     | 
| 
      
 934 
     | 
    
         
            +
             
     | 
| 
      
 935 
     | 
    
         
            +
                        centre_point = np.nanmean(p_xy.reshape((-1, 3)), axis = 0)  # work out the radius for the flange points
         
     | 
| 
      
 936 
     | 
    
         
            +
                        p_radius_v = np.nanmax(np.abs(p.reshape((-1, 3)) - np.expand_dims(centre_point, axis = 0)), axis = 0)[:2]
         
     | 
| 
      
 937 
     | 
    
         
            +
                        p_radius = maths.sqrt(np.sum(p_radius_v * p_radius_v))
         
     | 
| 
      
 938 
     | 
    
         
            +
                        radius = p_radius * flange_radial_factor
         
     | 
| 
      
 939 
     | 
    
         
            +
                        if flange_radial_distance is not None and flange_radial_distance > radius:
         
     | 
| 
      
 940 
     | 
    
         
            +
                            radius = flange_radial_distance
         
     | 
| 
      
 941 
     | 
    
         
            +
             
     | 
| 
      
 942 
     | 
    
         
            +
                        de, dc = self.distinct_edges_and_counts()  # find the distinct edges and counts
         
     | 
| 
      
 943 
     | 
    
         
            +
                        unique_edge = de[dc == 1]  # find hull edges (edges on only a single triangle)
         
     | 
| 
      
 944 
     | 
    
         
            +
                        hull_points = p_xy[unique_edge]  # find points defining the hull edges
         
     | 
| 
      
 945 
     | 
    
         
            +
                        hull_centres = np.mean(hull_points, axis = 1)  # find the centre of each edge
         
     | 
| 
      
 946 
     | 
    
         
            +
             
     | 
| 
      
 947 
     | 
    
         
            +
                        flange_points = np.empty(
         
     | 
| 
      
 948 
     | 
    
         
            +
                            shape = (hull_centres.shape), dtype = float
         
     | 
| 
      
 949 
     | 
    
         
            +
                        )  # loop over all the hull centres, generating a flange point and finding the azimuth from the centre to the hull centre point
         
     | 
| 
      
 950 
     | 
    
         
            +
                        az = np.empty(shape = len(hull_centres), dtype = float)
         
     | 
| 
      
 951 
     | 
    
         
            +
                        for i, c in enumerate(hull_centres):
         
     | 
| 
      
 952 
     | 
    
         
            +
                            v = [centre_point[0] - c[0], centre_point[1] - c[1], centre_point[2] - c[2]]
         
     | 
| 
      
 953 
     | 
    
         
            +
                            uv = -vec.unit_vector(v)
         
     | 
| 
      
 954 
     | 
    
         
            +
                            az[i] = vec.azimuth(uv)
         
     | 
| 
      
 955 
     | 
    
         
            +
                            flange_point = centre_point + radius * uv
         
     | 
| 
      
 956 
     | 
    
         
            +
                            if simple_saucer_angle is not None:
         
     | 
| 
      
 957 
     | 
    
         
            +
                                z_shift = radius * maths.tan(vec.radians_from_degrees(simple_saucer_angle))
         
     | 
| 
      
 958 
     | 
    
         
            +
                                if reorient:
         
     | 
| 
      
 959 
     | 
    
         
            +
                                    flange_point[2] -= z_shift
         
     | 
| 
      
 960 
     | 
    
         
            +
                                else:
         
     | 
| 
      
 961 
     | 
    
         
            +
                                    flange_point -= (-vec.unit_vector(normal) * z_shift)
         
     | 
| 
      
 962 
     | 
    
         
            +
                            flange_points[i] = flange_point
         
     | 
| 
      
 963 
     | 
    
         
            +
             
     | 
| 
      
 964 
     | 
    
         
            +
                        sort_az_ind = np.argsort(np.array(az))  # sort by azimuth, to run through the hull points
         
     | 
| 
      
 965 
     | 
    
         
            +
                        new_points = np.empty(shape = (len(flange_points), 3), dtype = float)
         
     | 
| 
      
 966 
     | 
    
         
            +
                        new_triangles = np.empty(shape = (len(flange_points) * 2, 3), dtype = int)
         
     | 
| 
      
 967 
     | 
    
         
            +
                        point_offset = len(p_xy)  # the indices of the new triangles will begin after this
         
     | 
| 
      
 968 
     | 
    
         
            +
                        for i, ind in enumerate(sort_az_ind):  # loop over each point in azimuth order
         
     | 
| 
      
 969 
     | 
    
         
            +
                            new_points[i] = flange_points[ind]
         
     | 
| 
      
 970 
     | 
    
         
            +
                            this_hull_edge = unique_edge[ind]
         
     | 
| 
      
 971 
     | 
    
         
            +
             
     | 
| 
      
 972 
     | 
    
         
            +
                            def az_for_point(c):
         
     | 
| 
      
 973 
     | 
    
         
            +
                                v = [centre_point[0] - c[0], centre_point[1] - c[1], centre_point[2] - c[2]]
         
     | 
| 
      
 974 
     | 
    
         
            +
                                uv = -vec.unit_vector(v)
         
     | 
| 
      
 975 
     | 
    
         
            +
                                return vec.azimuth(uv)
         
     | 
| 
      
 976 
     | 
    
         
            +
             
     | 
| 
      
 977 
     | 
    
         
            +
                            this_edge_az_sort = np.array(
         
     | 
| 
      
 978 
     | 
    
         
            +
                                [az_for_point(p_xy[this_hull_edge[0]]),
         
     | 
| 
      
 979 
     | 
    
         
            +
                                 az_for_point(p_xy[this_hull_edge[1]])])
         
     | 
| 
      
 980 
     | 
    
         
            +
                            if np.min(this_edge_az_sort) < az[ind] < np.max(this_edge_az_sort):
         
     | 
| 
      
 981 
     | 
    
         
            +
                                first, second = np.argsort(this_edge_az_sort)
         
     | 
| 
      
 982 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 983 
     | 
    
         
            +
                                second, first = np.argsort(this_edge_az_sort)
         
     | 
| 
      
 984 
     | 
    
         
            +
                            if i != len(sort_az_ind) - 1:
         
     | 
| 
      
 985 
     | 
    
         
            +
                                new_triangles[2 * i] = np.array(
         
     | 
| 
      
 986 
     | 
    
         
            +
                                    [this_hull_edge[first], this_hull_edge[second],
         
     | 
| 
      
 987 
     | 
    
         
            +
                                     i + point_offset])  # add a triangle between the two hull points and the flange point
         
     | 
| 
      
 988 
     | 
    
         
            +
                                new_triangles[(2 * i) + 1] = np.array(
         
     | 
| 
      
 989 
     | 
    
         
            +
                                    [this_hull_edge[second], i + point_offset,
         
     | 
| 
      
 990 
     | 
    
         
            +
                                     i + point_offset + 1])  # for all but the last point, hookup triangle to the next flange point
         
     | 
| 
      
 991 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 992 
     | 
    
         
            +
                                new_triangles[2 * i] = np.array(
         
     | 
| 
      
 993 
     | 
    
         
            +
                                    [this_hull_edge[first], this_hull_edge[second],
         
     | 
| 
      
 994 
     | 
    
         
            +
                                     i + point_offset])  # add a triangle between the two hull points and the first flange point
         
     | 
| 
      
 995 
     | 
    
         
            +
                                new_triangles[(2 * i) + 1] = np.array(
         
     | 
| 
      
 996 
     | 
    
         
            +
                                    [this_hull_edge[second], point_offset,
         
     | 
| 
      
 997 
     | 
    
         
            +
                                     i + point_offset])  # add in the final triangle between the first and last flange points
         
     | 
| 
      
 998 
     | 
    
         
            +
             
     | 
| 
      
 999 
     | 
    
         
            +
                        all_points = np.concatenate((p_xy, new_points))  # concatenate triangle and points arrays
         
     | 
| 
      
 1000 
     | 
    
         
            +
                        all_triangles = np.concatenate((prev_t, new_triangles))
         
     | 
| 
      
 1001 
     | 
    
         
            +
             
     | 
| 
      
 1002 
     | 
    
         
            +
                        flange_array = np.zeros(shape = all_triangles.shape[0], dtype = bool)
         
     | 
| 
      
 1003 
     | 
    
         
            +
                        flange_array[
         
     | 
| 
      
 1004 
     | 
    
         
            +
                            len(prev_t):] = True  # make a flange bool array, where all new triangles are flange and therefore True
         
     | 
| 
      
 1005 
     | 
    
         
            +
             
     | 
| 
      
 1006 
     | 
    
         
            +
                        assert len(all_points) == (
         
     | 
| 
      
 1007 
     | 
    
         
            +
                            point_offset + len(flange_points)), "New point count should be old point count + flange point count"
         
     | 
| 
      
 1008 
     | 
    
         
            +
                        assert len(all_triangles) == (
         
     | 
| 
      
 1009 
     | 
    
         
            +
                            len(prev_t) +
         
     | 
| 
      
 1010 
     | 
    
         
            +
                            2 * len(flange_points)), "New triangle count should be old triangle count + 2 x #flange points"
         
     | 
| 
      
 1011 
     | 
    
         
            +
             
     | 
| 
      
 1012 
     | 
    
         
            +
                        if saucer_parameter is not None:
         
     | 
| 
      
 1013 
     | 
    
         
            +
                            _adjust_flange_z(self.model, crs.uuid, all_points, len(all_points), all_triangles, flange_array,
         
     | 
| 
      
 1014 
     | 
    
         
            +
                                             saucer_parameter)  # adjust the flange points if in saucer mode
         
     | 
| 
      
 1015 
     | 
    
         
            +
                        if reorient:
         
     | 
| 
      
 1016 
     | 
    
         
            +
                            all_points = vec.rotate_array(reorient_matrix.T, all_points)
         
     | 
| 
      
 1017 
     | 
    
         
            +
                        if crs.xy_units != crs.z_units and reorient:
         
     | 
| 
      
 1018 
     | 
    
         
            +
                            wam.convert_lengths(all_points[:, 2], crs.xy_units, crs.z_units)
         
     | 
| 
      
 1019 
     | 
    
         
            +
             
     | 
| 
      
 1020 
     | 
    
         
            +
                        if make_clockwise:
         
     | 
| 
      
 1021 
     | 
    
         
            +
                            triangulate.make_all_clockwise_xy(all_triangles, all_points)  # modifies t in situ
         
     | 
| 
      
 1022 
     | 
    
         
            +
             
     | 
| 
      
 1023 
     | 
    
         
            +
                        out_surf = Surface(self.model, crs_uuid = self.crs_uuid, title = self.title)
         
     | 
| 
      
 1024 
     | 
    
         
            +
                        out_surf.set_from_triangles_and_points(all_triangles, all_points)  # create the new surface
         
     | 
| 
      
 1025 
     | 
    
         
            +
                        return out_surf, flange_array
         
     | 
| 
      
 1026 
     | 
    
         
            +
             
     | 
| 
       709 
1027 
     | 
    
         
             
                def make_all_clockwise_xy(self, reorient = False):
         
     | 
| 
       710 
1028 
     | 
    
         
             
                    """Reorders cached triangles data such that all triangles are clockwise when viewed from -ve z axis.
         
     | 
| 
       711 
1029 
     | 
    
         | 
| 
         @@ -740,9 +1058,10 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       740 
1058 
     | 
    
         
             
                    notes:
         
     | 
| 
       741 
1059 
     | 
    
         
             
                       the result becomes more meaningless the less planar the surface is;
         
     | 
| 
       742 
1060 
     | 
    
         
             
                       even for a parfectly planar surface, the result is approximate;
         
     | 
| 
       743 
     | 
    
         
            -
                       true normal vector is found when xy & z units differ
         
     | 
| 
      
 1061 
     | 
    
         
            +
                       true normal vector is found when xy & z units differ, ie. for consistent units
         
     | 
| 
       744 
1062 
     | 
    
         
             
                    """
         
     | 
| 
       745 
1063 
     | 
    
         | 
| 
      
 1064 
     | 
    
         
            +
                    self._load_normal_vector_from_extra_metadata()
         
     | 
| 
       746 
1065 
     | 
    
         
             
                    if self.normal_vector is None:
         
     | 
| 
       747 
1066 
     | 
    
         
             
                        p = self.unit_adjusted_points()
         
     | 
| 
       748 
1067 
     | 
    
         
             
                        _, self.normal_vector, _ = triangulate.reorient(p)
         
     | 
| 
         @@ -845,7 +1164,7 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       845 
1164 
     | 
    
         | 
| 
       846 
1165 
     | 
    
         
             
                def set_to_multi_cell_faces_from_corner_points(self, cp, quad_triangles = True):
         
     | 
| 
       847 
1166 
     | 
    
         
             
                    """Populates this (empty) surface to represent faces of a set of cells.
         
     | 
| 
       848 
     | 
    
         
            -
             
     | 
| 
      
 1167 
     | 
    
         
            +
             
     | 
| 
       849 
1168 
     | 
    
         
             
                    From corner points of shape (N, 2, 2, 2, 3).
         
     | 
| 
       850 
1169 
     | 
    
         
             
                    """
         
     | 
| 
       851 
1170 
     | 
    
         
             
                    assert cp.size % 24 == 0
         
     | 
| 
         @@ -881,7 +1200,7 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       881 
1200 
     | 
    
         | 
| 
       882 
1201 
     | 
    
         
             
                def set_to_horizontal_plane(self, depth, box_xyz, border = 0.0, quad_triangles = False):
         
     | 
| 
       883 
1202 
     | 
    
         
             
                    """Populate this (empty) surface with a patch of two triangles.
         
     | 
| 
       884 
     | 
    
         
            -
             
     | 
| 
      
 1203 
     | 
    
         
            +
             
     | 
| 
       885 
1204 
     | 
    
         
             
                    Triangles define a flat, horizontal plane at a given depth.
         
     | 
| 
       886 
1205 
     | 
    
         | 
| 
       887 
1206 
     | 
    
         
             
                    arguments:
         
     | 
| 
         @@ -985,7 +1304,7 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       985 
1304 
     | 
    
         | 
| 
       986 
1305 
     | 
    
         
             
                def vertical_rescale_points(self, ref_depth = None, scaling_factor = 1.0):
         
     | 
| 
       987 
1306 
     | 
    
         
             
                    """Modify the z values of points by rescaling.
         
     | 
| 
       988 
     | 
    
         
            -
             
     | 
| 
      
 1307 
     | 
    
         
            +
             
     | 
| 
       989 
1308 
     | 
    
         
             
                    Stretches the distance from reference depth by scaling factor.
         
     | 
| 
       990 
1309 
     | 
    
         
             
                    """
         
     | 
| 
       991 
1310 
     | 
    
         
             
                    if scaling_factor == 1.0:
         
     | 
| 
         @@ -1170,12 +1489,16 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       1170 
1489 
     | 
    
         
             
                    return resampled
         
     | 
| 
       1171 
1490 
     | 
    
         | 
| 
       1172 
1491 
     | 
    
         
             
                def resample_surface_unique_edges(self):
         
     | 
| 
       1173 
     | 
    
         
            -
                    """Returns a new surface, with the same model, title and crs as the original 
     | 
| 
       1174 
     | 
    
         
            -
             
     | 
| 
       1175 
     | 
    
         
            -
                    Each edge forming a tear or outer edge in the surface will have 3 additional points added, with 2 additional points 
     | 
| 
      
 1492 
     | 
    
         
            +
                    """Returns a new surface, with the same model, title and crs as the original, but with additional refined points along tears and edges.
         
     | 
| 
      
 1493 
     | 
    
         
            +
             
     | 
| 
      
 1494 
     | 
    
         
            +
                    Each edge forming a tear or outer edge in the surface will have 3 additional points added, with 2 additional points
         
     | 
| 
      
 1495 
     | 
    
         
            +
                    on each edge of the original triangle. The output surface is re-triangulated using these new points (tears will be filled)
         
     | 
| 
       1176 
1496 
     | 
    
         | 
| 
       1177 
1497 
     | 
    
         
             
                    returns: 
         
     | 
| 
       1178 
     | 
    
         
            -
                         
     | 
| 
      
 1498 
     | 
    
         
            +
                        new Surface object with extra_metadata ('unique edges resampled from surface': uuid), where uuid is for the original surface uuid
         
     | 
| 
      
 1499 
     | 
    
         
            +
                        
         
     | 
| 
      
 1500 
     | 
    
         
            +
                    note:
         
     | 
| 
      
 1501 
     | 
    
         
            +
                        this method involves a tr-triangulation
         
     | 
| 
       1179 
1502 
     | 
    
         
             
                    """
         
     | 
| 
       1180 
1503 
     | 
    
         
             
                    _, op = self.triangles_and_points()
         
     | 
| 
       1181 
1504 
     | 
    
         
             
                    ref = self.resampled_surface()  # resample the original surface
         
     | 
| 
         @@ -1246,7 +1569,8 @@ class Surface(rqsb.BaseSurface): 
     | 
|
| 
       1246 
1569 
     | 
    
         
             
                        self.title = 'surface'
         
     | 
| 
       1247 
1570 
     | 
    
         | 
| 
       1248 
1571 
     | 
    
         
             
                    em = None
         
     | 
| 
       1249 
     | 
    
         
            -
                    if self.normal_vector is not None 
     | 
| 
      
 1572 
     | 
    
         
            +
                    if self.normal_vector is not None and (self.extra_metadata is None or
         
     | 
| 
      
 1573 
     | 
    
         
            +
                                                           'normal vector' not in self.extra_metadata):
         
     | 
| 
       1250 
1574 
     | 
    
         
             
                        assert len(self.normal_vector) == 3
         
     | 
| 
       1251 
1575 
     | 
    
         
             
                        em = {'normal vector': f'{self.normal_vector[0]},{self.normal_vector[1]},{self.normal_vector[2]}'}
         
     | 
| 
       1252 
1576 
     | 
    
         | 
    
        resqpy/surface/_tri_mesh.py
    CHANGED
    
    | 
         @@ -219,6 +219,7 @@ class TriMesh(rqs.Mesh): 
     | 
|
| 
       219 
219 
     | 
    
         
             
                        base edge towards point 2; f1 is component towards point 1; f0 is component towards point 0;
         
     | 
| 
       220 
220 
     | 
    
         
             
                        the trilinear coordinates sum to one and can be used as weights to interpolate z values at points
         
     | 
| 
       221 
221 
     | 
    
         
             
                    """
         
     | 
| 
      
 222 
     | 
    
         
            +
                    assert xy_array.ndim > 1 and 2 <= xy_array.shape[-1] <= 3
         
     | 
| 
       222 
223 
     | 
    
         
             
                    x = xy_array[..., 0].copy()
         
     | 
| 
       223 
224 
     | 
    
         
             
                    y = xy_array[..., 1].copy()
         
     | 
| 
       224 
225 
     | 
    
         
             
                    if self.origin is not None:
         
     | 
| 
         @@ -236,7 +237,7 @@ class TriMesh(rqs.Mesh): 
     | 
|
| 
       236 
237 
     | 
    
         
             
                    mask = np.logical_or(mask, np.logical_or(i < 0, i >= self.ni - 1))
         
     | 
| 
       237 
238 
     | 
    
         
             
                    fx = ip - i.astype(float)
         
     | 
| 
       238 
239 
     | 
    
         
             
                    i *= 2
         
     | 
| 
       239 
     | 
    
         
            -
                    am =  
     | 
| 
      
 240 
     | 
    
         
            +
                    am = (fx > 1.0 - fy).astype(bool)
         
     | 
| 
       240 
241 
     | 
    
         
             
                    i[am] += 1
         
     | 
| 
       241 
242 
     | 
    
         
             
                    fx[am] -= 1.0 - fy[am]
         
     | 
| 
       242 
243 
     | 
    
         
             
                    fy[am] = 1.0 - fy[am]
         
     | 
| 
         @@ -275,6 +276,7 @@ class TriMesh(rqs.Mesh): 
     | 
|
| 
       275 
276 
     | 
    
         
             
                        of three vertices of triangles containing xy points; float triplets contain corresponding weights (summing to
         
     | 
| 
       276 
277 
     | 
    
         
             
                        one per triangle) which can be used to interpolate z values at points xy_array
         
     | 
| 
       277 
278 
     | 
    
         
             
                    """
         
     | 
| 
      
 279 
     | 
    
         
            +
                    assert xy_array.ndim > 1 and 2 <= xy_array.shape[-1] <= 3
         
     | 
| 
       278 
280 
     | 
    
         
             
                    tji, tc = self.tji_tc_for_xy_array(xy_array)
         
     | 
| 
       279 
281 
     | 
    
         
             
                    ji = self.tri_nodes_for_tji_array(tji)
         
     | 
| 
       280 
282 
     | 
    
         
             
                    return (ji, tc)
         
     | 
| 
         @@ -34,14 +34,15 @@ class AnyTimeSeries(BaseResqpy): 
     | 
|
| 
       34 
34 
     | 
    
         
             
                        dt_text = rqet.find_tag_text(child, 'DateTime')
         
     | 
| 
       35 
35 
     | 
    
         
             
                        assert dt_text, 'missing DateTime field in xml for time series'
         
     | 
| 
       36 
36 
     | 
    
         
             
                        year_offset = rqet.find_tag_int(child, 'YearOffset')
         
     | 
| 
       37 
     | 
    
         
            -
                        if year_offset is not None:
         
     | 
| 
       38 
     | 
    
         
            -
                            assert self.timeframe == 'geologic'
         
     | 
| 
      
 37 
     | 
    
         
            +
                        if self.timeframe == 'geologic' and year_offset is not None:
         
     | 
| 
       39 
38 
     | 
    
         
             
                            if year_offset > 0:
         
     | 
| 
       40 
39 
     | 
    
         
             
                                log.warning(f'positive year offset in xml indicates future geological time: {year_offset}')
         
     | 
| 
       41 
40 
     | 
    
         
             
                            self.timestamps.append(year_offset)  # todo: trim and check timestamp
         
     | 
| 
       42 
     | 
    
         
            -
                         
     | 
| 
       43 
     | 
    
         
            -
                             
     | 
| 
      
 41 
     | 
    
         
            +
                        elif self.timeframe == 'human' and not year_offset:
         
     | 
| 
      
 42 
     | 
    
         
            +
                            # year_offset can be 0 for "human" time frames, indicating None.
         
     | 
| 
       44 
43 
     | 
    
         
             
                            self.timestamps.append(dt_text)  # todo: trim and check timestamp
         
     | 
| 
      
 44 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 45 
     | 
    
         
            +
                            raise AssertionError(f'Invalid combination of timeframe {self.timeframe} and year_offset {year_offset}')
         
     | 
| 
       45 
46 
     | 
    
         
             
                        self.timestamps.sort()
         
     | 
| 
       46 
47 
     | 
    
         | 
| 
       47 
48 
     | 
    
         
             
                def is_equivalent(self, other_ts):
         
     |