wolph / numpy-stl

Simple library to make working with STL files (and 3D objects in general) fast and easy.
http://numpy-stl.readthedocs.org/
BSD 3-Clause "New" or "Revised" License
624 stars 105 forks source link

How to add lighting when a stl file is displayed using mplot3d? #163

Closed n2d7 closed 2 years ago

n2d7 commented 3 years ago

Hi, Is there a way to add lighting when displaying an stl file with mplot3d? In mplot3d, lighting is added for a surface plot in this manner.

rgb = np.ones((Z.shape[0], Z.shape[1], 3))
illuminated_surface = light.shade_rgb(rgb, Z)
ax = Axes3D(plt.figure())
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, linewidth=0, antialiased=False,
                facecolors=illuminated_surface)

but I'm not sure how to pass the parameters in ax.add_collection3d(mplot3d.art3d.Poly3DCollection(new_mesh.vectors))

appreciate any help

wolph commented 3 years ago

Perhaps with the lightsource parameter to plot_surface? I'm not really sure, I don't know too much about matplotlib :)

https://matplotlib.org/stable/tutorials/toolkits/mplot3d.html#mpl_toolkits.mplot3d.Axes3D.plot_surface

mooomooo commented 3 years ago

This is the function I use. It sometimes does some weird things where faces that should be hidden peek through faces that are in the front. I'm not sure if that's a bug in matplotlib or my code or what; if anyone has any suggestions on how to fix that, I'd be happy to hear.

import numpy
from stl import mesh
from mpl_toolkits import mplot3d
from matplotlib import pyplot
from matplotlib.colors import LightSource

def plotSTL(filename):
    # Create a new plot
    figure = pyplot.figure()
    axes = mplot3d.Axes3D(figure)

    # Load the STL mesh
    stlmesh = mesh.Mesh.from_file(filename)
    polymesh = mplot3d.art3d.Poly3DCollection(stlmesh.vectors)

    # Create light source
    ls = LightSource(azdeg=225, altdeg=45)

    # Darkest shadowed surface, in rgba
    dk = numpy.array([0.2, 0.0, 0.0, 1])
    # Brightest lit surface, in rgba
    lt = numpy.array([0.7, 0.7, 1.0, 1])
    # Interpolate between the two, based on face normal
    shade = lambda s: (lt-dk) * s + dk

    # Set face colors 
    sns = ls.shade_normals(stlmesh.get_unit_normals(), fraction=1.0)
    rgba = numpy.array([shade(s) for s in sns])
    polymesh.set_facecolor(rgba)

    axes.add_collection3d(polymesh)

    # Adjust limits of axes to fill the mesh, but keep 1:1:1 aspect ratio
    pts = stlmesh.points.reshape(-1,3)
    ptp = max(numpy.ptp(pts, 0))/2
    ctrs = [(min(pts[:,i]) + max(pts[:,i]))/2 for i in range(3)]
    lims = [[ctrs[i] - ptp, ctrs[i] + ptp] for i in range(3)]
    axes.auto_scale_xyz(*lims)

    pyplot.show()
stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

rcarmo commented 6 months ago

I landed here looking for the same thing, after getting really close to the sample function shown. Is there a good way to do this using solely LightSource and Poly3DCollection? I can get rid of edges, but shading facets (and culling invisible ones) is a major pain.