{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# SDF Utilities\n",
    "\n",
    "`isoext.sdf` provides **optional** utilities for working with signed distance functions. These are convenient for testing and demos, but you can always compute values with your own code.\n",
    "\n",
    "**Note:** These utilities are not required — see [Working with Grids](grids.ipynb) for examples using raw PyTorch.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-05T04:06:36.218257Z",
     "iopub.status.busy": "2026-07-05T04:06:36.218168Z",
     "iopub.status.idle": "2026-07-05T04:06:36.970296Z",
     "shell.execute_reply": "2026-07-05T04:06:36.969795Z"
    }
   },
   "outputs": [],
   "source": [
    "import isoext\n",
    "from isoext.sdf import *\n",
    "from isoext import viewer\n",
    "\n",
    "grid = isoext.UniformGrid([128, 128, 128])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Primitives\n",
    "\n",
    "### SphereSDF\n",
    "```python\n",
    "SphereSDF(radius: float)\n",
    "```\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-05T04:06:36.971867Z",
     "iopub.status.busy": "2026-07-05T04:06:36.971741Z",
     "iopub.status.idle": "2026-07-05T04:06:37.126576Z",
     "shell.execute_reply": "2026-07-05T04:06:37.125962Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "        <iframe\n",
       "            width=\"100%\"\n",
       "            height=\"420\"\n",
       "            src=\"_static/viser/index.html?playbackPath=../scenes/73da1675db599904.viser\"\n",
       "            frameborder=\"0\"\n",
       "            allowfullscreen\n",
       "            style=\"border: 1px solid #8888; border-radius: 4px;\"\n",
       "        ></iframe>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.lib.display.IFrame at 0x773b30956f30>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sphere = SphereSDF(radius=0.7)\n",
    "grid.set_values(sphere(grid.get_points()))\n",
    "v, f = isoext.marching_cubes(grid)\n",
    "viewer.embed(v, f)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### TorusSDF\n",
    "```python\n",
    "TorusSDF(R: float, r: float)  # R=major radius, r=tube radius\n",
    "```\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-05T04:06:37.141423Z",
     "iopub.status.busy": "2026-07-05T04:06:37.141273Z",
     "iopub.status.idle": "2026-07-05T04:06:37.214995Z",
     "shell.execute_reply": "2026-07-05T04:06:37.214611Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "        <iframe\n",
       "            width=\"100%\"\n",
       "            height=\"420\"\n",
       "            src=\"_static/viser/index.html?playbackPath=../scenes/dd32093f47db847d.viser\"\n",
       "            frameborder=\"0\"\n",
       "            allowfullscreen\n",
       "            style=\"border: 1px solid #8888; border-radius: 4px;\"\n",
       "        ></iframe>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.lib.display.IFrame at 0x7738f65173b0>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "torus = TorusSDF(R=0.6, r=0.2)\n",
    "grid.set_values(torus(grid.get_points()))\n",
    "v, f = isoext.marching_cubes(grid)\n",
    "viewer.embed(v, f, color=\"gold\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### CuboidSDF\n",
    "```python\n",
    "CuboidSDF(size: list[float])  # Full size in [x, y, z]\n",
    "```\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-05T04:06:37.216335Z",
     "iopub.status.busy": "2026-07-05T04:06:37.216207Z",
     "iopub.status.idle": "2026-07-05T04:06:37.261344Z",
     "shell.execute_reply": "2026-07-05T04:06:37.261025Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "        <iframe\n",
       "            width=\"100%\"\n",
       "            height=\"420\"\n",
       "            src=\"_static/viser/index.html?playbackPath=../scenes/16f2853e6bc0c7c7.viser\"\n",
       "            frameborder=\"0\"\n",
       "            allowfullscreen\n",
       "            style=\"border: 1px solid #8888; border-radius: 4px;\"\n",
       "        ></iframe>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.lib.display.IFrame at 0x7738f6555820>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cube = CuboidSDF(size=[1.0, 1.0, 1.0])\n",
    "grid.set_values(cube(grid.get_points()))\n",
    "v, f = isoext.marching_cubes(grid)\n",
    "viewer.embed(v, f, color=\"salmon\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## CSG Operations\n",
    "\n",
    "Combine shapes using Constructive Solid Geometry:\n",
    "\n",
    "| Operation | Description |\n",
    "|-----------|-------------|\n",
    "| `UnionOp([...])` | Combine shapes (min of SDFs) |\n",
    "| `IntersectionOp([...])` | Keep overlap (max of SDFs) |\n",
    "| `NegationOp(sdf)` | Invert inside/outside |\n",
    "| `SmoothUnionOp([...], k)` | Smooth blend with radius k |\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-05T04:06:37.262659Z",
     "iopub.status.busy": "2026-07-05T04:06:37.262583Z",
     "iopub.status.idle": "2026-07-05T04:06:37.298129Z",
     "shell.execute_reply": "2026-07-05T04:06:37.297795Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "        <iframe\n",
       "            width=\"100%\"\n",
       "            height=\"420\"\n",
       "            src=\"_static/viser/index.html?playbackPath=../scenes/f00cb923e2f106ed.viser\"\n",
       "            frameborder=\"0\"\n",
       "            allowfullscreen\n",
       "            style=\"border: 1px solid #8888; border-radius: 4px;\"\n",
       "        ></iframe>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.lib.display.IFrame at 0x7738f65547a0>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Sphere with a hole drilled through it\n",
    "sphere = SphereSDF(radius=0.7)\n",
    "hole = CuboidSDF(size=[0.3, 0.3, 2.0])\n",
    "drilled = IntersectionOp([sphere, NegationOp(hole)])\n",
    "\n",
    "grid.set_values(drilled(grid.get_points()))\n",
    "v, f = isoext.marching_cubes(grid)\n",
    "viewer.embed(v, f, color=\"orchid\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Transformations\n",
    "\n",
    "| Transform | Description |\n",
    "|-----------|-------------|\n",
    "| `TranslationOp(sdf, offset)` | Move by `[x, y, z]` |\n",
    "| `RotationOp(sdf, axis, angle)` | Rotate around axis (degrees by default) |\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-05T04:06:37.299528Z",
     "iopub.status.busy": "2026-07-05T04:06:37.299451Z",
     "iopub.status.idle": "2026-07-05T04:06:37.389830Z",
     "shell.execute_reply": "2026-07-05T04:06:37.389484Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "        <iframe\n",
       "            width=\"100%\"\n",
       "            height=\"420\"\n",
       "            src=\"_static/viser/index.html?playbackPath=../scenes/ffc9c5be9010a4df.viser\"\n",
       "            frameborder=\"0\"\n",
       "            allowfullscreen\n",
       "            style=\"border: 1px solid #8888; border-radius: 4px;\"\n",
       "        ></iframe>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.lib.display.IFrame at 0x7738d375be00>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Two spheres with smooth blending\n",
    "s1 = TranslationOp(SphereSDF(radius=0.4), offset=[-0.3, 0, 0])\n",
    "s2 = TranslationOp(SphereSDF(radius=0.4), offset=[0.3, 0, 0])\n",
    "blended = SmoothUnionOp([s1, s2], k=0.15)\n",
    "\n",
    "grid.set_values(blended(grid.get_points()))\n",
    "v, f = isoext.marching_cubes(grid)\n",
    "viewer.embed(v, f, color=\"tomato\")\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
