@@ -2649,6 +2649,103 @@ def calc_arrow(uvw, angle=15):
2649
2649
2650
2650
quiver3D = quiver
2651
2651
2652
+ def voxels (self , filled , color = None ):
2653
+ """
2654
+ Plot a set of filled voxels
2655
+
2656
+ All voxels are plotted as 1x1x1 cubes on the axis, with filled[0,0,0]
2657
+ placed with its lower corner at the origin. Occluded faces are not plotted
2658
+
2659
+ Parameters
2660
+ ----------
2661
+ filled : np.array
2662
+ A 3d array of values, with truthy values indicating which voxels
2663
+ to fill
2664
+
2665
+ color : array_like
2666
+ Either a single value or an array the same shape as filled,
2667
+ indicating what color to draw the faces of the voxels. If None,
2668
+ plot all voxels in the same color, the next in the color sequence
2669
+ """
2670
+ # check dimensions, and deal with a single color
2671
+ if filled .ndim != 3 :
2672
+ raise ValueError ("Argument filled must be 3-dimensional" )
2673
+
2674
+ if color is None :
2675
+ color = next (self ._get_patches_for_fill .prop_cycler )['color' ]
2676
+ if np .ndim (color ) <= 1 :
2677
+ color , _ = np .broadcast_arrays (
2678
+ color ,
2679
+ filled [np .index_exp [...] + np .index_exp [np .newaxis ] * np .ndim (color )]
2680
+ )
2681
+ elif np .ndim (color ) < 3 :
2682
+ raise ValueError ("Argument color must be at least 3-dimensional" )
2683
+ elif np .shape (color )[:3 ] != filled .shape :
2684
+ raise ValueError ("Argument color must match the shape of filled, if multidimensional" )
2685
+
2686
+ self .auto_scale_xyz (
2687
+ [0 , filled .shape [0 ]],
2688
+ [0 , filled .shape [1 ]],
2689
+ [0 , filled .shape [2 ]]
2690
+ )
2691
+
2692
+
2693
+ # points lying on corners of a square
2694
+ square = np .array ([
2695
+ [0 , 0 , 0 ],
2696
+ [0 , 1 , 0 ],
2697
+ [1 , 1 , 0 ],
2698
+ [1 , 0 , 0 ]
2699
+ ])
2700
+
2701
+ def boundary_found (corners , color ):
2702
+ """ Plot a square at corners, with the specificed color """
2703
+ poly = art3d .Poly3DCollection ([corners ])
2704
+ poly .set_facecolor (color )
2705
+ self .add_collection3d (poly )
2706
+
2707
+ def permutation_matrices (n ):
2708
+ """ Generator of cyclic permutation matices """
2709
+ mat = np .eye (n )
2710
+ for i in range (n ):
2711
+ yield mat
2712
+ mat = np .roll (mat , 1 , axis = 0 )
2713
+
2714
+ for permute in permutation_matrices (3 ):
2715
+ # find the set of ranges to iterate over
2716
+ pc , qc , rc = permute .T .dot (filled .shape [:3 ])
2717
+ pinds = np .arange (pc )
2718
+ qinds = np .arange (qc )
2719
+ rinds = np .arange (rc )
2720
+
2721
+ for p in pinds :
2722
+ for q in qinds :
2723
+ # draw lower faces
2724
+ p0 = permute .dot ([p , q , 0 ])
2725
+ i0 = tuple (p0 )
2726
+ if filled [i0 ]:
2727
+ boundary_found (p0 + square .dot (permute .T ), color [i0 ])
2728
+
2729
+ # draw middle faces
2730
+ for r1 , r2 in zip (rinds , rinds [1 :]):
2731
+ p1 = permute .dot ([p , q , r1 ])
2732
+ p2 = permute .dot ([p , q , r2 ])
2733
+
2734
+ i1 = tuple (p1 )
2735
+ i2 = tuple (p2 )
2736
+
2737
+ if filled [i1 ] and not filled [i2 ]:
2738
+ boundary_found (p2 + square .dot (permute .T ), color [i1 ])
2739
+ elif not filled [i1 ] and filled [i2 ]:
2740
+ boundary_found (p2 + square .dot (permute .T ), color [i2 ])
2741
+
2742
+ # draw upper faces
2743
+ pk = permute .dot ([p , q , rc - 1 ])
2744
+ pk2 = permute .dot ([p , q , rc ])
2745
+ ik = tuple (pk )
2746
+ if filled [ik ]:
2747
+ boundary_found (pk2 + square .dot (permute .T ), color [ik ])
2748
+
2652
2749
2653
2750
def get_test_data (delta = 0.05 ):
2654
2751
'''
0 commit comments