{ "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 }