{"id":13631971,"url":"https://github.com/fogleman/sdf","last_synced_at":"2025-05-15T04:00:26.641Z","repository":{"id":43924234,"uuid":"330865354","full_name":"fogleman/sdf","owner":"fogleman","description":"Simple SDF mesh generation in Python","archived":false,"fork":false,"pushed_at":"2024-08-10T20:40:33.000Z","size":8445,"stargazers_count":1670,"open_issues_count":25,"forks_count":146,"subscribers_count":34,"default_branch":"main","last_synced_at":"2025-04-14T04:59:07.334Z","etag":null,"topics":["3d","3d-models","3d-printing","mesh","python","sdf","signed-distance-functions"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fogleman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-01-19T04:39:21.000Z","updated_at":"2025-04-10T20:09:00.000Z","dependencies_parsed_at":"2024-06-21T16:38:38.365Z","dependency_job_id":"ae0d860c-b192-47c0-ab57-8380fb17a063","html_url":"https://github.com/fogleman/sdf","commit_stats":{"total_commits":206,"total_committers":4,"mean_commits":51.5,"dds":0.01941747572815533,"last_synced_commit":"d58a6fc63b75fc1cf1ebb71e0b42bf552319c8f1"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fogleman%2Fsdf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fogleman%2Fsdf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fogleman%2Fsdf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fogleman%2Fsdf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fogleman","download_url":"https://codeload.github.com/fogleman/sdf/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254270640,"owners_count":22042858,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["3d","3d-models","3d-printing","mesh","python","sdf","signed-distance-functions"],"created_at":"2024-08-01T22:02:46.654Z","updated_at":"2025-05-15T04:00:26.592Z","avatar_url":"https://github.com/fogleman.png","language":"Python","readme":"# sdf\n\nGenerate 3D meshes based on SDFs (signed distance functions) with a\ndirt simple Python API.\n\nSpecial thanks to [Inigo Quilez](https://iquilezles.org/) for his excellent documentation on signed distance functions:\n\n- [3D Signed Distance Functions](https://iquilezles.org/www/articles/distfunctions/distfunctions.htm)\n- [2D Signed Distance Functions](https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm)\n\n## Example\n\n\u003cimg width=350 align=\"right\" src=\"docs/images/example.png\"\u003e\n\nHere is a complete example that generates the model shown. This is the\ncanonical [Constructive Solid Geometry](https://en.wikipedia.org/wiki/Constructive_solid_geometry)\nexample. Note the use of operators for union, intersection, and difference.\n\n```python\nfrom sdf import *\n\nf = sphere(1) \u0026 box(1.5)\n\nc = cylinder(0.5)\nf -= c.orient(X) | c.orient(Y) | c.orient(Z)\n\nf.save('out.stl')\n```\n\nYes, that's really the entire code! You can 3D print that model or use it\nin a 3D application.\n\n## More Examples\n\nHave a cool example? Submit a PR!\n\n| [gearlike.py](examples/gearlike.py) | [knurling.py](examples/knurling.py) | [blobby.py](examples/blobby.py) | [weave.py](examples/weave.py) |\n| --- | --- | --- | --- |\n| ![gearlike](docs/images/gearlike.png) | ![knurling](docs/images/knurling.png) | ![blobby](docs/images/blobby.png) | ![weave](docs/images/weave.png) |\n| ![gearlike](docs/images/gearlike.jpg) | ![knurling](docs/images/knurling.jpg) | ![blobby](docs/images/blobby.jpg) | ![weave](docs/images/weave.jpg) |\n\n## Requirements\n\nNote that the dependencies will be automatically installed by setup.py when\nfollowing the directions below.\n\n- Python 3\n- matplotlib\n- meshio\n- numpy\n- Pillow\n- scikit-image\n- scipy\n\n## Installation\n\nUse the commands below to clone the repository and install the `sdf` library\nin a Python virtualenv.\n\n```bash\ngit clone https://github.com/fogleman/sdf.git\ncd sdf\nvirtualenv env\n. env/bin/activate\npip install -e .\n```\n\nConfirm that it works:\n\n```bash\npython examples/example.py # should generate a file named out.stl\n```\n\nYou can skip the installation if you always run scripts that import `sdf`\nfrom the root folder.\n\n## OpenVDB\n\nOpenVDB and its Python module are also required if you want to use meshes as SDFs.\nIt doesn't seem like this can easily be installed via pip. The basic approach for\nbuilding it is as follows:\n\n```bash\ngit clone https://github.com/AcademySoftwareFoundation/openvdb.git\ncd openvdb\nmkdir build\ncd build\ncmake -D OPENVDB_BUILD_PYTHON_MODULE=ON -D USE_NUMPY=ON ..\nmake -j8\ncp openvdb/openvdb/python/pyopenvdb.* `python -c 'import site; print(site.getsitepackages()[0])'`\n```\n\n## File Formats\n\n`sdf` natively writes binary STL files. For other formats, [meshio](https://github.com/nschloe/meshio)\nis used (based on your output file extension). This adds support for over 20 different 3D file formats,\nincluding OBJ, PLY, VTK, and many more.\n\n## Viewing the Mesh\n\n\u003cimg width=250 align=\"right\" src=\"docs/images/meshview.png\"\u003e\n\nFind and install a 3D mesh viewer for your platform, such as [MeshLab](https://www.meshlab.net/).\n\nI have developed and use my own cross-platform mesh viewer called [meshview](https://github.com/fogleman/meshview) (see screenshot).\nInstallation is easy if you have [Go](https://golang.org/) and [glfw](https://www.glfw.org/) installed:\n\n```bash\n$ brew install go glfw # on macOS with homebrew\n$ go get -u github.com/fogleman/meshview/cmd/meshview\n```\n\nThen you can view any mesh from the command line with:\n\n```bash\n$ meshview your-mesh.stl\n```\n\nSee the meshview [README](https://github.com/fogleman/meshview) for more complete installation instructions.\n\nOn macOS you can just use the built-in Quick Look (press spacebar after selecting the STL file in Finder) in a pinch.\n\n# API\n\nIn all of the below examples, `f` is any 3D SDF, such as:\n\n```python\nf = sphere()\n```\n\n## Bounds\n\nThe bounding box of the SDF is automatically estimated. Inexact SDFs such as\nnon-uniform scaling may cause issues with this process. In that case you can\nspecify the bounds to sample manually:\n\n```python\nf.save('out.stl', bounds=((-1, -1, -1), (1, 1, 1)))\n```\n\n## Resolution\n\nThe resolution of the mesh is also computed automatically. There are two ways\nto specify the resolution. You can set the resolution directly with `step`:\n\n```python\nf.save('out.stl', step=0.01)\nf.save('out.stl', step=(0.01, 0.02, 0.03)) # non-uniform resolution\n```\n\nOr you can specify approximately how many points to sample:\n\n```python\nf.save('out.stl', samples=2**24) # sample about 16M points\n```\n\nBy default, `samples=2**22` is used.\n\n*Tip*: Use the default resolution while developing your SDF. Then when you're done,\ncrank up the resolution for your final output.\n\n## Batches\n\nThe SDF is sampled in batches. By default the batches have `32**3 = 32768`\npoints each. This batch size can be overridden:\n\n```python\nf.save('out.stl', batch_size=64) # instead of 32\n```\n\nThe code attempts to skip any batches that are far away from the surface of\nthe mesh. Inexact SDFs such as non-uniform scaling may cause issues with this\nprocess, resulting in holes in the output mesh (where batches were skipped when\nthey shouldn't have been). To avoid this, you can disable sparse sampling:\n\n```python\nf.save('out.stl', sparse=False) # force all batches to be completely sampled\n```\n\n## Worker Threads\n\nThe SDF is sampled in batches using worker threads. By default,\n`multiprocessing.cpu_count()` worker threads are used. This can be overridden:\n\n```python\nf.save('out.stl', workers=1) # only use one worker thread\n```\n\n## Without Saving\n\nYou can of course generate a mesh without writing it to an STL file:\n\n```python\npoints = f.generate() # takes the same optional arguments as `save`\nprint(len(points)) # print number of points (3x the number of triangles)\nprint(points[:3]) # print the vertices of the first triangle\n```\n\nIf you want to save an STL after `generate`, just use:\n\n```python\nwrite_binary_stl(path, points)\n```\n\n## Visualizing the SDF\n\n\u003cimg width=350 align=\"right\" src=\"docs/images/show_slice.png\"\u003e\n\nYou can plot a visualization of a 2D slice of the SDF using matplotlib.\nThis can be useful for debugging purposes.\n\n```python\nf.show_slice(z=0)\nf.show_slice(z=0, abs=True) # show abs(f)\n```\n\nYou can specify a slice plane at any X, Y, or Z coordinate. You can\nalso specify the bounds to plot.\n\nNote that `matplotlib` is only imported if this function is called, so it\nisn't strictly required as a dependency.\n\n\u003cbr clear=\"right\"\u003e\n\n## How it Works\n\nThe code simply uses the [Marching Cubes](https://en.wikipedia.org/wiki/Marching_cubes)\nalgorithm to generate a mesh from the [Signed Distance Function](https://en.wikipedia.org/wiki/Signed_distance_function).\n\nThis would normally be abysmally slow in Python. However, numpy is used to\nevaluate the SDF on entire batches of points simultaneously. Furthermore,\nmultiple threads are used to process batches in parallel. The result is\nsurprisingly fast (for marching cubes). Meshes of adequate detail can\nstill be quite large in terms of number of triangles.\n\nThe core \"engine\" of the `sdf` library is very small and can be found in\n[core.py](https://github.com/fogleman/sdf/blob/main/sdf/core.py).\n\nIn short, there is nothing algorithmically revolutionary here. The goal is\nto provide a simple, fun, and easy-to-use API for generating 3D models in our\nfavorite language Python.\n\n## Files\n\n- [sdf/core.py](https://github.com/fogleman/sdf/blob/main/sdf/core.py): The core mesh-generation engine. Also includes code for estimating the bounding box of an SDF and for plotting a 2D slice of an SDF with matplotlib.\n- [sdf/d2.py](https://github.com/fogleman/sdf/blob/main/sdf/d2.py): 2D signed distance functions\n- [sdf/d3.py](https://github.com/fogleman/sdf/blob/main/sdf/d3.py): 3D signed distance functions\n- [sdf/dn.py](https://github.com/fogleman/sdf/blob/main/sdf/dn.py): Dimension-agnostic signed distance functions\n- [sdf/ease.py](https://github.com/fogleman/sdf/blob/main/sdf/ease.py): [Easing functions](https://easings.net/) that operate on numpy arrays. Some SDFs take an easing function as a parameter.\n- [sdf/mesh.py](https://github.com/fogleman/sdf/blob/main/sdf/mesh.py): Code for loading meshes and using them as SDFs.\n- [sdf/progress.py](https://github.com/fogleman/sdf/blob/main/sdf/progress.py): A console progress bar.\n- [sdf/stl.py](https://github.com/fogleman/sdf/blob/main/sdf/stl.py): Code for writing a binary [STL file](https://en.wikipedia.org/wiki/STL_(file_format)).\n- [sdf/text.py](https://github.com/fogleman/sdf/blob/main/sdf/text.py): Generate 2D SDFs for text (which can then be extruded)\n- [sdf/util.py](https://github.com/fogleman/sdf/blob/main/sdf/util.py): Utility constants and functions.\n\n## SDF Implementation\n\nIt is reasonable to write your own SDFs beyond those provided by the\nbuilt-in library. Browse the SDF implementations to understand how they are\nimplemented. Here are some simple examples:\n\n```python\n@sdf3\ndef sphere(radius=1, center=ORIGIN):\n    def f(p):\n        return np.linalg.norm(p - center, axis=1) - radius\n    return f\n```\n\nAn SDF is simply a function that takes a numpy array of points with shape `(N, 3)`\nfor 3D SDFs or shape `(N, 2)` for 2D SDFs and returns the signed distance for each\nof those points as an array of shape `(N, 1)`. They are wrapped with the\n`@sdf3` decorator (or `@sdf2` for 2D SDFs) which make boolean operators work,\nadd the `save` method, add the operators like `translate`, etc.\n\n```python\n@op3\ndef translate(other, offset):\n    def f(p):\n        return other(p - offset)\n    return f\n```\n\nAn SDF that operates on another SDF (like the above `translate`) should use\nthe `@op3` decorator instead. This will register the function such that SDFs\ncan be chained together like:\n\n```python\nf = sphere(1).translate((1, 2, 3))\n```\n\nInstead of what would otherwise be required:\n\n```python\nf = translate(sphere(1), (1, 2, 3))\n```\n\n## Remember, it's Python!\n\n\u003cimg width=250 align=\"right\" src=\"docs/images/customizable_box.png\"\u003e\n\nRemember, this is Python, so it's fully programmable. You can and should split up your\nmodel into parameterized sub-components, for example. You can use for loops and\nconditionals wherever applicable. The sky is the limit!\n\nSee the [customizable box example](examples/customizable_box.py) for some starting ideas.\n\n\u003cbr clear=\"right\"\u003e\n\n# Function Reference\n\n## 3D Primitives\n\n### sphere\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/sphere.png\"\u003e\n\n`sphere(radius=1, center=ORIGIN)`\n\n```python\nf = sphere() # unit sphere\nf = sphere(2) # specify radius\nf = sphere(1, (1, 2, 3)) # translated sphere\n```\n\n### box\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/box2.png\"\u003e\n\n`box(size=1, center=ORIGIN, a=None, b=None)`\n\n```python\nf = box(1) # all side lengths = 1\nf = box((1, 2, 3)) # different side lengths\nf = box(a=(-1, -1, -1), b=(3, 4, 5)) # specified by bounds\n```\n\n### rounded_box\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/rounded_box.png\"\u003e\n\n`rounded_box(size, radius)`\n\n```python\nf = rounded_box((1, 2, 3), 0.25)\n```\n\n### wireframe_box\n\u003cimg width=128 align=\"right\" src=\"docs/images/wireframe_box.png\"\u003e\n\n`wireframe_box(size, thickness)`\n\n```python\nf = wireframe_box((1, 2, 3), 0.05)\n```\n\n### torus\n\u003cimg width=128 align=\"right\" src=\"docs/images/torus.png\"\u003e\n\n`torus(r1, r2)`\n\n```python\nf = torus(1, 0.25)\n```\n\n### capsule\n\u003cimg width=128 align=\"right\" src=\"docs/images/capsule.png\"\u003e\n\n`capsule(a, b, radius)`\n\n```python\nf = capsule(-Z, Z, 0.5)\n```\n\n### capped_cylinder\n\u003cimg width=128 align=\"right\" src=\"docs/images/capped_cylinder.png\"\u003e\n\n`capped_cylinder(a, b, radius)`\n\n```python\nf = capped_cylinder(-Z, Z, 0.5)\n```\n\n### rounded_cylinder\n\u003cimg width=128 align=\"right\" src=\"docs/images/rounded_cylinder.png\"\u003e\n\n`rounded_cylinder(ra, rb, h)`\n\n```python\nf = rounded_cylinder(0.5, 0.1, 2)\n```\n\n### capped_cone\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/capped_cone.png\"\u003e\n\n`capped_cone(a, b, ra, rb)`\n\n```python\nf = capped_cone(-Z, Z, 1, 0.5)\n```\n\n### rounded_cone\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/rounded_cone.png\"\u003e\n\n`rounded_cone(r1, r2, h)`\n\n```python\nf = rounded_cone(0.75, 0.25, 2)\n```\n\n### ellipsoid\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/ellipsoid.png\"\u003e\n\n`ellipsoid(size)`\n\n```python\nf = ellipsoid((1, 2, 3))\n```\n\n### pyramid\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/pyramid.png\"\u003e\n\n`pyramid(h)`\n\n```python\nf = pyramid(1)\n```\n\n## Platonic Solids\n\n### tetrahedron\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/tetrahedron.png\"\u003e\n\n`tetrahedron(r)`\n\n```python\nf = tetrahedron(1)\n```\n\n### octahedron\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/octahedron.png\"\u003e\n\n`octahedron(r)`\n\n```python\nf = octahedron(1)\n```\n\n### dodecahedron\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/dodecahedron.png\"\u003e\n\n`dodecahedron(r)`\n\n```python\nf = dodecahedron(1)\n```\n\n### icosahedron\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/icosahedron.png\"\u003e\n\n`icosahedron(r)`\n\n```python\nf = icosahedron(1)\n```\n\n## Infinite 3D Primitives\n\nThe following SDFs extend to infinity in some or all axes.\nThey can only effectively be used in combination with other shapes, as shown in the examples below.\n\n### plane\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/plane.png\"\u003e\n\n`plane(normal=UP, point=ORIGIN)`\n\n`plane` is an infinite plane, with one side being positive (outside) and one side being negative (inside).\n\n```python\nf = sphere() \u0026 plane()\n```\n\n### slab\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/slab.png\"\u003e\n\n`slab(x0=None, y0=None, z0=None, x1=None, y1=None, z1=None, k=None)`\n\n`slab` is useful for cutting a shape on one or more axis-aligned planes.\n\n```python\nf = sphere() \u0026 slab(z0=-0.5, z1=0.5, x0=0)\n```\n\n### cylinder\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/cylinder.png\"\u003e\n\n`cylinder(radius)`\n\n`cylinder` is an infinite cylinder along the Z axis.\n\n```python\nf = sphere() - cylinder(0.5)\n```\n\n## Text\n\nYes, even text is supported!\n\n![Text](docs/images/text-large.png)\n\n`text(font_name, text, width=None, height=None, pixels=PIXELS, points=512)`\n\n```python\nFONT = 'Arial'\nTEXT = 'Hello, world!'\n\nw, h = measure_text(FONT, TEXT)\n\nf = rounded_box((w + 1, h + 1, 0.2), 0.1)\nf -= text(FONT, TEXT).extrude(1)\n```\n\nNote: [PIL.ImageFont](https://pillow.readthedocs.io/en/stable/reference/ImageFont.html),\nwhich is used to load fonts, does not search for the font by name on all operating systems.\nFor example, on Ubuntu the full path to the font has to be provided.\n(e.g. `/usr/share/fonts/truetype/freefont/FreeMono.ttf`)\n\n## Images\n\nImage masks can be extruded and incorporated into your 3D model.\n\n![Image Mask](docs/images/butterfly.png)\n\n`image(path_or_array, width=None, height=None, pixels=PIXELS)`\n\n```python\nIMAGE = 'examples/butterfly.png'\n\nw, h = measure_image(IMAGE)\n\nf = rounded_box((w * 1.1, h * 1.1, 0.1), 0.05)\nf |= image(IMAGE).extrude(1) \u0026 slab(z0=0, z1=0.075)\n```\n\n## Positioning\n\n### translate\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/translate.png\"\u003e\n\n`translate(other, offset)`\n\n```python\nf = sphere().translate((0, 0, 2))\n```\n\n### scale\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/scale.png\"\u003e\n\n`scale(other, factor)`\n\nNote that non-uniform scaling is an inexact SDF.\n\n```python\nf = sphere().scale(2)\nf = sphere().scale((1, 2, 3)) # non-uniform scaling\n```\n\n### rotate\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/rotate.png\"\u003e\n\n`rotate(other, angle, vector=Z)`\n\n```python\nf = capped_cylinder(-Z, Z, 0.5).rotate(pi / 4, X)\n```\n\n### orient\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/orient.png\"\u003e\n\n`orient(other, axis)`\n\n`orient` rotates the shape such that whatever was pointing in the +Z direction\nis now pointing in the specified direction.\n\n```python\nc = capped_cylinder(-Z, Z, 0.25)\nf = c.orient(X) | c.orient(Y) | c.orient(Z)\n```\n\n## Boolean Operations\n\nThe following primitives `a` and `b` are used in all of the following\nboolean operations.\n\n```python\na = box((3, 3, 0.5))\nb = sphere()\n```\n\nThe named versions (`union`, `difference`, `intersection`) can all take\none or more SDFs as input. They all take an optional `k` parameter to define the amount\nof smoothing to apply. When using operators (`|`, `-`, `\u0026`) the smoothing can\nstill be applied via the `.k(...)` function.\n\n### union\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/union.png\"\u003e\n\n```python\nf = a | b\nf = union(a, b) # equivalent\n```\n\n\u003cbr clear=\"right\"\u003e\n\n### difference\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/difference.png\"\u003e\n\n```python\nf = a - b\nf = difference(a, b) # equivalent\n```\n\n\u003cbr clear=\"right\"\u003e\n\n### intersection\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/intersection.png\"\u003e\n\n```python\nf = a \u0026 b\nf = intersection(a, b) # equivalent\n```\n\n\u003cbr clear=\"right\"\u003e\n\n### smooth_union\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/smooth_union.png\"\u003e\n\n```python\nf = a | b.k(0.25)\nf = union(a, b, k=0.25) # equivalent\n```\n\n\u003cbr clear=\"right\"\u003e\n\n### smooth_difference\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/smooth_difference.png\"\u003e\n\n```python\nf = a - b.k(0.25)\nf = difference(a, b, k=0.25) # equivalent\n```\n\n\u003cbr clear=\"right\"\u003e\n\n### smooth_intersection\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/smooth_intersection.png\"\u003e\n\n```python\nf = a \u0026 b.k(0.25)\nf = intersection(a, b, k=0.25) # equivalent\n```\n\n\u003cbr clear=\"right\"\u003e\n\n## Repetition\n\n### repeat\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/repeat.png\"\u003e\n\n`repeat(other, spacing, count=None, padding=0)`\n\n`repeat` can repeat the underlying SDF infinitely or a finite number of times.\nIf finite, the number of repetitions must be odd, because the count specifies\nthe number of copies to make on each side of the origin. If the repeated\nelements overlap or come close together, you may need to specify a `padding`\ngreater than zero to compute a correct SDF.\n\n```python\nf = sphere().repeat(3, (1, 1, 0))\n```\n\n### circular_array\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/circular_array.png\"\u003e\n\n`circular_array(other, count, offset)`\n\n`circular_array` makes `count` copies of the underlying SDF, arranged in a\ncircle around the Z axis. `offset` specifies how far to translate the shape\nin X before arraying it. The underlying SDF is only evaluated twice (instead\nof `count` times), so this is more performant than instantiating `count` copies\nof a shape.\n\n```python\nf = capped_cylinder(-Z, Z, 0.5).circular_array(8, 4)\n```\n\n## Miscellaneous\n\n### blend\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/blend.png\"\u003e\n\n`blend(a, *bs, k=0.5)`\n\n```python\nf = sphere().blend(box())\n```\n\n### dilate\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/dilate.png\"\u003e\n\n`dilate(other, r)`\n\n```python\nf = example.dilate(0.1)\n```\n\n### erode\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/erode.png\"\u003e\n\n`erode(other, r)`\n\n```python\nf = example.erode(0.1)\n```\n\n### shell\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/shell.png\"\u003e\n\n`shell(other, thickness)`\n\n```python\nf = sphere().shell(0.05) \u0026 plane(-Z)\n```\n\n### elongate\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/elongate.png\"\u003e\n\n`elongate(other, size)`\n\n```python\nf = example.elongate((0.25, 0.5, 0.75))\n```\n\n### twist\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/twist.png\"\u003e\n\n`twist(other, k)`\n\n```python\nf = box().twist(pi / 2)\n```\n\n### bend\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/bend.png\"\u003e\n\n`bend(other, k)`\n\n```python\nf = box().bend(1)\n```\n\n### bend_linear\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/bend_linear.png\"\u003e\n\n`bend_linear(other, p0, p1, v, e=ease.linear)`\n\n```python\nf = capsule(-Z * 2, Z * 2, 0.25).bend_linear(-Z, Z, X, ease.in_out_quad)\n```\n\n### bend_radial\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/bend_radial.png\"\u003e\n\n`bend_radial(other, r0, r1, dz, e=ease.linear)`\n\n```python\nf = box((5, 5, 0.25)).bend_radial(1, 2, -1, ease.in_out_quad)\n```\n\n### transition_linear\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/transition_linear.png\"\u003e\n\n`transition_linear(f0, f1, p0=-Z, p1=Z, e=ease.linear)`\n\n```python\nf = box().transition_linear(sphere(), e=ease.in_out_quad)\n```\n\n### transition_radial\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/transition_radial.png\"\u003e\n\n`transition_radial(f0, f1, r0=0, r1=1, e=ease.linear)`\n\n```python\nf = box().transition_radial(sphere(), e=ease.in_out_quad)\n```\n\n### wrap_around\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/wrap_around.png\"\u003e\n\n`wrap_around(other, x0, x1, r=None, e=ease.linear)`\n\n```python\nFONT = 'Arial'\nTEXT = ' wrap_around ' * 3\nw, h = measure_text(FONT, TEXT)\nf = text(FONT, TEXT).extrude(0.1).orient(Y).wrap_around(-w / 2, w / 2)\n```\n\n## 2D to 3D Operations\n\n### extrude\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/extrude.png\"\u003e\n\n`extrude(other, h)`\n\n```python\nf = hexagon(1).extrude(1)\n```\n\n### extrude_to\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/extrude_to.png\"\u003e\n\n`extrude_to(a, b, h, e=ease.linear)`\n\n```python\nf = rectangle(2).extrude_to(circle(1), 2, ease.in_out_quad)\n```\n\n### revolve\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/revolve.png\"\u003e\n\n`revolve(other, offset=0)`\n\n```python\nf = hexagon(1).revolve(3)\n```\n\n## 3D to 2D Operations\n\n### slice\n\n\u003cimg width=128 align=\"right\" src=\"docs/images/slice.png\"\u003e\n\n`slice(other)`\n\n```python\nf = example.translate((0, 0, 0.55)).slice().extrude(0.1)\n```\n\n## 2D Primitives\n\n### circle\n### line\n### rectangle\n### rounded_rectangle\n### equilateral_triangle\n### hexagon\n### rounded_x\n### polygon\n","funding_links":[],"categories":["Python","Specialty Topics"],"sub_categories":["Signed Distance Fields"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffogleman%2Fsdf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffogleman%2Fsdf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffogleman%2Fsdf/lists"}