{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Boolean Operations {#boolean_example}\n\nPerform boolean operations with closed (manifold) surfaces.\n\nBoolean/topological operations (intersect, union, difference) methods\nare implemented for `pyvista.PolyData`{.interpreted-text role=\"class\"}\nmesh types only and are accessible directly from any\n`pyvista.PolyData`{.interpreted-text role=\"class\"} mesh. Check out\n`pyvista.PolyDataFilters`{.interpreted-text role=\"class\"} and take a\nlook at the following filters:\n\n-   `pyvista.PolyDataFilters.boolean_difference`{.interpreted-text\n    role=\"func\"}\n-   `pyvista.PolyDataFilters.boolean_union`{.interpreted-text\n    role=\"func\"}\n-   `pyvista.PolyDataFilters.boolean_intersection`{.interpreted-text\n    role=\"func\"}\n\nEssentially, boolean union, difference, and intersection are all the\nsame operation. Just different parts of the objects are kept at the end.\n\nThe `-` operator can be used between any two\n`pyvista.PolyData`{.interpreted-text role=\"class\"} meshes in PyVista to\ncut the first mesh by the second. These meshes must be all triangle\nmeshes, which you can check with\n`pyvista.PolyData.is_all_triangles`{.interpreted-text role=\"attr\"}.\n\n::: note\n::: title\nNote\n:::\n\nFor merging, the `+` operator can be used between any two meshes in\nPyVista which simply calls the `.merge()` filter to combine any two\nmeshes. This is different from `boolean_union` as it simply superimposes\nthe two meshes without performing additional calculations on the result.\n:::\n\n::: warning\n::: title\nWarning\n:::\n\nIf your boolean operations don\\'t react the way you think they should\n(i.e. the wrong parts disappear), one of your meshes probably has its\nnormals pointing inward. Use\n`pyvista.PolyDataFilters.plot_normals`{.interpreted-text role=\"func\"} to\nvisualize the normals.\n:::\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import pyvista as pv\n\nsphere_a = pv.Sphere()\nsphere_b = pv.Sphere(center=(0.5, 0, 0))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Boolean Union\n\nPerform a boolean union of `A` and `B` using the\n`pyvista.PolyDataFilters.boolean_union`{.interpreted-text role=\"func\"}\nfilter.\n\nThe union of two manifold meshes `A` and `B` is the mesh which is in\n`A`, in `B`, or in both `A` and `B`.\n\nOrder of operands does not matter for boolean union (the operation is\ncommutative).\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "result = sphere_a.boolean_union(sphere_b)\npl = pv.Plotter()\n_ = pl.add_mesh(sphere_a, color='r', style='wireframe', line_width=3)\n_ = pl.add_mesh(sphere_b, color='b', style='wireframe', line_width=3)\n_ = pl.add_mesh(result, color='lightblue')\npl.camera_position = 'xz'\npl.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Boolean Difference\n\nPerform a boolean difference of `A` and `B` using the\n`pyvista.PolyDataFilters.boolean_difference`{.interpreted-text\nrole=\"func\"} filter or the `-` operator since both meshes are\n`pyvista.PolyData`{.interpreted-text role=\"class\"}.\n\nThe difference of two manifold meshes `A` and `B` is the volume of the\nmesh in `A` not belonging to `B`.\n\nOrder of operands matters for boolean difference.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "result = sphere_a.boolean_difference(sphere_b)\npl = pv.Plotter()\n_ = pl.add_mesh(sphere_a, color='r', style='wireframe', line_width=3)\n_ = pl.add_mesh(sphere_b, color='b', style='wireframe', line_width=3)\n_ = pl.add_mesh(result, color='lightblue')\npl.camera_position = 'xz'\npl.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Boolean Intersection\n\nPerform a boolean intersection of `A` and `B` using the\n`pyvista.PolyDataFilters.boolean_intersection`{.interpreted-text\nrole=\"func\"} filter.\n\nThe intersection of two manifold meshes `A` and `B` is the mesh which is\nthe volume of `A` that is also in `B`.\n\nOrder of operands does not matter for boolean intersection (the\noperation is commutative).\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "result = sphere_a.boolean_intersection(sphere_b)\npl = pv.Plotter()\n_ = pl.add_mesh(sphere_a, color='r', style='wireframe', line_width=3)\n_ = pl.add_mesh(sphere_b, color='b', style='wireframe', line_width=3)\n_ = pl.add_mesh(result, color='lightblue')\npl.camera_position = 'xz'\npl.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Behavior due to flipped normals\n\nNote that these boolean filters behave differently depending on the\norientation of the normals.\n\nBoolean difference with both cube and sphere normals pointed outward.\nThis is the \\\"normal\\\" behavior.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "cube = pv.Cube().triangulate().subdivide(3)\nsphere = pv.Sphere(radius=0.6)\nresult = cube.boolean_difference(sphere)\nresult.plot(color='lightblue')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Boolean difference with cube normals outward, sphere inward.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "cube = pv.Cube().triangulate().subdivide(3)\nsphere = pv.Sphere(radius=0.6)\nsphere.flip_normals()\nresult = cube.boolean_difference(sphere)\nresult.plot(color='lightblue')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Boolean difference with cube normals inward, sphere outward.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "cube = pv.Cube().triangulate().subdivide(3)\ncube.flip_normals()\nsphere = pv.Sphere(radius=0.6)\nresult = cube.boolean_difference(sphere)\nresult.plot(color='lightblue')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Both cube and sphere normals inward.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "cube = pv.Cube().triangulate().subdivide(3)\ncube.flip_normals()\nsphere = pv.Sphere(radius=0.6)\nsphere.flip_normals()\nresult = cube.boolean_difference(sphere)\nresult.plot(color='lightblue')"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12.2"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}