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

Commitde8739f

Browse files
authored
Merge pull request#23914 from oscargus/shaderefactor
Add shading of Poly3DCollection
2 parents185a421 +d9d75f2 commitde8739f

File tree

3 files changed

+166
-124
lines changed

3 files changed

+166
-124
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: 113 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+
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,17 @@ 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*. When activating
826+
*shade*, *facecolors* and/or *edgecolors* must be provided.
827+
828+
.. versionadded:: 3.7
829+
830+
lightsource : `~matplotlib.colors.LightSource`
831+
The lightsource to use when *shade* is True.
832+
833+
.. versionadded:: 3.7
834+
822835
*args, **kwargs
823836
All other parameters are forwarded to `.PolyCollection`.
824837
@@ -827,6 +840,23 @@ def __init__(self, verts, *args, zsort='average', **kwargs):
827840
Note that this class does a bit of magic with the _facecolors
828841
and _edgecolors properties.
829842
"""
843+
ifshade:
844+
normals=_generate_normals(verts)
845+
facecolors=kwargs.get('facecolors',None)
846+
iffacecolorsisnotNone:
847+
kwargs['facecolors']=_shade_colors(
848+
facecolors,normals,lightsource
849+
)
850+
851+
edgecolors=kwargs.get('edgecolors',None)
852+
ifedgecolorsisnotNone:
853+
kwargs['edgecolors']=_shade_colors(
854+
edgecolors,normals,lightsource
855+
)
856+
iffacecolorsisNoneandedgecolorsinNone:
857+
raiseValueError(
858+
"You must provide facecolors, edgecolors, or both for "
859+
"shade to work.")
830860
super().__init__(verts,*args,**kwargs)
831861
ifisinstance(verts,np.ndarray):
832862
ifverts.ndim!=3:
@@ -1086,3 +1116,84 @@ def _zalpha(colors, zs):
10861116
sats=1-norm(zs)*0.7
10871117
rgba=np.broadcast_to(mcolors.to_rgba_array(colors), (len(zs),4))
10881118
returnnp.column_stack([rgba[:, :3],rgba[:,3]*sats])
1119+
1120+
1121+
def_generate_normals(polygons):
1122+
"""
1123+
Compute the normals of a list of polygons, one normal per polygon.
1124+
1125+
Normals point towards the viewer for a face with its vertices in
1126+
counterclockwise order, following the right hand rule.
1127+
1128+
Uses three points equally spaced around the polygon. This method assumes
1129+
that the points are in a plane. Otherwise, more than one shade is required,
1130+
which is not supported.
1131+
1132+
Parameters
1133+
----------
1134+
polygons : list of (M_i, 3) array-like, or (..., M, 3) array-like
1135+
A sequence of polygons to compute normals for, which can have
1136+
varying numbers of vertices. If the polygons all have the same
1137+
number of vertices and array is passed, then the operation will
1138+
be vectorized.
1139+
1140+
Returns
1141+
-------
1142+
normals : (..., 3) array
1143+
A normal vector estimated for the polygon.
1144+
"""
1145+
ifisinstance(polygons,np.ndarray):
1146+
# optimization: polygons all have the same number of points, so can
1147+
# vectorize
1148+
n=polygons.shape[-2]
1149+
i1,i2,i3=0,n//3,2*n//3
1150+
v1=polygons[...,i1, :]-polygons[...,i2, :]
1151+
v2=polygons[...,i2, :]-polygons[...,i3, :]
1152+
else:
1153+
# The subtraction doesn't vectorize because polygons is jagged.
1154+
v1=np.empty((len(polygons),3))
1155+
v2=np.empty((len(polygons),3))
1156+
forpoly_i,psinenumerate(polygons):
1157+
n=len(ps)
1158+
i1,i2,i3=0,n//3,2*n//3
1159+
v1[poly_i, :]=ps[i1, :]-ps[i2, :]
1160+
v2[poly_i, :]=ps[i2, :]-ps[i3, :]
1161+
returnnp.cross(v1,v2)
1162+
1163+
1164+
def_shade_colors(color,normals,lightsource=None):
1165+
"""
1166+
Shade *color* using normal vectors given by *normals*,
1167+
assuming a *lightsource* (using default position if not given).
1168+
*color* can also be an array of the same length as *normals*.
1169+
"""
1170+
iflightsourceisNone:
1171+
# chosen for backwards-compatibility
1172+
lightsource=mcolors.LightSource(azdeg=225,altdeg=19.4712)
1173+
1174+
withnp.errstate(invalid="ignore"):
1175+
shade= ((normals/np.linalg.norm(normals,axis=1,keepdims=True))
1176+
@lightsource.direction)
1177+
mask=~np.isnan(shade)
1178+
1179+
ifmask.any():
1180+
# convert dot product to allowed shading fractions
1181+
in_norm=mcolors.Normalize(-1,1)
1182+
out_norm=mcolors.Normalize(0.3,1).inverse
1183+
1184+
defnorm(x):
1185+
returnout_norm(in_norm(x))
1186+
1187+
shade[~mask]=0
1188+
1189+
color=mcolors.to_rgba_array(color)
1190+
# shape of color should be (M, 4) (where M is number of faces)
1191+
# shape of shade should be (M,)
1192+
# colors should have final shape of (M, 4)
1193+
alpha=color[:,3]
1194+
colors=norm(shade)[:,np.newaxis]*color
1195+
colors[:,3]=alpha
1196+
else:
1197+
colors=np.asanyarray(color).copy()
1198+
1199+
returncolors

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp