Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Simple SDF mesh generation in Python

License

NotificationsYou must be signed in to change notification settings

fogleman/sdf

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Generate 3D meshes based on SDFs (signed distance functions) with adirt simple Python API.

Special thanks toInigo Quilez for his excellent documentation on signed distance functions:

Example

Here is a complete example that generates the model shown. This is thecanonicalConstructive Solid Geometryexample. Note the use of operators for union, intersection, and difference.

fromsdfimport*f=sphere(1)&box(1.5)c=cylinder(0.5)f-=c.orient(X)|c.orient(Y)|c.orient(Z)f.save('out.stl')

Yes, that's really the entire code! You can 3D print that model or use itin a 3D application.

More Examples

Have a cool example? Submit a PR!

gearlike.pyknurling.pyblobby.pyweave.py
gearlikeknurlingblobbyweave
gearlikeknurlingblobbyweave

Requirements

Note that the dependencies will be automatically installed by setup.py whenfollowing the directions below.

  • Python 3
  • matplotlib
  • meshio
  • numpy
  • Pillow
  • scikit-image
  • scipy

Installation

Use the commands below to clone the repository and install thesdf libraryin a Python virtualenv.

git clone https://github.com/fogleman/sdf.gitcd sdfvirtualenv env. env/bin/activatepip install -e.

Confirm that it works:

python examples/example.py# should generate a file named out.stl

You can skip the installation if you always run scripts that importsdffrom the root folder.

OpenVDB

OpenVDB and its Python module are also required if you want to use meshes as SDFs.It doesn't seem like this can easily be installed via pip. The basic approach forbuilding it is as follows:

git clone https://github.com/AcademySoftwareFoundation/openvdb.gitcd openvdbmkdir buildcd buildcmake -D OPENVDB_BUILD_PYTHON_MODULE=ON -D USE_NUMPY=ON ..make -j8cp openvdb/openvdb/python/pyopenvdb.*`python -c'import site; print(site.getsitepackages()[0])'`

File Formats

sdf natively writes binary STL files. For other formats,meshiois used (based on your output file extension). This adds support for over 20 different 3D file formats,including OBJ, PLY, VTK, and many more.

Viewing the Mesh

Find and install a 3D mesh viewer for your platform, such asMeshLab.

I have developed and use my own cross-platform mesh viewer calledmeshview (see screenshot).Installation is easy if you haveGo andglfw installed:

$ brew install go glfw# on macOS with homebrew$ go get -u github.com/fogleman/meshview/cmd/meshview

Then you can view any mesh from the command line with:

$ meshview your-mesh.stl

See the meshviewREADME for more complete installation instructions.

On macOS you can just use the built-in Quick Look (press spacebar after selecting the STL file in Finder) in a pinch.

API

In all of the below examples,f is any 3D SDF, such as:

f=sphere()

Bounds

The bounding box of the SDF is automatically estimated. Inexact SDFs such asnon-uniform scaling may cause issues with this process. In that case you canspecify the bounds to sample manually:

f.save('out.stl',bounds=((-1,-1,-1), (1,1,1)))

Resolution

The resolution of the mesh is also computed automatically. There are two waysto specify the resolution. You can set the resolution directly withstep:

f.save('out.stl',step=0.01)f.save('out.stl',step=(0.01,0.02,0.03))# non-uniform resolution

Or you can specify approximately how many points to sample:

f.save('out.stl',samples=2**24)# sample about 16M points

By default,samples=2**22 is used.

Tip: Use the default resolution while developing your SDF. Then when you're done,crank up the resolution for your final output.

Batches

The SDF is sampled in batches. By default the batches have32**3 = 32768points each. This batch size can be overridden:

f.save('out.stl',batch_size=64)# instead of 32

The code attempts to skip any batches that are far away from the surface ofthe mesh. Inexact SDFs such as non-uniform scaling may cause issues with thisprocess, resulting in holes in the output mesh (where batches were skipped whenthey shouldn't have been). To avoid this, you can disable sparse sampling:

f.save('out.stl',sparse=False)# force all batches to be completely sampled

Worker Threads

The SDF is sampled in batches using worker threads. By default,multiprocessing.cpu_count() worker threads are used. This can be overridden:

f.save('out.stl',workers=1)# only use one worker thread

Without Saving

You can of course generate a mesh without writing it to an STL file:

points=f.generate()# takes the same optional arguments as `save`print(len(points))# print number of points (3x the number of triangles)print(points[:3])# print the vertices of the first triangle

If you want to save an STL aftergenerate, just use:

write_binary_stl(path,points)

Visualizing the SDF

You can plot a visualization of a 2D slice of the SDF using matplotlib.This can be useful for debugging purposes.

f.show_slice(z=0)f.show_slice(z=0,abs=True)# show abs(f)

You can specify a slice plane at any X, Y, or Z coordinate. You canalso specify the bounds to plot.

Note thatmatplotlib is only imported if this function is called, so itisn't strictly required as a dependency.


How it Works

The code simply uses theMarching Cubesalgorithm to generate a mesh from theSigned Distance Function.

This would normally be abysmally slow in Python. However, numpy is used toevaluate the SDF on entire batches of points simultaneously. Furthermore,multiple threads are used to process batches in parallel. The result issurprisingly fast (for marching cubes). Meshes of adequate detail canstill be quite large in terms of number of triangles.

The core "engine" of thesdf library is very small and can be found incore.py.

In short, there is nothing algorithmically revolutionary here. The goal isto provide a simple, fun, and easy-to-use API for generating 3D models in ourfavorite language Python.

Files

  • 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.
  • sdf/d2.py: 2D signed distance functions
  • sdf/d3.py: 3D signed distance functions
  • sdf/dn.py: Dimension-agnostic signed distance functions
  • sdf/ease.py:Easing functions that operate on numpy arrays. Some SDFs take an easing function as a parameter.
  • sdf/mesh.py: Code for loading meshes and using them as SDFs.
  • sdf/progress.py: A console progress bar.
  • sdf/stl.py: Code for writing a binarySTL file.
  • sdf/text.py: Generate 2D SDFs for text (which can then be extruded)
  • sdf/util.py: Utility constants and functions.

SDF Implementation

It is reasonable to write your own SDFs beyond those provided by thebuilt-in library. Browse the SDF implementations to understand how they areimplemented. Here are some simple examples:

@sdf3defsphere(radius=1,center=ORIGIN):deff(p):returnnp.linalg.norm(p-center,axis=1)-radiusreturnf

An SDF is simply a function that takes a numpy array of points with shape(N, 3)for 3D SDFs or shape(N, 2) for 2D SDFs and returns the signed distance for eachof those points as an array of shape(N, 1). They are wrapped with the@sdf3 decorator (or@sdf2 for 2D SDFs) which make boolean operators work,add thesave method, add the operators liketranslate, etc.

@op3deftranslate(other,offset):deff(p):returnother(p-offset)returnf

An SDF that operates on another SDF (like the abovetranslate) should usethe@op3 decorator instead. This will register the function such that SDFscan be chained together like:

f=sphere(1).translate((1,2,3))

Instead of what would otherwise be required:

f=translate(sphere(1), (1,2,3))

Remember, it's Python!

Remember, this is Python, so it's fully programmable. You can and should split up yourmodel into parameterized sub-components, for example. You can use for loops andconditionals wherever applicable. The sky is the limit!

See thecustomizable box example for some starting ideas.


Function Reference

3D Primitives

sphere

sphere(radius=1, center=ORIGIN)

f=sphere()# unit spheref=sphere(2)# specify radiusf=sphere(1, (1,2,3))# translated sphere

box

box(size=1, center=ORIGIN, a=None, b=None)

f=box(1)# all side lengths = 1f=box((1,2,3))# different side lengthsf=box(a=(-1,-1,-1),b=(3,4,5))# specified by bounds

rounded_box

rounded_box(size, radius)

f=rounded_box((1,2,3),0.25)

wireframe_box

wireframe_box(size, thickness)

f=wireframe_box((1,2,3),0.05)

torus

torus(r1, r2)

f=torus(1,0.25)

capsule

capsule(a, b, radius)

f=capsule(-Z,Z,0.5)

capped_cylinder

capped_cylinder(a, b, radius)

f=capped_cylinder(-Z,Z,0.5)

rounded_cylinder

rounded_cylinder(ra, rb, h)

f=rounded_cylinder(0.5,0.1,2)

capped_cone

capped_cone(a, b, ra, rb)

f=capped_cone(-Z,Z,1,0.5)

rounded_cone

rounded_cone(r1, r2, h)

f=rounded_cone(0.75,0.25,2)

ellipsoid

ellipsoid(size)

f=ellipsoid((1,2,3))

pyramid

pyramid(h)

f=pyramid(1)

Platonic Solids

tetrahedron

tetrahedron(r)

f=tetrahedron(1)

octahedron

octahedron(r)

f=octahedron(1)

dodecahedron

dodecahedron(r)

f=dodecahedron(1)

icosahedron

icosahedron(r)

f=icosahedron(1)

Infinite 3D Primitives

The following SDFs extend to infinity in some or all axes.They can only effectively be used in combination with other shapes, as shown in the examples below.

plane

plane(normal=UP, point=ORIGIN)

plane is an infinite plane, with one side being positive (outside) and one side being negative (inside).

f=sphere()&plane()

slab

slab(x0=None, y0=None, z0=None, x1=None, y1=None, z1=None, k=None)

slab is useful for cutting a shape on one or more axis-aligned planes.

f=sphere()&slab(z0=-0.5,z1=0.5,x0=0)

cylinder

cylinder(radius)

cylinder is an infinite cylinder along the Z axis.

f=sphere()-cylinder(0.5)

Text

Yes, even text is supported!

Text

text(font_name, text, width=None, height=None, pixels=PIXELS, points=512)

FONT='Arial'TEXT='Hello, world!'w,h=measure_text(FONT,TEXT)f=rounded_box((w+1,h+1,0.2),0.1)f-=text(FONT,TEXT).extrude(1)

Note:PIL.ImageFont,which is used to load fonts, does not search for the font by name on all operating systems.For example, on Ubuntu the full path to the font has to be provided.(e.g./usr/share/fonts/truetype/freefont/FreeMono.ttf)

Images

Image masks can be extruded and incorporated into your 3D model.

Image Mask

image(path_or_array, width=None, height=None, pixels=PIXELS)

IMAGE='examples/butterfly.png'w,h=measure_image(IMAGE)f=rounded_box((w*1.1,h*1.1,0.1),0.05)f|=image(IMAGE).extrude(1)&slab(z0=0,z1=0.075)

Positioning

translate

translate(other, offset)

f=sphere().translate((0,0,2))

scale

scale(other, factor)

Note that non-uniform scaling is an inexact SDF.

f=sphere().scale(2)f=sphere().scale((1,2,3))# non-uniform scaling

rotate

rotate(other, angle, vector=Z)

f=capped_cylinder(-Z,Z,0.5).rotate(pi/4,X)

orient

orient(other, axis)

orient rotates the shape such that whatever was pointing in the +Z directionis now pointing in the specified direction.

c=capped_cylinder(-Z,Z,0.25)f=c.orient(X)|c.orient(Y)|c.orient(Z)

Boolean Operations

The following primitivesa andb are used in all of the followingboolean operations.

a=box((3,3,0.5))b=sphere()

The named versions (union,difference,intersection) can all takeone or more SDFs as input. They all take an optionalk parameter to define the amountof smoothing to apply. When using operators (|,-,&) the smoothing canstill be applied via the.k(...) function.

union

f=a|bf=union(a,b)# equivalent

difference

f=a-bf=difference(a,b)# equivalent

intersection

f=a&bf=intersection(a,b)# equivalent

smooth_union

f=a|b.k(0.25)f=union(a,b,k=0.25)# equivalent

smooth_difference

f=a-b.k(0.25)f=difference(a,b,k=0.25)# equivalent

smooth_intersection

f=a&b.k(0.25)f=intersection(a,b,k=0.25)# equivalent

Repetition

repeat

repeat(other, spacing, count=None, padding=0)

repeat can repeat the underlying SDF infinitely or a finite number of times.If finite, the number of repetitions must be odd, because the count specifiesthe number of copies to make on each side of the origin. If the repeatedelements overlap or come close together, you may need to specify apaddinggreater than zero to compute a correct SDF.

f=sphere().repeat(3, (1,1,0))

circular_array

circular_array(other, count, offset)

circular_array makescount copies of the underlying SDF, arranged in acircle around the Z axis.offset specifies how far to translate the shapein X before arraying it. The underlying SDF is only evaluated twice (insteadofcount times), so this is more performant than instantiatingcount copiesof a shape.

f=capped_cylinder(-Z,Z,0.5).circular_array(8,4)

Miscellaneous

blend

blend(a, *bs, k=0.5)

f=sphere().blend(box())

dilate

dilate(other, r)

f=example.dilate(0.1)

erode

erode(other, r)

f=example.erode(0.1)

shell

shell(other, thickness)

f=sphere().shell(0.05)&plane(-Z)

elongate

elongate(other, size)

f=example.elongate((0.25,0.5,0.75))

twist

twist(other, k)

f=box().twist(pi/2)

bend

bend(other, k)

f=box().bend(1)

bend_linear

bend_linear(other, p0, p1, v, e=ease.linear)

f=capsule(-Z*2,Z*2,0.25).bend_linear(-Z,Z,X,ease.in_out_quad)

bend_radial

bend_radial(other, r0, r1, dz, e=ease.linear)

f=box((5,5,0.25)).bend_radial(1,2,-1,ease.in_out_quad)

transition_linear

transition_linear(f0, f1, p0=-Z, p1=Z, e=ease.linear)

f=box().transition_linear(sphere(),e=ease.in_out_quad)

transition_radial

transition_radial(f0, f1, r0=0, r1=1, e=ease.linear)

f=box().transition_radial(sphere(),e=ease.in_out_quad)

wrap_around

wrap_around(other, x0, x1, r=None, e=ease.linear)

FONT='Arial'TEXT=' wrap_around '*3w,h=measure_text(FONT,TEXT)f=text(FONT,TEXT).extrude(0.1).orient(Y).wrap_around(-w/2,w/2)

2D to 3D Operations

extrude

extrude(other, h)

f=hexagon(1).extrude(1)

extrude_to

extrude_to(a, b, h, e=ease.linear)

f=rectangle(2).extrude_to(circle(1),2,ease.in_out_quad)

revolve

revolve(other, offset=0)

f=hexagon(1).revolve(3)

3D to 2D Operations

slice

slice(other)

f=example.translate((0,0,0.55)).slice().extrude(0.1)

2D Primitives

circle

line

rectangle

rounded_rectangle

equilateral_triangle

hexagon

rounded_x

polygon

Releases

No releases published

Packages

No packages published

Languages


[8]ページ先頭

©2009-2025 Movatter.jp