Wednesday, 1 February 2012

Document Header

Sandstorm project only: Document Header is a component of the framework and is bound to the DocumentHeader property of AbstractDocumentViewModel.
It takes a simple string but if the string is empty the DocumentHeader is Hidden.

Drawing a semi-circle using Ellipse and Clipping

I needed to draw a filled semi-circle and Clip seems to do the job nicely. The RectangleGeometry describes the part of the clipped image that you want to be able to see.
<Ellipse
   Width="20"
   Height="20"
   Canvas.Left="20"
   Canvas.Top="0"
   Stretch="Fill" 
   Stroke="Black" 
   Fill="Red">
   <Ellipse.Clip>
      <RectangleGeometry Rect="0,10,20,20"/>
   </Ellipse.Clip>
</Ellipse>
And here is a fuller example with all four semi-circle rotations:
<Canvas VerticalAlignment="Center">
   <Ellipse
      Width="20"
      Height="20"
      Canvas.Left="20"
      Canvas.Top="0"
      Stretch="Fill" 
      Stroke="Black" 
      Fill="Red">
      <Ellipse.Clip>
         <RectangleGeometry Rect="0,10,20,20"/>
      </Ellipse.Clip>
   </Ellipse>
   <Ellipse
      Width="20"
      Height="20"
      Canvas.Left="60"
      Canvas.Top="0"
      Stretch="Fill" 
      Stroke="Black" 
      Fill="Red">
      <Ellipse.Clip>
         <RectangleGeometry Rect="0,0,10,20"/>
      </Ellipse.Clip>
   </Ellipse>
   <Ellipse
      Width="20"
      Height="20"
      Canvas.Left="100"
      Canvas.Top="0"
      Stretch="Fill" 
      Stroke="Black" 
      Fill="Red">
      <Ellipse.Clip>
         <RectangleGeometry Rect="10,0,10,20"/>
      </Ellipse.Clip>
   </Ellipse>
   <Ellipse
      Width="20"
      Height="20"
      Canvas.Left="140"
      Canvas.Top="0"
      Stretch="Fill" 
      Stroke="Black" 
      Fill="Red">
      <Ellipse.Clip>
         <RectangleGeometry Rect="0,10,20,20"/>
      </Ellipse.Clip>
   </Ellipse>
</Canvas>
Which gives this result:

Tuesday, 31 January 2012

DataGridTextColumn Text Alignment

From Martin: I’ve been using this technique DataGridTextColumn Text Alignment, but I found it had 2 weird effects which I ran by Katharine yesterday.

1. If you clicked on the row just to the left of the value, it wasn’t registering and selecting the row
2. When the row was selected, the value was highlighted weirdly in a dark blue “selected” box

Apparently, that approach was overwriting the usual cell style template and making the textblock behave a little strangely.
With the ElementStyle change she suggested, the 2 issues above went away, so it seems to be the right way to do it.

Courtesy of Katharine, this is the correct way:
<DataGridTextColumn Header="Blah" Binding="{Binding Blah}">
   <DataGridTextColumn.ElementStyle>
      <style TargetType="{x:Type TextBlock}">
         <setter Property="TextAlignment" Value="Right" />
      </Style>
   </DataGridTextColumn.ElementStyle>
</DataGridTextColumn>

Monday, 5 December 2011

RadioButton groups

A workaround from Martin for RadioButton groups.

There’s a bug in WPF (still) around RadioButton groups which doesn’t immediately manifest but will eventually seem to strike.

There’s a slightly confusing summary of issues here : http://social.msdn.microsoft.com/forums/en-US/wpf/thread/8eb8280a-19c4-4502-8260-f74633a9e2f2/

Suffice to say if you have 2 (or more) radio buttons and put them in the same group, they will reliably update each others status for a while as you’d expect. At some point (in my case I found this reliably happens after changing Document tabs a few times, or going forwards & back again in a Wizard workflow), the bindings stop working and you end up unable to check either RadioButton anymore.

I’ve messed around with various approaches – a couple are suggested in the post above.
The simplest option appears however to simply drop the GroupName=”x” from the XAML and simply do the “single-selection” logic yourself in the backing properties on your VM.

i.e. :
<RadioButton ssform:FormItem.LabelContent="{Binding Source={StaticResource ViewModel}, Path=Strings.V_Coupon, Mode=OneTime}"
                            Content="{Binding Source={StaticResource ViewModel}, Path=Strings.V_eCoupon, Mode=OneTime}"
                            IsEnabled="{Binding Source={StaticResource ViewModel}, Path=IsCouponATrigger}"
                            IsChecked="{Binding Source={StaticResource ViewModel}, Path=IsECouponATrigger, Mode=TwoWay}" />
<RadioButton Content="{Binding Source={StaticResource ViewModel}, Path=Strings.V_PrintCoupon, Mode=OneTime}"
                            IsEnabled="{Binding Source={StaticResource ViewModel}, Path=IsCouponATrigger}"
                            IsChecked="{Binding Source={StaticResource ViewModel}, Path=IsPrintedCouponATrigger, Mode=TwoWay}" />

and
public bool IsPrintedCouponATrigger
{
   get { return PromoBase.UIChoices.IsPrintedCouponATrigger; }
   set
   {
      if (value != PromoBase.UIChoices.IsPrintedCouponATrigger)
      {
         PromoBase.UIChoices.IsPrintedCouponATrigger = value;
         IsECouponATrigger = !value;
         OnPropertyChanged("IsPrintedCouponATrigger");
      }
   }
}
public bool IsECouponATrigger
{
   get { return PromoBase.UIChoices.IsECouponATrigger; }
   set
   {
      if (value != PromoBase.UIChoices.IsECouponATrigger)
      {
         PromoBase.UIChoices.IsECouponATrigger = value;
         IsPrintedCouponATrigger = !value;
         OnPropertyChanged("IsECouponATrigger");
      }
   }
}
Note the check that the value has actually changed in the setter – without this, you get into a nice infinite loop of PropertyChange notifications as each changes the other in turn!

Annoying bug, but simple enough to work around once you know about it..

Saturday, 3 December 2011

3D VisualBrush and TextureCoordinates

This post starts with the last example from the 3D Towards a Solid post.
The previous examples all used a single colour for the Material applied to a surface.
This example replaces the Brush with a StackPanel containing a TextBlock and Button.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <Model3DGroup>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, +0.5  0.5, -0.5, +0.5  0.5, 0.5, +0.5  -0.5, 0.5, +0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>

                     <DiffuseMaterial.Brush>
                        <VisualBrush>
                           <VisualBrush.Visual>
                              <StackPanel>
                                 <TextBlock FontSize="10pt"
                                            Margin="2">Hello, from the Front!</TextBlock>
                                 <Button Margin="2">A Button</Button>
                              </StackPanel>
                           </VisualBrush.Visual>
                        </VisualBrush>
                     </DiffuseMaterial.Brush>

                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  -0.5, -0.5, 0.5  -0.5, 0.5, 0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>0.5, -0.5, -0.5  0.5, -0.5, 0.5  0.5, 0.5, 0.5  0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            </Model3DGroup>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>
At this point the front of the cube will be transparent and the TextBlock and Button will not appear. We need to add TextCoordinates to map the VisualBrush to the Mesh.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <Model3DGroup>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, +0.5  0.5, -0.5, +0.5  0.5, 0.5, +0.5  -0.5, 0.5, +0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>

                     <MeshGeometry3D.TextureCoordinates>0,1 1,1 1,0 0,0</MeshGeometry3D.TextureCoordinates>

                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>
                        <VisualBrush>
                           <VisualBrush.Visual>
                              <StackPanel>
                                 <TextBlock FontSize="10pt"
                                            Margin="2">Hello, from the Front!</TextBlock>
                                 <Button Margin="2">A Button</Button>
                              </StackPanel>
                           </VisualBrush.Visual>
                        </VisualBrush>
                     </DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  -0.5, -0.5, 0.5  -0.5, 0.5, 0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>0.5, -0.5, -0.5  0.5, -0.5, 0.5  0.5, 0.5, 0.5  0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            </Model3DGroup>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>
To apply the texture to the "back" of the we need to "flip" the TextureCoordinates horizontally around the Y axis to match the way we previously changed the TriangleIndices.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <Model3DGroup>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>

                     <MeshGeometry3D.TextureCoordinates>1,1 0,1 0,0 1,0</MeshGeometry3D.TextureCoordinates>

                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>
                        <VisualBrush>
                           <VisualBrush.Visual>
                              <StackPanel>
                                 <TextBlock FontSize="10pt"
                                            Margin="2">Hello, from the Back!</TextBlock>
                                 <Button Margin="2">A Button</Button>
                              </StackPanel>
                           </VisualBrush.Visual>
                        </VisualBrush>
                     </DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, +0.5  0.5, -0.5, +0.5  0.5, 0.5, +0.5  -0.5, 0.5, +0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                     <MeshGeometry3D.TextureCoordinates>0,1 1,1 1,0 0,0</MeshGeometry3D.TextureCoordinates>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>
                        <VisualBrush>
                           <VisualBrush.Visual>
                              <StackPanel>
                                 <TextBlock FontSize="10pt"
                                            Margin="2">Hello, from the Front!</TextBlock>
                                 <Button Margin="2">A Button</Button>
                              </StackPanel>
                           </VisualBrush.Visual>
                        </VisualBrush>
                     </DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  -0.5, -0.5, 0.5  -0.5, 0.5, 0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>0.5, -0.5, -0.5  0.5, -0.5, 0.5  0.5, 0.5, 0.5  0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            </Model3DGroup>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>

3D Towards a Solid

This post starts with the last example from the 3D Rotation post. I want to add a second face of a cube, opposite the original surface. First add a Model3DGroup to contain all the GeometryModel3D elements that will form the cube.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>

            <Model3DGroup>

            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>

            </Model3DGroup>

         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>
And add the second surface exactly the same as the first but with the Z coordinates moved from -0.5 to +0.5.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <Model3DGroup>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>

            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, +0.5  0.5, -0.5, +0.5  0.5, 0.5, +0.5  -0.5, 0.5, +0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>

            </Model3DGroup>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>
We can now see two Maroon/Blue surfaces rotating but we always see the same colour for both surfaces. The original surface needs to point "out" from the centre of the cube. The easiest way would appear to be drawing the Triangles in a different order so their Normals are reversed. Swap 0 and 1, swap 2 and 3 in the TriangleIndices list.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <Model3DGroup>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>

                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>

                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, +0.5  0.5, -0.5, +0.5  0.5, 0.5, +0.5  -0.5, 0.5, +0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            </Model3DGroup>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>
Finally, add the remaining two vertical surfaces of the cube. The "left" surface uses the same TriangleIndices as the "front" whereas the "right" surface uses the same TriangleIndices as the "back".
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <Model3DGroup>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, +0.5  0.5, -0.5, +0.5  0.5, 0.5, +0.5  -0.5, 0.5, +0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>

                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  -0.5, -0.5, 0.5  -0.5, 0.5, 0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>

                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>

                     <MeshGeometry3D.Positions>0.5, -0.5, -0.5  0.5, -0.5, 0.5  0.5, 0.5, 0.5  0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>1 0 3 3 2 1</MeshGeometry3D.TriangleIndices>

                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
            </Model3DGroup>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>
</Grid>

3D Rotation

This post starts with the simple 3D model example from the first 3D post but with the camera moved back to its original position.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
</Grid>
We want to rotate the Model around the Y axis, represented by the "1" in the definition of AxisAngleRotation3D.Axis below. Because the Model has a negative Z coordinate (-0.5) the Z component will gradually increase until the angle of rotation is 180 degrees at which point the Z coordinate becomes 0.5. Because the Z coordinate is closer to use the Model will appear larger.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
         </ModelVisual3D.Content>

         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D>
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>
                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>
                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>

      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
</Grid>
Starting with an Angle of 0 (zero) degrees gives the same result as previously. Increasing the Angle to more than 90 degrees the reverse side of the Model comes into view.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D>
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>

                     <AxisAngleRotation3D.Angle>120</AxisAngleRotation3D.Angle>

                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>
</Grid>
The rotation can be animated by applying a DoubleAnimation to the RotateTransform.
Reset the Angle to zero, name the AxisAngleRotation3D and add a Grid.Trigger to start the animation.
<Grid Background="AliceBlue">
   <Viewport3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <GeometryModel3D>
               <GeometryModel3D.Geometry>
                  <MeshGeometry3D>
                     <MeshGeometry3D.Positions>-0.5, -0.5, -0.5  0.5, -0.5, -0.5  0.5, 0.5, -0.5  -0.5, 0.5, -0.5</MeshGeometry3D.Positions>
                     <MeshGeometry3D.TriangleIndices>0 1 2 2 3 0</MeshGeometry3D.TriangleIndices>
                  </MeshGeometry3D>
               </GeometryModel3D.Geometry>
               <GeometryModel3D.Material>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Maroon</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.Material>
               <GeometryModel3D.BackMaterial>
                  <DiffuseMaterial>
                     <DiffuseMaterial.Brush>Blue</DiffuseMaterial.Brush>
                  </DiffuseMaterial>
               </GeometryModel3D.BackMaterial>
            </GeometryModel3D>
         </ModelVisual3D.Content>
         <ModelVisual3D.Transform>
            <RotateTransform3D>
               <RotateTransform3D.Rotation>
                  <AxisAngleRotation3D  x:Name="Rotate">
                     <AxisAngleRotation3D.Axis>0,1,0</AxisAngleRotation3D.Axis>

                     <AxisAngleRotation3D.Angle>0</AxisAngleRotation3D.Angle>

                  </AxisAngleRotation3D>
               </RotateTransform3D.Rotation>
            </RotateTransform3D>
         </ModelVisual3D.Transform>
      </ModelVisual3D>
      <ModelVisual3D>
         <ModelVisual3D.Content>
            <AmbientLight Color="White" />
         </ModelVisual3D.Content>
      </ModelVisual3D>
      <Viewport3D.Camera>
         <PerspectiveCamera>
            <PerspectiveCamera.LookDirection>0,0,-1</PerspectiveCamera.LookDirection>
            <PerspectiveCamera.Position>0,0,2</PerspectiveCamera.Position>
            <PerspectiveCamera.FieldOfView>90</PerspectiveCamera.FieldOfView>
         </PerspectiveCamera>
      </Viewport3D.Camera>
   </Viewport3D>

   <Grid.Triggers>
      <EventTrigger RoutedEvent="Grid.MouseDown">
         <BeginStoryboard>
            <Storyboard>
               <DoubleAnimation From="0"
                                To="360"
                                BeginTime="0:0:0"
                                Duration="0:0:4"
                                Storyboard.TargetName="Rotate"
                                Storyboard.TargetProperty="Angle" />
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>
   </Grid.Triggers>

</Grid>