Repository: pyvista/pyvista

Issue:
Boolean Operation freezes/crashes 
### Describe the bug, what's wrong, and what you expected.

Apparently, if two polyData have the exact same shape, their boolean operation freezes/crashes the application!


### Steps to reproduce the bug.

```python
p1 = pv.Sphere().triangulate()
p2 = pv.Sphere().triangulate()

p1.boolean_intersection(p2)
``````

### System Information

```shell
--------------------------------------------------------------------------------
  Date: Tue Aug 22 12:17:01 2023 EEST

                OS : Darwin
            CPU(s) : 12
           Machine : x86_64
      Architecture : 64bit
               RAM : 16.0 GiB
       Environment : Jupyter
       File system : apfs
        GPU Vendor : ATI Technologies Inc.
      GPU Renderer : AMD Radeon Pro 5300M OpenGL Engine
       GPU Version : 4.1 ATI-4.14.1
  MathText Support : False

  Python 3.10.11 (v3.10.11:7d4cc5aa85, Apr  4 2023, 19:05:19) [Clang 13.0.0
  (clang-1300.0.29.30)]

           pyvista : 0.41.1
               vtk : 9.2.6
             numpy : 1.24.2
        matplotlib : 3.7.1
            scooby : 0.7.2
             pooch : v1.7.0
           IPython : 8.14.0
             scipy : 1.10.1
        jupyterlab : 4.0.5
      nest_asyncio : 1.5.7
--------------------------------------------------------------------------------
```


### Screenshots

_No response_

Discussion:

Patch:
diff --git a/pyvista/core/dataset.py b/pyvista/core/dataset.py
index 3d528f35..34712453 100644
--- a/pyvista/core/dataset.py
+++ b/pyvista/core/dataset.py
@@ -1920,7 +1920,7 @@ class DataSet(DataSetFilters, DataObject):
         return self.array_names
 
     def __setitem__(
-        self, name: str, scalars: Union[np.ndarray, collections.abc.Sequence]
+        self, name: str, scalars: Union[np.ndarray, collections.abc.Sequence, float]
     ):  # numpydoc ignore=PR01,RT01
         """Add/set an array in the point_data, or cell_data accordingly.
 
@@ -1932,8 +1932,13 @@ class DataSet(DataSetFilters, DataObject):
         #   the data to be on the nodes.
         if scalars is None:
             raise TypeError('Empty array unable to be added.')
-        if not isinstance(scalars, np.ndarray):
-            scalars = np.array(scalars)
+        else:
+            scalars = np.asanyarray(scalars)
+
+        # reshape single scalar values from 0D to 1D so that shape[0] can be indexed
+        if scalars.ndim == 0:
+            scalars = scalars.reshape((1,))
+
         # Now check array size to determine which field to place array
         if scalars.shape[0] == self.n_points:
             self.point_data[name] = scalars
diff --git a/pyvista/core/datasetattributes.py b/pyvista/core/datasetattributes.py
index 33d5e6f2..639cb4cc 100644
--- a/pyvista/core/datasetattributes.py
+++ b/pyvista/core/datasetattributes.py
@@ -211,7 +211,7 @@ class DataSetAttributes(_vtk.VTKObjectWrapper):
         return self.get_array(key)
 
     def __setitem__(
-        self, key: str, value: Union[np.ndarray, Sequence]
+        self, key: str, value: Union[np.ndarray, Sequence, float]
     ):  # numpydoc ignore=PR01,RT01
         """Implement setting with the ``[]`` operator."""
         if not isinstance(key, str):
@@ -749,6 +749,24 @@ class DataSetAttributes(_vtk.VTKObjectWrapper):
         if data is None:
             raise TypeError('``data`` cannot be None.')
 
+        # convert to numpy type if necessary
+        data = np.asanyarray(data)
+
+        if self.association == FieldAssociation.POINT:
+            array_len = self.dataset.GetNumberOfPoints()
+        elif self.association == FieldAssociation.CELL:
+            array_len = self.dataset.GetNumberOfCells()
+        else:
+            array_len = 1 if data.ndim == 0 else data.shape[0]
+
+        # Fixup input array length for scalar input
+        if not isinstance(data, np.ndarray) or np.ndim(data) == 0:
+            tmparray = np.empty(array_len)
+            tmparray.fill(data)
+            data = tmparray
+        if data.shape[0] != array_len:
+            raise ValueError(f'data length of ({data.shape[0]}) != required length ({array_len})')
+
         # attempt to reuse the existing pointer to underlying VTK data
         if isinstance(data, pyvista_ndarray):
             # pyvista_ndarray already contains the reference to the vtk object
@@ -768,24 +786,6 @@ class DataSetAttributes(_vtk.VTKObjectWrapper):
                         vtk_arr.SetName(name)
                     return vtk_arr
 
-        # convert to numpy type if necessary
-        data = np.asanyarray(data)
-
-        if self.association == FieldAssociation.POINT:
-            array_len = self.dataset.GetNumberOfPoints()
-        elif self.association == FieldAssociation.CELL:
-            array_len = self.dataset.GetNumberOfCells()
-        else:
-            array_len = data.shape[0] if isinstance(data, np.ndarray) else 1
-
-        # Fixup input array length for scalar input
-        if not isinstance(data, np.ndarray) or np.ndim(data) == 0:
-            tmparray = np.empty(array_len)
-            tmparray.fill(data)
-            data = tmparray
-        if data.shape[0] != array_len:
-            raise ValueError(f'data length of ({data.shape[0]}) != required length ({array_len})')
-
         # reset data association
         if name in self.dataset._association_bitarray_names[self.association.name]:
             self.dataset._association_bitarray_names[self.association.name].remove(name)
diff --git a/pyvista/core/filters/poly_data.py b/pyvista/core/filters/poly_data.py
index 5c0228dd..7d150f72 100644
--- a/pyvista/core/filters/poly_data.py
+++ b/pyvista/core/filters/poly_data.py
@@ -81,6 +81,11 @@ class PolyDataFilters(DataSetFilters):
 
     def _boolean(self, btype, other_mesh, tolerance, progress_bar=False):
         """Perform boolean operation."""
+        if self.n_points == other_mesh.n_points:
+            if np.allclose(self.points, other_mesh.points):
+                raise ValueError(
+                    "The input mesh contains identical points to the surface being operated on. Unable to perform boolean operations on an identical surface."
+                )
         if not isinstance(other_mesh, pyvista.PolyData):
             raise TypeError("Input mesh must be PolyData.")
         if not self.is_all_triangles or not other_mesh.is_all_triangles:


Test patch:
diff --git a/tests/core/test_dataset.py b/tests/core/test_dataset.py
index 5635ef42..f5c81d1b 100644
--- a/tests/core/test_dataset.py
+++ b/tests/core/test_dataset.py
@@ -131,6 +131,16 @@ def test_cell_data_bad_value(grid):
         grid.cell_data['new_array'] = np.arange(grid.n_cells - 1)
 
 
+def test_point_cell_data_single_scalar_no_exception_raised():
+    try:
+        m = pv.PolyData([0, 0, 0.0])
+        m.point_data["foo"] = 1
+        m.cell_data["bar"] = 1
+        m["baz"] = 1
+    except Exception as e:
+        pytest.fail(f"Unexpected exception raised: {e}")
+
+
 def test_field_data(grid):
     key = 'test_array_field'
     # Add array of length not equal to n_cells or n_points
diff --git a/tests/core/test_datasetattributes.py b/tests/core/test_datasetattributes.py
index 20020fe2..89b43a19 100644
--- a/tests/core/test_datasetattributes.py
+++ b/tests/core/test_datasetattributes.py
@@ -10,7 +10,7 @@ import numpy as np
 from pytest import fixture, mark, raises
 
 import pyvista as pv
-from pyvista.core.utilities.arrays import FieldAssociation
+from pyvista.core.utilities.arrays import FieldAssociation, convert_array
 
 skip_windows = mark.skipif(os.name == 'nt', reason='Test fails on Windows')
 skip_apple_silicon = mark.skipif(
@@ -180,6 +180,22 @@ def test_add_matrix(hexbeam):
     assert np.allclose(mat, matout)
 
 
+def test_set_fails_with_wrong_shape(hexbeam):
+    with raises(ValueError):
+        hexbeam['foo'] = [1, 2, 3]
+    with raises(ValueError):
+        hexbeam.point_data['foo'] = [1, 2, 3]
+    with raises(ValueError):
+        hexbeam.cell_data['foo'] = [1, 2, 3]
+
+    # Use vtk methods directly to add bad data. This can simulate
+    # cases where buggy vtk methods may set arrays with incorrect shape
+    bad_data = convert_array([1, 2, 3], 'foo')
+    hexbeam.cell_data.VTKObject.AddArray(bad_data)
+    with raises(ValueError):
+        hexbeam.cell_data['foo'] = hexbeam.cell_data['foo']
+
+
 def test_set_active_scalars_fail(hexbeam):
     with raises(ValueError):
         hexbeam.set_active_scalars('foo', preference='field')
diff --git a/tests/core/test_polydata.py b/tests/core/test_polydata.py
index a6f00505..f25678d7 100644
--- a/tests/core/test_polydata.py
+++ b/tests/core/test_polydata.py
@@ -280,9 +280,9 @@ def test_boolean_difference(sphere, sphere_shifted):
     assert np.isclose(difference.volume, expected_volume, atol=1e-3)
 
 
-def test_boolean_difference_fail(plane):
+def test_boolean_difference_fail(plane, sphere):
     with pytest.raises(NotAllTrianglesError):
-        plane - plane
+        plane - sphere
 
 
 def test_subtract(sphere, sphere_shifted):
diff --git a/tests/core/test_polydata_filters.py b/tests/core/test_polydata_filters.py
index 5a286fe7..65e957cc 100644
--- a/tests/core/test_polydata_filters.py
+++ b/tests/core/test_polydata_filters.py
@@ -45,3 +45,8 @@ def test_boolean_intersect_edge_case():
 
     with pytest.warns(UserWarning, match='contained within another'):
         a.boolean_intersection(b)
+
+
+def test_identical_boolean(sphere):
+    with pytest.raises(ValueError, match='identical points'):
+        sphere.boolean_intersection(sphere.copy())