Repository: openmc-dev/openmc

Issue:
Plot.from_xml_element doesn't grab colors from XML file
Code to reproduce below. We really should set something up to avoid having to write all this XML I/O boilerplate, maybe something similar to the dataclass functionality introduced recently to python.

```
gpad% cat plots.xml
<?xml version='1.0' encoding='utf-8'?>
<plots>
  <plot color_by="material" filename="radial_cells_midplane_proj" id="2" type="slice" basis="xy">
    <origin>0 0 230.0</origin>
    <pixels>200 200 </pixels>
    <width>10.0 10.0</width>
    <color id="1" rgb="255 255 255"/>
    <color id="2" rgb="0 0 0"/>
    <color id="3" rgb="255 0 0"/>
    <color id="4" rgb="200 50 50"/>
    <color id="5" rgb="255 218 185"/>
    <color id="6" rgb="101 101 101"/>
    <color id="7" rgb="111 111 111"/>
    <color id="8" rgb="50 50 50"/>
    <color id="9" rgb="142 35 35"/>
    <color id="10" rgb="255 215 0"/>
    <color id="11" rgb="0 0 128"/>
    <color id="14" rgb="0 255 0"/>
    <color id="15" rgb="198 226 255"/>
    <color id="16" rgb="176 196 222"/>
    <color id="17" rgb="112 128 144"/>
  </plot>
</plots>
gpad% python
Python 3.10.4 (main, Mar 23 2022, 23:05:40) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import openmc
>>> p = openmc.Plots.from_xml()
>>> p
[Plot
        ID             =        2
        Name           =
        Filename       =        radial_cells_midplane_proj
        Type           =        slice
        Basis          =        xy
        Width          =        (10.0, 10.0)
        Origin         =        (0.0, 0.0, 230.0)
        Pixels         =        (200, 200)
        Color by       =        material
        Background     =        None
        Mask components=        None
        Mask background=        None
        Overlap Color  =        None
        Colors         =        {}
        Level          =        None
        Meshlines      =        None
]

```

Discussion:

Patch:
diff --git a/openmc/cell.py b/openmc/cell.py
index feba18da5..103a34fe6 100644
--- a/openmc/cell.py
+++ b/openmc/cell.py
@@ -690,7 +690,10 @@ class Cell(IDManagerMixin):
         for key in ('temperature', 'rotation', 'translation'):
             value = get_text(elem, key)
             if value is not None:
-                setattr(c, key, [float(x) for x in value.split()])
+                values = [float(x) for x in value.split()]
+                if key == 'rotation' and len(values) == 9:
+                    values = np.array(values).reshape(3, 3)
+                setattr(c, key, values)
 
         # Add this cell to appropriate universe
         univ_id = int(get_text(elem, 'universe', 0))
diff --git a/openmc/material.py b/openmc/material.py
index e5c3f8e4b..de1b2187b 100644
--- a/openmc/material.py
+++ b/openmc/material.py
@@ -29,9 +29,9 @@ class Material(IDManagerMixin):
 
     To create a material, one should create an instance of this class, add
     nuclides or elements with :meth:`Material.add_nuclide` or
-    `Material.add_element`, respectively, and set the total material density
-    with `Material.set_density()`. The material can then be assigned to a cell
-    using the :attr:`Cell.fill` attribute.
+    :meth:`Material.add_element`, respectively, and set the total material
+    density with :meth:`Material.set_density()`. The material can then be
+    assigned to a cell using the :attr:`Cell.fill` attribute.
 
     Parameters
     ----------
diff --git a/openmc/plots.py b/openmc/plots.py
index 5e9c48413..b22ee9125 100644
--- a/openmc/plots.py
+++ b/openmc/plots.py
@@ -213,8 +213,8 @@ class Plot(IDManagerMixin):
         The basis directions for the plot
     background : Iterable of int or str
         Color of the background
-    mask_components : Iterable of openmc.Cell or openmc.Material
-        The cells or materials to plot
+    mask_components : Iterable of openmc.Cell or openmc.Material or int
+        The cells or materials (or corresponding IDs) to mask
     mask_background : Iterable of int or str
         Color to apply to all cells/materials not listed in mask_components
     show_overlaps : bool
@@ -222,8 +222,10 @@ class Plot(IDManagerMixin):
     overlap_color : Iterable of int or str
         Color to apply to overlapping regions
     colors : dict
-        Dictionary indicating that certain cells/materials (keys) should be
-        displayed with a particular color.
+        Dictionary indicating that certain cells/materials should be
+        displayed with a particular color. The keys can be of type
+        :class:`~openmc.Cell`, :class:`~openmc.Material`, or int (ID for a
+        cell/material).
     level : int
         Universe depth to plot at
     meshlines : dict
@@ -373,14 +375,15 @@ class Plot(IDManagerMixin):
     def colors(self, colors):
         cv.check_type('plot colors', colors, Mapping)
         for key, value in colors.items():
-            cv.check_type('plot color key', key, (openmc.Cell, openmc.Material))
+            cv.check_type('plot color key', key,
+                          (openmc.Cell, openmc.Material, Integral))
             self._check_color('plot color value', value)
         self._colors = colors
 
     @mask_components.setter
     def mask_components(self, mask_components):
         cv.check_type('plot mask components', mask_components, Iterable,
-                      (openmc.Cell, openmc.Material))
+                      (openmc.Cell, openmc.Material, Integral))
         self._mask_components = mask_components
 
     @mask_background.setter
@@ -634,11 +637,16 @@ class Plot(IDManagerMixin):
                 color = _SVG_COLORS[color.lower()]
             subelement.text = ' '.join(str(x) for x in color)
 
+        # Helper function that returns the domain ID given either a
+        # Cell/Material object or the domain ID itself
+        def get_id(domain):
+            return domain if isinstance(domain, Integral) else domain.id
+
         if self._colors:
             for domain, color in sorted(self._colors.items(),
-                                        key=lambda x: x[0].id):
+                                        key=lambda x: get_id(x[0])):
                 subelement = ET.SubElement(element, "color")
-                subelement.set("id", str(domain.id))
+                subelement.set("id", str(get_id(domain)))
                 if isinstance(color, str):
                     color = _SVG_COLORS[color.lower()]
                 subelement.set("rgb", ' '.join(str(x) for x in color))
@@ -646,7 +654,7 @@ class Plot(IDManagerMixin):
         if self._mask_components is not None:
             subelement = ET.SubElement(element, "mask")
             subelement.set("components", ' '.join(
-                str(d.id) for d in self._mask_components))
+                str(get_id(d)) for d in self._mask_components))
             color = self._mask_background
             if color is not None:
                 if isinstance(color, str):
@@ -720,15 +728,14 @@ class Plot(IDManagerMixin):
         # Set plot colors
         colors = {}
         for color_elem in elem.findall("color"):
-            uid = color_elem.get("id")
+            uid = int(color_elem.get("id"))
             colors[uid] = tuple([int(x) for x in color_elem.get("rgb").split()])
-        # TODO: set colors (needs geometry information)
+        plot.colors = colors
 
         # Set masking information
         mask_elem = elem.find("mask")
         if mask_elem is not None:
-            mask_components = [int(x) for x in mask_elem.get("components").split()]
-            # TODO: set mask components (needs geometry information)
+            plot.mask_components = [int(x) for x in mask_elem.get("components").split()]
             background = mask_elem.get("background")
             if background is not None:
                 plot.mask_background = tuple([int(x) for x in background.split()])
diff --git a/openmc/surface.py b/openmc/surface.py
index 926b2fedc..b3bcebb11 100644
--- a/openmc/surface.py
+++ b/openmc/surface.py
@@ -760,7 +760,7 @@ class XPlane(PlaneMixin, Surface):
     Parameters
     ----------
     x0 : float, optional
-        Location of the plane. Defaults to 0.
+        Location of the plane in [cm]. Defaults to 0.
     boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional
         Boundary condition that defines the behavior for particles hitting the
         surface. Defaults to transmissive boundary condition where particles
@@ -775,7 +775,7 @@ class XPlane(PlaneMixin, Surface):
     Attributes
     ----------
     x0 : float
-        Location of the plane
+        Location of the plane in [cm]
     boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}
         Boundary condition that defines the behavior for particles hitting the
         surface.
@@ -819,7 +819,7 @@ class YPlane(PlaneMixin, Surface):
     Parameters
     ----------
     y0 : float, optional
-        Location of the plane
+        Location of the plane in [cm]
     boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional
         Boundary condition that defines the behavior for particles hitting the
         surface. Defaults to transmissive boundary condition where particles
@@ -834,7 +834,7 @@ class YPlane(PlaneMixin, Surface):
     Attributes
     ----------
     y0 : float
-        Location of the plane
+        Location of the plane in [cm]
     boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}
         Boundary condition that defines the behavior for particles hitting the
         surface.
@@ -878,7 +878,7 @@ class ZPlane(PlaneMixin, Surface):
     Parameters
     ----------
     z0 : float, optional
-        Location of the plane. Defaults to 0.
+        Location of the plane in [cm]. Defaults to 0.
     boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}, optional
         Boundary condition that defines the behavior for particles hitting the
         surface. Defaults to transmissive boundary condition where particles
@@ -893,7 +893,7 @@ class ZPlane(PlaneMixin, Surface):
     Attributes
     ----------
     z0 : float
-        Location of the plane
+        Location of the plane in [cm]
     boundary_type : {'transmission, 'vacuum', 'reflective', 'periodic', 'white'}
         Boundary condition that defines the behavior for particles hitting the
         surface.
@@ -991,8 +991,8 @@ class QuadricMixin:
         Parameters
         ----------
         point : 3-tuple of float
-            The Cartesian coordinates, :math:`(x',y',z')`, at which the surface
-            equation should be evaluated.
+            The Cartesian coordinates, :math:`(x',y',z')`, in [cm] at which the
+            surface equation should be evaluated.
 
         Returns
         -------
@@ -1106,13 +1106,13 @@ class Cylinder(QuadricMixin, Surface):
     Parameters
     ----------
     x0 : float, optional
-        x-coordinate for the origin of the Cylinder. Defaults to 0
+        x-coordinate for the origin of the Cylinder in [cm]. Defaults to 0
     y0 : float, optional
-        y-coordinate for the origin of the Cylinder. Defaults to 0
+        y-coordinate for the origin of the Cylinder in [cm]. Defaults to 0
     z0 : float, optional
-        z-coordinate for the origin of the Cylinder. Defaults to 0
+        z-coordinate for the origin of the Cylinder in [cm]. Defaults to 0
     r : float, optional
-        Radius of the cylinder. Defaults to 1.
+        Radius of the cylinder in [cm]. Defaults to 1.
     dx : float, optional
         x-component of the vector representing the axis of the cylinder.
         Defaults to 0.
@@ -1136,13 +1136,13 @@ class Cylinder(QuadricMixin, Surface):
     Attributes
     ----------
     x0 : float
-        x-coordinate for the origin of the Cylinder
+        x-coordinate for the origin of the Cylinder in [cm]
     y0 : float
-        y-coordinate for the origin of the Cylinder
+        y-coordinate for the origin of the Cylinder in [cm]
     z0 : float
-        z-coordinate for the origin of the Cylinder
+        z-coordinate for the origin of the Cylinder in [cm]
     r : float
-        Radius of the cylinder
+        Radius of the cylinder in [cm]
     dx : float
         x-component of the vector representing the axis of the cylinder
     dy : float
@@ -1243,7 +1243,7 @@ class Cylinder(QuadricMixin, Surface):
         p1, p2 : 3-tuples
             Points that pass through the plane, p1 will be used as (x0, y0, z0)
         r : float, optional
-            Radius of the cylinder. Defaults to 1.
+            Radius of the cylinder in [cm]. Defaults to 1.
         kwargs : dict
             Keyword arguments passed to the :class:`Cylinder` constructor
 
@@ -1288,11 +1288,11 @@ class XCylinder(QuadricMixin, Surface):
     Parameters
     ----------
     y0 : float, optional
-        y-coordinate for the origin of the Cylinder. Defaults to 0
+        y-coordinate for the origin of the Cylinder in [cm]. Defaults to 0
     z0 : float, optional
-        z-coordinate for the origin of the Cylinder. Defaults to 0
+        z-coordinate for the origin of the Cylinder in [cm]. Defaults to 0
     r : float, optional
-        Radius of the cylinder. Defaults to 1.
+        Radius of the cylinder in [cm]. Defaults to 1.
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional
         Boundary condition that defines the behavior for particles hitting the
         surface. Defaults to transmissive boundary condition where particles
@@ -1307,11 +1307,11 @@ class XCylinder(QuadricMixin, Surface):
     Attributes
     ----------
     y0 : float
-        y-coordinate for the origin of the Cylinder
+        y-coordinate for the origin of the Cylinder in [cm]
     z0 : float
-        z-coordinate for the origin of the Cylinder
+        z-coordinate for the origin of the Cylinder in [cm]
     r : float
-        Radius of the cylinder
+        Radius of the cylinder in [cm]
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
         Boundary condition that defines the behavior for particles hitting the
         surface.
@@ -1379,11 +1379,11 @@ class YCylinder(QuadricMixin, Surface):
     Parameters
     ----------
     x0 : float, optional
-        x-coordinate for the origin of the Cylinder. Defaults to 0
+        x-coordinate for the origin of the Cylinder in [cm]. Defaults to 0
     z0 : float, optional
-        z-coordinate for the origin of the Cylinder. Defaults to 0
+        z-coordinate for the origin of the Cylinder in [cm]. Defaults to 0
     r : float, optional
-        Radius of the cylinder. Defaults to 1.
+        Radius of the cylinder in [cm]. Defaults to 1.
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional
         Boundary condition that defines the behavior for particles hitting the
         surface. Defaults to transmissive boundary condition where particles
@@ -1398,11 +1398,11 @@ class YCylinder(QuadricMixin, Surface):
     Attributes
     ----------
     x0 : float
-        x-coordinate for the origin of the Cylinder
+        x-coordinate for the origin of the Cylinder in [cm]
     z0 : float
-        z-coordinate for the origin of the Cylinder
+        z-coordinate for the origin of the Cylinder in [cm]
     r : float
-        Radius of the cylinder
+        Radius of the cylinder in [cm]
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
         Boundary condition that defines the behavior for particles hitting the
         surface.
@@ -1470,11 +1470,11 @@ class ZCylinder(QuadricMixin, Surface):
     Parameters
     ----------
     x0 : float, optional
-        x-coordinate for the origin of the Cylinder. Defaults to 0
+        x-coordinate for the origin of the Cylinder in [cm]. Defaults to 0
     y0 : float, optional
-        y-coordinate for the origin of the Cylinder. Defaults to 0
+        y-coordinate for the origin of the Cylinder in [cm]. Defaults to 0
     r : float, optional
-        Radius of the cylinder. Defaults to 1.
+        Radius of the cylinder in [cm]. Defaults to 1.
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional
         Boundary condition that defines the behavior for particles hitting the
         surface. Defaults to transmissive boundary condition where particles
@@ -1489,11 +1489,11 @@ class ZCylinder(QuadricMixin, Surface):
     Attributes
     ----------
     x0 : float
-        x-coordinate for the origin of the Cylinder
+        x-coordinate for the origin of the Cylinder in [cm]
     y0 : float
-        y-coordinate for the origin of the Cylinder
+        y-coordinate for the origin of the Cylinder in [cm]
     r : float
-        Radius of the cylinder
+        Radius of the cylinder in [cm]
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
         Boundary condition that defines the behavior for particles hitting the
         surface.
@@ -1560,13 +1560,13 @@ class Sphere(QuadricMixin, Surface):
     Parameters
     ----------
     x0 : float, optional
-        x-coordinate of the center of the sphere. Defaults to 0.
+        x-coordinate of the center of the sphere in [cm]. Defaults to 0.
     y0 : float, optional
-        y-coordinate of the center of the sphere. Defaults to 0.
+        y-coordinate of the center of the sphere in [cm]. Defaults to 0.
     z0 : float, optional
-        z-coordinate of the center of the sphere. Defaults to 0.
+        z-coordinate of the center of the sphere in [cm]. Defaults to 0.
     r : float, optional
-        Radius of the sphere. Defaults to 1.
+        Radius of the sphere in [cm]. Defaults to 1.
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional
         Boundary condition that defines the behavior for particles hitting the
         surface. Defaults to transmissive boundary condition where particles
@@ -1580,13 +1580,13 @@ class Sphere(QuadricMixin, Surface):
     Attributes
     ----------
     x0 : float
-        x-coordinate of the center of the sphere
+        x-coordinate of the center of the sphere in [cm]
     y0 : float
-        y-coordinate of the center of the sphere
+        y-coordinate of the center of the sphere in [cm]
     z0 : float
-        z-coordinate of the center of the sphere
+        z-coordinate of the center of the sphere in [cm]
     r : float
-        Radius of the sphere
+        Radius of the sphere in [cm]
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
         Boundary condition that defines the behavior for particles hitting the
         surface.
@@ -1653,11 +1653,11 @@ class Cone(QuadricMixin, Surface):
     Parameters
     ----------
     x0 : float, optional
-        x-coordinate of the apex. Defaults to 0.
+        x-coordinate of the apex in [cm]. Defaults to 0.
     y0 : float, optional
-        y-coordinate of the apex. Defaults to 0.
+        y-coordinate of the apex in [cm]. Defaults to 0.
     z0 : float, optional
-        z-coordinate of the apex. Defaults to 0.
+        z-coordinate of the apex in [cm]. Defaults to 0.
     r2 : float, optional
         Parameter related to the aperature. Defaults to 1.
     dx : float, optional
@@ -1682,11 +1682,11 @@ class Cone(QuadricMixin, Surface):
     Attributes
     ----------
     x0 : float
-        x-coordinate of the apex
+        x-coordinate of the apex in [cm]
     y0 : float
-        y-coordinate of the apex
+        y-coordinate of the apex in [cm]
     z0 : float
-        z-coordinate of the apex
+        z-coordinate of the apex in [cm]
     r2 : float
         Parameter related to the aperature
     dx : float
@@ -1796,11 +1796,11 @@ class XCone(QuadricMixin, Surface):
     Parameters
     ----------
     x0 : float, optional
-        x-coordinate of the apex. Defaults to 0.
+        x-coordinate of the apex in [cm]. Defaults to 0.
     y0 : float, optional
-        y-coordinate of the apex. Defaults to 0.
+        y-coordinate of the apex in [cm]. Defaults to 0.
     z0 : float, optional
-        z-coordinate of the apex. Defaults to 0.
+        z-coordinate of the apex in [cm]. Defaults to 0.
     r2 : float, optional
         Parameter related to the aperature. Defaults to 1.
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional
@@ -1816,11 +1816,11 @@ class XCone(QuadricMixin, Surface):
     Attributes
     ----------
     x0 : float
-        x-coordinate of the apex
+        x-coordinate of the apex in [cm]
     y0 : float
-        y-coordinate of the apex
+        y-coordinate of the apex in [cm]
     z0 : float
-        z-coordinate of the apex
+        z-coordinate of the apex in [cm]
     r2 : float
         Parameter related to the aperature
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
@@ -1885,11 +1885,11 @@ class YCone(QuadricMixin, Surface):
     Parameters
     ----------
     x0 : float, optional
-        x-coordinate of the apex. Defaults to 0.
+        x-coordinate of the apex in [cm]. Defaults to 0.
     y0 : float, optional
-        y-coordinate of the apex. Defaults to 0.
+        y-coordinate of the apex in [cm]. Defaults to 0.
     z0 : float, optional
-        z-coordinate of the apex. Defaults to 0.
+        z-coordinate of the apex in [cm]. Defaults to 0.
     r2 : float, optional
         Parameter related to the aperature. Defaults to 1.
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional
@@ -1905,11 +1905,11 @@ class YCone(QuadricMixin, Surface):
     Attributes
     ----------
     x0 : float
-        x-coordinate of the apex
+        x-coordinate of the apex in [cm]
     y0 : float
-        y-coordinate of the apex
+        y-coordinate of the apex in [cm]
     z0 : float
-        z-coordinate of the apex
+        z-coordinate of the apex in [cm]
     r2 : float
         Parameter related to the aperature
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
@@ -1974,11 +1974,11 @@ class ZCone(QuadricMixin, Surface):
     Parameters
     ----------
     x0 : float, optional
-        x-coordinate of the apex. Defaults to 0.
+        x-coordinate of the apex in [cm]. Defaults to 0.
     y0 : float, optional
-        y-coordinate of the apex. Defaults to 0.
+        y-coordinate of the apex in [cm]. Defaults to 0.
     z0 : float, optional
-        z-coordinate of the apex. Defaults to 0.
+        z-coordinate of the apex in [cm]. Defaults to 0.
     r2 : float, optional
         Parameter related to the aperature. Defaults to 1.
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}, optional
@@ -1994,11 +1994,11 @@ class ZCone(QuadricMixin, Surface):
     Attributes
     ----------
     x0 : float
-        x-coordinate of the apex
+        x-coordinate of the apex in [cm]
     y0 : float
-        y-coordinate of the apex
+        y-coordinate of the apex in [cm]
     z0 : float
-        z-coordinate of the apex
+        z-coordinate of the apex in [cm]
     r2 : float
         Parameter related to the aperature
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
@@ -2198,34 +2198,34 @@ class XTorus(TorusMixin, Surface):
     Parameters
     ----------
     x0 : float
-        x-coordinate of the center of the axis of revolution
+        x-coordinate of the center of the axis of revolution in [cm]
     y0 : float
-        y-coordinate of the center of the axis of revolution
+        y-coordinate of the center of the axis of revolution in [cm]
     z0 : float
-        z-coordinate of the center of the axis of revolution
+        z-coordinate of the center of the axis of revolution in [cm]
     a : float
-        Major radius of the torus
+        Major radius of the torus in [cm]
     b : float
-        Minor radius of the torus (parallel to axis of revolution)
+        Minor radius of the torus in [cm] (parallel to axis of revolution)
     c : float
-        Minor radius of the torus (perpendicular to axis of revolution)
+        Minor radius of the torus in [cm] (perpendicular to axis of revolution)
     kwargs : dict
         Keyword arguments passed to the :class:`Surface` constructor
 
     Attributes
     ----------
     x0 : float
-        x-coordinate of the center of the axis of revolution
+        x-coordinate of the center of the axis of revolution in [cm]
     y0 : float
-        y-coordinate of the center of the axis of revolution
+        y-coordinate of the center of the axis of revolution in [cm]
     z0 : float
-        z-coordinate of the center of the axis of revolution
+        z-coordinate of the center of the axis of revolution in [cm]
     a : float
-        Major radius of the torus
+        Major radius of the torus in [cm]
     b : float
-        Minor radius of the torus (parallel to axis of revolution)
+        Minor radius of the torus in [cm] (parallel to axis of revolution)
     c : float
-        Minor radius of the torus (perpendicular to axis of revolution)
+        Minor radius of the torus in [cm] (perpendicular to axis of revolution)
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
         Boundary condition that defines the behavior for particles hitting the
         surface.
@@ -2269,32 +2269,32 @@ class YTorus(TorusMixin, Surface):
     Parameters
     ----------
     x0 : float
-        x-coordinate of the center of the axis of revolution
+        x-coordinate of the center of the axis of revolution in [cm]
     y0 : float
-        y-coordinate of the center of the axis of revolution
+        y-coordinate of the center of the axis of revolution in [cm]
     z0 : float
-        z-coordinate of the center of the axis of revolution
+        z-coordinate of the center of the axis of revolution in [cm]
     a : float
-        Major radius of the torus
+        Major radius of the torus in [cm]
     b : float
-        Minor radius of the torus (parallel to axis of revolution)
+        Minor radius of the torus in [cm] (parallel to axis of revolution)
     c : float
-        Minor radius of the torus (perpendicular to axis of revolution)
+        Minor radius of the torus in [cm] (perpendicular to axis of revolution)
     kwargs : dict
         Keyword arguments passed to the :class:`Surface` constructor
 
     Attributes
     ----------
     x0 : float
-        x-coordinate of the center of the axis of revolution
+        x-coordinate of the center of the axis of revolution in [cm]
     y0 : float
-        y-coordinate of the center of the axis of revolution
+        y-coordinate of the center of the axis of revolution in [cm]
     z0 : float
-        z-coordinate of the center of the axis of revolution
+        z-coordinate of the center of the axis of revolution in [cm]
     a : float
-        Major radius of the torus
+        Major radius of the torus in [cm]
     b : float
-        Minor radius of the torus (parallel to axis of revolution)
+        Minor radius of the torus in [cm] (parallel to axis of revolution)
     c : float
         Minor radius of the torus (perpendicular to axis of revolution)
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
@@ -2340,34 +2340,34 @@ class ZTorus(TorusMixin, Surface):
     Parameters
     ----------
     x0 : float
-        x-coordinate of the center of the axis of revolution
+        x-coordinate of the center of the axis of revolution in [cm]
     y0 : float
-        y-coordinate of the center of the axis of revolution
+        y-coordinate of the center of the axis of revolution in [cm]
     z0 : float
-        z-coordinate of the center of the axis of revolution
+        z-coordinate of the center of the axis of revolution in [cm]
     a : float
-        Major radius of the torus
+        Major radius of the torus in [cm]
     b : float
-        Minor radius of the torus (parallel to axis of revolution)
+        Minor radius of the torus in [cm] (parallel to axis of revolution)
     c : float
-        Minor radius of the torus (perpendicular to axis of revolution)
+        Minor radius of the torus in [cm] (perpendicular to axis of revolution)
     kwargs : dict
         Keyword arguments passed to the :class:`Surface` constructor
 
     Attributes
     ----------
     x0 : float
-        x-coordinate of the center of the axis of revolution
+        x-coordinate of the center of the axis of revolution in [cm]
     y0 : float
-        y-coordinate of the center of the axis of revolution
+        y-coordinate of the center of the axis of revolution in [cm]
     z0 : float
-        z-coordinate of the center of the axis of revolution
+        z-coordinate of the center of the axis of revolution in [cm]
     a : float
-        Major radius of the torus
+        Major radius of the torus in [cm]
     b : float
-        Minor radius of the torus (parallel to axis of revolution)
+        Minor radius of the torus in [cm] (parallel to axis of revolution)
     c : float
-        Minor radius of the torus (perpendicular to axis of revolution)
+        Minor radius of the torus in [cm] (perpendicular to axis of revolution)
     boundary_type : {'transmission, 'vacuum', 'reflective', 'white'}
         Boundary condition that defines the behavior for particles hitting the
         surface.


Test patch:
diff --git a/tests/unit_tests/test_cell.py b/tests/unit_tests/test_cell.py
index 9234e7e4b..faecf0b63 100644
--- a/tests/unit_tests/test_cell.py
+++ b/tests/unit_tests/test_cell.py
@@ -297,3 +297,20 @@ def test_to_xml_element(cell_with_lattice):
     elem = c.create_xml_subelement(root)
     assert elem.get('region') == str(c.region)
     assert elem.get('temperature') == str(c.temperature)
+
+
+@pytest.mark.parametrize("rotation", [
+    (90, 45, 0),
+    [[1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, -1.0, 0.0]]
+])
+def test_rotation_from_xml(rotation):
+    # Make sure rotation attribute (matrix) round trips through XML correctly
+    s = openmc.ZCylinder(r=10.0)
+    cell = openmc.Cell(region=-s)
+    cell.rotation = rotation
+    root = ET.Element('geometry')
+    elem = cell.create_xml_subelement(root)
+    new_cell = openmc.Cell.from_xml_element(
+        elem, {s.id: s}, {'void': None}, openmc.Universe
+    )
+    np.testing.assert_allclose(new_cell.rotation, cell.rotation)
diff --git a/tests/unit_tests/test_plots.py b/tests/unit_tests/test_plots.py
index 718f098ba..2db396cd5 100644
--- a/tests/unit_tests/test_plots.py
+++ b/tests/unit_tests/test_plots.py
@@ -92,6 +92,8 @@ def test_xml_element(myplot):
 def test_plots(run_in_tmpdir):
     p1 = openmc.Plot(name='plot1')
     p1.origin = (5., 5., 5.)
+    p1.colors = {10: (255, 100, 0)}
+    p1.mask_components = [2, 4, 6]
     p2 = openmc.Plot(name='plot2')
     p2.origin = (-3., -3., -3.)
     plots = openmc.Plots([p1, p2])
@@ -107,4 +109,6 @@ def test_plots(run_in_tmpdir):
     new_plots = openmc.Plots.from_xml()
     assert len(plots)
     assert plots[0].origin == p1.origin
+    assert plots[0].colors == p1.colors
+    assert plots[0].mask_components == p1.mask_components
     assert plots[1].origin == p2.origin