Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

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
Appearance settings

Commit0c248a2

Browse files
committed
Refactor shading
1 parentf6e7512 commit0c248a2

File tree

3 files changed

+173
-122
lines changed

3 files changed

+173
-122
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
``Poly3DCollection`` supports shading
2+
-------------------------------------
3+
4+
It is now possible to shade a `.Poly3DCollection`. This is useful if the
5+
polygons are obtained from e.g. a 3D model.
6+
7+
..plot::
8+
:include-source: true
9+
10+
import numpy as np
11+
import matplotlib.pyplot as plt
12+
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
13+
14+
# Define 3D shape
15+
block = np.array([
16+
[[1, 1, 0],
17+
[1, 0, 0],
18+
[0, 1, 0]],
19+
[[1, 1, 0],
20+
[1, 1, 1],
21+
[1, 0, 0]],
22+
[[1, 1, 0],
23+
[1, 1, 1],
24+
[0, 1, 0]],
25+
[[1, 0, 0],
26+
[1, 1, 1],
27+
[0, 1, 0]]
28+
])
29+
30+
ax = plt.subplot(projection='3d')
31+
pc = Poly3DCollection(block, facecolors='b', shade=True)
32+
ax.add_collection(pc)
33+
plt.show()

‎lib/mpl_toolkits/mplot3d/art3d.py

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
importnumpyasnp
1313

1414
frommatplotlibimport (
15-
artist,cbook,colorsasmcolors,lines,textasmtext,pathasmpath)
15+
_api,artist,cbook,colorsasmcolors,lines,textasmtext,
16+
pathasmpath)
1617
frommatplotlib.collectionsimport (
1718
LineCollection,PolyCollection,PatchCollection,PathCollection)
1819
frommatplotlib.colorsimportNormalize
@@ -808,7 +809,8 @@ class Poly3DCollection(PolyCollection):
808809
triangulation and thus generates consistent surfaces.
809810
"""
810811

811-
def__init__(self,verts,*args,zsort='average',**kwargs):
812+
def__init__(self,verts,*args,zsort='average',shade=False,
813+
lightsource=None,**kwargs):
812814
"""
813815
Parameters
814816
----------
@@ -819,6 +821,20 @@ def __init__(self, verts, *args, zsort='average', **kwargs):
819821
zsort : {'average', 'min', 'max'}, default: 'average'
820822
The calculation method for the z-order.
821823
See `~.Poly3DCollection.set_zsort` for details.
824+
shade : bool, default: False
825+
Whether to shade *facecolors* and *edgecolors*.
826+
827+
.. versionadded:: 3.7
828+
829+
.. note::
830+
*facecolors* and/or *edgecolors* must be provided for shading
831+
to work.
832+
833+
lightsource : `~matplotlib.colors.LightSource`
834+
The lightsource to use when *shade* is True.
835+
836+
.. versionadded:: 3.7
837+
822838
*args, **kwargs
823839
All other parameters are forwarded to `.PolyCollection`.
824840
@@ -827,6 +843,28 @@ def __init__(self, verts, *args, zsort='average', **kwargs):
827843
Note that this class does a bit of magic with the _facecolors
828844
and _edgecolors properties.
829845
"""
846+
if'facecolor'inkwargs:
847+
kwargs['facecolors']=kwargs.pop('facecolor')
848+
if'edgecolor'inkwargs:
849+
kwargs['edgecolors']=kwargs.pop('edgecolor')
850+
ifshade:
851+
normals=_generate_normals(verts)
852+
facecolors=kwargs.get('facecolors',None)
853+
iffacecolorsisnotNone:
854+
kwargs['facecolors']=_shade_colors(
855+
facecolors,normals,lightsource
856+
)
857+
858+
edgecolors=kwargs.get('edgecolors',None)
859+
ifedgecolorsisnotNone:
860+
kwargs['edgecolors']=_shade_colors(
861+
edgecolors,normals,lightsource
862+
)
863+
iffacecolorsisNoneandedgecolorsinNone:
864+
_api.warn_external(
865+
"You must provide at least one of facecolors and "
866+
"edgecolors for shade to work as expected.")
867+
830868
super().__init__(verts,*args,**kwargs)
831869
ifisinstance(verts,np.ndarray):
832870
ifverts.ndim!=3:
@@ -1086,3 +1124,84 @@ def _zalpha(colors, zs):
10861124
sats=1-norm(zs)*0.7
10871125
rgba=np.broadcast_to(mcolors.to_rgba_array(colors), (len(zs),4))
10881126
returnnp.column_stack([rgba[:, :3],rgba[:,3]*sats])
1127+
1128+
1129+
def_generate_normals(polygons):
1130+
"""
1131+
Compute the normals of a list of polygons, one normal per polygon.
1132+
1133+
Normals point towards the viewer for a face with its vertices in
1134+
counterclockwise order, following the right hand rule.
1135+
1136+
Uses three points equally spaced around the polygon.
1137+
If the polygon points are not in a plane, this does not make sense, but
1138+
it is on the other hand impossible to compute a single shade in that case.
1139+
1140+
Parameters
1141+
----------
1142+
polygons : list of (M_i, 3) array-like, or (..., M, 3) array-like
1143+
A sequence of polygons to compute normals for, which can have
1144+
varying numbers of vertices. If the polygons all have the same
1145+
number of vertices and array is passed, then the operation will
1146+
be vectorized.
1147+
1148+
Returns
1149+
-------
1150+
normals : (..., 3) array
1151+
A normal vector estimated for the polygon.
1152+
"""
1153+
ifisinstance(polygons,np.ndarray):
1154+
# optimization: polygons all have the same number of points, so can
1155+
# vectorize
1156+
n=polygons.shape[-2]
1157+
i1,i2,i3=0,n//3,2*n//3
1158+
v1=polygons[...,i1, :]-polygons[...,i2, :]
1159+
v2=polygons[...,i2, :]-polygons[...,i3, :]
1160+
else:
1161+
# The subtraction doesn't vectorize because polygons is jagged.
1162+
v1=np.empty((len(polygons),3))
1163+
v2=np.empty((len(polygons),3))
1164+
forpoly_i,psinenumerate(polygons):
1165+
n=len(ps)
1166+
i1,i2,i3=0,n//3,2*n//3
1167+
v1[poly_i, :]=ps[i1, :]-ps[i2, :]
1168+
v2[poly_i, :]=ps[i2, :]-ps[i3, :]
1169+
returnnp.cross(v1,v2)
1170+
1171+
1172+
def_shade_colors(color,normals,lightsource=None):
1173+
"""
1174+
Shade *color* using normal vectors given by *normals*,
1175+
assuming a *lightsource* (using default position if not given).
1176+
*color* can also be an array of the same length as *normals*.
1177+
"""
1178+
iflightsourceisNone:
1179+
# chosen for backwards-compatibility
1180+
lightsource=mcolors.LightSource(azdeg=225,altdeg=19.4712)
1181+
1182+
withnp.errstate(invalid="ignore"):
1183+
shade= ((normals/np.linalg.norm(normals,axis=1,keepdims=True))
1184+
@lightsource.direction)
1185+
mask=~np.isnan(shade)
1186+
1187+
ifmask.any():
1188+
# convert dot product to allowed shading fractions
1189+
in_norm=mcolors.Normalize(-1,1)
1190+
out_norm=mcolors.Normalize(0.3,1).inverse
1191+
1192+
defnorm(x):
1193+
returnout_norm(in_norm(x))
1194+
1195+
shade[~mask]=0
1196+
1197+
color=mcolors.to_rgba_array(color)
1198+
# shape of color should be (M, 4) (where M is number of faces)
1199+
# shape of shade should be (M,)
1200+
# colors should have final shape of (M, 4)
1201+
alpha=color[:,3]
1202+
colors=norm(shade)[:,np.newaxis]*color
1203+
colors[:,3]=alpha
1204+
else:
1205+
colors=np.asanyarray(color).copy()
1206+
1207+
returncolors

‎lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 19 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,15 +1668,13 @@ def plot_surface(self, X, Y, Z, *, norm=None, vmin=None,
16681668

16691669
# note that the striding causes some polygons to have more coordinates
16701670
# than others
1671-
polyc=art3d.Poly3DCollection(polys,**kwargs)
16721671

16731672
iffcolorsisnotNone:
1674-
ifshade:
1675-
colset=self._shade_colors(
1676-
colset,self._generate_normals(polys),lightsource)
1677-
polyc.set_facecolors(colset)
1678-
polyc.set_edgecolors(colset)
1673+
polyc=art3d.Poly3DCollection(
1674+
polys,edgecolors=colset,facecolors=colset,shade=shade,
1675+
lightsource=lightsource,**kwargs)
16791676
elifcmap:
1677+
polyc=art3d.Poly3DCollection(polys,**kwargs)
16801678
# can't always vectorize, because polys might be jagged
16811679
ifisinstance(polys,np.ndarray):
16821680
avg_z=polys[...,2].mean(axis=-1)
@@ -1688,97 +1686,15 @@ def plot_surface(self, X, Y, Z, *, norm=None, vmin=None,
16881686
ifnormisnotNone:
16891687
polyc.set_norm(norm)
16901688
else:
1691-
ifshade:
1692-
colset=self._shade_colors(
1693-
color,self._generate_normals(polys),lightsource)
1694-
else:
1695-
colset=color
1696-
polyc.set_facecolors(colset)
1689+
polyc=art3d.Poly3DCollection(
1690+
polys,facecolors=color,shade=shade,
1691+
lightsource=lightsource,**kwargs)
16971692

16981693
self.add_collection(polyc)
16991694
self.auto_scale_xyz(X,Y,Z,had_data)
17001695

17011696
returnpolyc
17021697

1703-
def_generate_normals(self,polygons):
1704-
"""
1705-
Compute the normals of a list of polygons.
1706-
1707-
Normals point towards the viewer for a face with its vertices in
1708-
counterclockwise order, following the right hand rule.
1709-
1710-
Uses three points equally spaced around the polygon.
1711-
This normal of course might not make sense for polygons with more than
1712-
three points not lying in a plane, but it's a plausible and fast
1713-
approximation.
1714-
1715-
Parameters
1716-
----------
1717-
polygons : list of (M_i, 3) array-like, or (..., M, 3) array-like
1718-
A sequence of polygons to compute normals for, which can have
1719-
varying numbers of vertices. If the polygons all have the same
1720-
number of vertices and array is passed, then the operation will
1721-
be vectorized.
1722-
1723-
Returns
1724-
-------
1725-
normals : (..., 3) array
1726-
A normal vector estimated for the polygon.
1727-
"""
1728-
ifisinstance(polygons,np.ndarray):
1729-
# optimization: polygons all have the same number of points, so can
1730-
# vectorize
1731-
n=polygons.shape[-2]
1732-
i1,i2,i3=0,n//3,2*n//3
1733-
v1=polygons[...,i1, :]-polygons[...,i2, :]
1734-
v2=polygons[...,i2, :]-polygons[...,i3, :]
1735-
else:
1736-
# The subtraction doesn't vectorize because polygons is jagged.
1737-
v1=np.empty((len(polygons),3))
1738-
v2=np.empty((len(polygons),3))
1739-
forpoly_i,psinenumerate(polygons):
1740-
n=len(ps)
1741-
i1,i2,i3=0,n//3,2*n//3
1742-
v1[poly_i, :]=ps[i1, :]-ps[i2, :]
1743-
v2[poly_i, :]=ps[i2, :]-ps[i3, :]
1744-
returnnp.cross(v1,v2)
1745-
1746-
def_shade_colors(self,color,normals,lightsource=None):
1747-
"""
1748-
Shade *color* using normal vectors given by *normals*.
1749-
*color* can also be an array of the same length as *normals*.
1750-
"""
1751-
iflightsourceisNone:
1752-
# chosen for backwards-compatibility
1753-
lightsource=mcolors.LightSource(azdeg=225,altdeg=19.4712)
1754-
1755-
withnp.errstate(invalid="ignore"):
1756-
shade= ((normals/np.linalg.norm(normals,axis=1,keepdims=True))
1757-
@lightsource.direction)
1758-
mask=~np.isnan(shade)
1759-
1760-
ifmask.any():
1761-
# convert dot product to allowed shading fractions
1762-
in_norm=mcolors.Normalize(-1,1)
1763-
out_norm=mcolors.Normalize(0.3,1).inverse
1764-
1765-
defnorm(x):
1766-
returnout_norm(in_norm(x))
1767-
1768-
shade[~mask]=0
1769-
1770-
color=mcolors.to_rgba_array(color)
1771-
# shape of color should be (M, 4) (where M is number of faces)
1772-
# shape of shade should be (M,)
1773-
# colors should have final shape of (M, 4)
1774-
alpha=color[:,3]
1775-
colors=norm(shade)[:,np.newaxis]*color
1776-
colors[:,3]=alpha
1777-
else:
1778-
colors=np.asanyarray(color).copy()
1779-
1780-
returncolors
1781-
17821698
defplot_wireframe(self,X,Y,Z,**kwargs):
17831699
"""
17841700
Plot a 3D wireframe.
@@ -1975,9 +1891,8 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None,
19751891
zt=z[triangles]
19761892
verts=np.stack((xt,yt,zt),axis=-1)
19771893

1978-
polyc=art3d.Poly3DCollection(verts,*args,**kwargs)
1979-
19801894
ifcmap:
1895+
polyc=art3d.Poly3DCollection(verts,*args,**kwargs)
19811896
# average over the three points of each triangle
19821897
avg_z=verts[:, :,2].mean(axis=1)
19831898
polyc.set_array(avg_z)
@@ -1986,12 +1901,9 @@ def plot_trisurf(self, *args, color=None, norm=None, vmin=None, vmax=None,
19861901
ifnormisnotNone:
19871902
polyc.set_norm(norm)
19881903
else:
1989-
ifshade:
1990-
normals=self._generate_normals(verts)
1991-
colset=self._shade_colors(color,normals,lightsource)
1992-
else:
1993-
colset=color
1994-
polyc.set_facecolors(colset)
1904+
polyc=art3d.Poly3DCollection(
1905+
verts,*args,shade=shade,lightsource=lightsource,
1906+
facecolors=color,**kwargs)
19951907

19961908
self.add_collection(polyc)
19971909
self.auto_scale_xyz(tri.x,tri.y,z,had_data)
@@ -2036,13 +1948,10 @@ def _3d_extend_contour(self, cset, stride=5):
20361948

20371949
# all polygons have 4 vertices, so vectorize
20381950
polyverts=np.array(polyverts)
2039-
normals=self._generate_normals(polyverts)
2040-
2041-
colors=self._shade_colors(color,normals)
2042-
colors2=self._shade_colors(color,normals)
20431951
polycol=art3d.Poly3DCollection(polyverts,
2044-
facecolors=colors,
2045-
edgecolors=colors2)
1952+
facecolors=color,
1953+
edgecolors=color,
1954+
shade=True)
20461955
polycol.set_sort_zpos(z)
20471956
self.add_collection3d(polycol)
20481957

@@ -2587,15 +2496,11 @@ def bar3d(self, x, y, z, dx, dy, dz, color=None,
25872496
iflen(facecolors)<len(x):
25882497
facecolors*= (6*len(x))
25892498

2590-
ifshade:
2591-
normals=self._generate_normals(polys)
2592-
sfacecolors=self._shade_colors(facecolors,normals,lightsource)
2593-
else:
2594-
sfacecolors=facecolors
2595-
25962499
col=art3d.Poly3DCollection(polys,
25972500
zsort=zsort,
2598-
facecolor=sfacecolors,
2501+
facecolors=facecolors,
2502+
shade=shade,
2503+
lightsource=lightsource,
25992504
*args,**kwargs)
26002505
self.add_collection(col)
26012506

@@ -2964,16 +2869,10 @@ def permutation_matrices(n):
29642869
# shade the faces
29652870
facecolor=facecolors[coord]
29662871
edgecolor=edgecolors[coord]
2967-
ifshade:
2968-
normals=self._generate_normals(faces)
2969-
facecolor=self._shade_colors(facecolor,normals,lightsource)
2970-
ifedgecolorisnotNone:
2971-
edgecolor=self._shade_colors(
2972-
edgecolor,normals,lightsource
2973-
)
29742872

29752873
poly=art3d.Poly3DCollection(
2976-
faces,facecolors=facecolor,edgecolors=edgecolor,**kwargs)
2874+
faces,facecolors=facecolor,edgecolors=edgecolor,
2875+
shade=shade,lightsource=lightsource,**kwargs)
29772876
self.add_collection3d(poly)
29782877
polygons[coord]=poly
29792878

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp