mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-21 06:25:30 -04:00
Introduce SlicePlot and VoxelPlot to replace the Plot class (#3528)
Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
This commit is contained in:
parent
f28139250a
commit
9b675adda5
16 changed files with 618 additions and 112 deletions
|
|
@ -176,7 +176,8 @@ Geometry Plotting
|
|||
:nosignatures:
|
||||
:template: myclass.rst
|
||||
|
||||
openmc.Plot
|
||||
openmc.SlicePlot
|
||||
openmc.VoxelPlot
|
||||
openmc.WireframeRayTracePlot
|
||||
openmc.SolidRayTracePlot
|
||||
openmc.Plots
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@ Geometry Visualization
|
|||
|
||||
.. currentmodule:: openmc
|
||||
|
||||
OpenMC is capable of producing two-dimensional slice plots of a geometry as well
|
||||
as three-dimensional voxel plots using the geometry plotting :ref:`run mode
|
||||
<usersguide_run_modes>`. The geometry plotting mode relies on the presence of a
|
||||
:ref:`plots.xml <io_plots>` file that indicates what plots should be created. To
|
||||
create this file, one needs to create one or more :class:`openmc.Plot`
|
||||
instances, add them to a :class:`openmc.Plots` collection, and then use the
|
||||
:class:`Plots.export_to_xml` method to write the ``plots.xml`` file.
|
||||
OpenMC is capable of producing two-dimensional slice plots of a geometry,
|
||||
three-dimensional voxel plots, and three-dimensional raytrace plots using the
|
||||
geometry plotting :ref:`run mode <usersguide_run_modes>`. The geometry plotting
|
||||
mode relies on the presence of a :ref:`plots.xml <io_plots>` file that indicates
|
||||
what plots should be created. To create this file, one needs to create one or
|
||||
more instances of the various plot classes described below, add them to a
|
||||
:class:`openmc.Plots` collection, and then use the :class:`Plots.export_to_xml`
|
||||
method to write the ``plots.xml`` file.
|
||||
|
||||
-----------
|
||||
Slice Plots
|
||||
|
|
@ -21,15 +22,14 @@ Slice Plots
|
|||
.. image:: ../_images/atr.png
|
||||
:width: 300px
|
||||
|
||||
By default, when an instance of :class:`openmc.Plot` is created, it indicates
|
||||
that a 2D slice plot should be made. You can specify the origin of the plot
|
||||
(:attr:`Plot.origin`), the width of the plot in each direction
|
||||
(:attr:`Plot.width`), the number of pixels to use in each direction
|
||||
(:attr:`Plot.pixels`), and the basis directions for the plot. For example, to
|
||||
create a :math:`x` - :math:`z` plot centered at (5.0, 2.0, 3.0) with a width of
|
||||
(50., 50.) and 400x400 pixels::
|
||||
The :class:`openmc.SlicePlot` class indicates that a 2D slice plot should be
|
||||
made. You can specify the origin of the plot (:attr:`SlicePlot.origin`), the
|
||||
width of the plot in each direction (:attr:`SlicePlot.width`), the number of
|
||||
pixels to use in each direction (:attr:`SlicePlot.pixels`), and the basis
|
||||
directions for the plot. For example, to create a :math:`x` - :math:`z` plot
|
||||
centered at (5.0, 2.0, 3.0) with a width of (50., 50.) and 400x400 pixels::
|
||||
|
||||
plot = openmc.Plot()
|
||||
plot = openmc.SlicePlot()
|
||||
plot.basis = 'xz'
|
||||
plot.origin = (5.0, 2.0, 3.0)
|
||||
plot.width = (50., 50.)
|
||||
|
|
@ -47,7 +47,7 @@ that location.
|
|||
|
||||
By default, a unique color will be assigned to each cell in the geometry. If you
|
||||
want your plot to be colored by material instead, change the
|
||||
:attr:`Plot.color_by` attribute::
|
||||
:attr:`SlicePlot.color_by` attribute::
|
||||
|
||||
plot.color_by = 'material'
|
||||
|
||||
|
|
@ -68,8 +68,8 @@ particular cells/materials should be given colors of your choosing::
|
|||
Note that colors can be given as RGB tuples or by a string indicating a valid
|
||||
`SVG color <https://www.w3.org/TR/SVG11/types.html#ColorKeywords>`_.
|
||||
|
||||
When you're done creating your :class:`openmc.Plot` instances, you need to then
|
||||
assign them to a :class:`openmc.Plots` collection and export it to XML::
|
||||
When you're done creating your :class:`openmc.SlicePlot` instances, you need to
|
||||
then assign them to a :class:`openmc.Plots` collection and export it to XML::
|
||||
|
||||
plots = openmc.Plots([plot1, plot2, plot3])
|
||||
plots.export_to_xml()
|
||||
|
|
@ -97,13 +97,11 @@ Voxel Plots
|
|||
.. image:: ../_images/3dba.png
|
||||
:width: 200px
|
||||
|
||||
The :class:`openmc.Plot` class can also be told to generate a 3D voxel plot
|
||||
instead of a 2D slice plot. Simply change the :attr:`Plot.type` attribute to
|
||||
'voxel'. In this case, the :attr:`Plot.width` and :attr:`Plot.pixels` attributes
|
||||
should be three items long, e.g.::
|
||||
The :class:`openmc.VoxelPlot` class enables the generation of a 3D voxel plot
|
||||
instead of a 2D slice plot. In this case, the :attr:`VoxelPlot.width` and
|
||||
:attr:`VoxelPlot.pixels` attributes should be three items long, e.g.::
|
||||
|
||||
vox_plot = openmc.Plot()
|
||||
vox_plot.type = 'voxel'
|
||||
vox_plot = openmc.VoxelPlot()
|
||||
vox_plot.width = (100., 100., 50.)
|
||||
vox_plot.pixels = (400, 400, 200)
|
||||
|
||||
|
|
|
|||
|
|
@ -1131,11 +1131,10 @@ given below:
|
|||
tallies.export_to_xml()
|
||||
|
||||
# Create voxel plot
|
||||
plot = openmc.Plot()
|
||||
plot = openmc.VoxelPlot()
|
||||
plot.origin = [0, 0, 0]
|
||||
plot.width = [2*pitch, 2*pitch, 1]
|
||||
plot.pixels = [1000, 1000, 1]
|
||||
plot.type = 'voxel'
|
||||
|
||||
# Instantiate a Plots collection and export to XML
|
||||
plots = openmc.Plots([plot])
|
||||
|
|
@ -1215,11 +1214,10 @@ given below:
|
|||
tallies.export_to_xml()
|
||||
|
||||
# Create voxel plot
|
||||
plot = openmc.Plot()
|
||||
plot = openmc.VoxelPlot()
|
||||
plot.origin = [0, 0, 0]
|
||||
plot.width = [2*pitch, 2*pitch, 1]
|
||||
plot.pixels = [1000, 1000, 1]
|
||||
plot.type = 'voxel'
|
||||
|
||||
# Instantiate a Plots collection and export to XML
|
||||
plots = openmc.Plots([plot])
|
||||
|
|
|
|||
|
|
@ -128,14 +128,14 @@ settings_file.export_to_xml()
|
|||
# Exporting to OpenMC plots.xml file
|
||||
###############################################################################
|
||||
|
||||
plot_xy = openmc.Plot(plot_id=1)
|
||||
plot_xy = openmc.SlicePlot(plot_id=1)
|
||||
plot_xy.filename = 'plot_xy'
|
||||
plot_xy.origin = [0, 0, 0]
|
||||
plot_xy.width = [6, 6]
|
||||
plot_xy.pixels = [400, 400]
|
||||
plot_xy.color_by = 'material'
|
||||
|
||||
plot_yz = openmc.Plot(plot_id=2)
|
||||
plot_yz = openmc.SlicePlot(plot_id=2)
|
||||
plot_yz.filename = 'plot_yz'
|
||||
plot_yz.basis = 'yz'
|
||||
plot_yz.origin = [0, 0, 0]
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ settings_file.export_to_xml()
|
|||
# Exporting to OpenMC plots.xml file
|
||||
###############################################################################
|
||||
|
||||
plot = openmc.Plot(plot_id=1)
|
||||
plot = openmc.SlicePlot(plot_id=1)
|
||||
plot.origin = [0, 0, 0]
|
||||
plot.width = [4, 4]
|
||||
plot.pixels = [400, 400]
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ settings_file.export_to_xml()
|
|||
# Exporting to OpenMC plots.xml file
|
||||
###############################################################################
|
||||
|
||||
plot = openmc.Plot(plot_id=1)
|
||||
plot = openmc.SlicePlot(plot_id=1)
|
||||
plot.origin = [0, 0, 0]
|
||||
plot.width = [4, 4]
|
||||
plot.pixels = [400, 400]
|
||||
|
|
|
|||
|
|
@ -192,11 +192,10 @@ tallies.export_to_xml()
|
|||
# Exporting to OpenMC plots.xml file
|
||||
###############################################################################
|
||||
|
||||
plot = openmc.Plot()
|
||||
plot = openmc.VoxelPlot()
|
||||
plot.origin = [0, 0, 0]
|
||||
plot.width = [pitch, pitch, pitch]
|
||||
plot.pixels = [1000, 1000, 1]
|
||||
plot.type = 'voxel'
|
||||
|
||||
# Instantiate a Plots collection and export to XML
|
||||
plots = openmc.Plots([plot])
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ def pwr_pin_cell() -> openmc.Model:
|
|||
constraints={'fissionable': True}
|
||||
)
|
||||
|
||||
plot = openmc.Plot.from_geometry(model.geometry)
|
||||
plot = openmc.SlicePlot.from_geometry(model.geometry)
|
||||
plot.pixels = (300, 300)
|
||||
plot.color_by = 'material'
|
||||
model.plots.append(plot)
|
||||
|
|
@ -429,7 +429,7 @@ def pwr_core() -> openmc.Model:
|
|||
model.settings.source = openmc.IndependentSource(space=openmc.stats.Box(
|
||||
[-160, -160, -183], [160, 160, 183]))
|
||||
|
||||
plot = openmc.Plot()
|
||||
plot = openmc.SlicePlot()
|
||||
plot.origin = (125, 125, 0)
|
||||
plot.width = (250, 250)
|
||||
plot.pixels = (3000, 3000)
|
||||
|
|
@ -544,7 +544,7 @@ def pwr_assembly() -> openmc.Model:
|
|||
constraints={'fissionable': True}
|
||||
)
|
||||
|
||||
plot = openmc.Plot()
|
||||
plot = openmc.SlicePlot()
|
||||
plot.origin = (0.0, 0.0, 0)
|
||||
plot.width = (21.42, 21.42)
|
||||
plot.pixels = (300, 300)
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ def plot_inline(plots, openmc_exec='openmc', cwd='.', path_input=None):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
plots : Iterable of openmc.Plot
|
||||
plots : Iterable of openmc.PlotBase
|
||||
Plots to display
|
||||
openmc_exec : str
|
||||
Path to OpenMC executable
|
||||
|
|
|
|||
|
|
@ -1170,7 +1170,7 @@ class Model:
|
|||
self.settings.plot_seed = seed
|
||||
|
||||
# Create plot object matching passed arguments
|
||||
plot = openmc.Plot()
|
||||
plot = openmc.SlicePlot()
|
||||
plot.origin = origin
|
||||
plot.width = width
|
||||
plot.pixels = pixels
|
||||
|
|
|
|||
324
openmc/plots.py
324
openmc/plots.py
|
|
@ -2,6 +2,7 @@ from collections.abc import Iterable, Mapping
|
|||
from numbers import Integral, Real
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
import warnings
|
||||
|
||||
import h5py
|
||||
import lxml.etree as ET
|
||||
|
|
@ -626,14 +627,15 @@ class PlotBase(IDManagerMixin):
|
|||
return element
|
||||
|
||||
|
||||
class Plot(PlotBase):
|
||||
"""Definition of a finite region of space to be plotted.
|
||||
class SlicePlot(PlotBase):
|
||||
"""Definition of a 2D slice plot of the geometry.
|
||||
|
||||
OpenMC is capable of generating two-dimensional slice plots, or
|
||||
three-dimensional voxel or projection plots. Colors that are used in plots can be given as
|
||||
RGB tuples, e.g. (255, 255, 255) would be white, or by a string indicating a
|
||||
Colors that are used in plots can be given as RGB tuples, e.g.
|
||||
(255, 255, 255) would be white, or by a string indicating a
|
||||
valid `SVG color <https://www.w3.org/TR/SVG11/types.html#ColorKeywords>`_.
|
||||
|
||||
.. versionadded:: 0.15.4
|
||||
|
||||
Parameters
|
||||
----------
|
||||
plot_id : int
|
||||
|
|
@ -648,7 +650,7 @@ class Plot(PlotBase):
|
|||
name : str
|
||||
Name of the plot
|
||||
pixels : Iterable of int
|
||||
Number of pixels to use in each direction
|
||||
Number of pixels to use in each direction (2 values)
|
||||
filename : str
|
||||
Path to write the plot to
|
||||
color_by : {'cell', 'material'}
|
||||
|
|
@ -671,11 +673,9 @@ class Plot(PlotBase):
|
|||
level : int
|
||||
Universe depth to plot at
|
||||
width : Iterable of float
|
||||
Width of the plot in each basis direction
|
||||
Width of the plot in each basis direction (2 values)
|
||||
origin : tuple or list of ndarray
|
||||
Origin (center) of the plot
|
||||
type : {'slice', 'voxel'}
|
||||
The type of the plot
|
||||
Origin (center) of the plot (3 values)
|
||||
basis : {'xy', 'xz', 'yz'}
|
||||
The basis directions for the plot
|
||||
meshlines : dict
|
||||
|
|
@ -688,10 +688,37 @@ class Plot(PlotBase):
|
|||
super().__init__(plot_id, name)
|
||||
self._width = [4.0, 4.0]
|
||||
self._origin = [0., 0., 0.]
|
||||
self._type = 'slice'
|
||||
self._basis = 'xy'
|
||||
self._meshlines = None
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
warnings.warn(
|
||||
"The 'type' attribute is deprecated and will be removed in a future version. "
|
||||
"This is a SlicePlot instance.",
|
||||
FutureWarning, stacklevel=2
|
||||
)
|
||||
return 'slice'
|
||||
|
||||
@type.setter
|
||||
def type(self, value):
|
||||
raise TypeError(
|
||||
"Setting plot.type is no longer supported. "
|
||||
"Use openmc.SlicePlot() for 2D slice plots or openmc.VoxelPlot() for 3D voxel plots."
|
||||
)
|
||||
|
||||
@property
|
||||
def pixels(self):
|
||||
return self._pixels
|
||||
|
||||
@pixels.setter
|
||||
def pixels(self, pixels):
|
||||
cv.check_type('plot pixels', pixels, Iterable, Integral)
|
||||
cv.check_length('plot pixels', pixels, 2, 2)
|
||||
for dim in pixels:
|
||||
cv.check_greater_than('plot pixels', dim, 0)
|
||||
self._pixels = pixels
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width
|
||||
|
|
@ -699,7 +726,7 @@ class Plot(PlotBase):
|
|||
@width.setter
|
||||
def width(self, width):
|
||||
cv.check_type('plot width', width, Iterable, Real)
|
||||
cv.check_length('plot width', width, 2, 3)
|
||||
cv.check_length('plot width', width, 2, 2)
|
||||
self._width = width
|
||||
|
||||
@property
|
||||
|
|
@ -712,15 +739,6 @@ class Plot(PlotBase):
|
|||
cv.check_length('plot origin', origin, 3)
|
||||
self._origin = origin
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return self._type
|
||||
|
||||
@type.setter
|
||||
def type(self, plottype):
|
||||
cv.check_value('plot type', plottype, ['slice', 'voxel'])
|
||||
self._type = plottype
|
||||
|
||||
@property
|
||||
def basis(self):
|
||||
return self._basis
|
||||
|
|
@ -763,11 +781,10 @@ class Plot(PlotBase):
|
|||
self._meshlines = meshlines
|
||||
|
||||
def __repr__(self):
|
||||
string = 'Plot\n'
|
||||
string = 'SlicePlot\n'
|
||||
string += '{: <16}=\t{}\n'.format('\tID', self._id)
|
||||
string += '{: <16}=\t{}\n'.format('\tName', self._name)
|
||||
string += '{: <16}=\t{}\n'.format('\tFilename', self._filename)
|
||||
string += '{: <16}=\t{}\n'.format('\tType', self._type)
|
||||
string += '{: <16}=\t{}\n'.format('\tBasis', self._basis)
|
||||
string += '{: <16}=\t{}\n'.format('\tWidth', self._width)
|
||||
string += '{: <16}=\t{}\n'.format('\tOrigin', self._origin)
|
||||
|
|
@ -883,7 +900,7 @@ class Plot(PlotBase):
|
|||
self._colors[domain] = (r, g, b)
|
||||
|
||||
def to_xml_element(self):
|
||||
"""Return XML representation of the slice/voxel plot
|
||||
"""Return XML representation of the slice plot
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
|
@ -893,10 +910,8 @@ class Plot(PlotBase):
|
|||
"""
|
||||
|
||||
element = super().to_xml_element()
|
||||
element.set("type", self._type)
|
||||
|
||||
if self._type == 'slice':
|
||||
element.set("basis", self._basis)
|
||||
element.set("type", "slice")
|
||||
element.set("basis", self._basis)
|
||||
|
||||
subelement = ET.SubElement(element, "origin")
|
||||
subelement.text = ' '.join(map(str, self._origin))
|
||||
|
|
@ -942,8 +957,8 @@ class Plot(PlotBase):
|
|||
|
||||
Returns
|
||||
-------
|
||||
openmc.Plot
|
||||
Plot object
|
||||
openmc.SlicePlot
|
||||
SlicePlot object
|
||||
|
||||
"""
|
||||
plot_id = int(get_text(elem, "id"))
|
||||
|
|
@ -952,9 +967,7 @@ class Plot(PlotBase):
|
|||
if "filename" in elem.keys():
|
||||
plot.filename = get_text(elem, "filename")
|
||||
plot.color_by = get_text(elem, "color_by")
|
||||
plot.type = get_text(elem, "type")
|
||||
if plot.type == 'slice':
|
||||
plot.basis = get_text(elem, "basis")
|
||||
plot.basis = get_text(elem, "basis")
|
||||
|
||||
plot.origin = tuple(get_elem_list(elem, "origin", float))
|
||||
plot.width = tuple(get_elem_list(elem, "width", float))
|
||||
|
|
@ -1036,9 +1049,215 @@ class Plot(PlotBase):
|
|||
# Return produced image
|
||||
return _get_plot_image(self, cwd)
|
||||
|
||||
|
||||
|
||||
class VoxelPlot(PlotBase):
|
||||
"""Definition of a 3D voxel plot of the geometry.
|
||||
|
||||
Colors that are used in plots can be given as RGB tuples, e.g.
|
||||
(255, 255, 255) would be white, or by a string indicating a
|
||||
valid `SVG color <https://www.w3.org/TR/SVG11/types.html#ColorKeywords>`_.
|
||||
|
||||
.. versionadded:: 0.15.1
|
||||
|
||||
Parameters
|
||||
----------
|
||||
plot_id : int
|
||||
Unique identifier for the plot
|
||||
name : str
|
||||
Name of the plot
|
||||
|
||||
Attributes
|
||||
----------
|
||||
id : int
|
||||
Unique identifier
|
||||
name : str
|
||||
Name of the plot
|
||||
pixels : Iterable of int
|
||||
Number of pixels to use in each direction (3 values)
|
||||
filename : str
|
||||
Path to write the plot to
|
||||
color_by : {'cell', 'material'}
|
||||
Indicate whether the plot should be colored by cell or by material
|
||||
background : Iterable of int or str
|
||||
Color of the background
|
||||
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 listed in mask_components
|
||||
show_overlaps : bool
|
||||
Indicate whether or not overlapping regions are shown
|
||||
overlap_color : Iterable of int or str
|
||||
Color to apply to overlapping regions
|
||||
colors : dict
|
||||
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
|
||||
width : Iterable of float
|
||||
Width of the plot in each dimension (3 values)
|
||||
origin : tuple or list of ndarray
|
||||
Origin (center) of the plot (3 values)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, plot_id=None, name=''):
|
||||
super().__init__(plot_id, name)
|
||||
self._width = [4.0, 4.0, 4.0]
|
||||
self._origin = [0., 0., 0.]
|
||||
self._pixels = [400, 400, 400]
|
||||
|
||||
@property
|
||||
def pixels(self):
|
||||
return self._pixels
|
||||
|
||||
@pixels.setter
|
||||
def pixels(self, pixels):
|
||||
cv.check_type('plot pixels', pixels, Iterable, Integral)
|
||||
cv.check_length('plot pixels', pixels, 3, 3)
|
||||
for dim in pixels:
|
||||
cv.check_greater_than('plot pixels', dim, 0)
|
||||
self._pixels = pixels
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
@width.setter
|
||||
def width(self, width):
|
||||
cv.check_type('plot width', width, Iterable, Real)
|
||||
cv.check_length('plot width', width, 3, 3)
|
||||
self._width = width
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
return self._origin
|
||||
|
||||
@origin.setter
|
||||
def origin(self, origin):
|
||||
cv.check_type('plot origin', origin, Iterable, Real)
|
||||
cv.check_length('plot origin', origin, 3)
|
||||
self._origin = origin
|
||||
|
||||
def __repr__(self):
|
||||
string = 'VoxelPlot\n'
|
||||
string += '{: <16}=\t{}\n'.format('\tID', self._id)
|
||||
string += '{: <16}=\t{}\n'.format('\tName', self._name)
|
||||
string += '{: <16}=\t{}\n'.format('\tFilename', self._filename)
|
||||
string += '{: <16}=\t{}\n'.format('\tWidth', self._width)
|
||||
string += '{: <16}=\t{}\n'.format('\tOrigin', self._origin)
|
||||
string += '{: <16}=\t{}\n'.format('\tPixels', self._pixels)
|
||||
string += '{: <16}=\t{}\n'.format('\tColor by', self._color_by)
|
||||
string += '{: <16}=\t{}\n'.format('\tBackground', self._background)
|
||||
string += '{: <16}=\t{}\n'.format('\tMask components',
|
||||
self._mask_components)
|
||||
string += '{: <16}=\t{}\n'.format('\tMask background',
|
||||
self._mask_background)
|
||||
string += '{: <16}=\t{}\n'.format('\tOverlap Color',
|
||||
self._overlap_color)
|
||||
string += '{: <16}=\t{}\n'.format('\tColors', self._colors)
|
||||
string += '{: <16}=\t{}\n'.format('\tLevel', self._level)
|
||||
return string
|
||||
|
||||
def to_xml_element(self):
|
||||
"""Return XML representation of the voxel plot
|
||||
|
||||
Returns
|
||||
-------
|
||||
element : lxml.etree._Element
|
||||
XML element containing plot data
|
||||
|
||||
"""
|
||||
|
||||
element = super().to_xml_element()
|
||||
element.set("type", "voxel")
|
||||
|
||||
subelement = ET.SubElement(element, "origin")
|
||||
subelement.text = ' '.join(map(str, self._origin))
|
||||
|
||||
subelement = ET.SubElement(element, "width")
|
||||
subelement.text = ' '.join(map(str, self._width))
|
||||
|
||||
if self._colors:
|
||||
self._colors_to_xml(element)
|
||||
|
||||
if self._show_overlaps:
|
||||
subelement = ET.SubElement(element, "show_overlaps")
|
||||
subelement.text = "true"
|
||||
|
||||
if self._overlap_color is not None:
|
||||
color = self._overlap_color
|
||||
if isinstance(color, str):
|
||||
color = _SVG_COLORS[color.lower()]
|
||||
subelement = ET.SubElement(element, "overlap_color")
|
||||
subelement.text = ' '.join(str(x) for x in color)
|
||||
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
def from_xml_element(cls, elem):
|
||||
"""Generate plot object from an XML element
|
||||
|
||||
Parameters
|
||||
----------
|
||||
elem : lxml.etree._Element
|
||||
XML element
|
||||
|
||||
Returns
|
||||
-------
|
||||
openmc.VoxelPlot
|
||||
VoxelPlot object
|
||||
|
||||
"""
|
||||
plot_id = int(get_text(elem, "id"))
|
||||
name = get_text(elem, 'name', '')
|
||||
plot = cls(plot_id, name)
|
||||
if "filename" in elem.keys():
|
||||
plot.filename = get_text(elem, "filename")
|
||||
plot.color_by = get_text(elem, "color_by")
|
||||
|
||||
plot.origin = tuple(get_elem_list(elem, "origin", float))
|
||||
plot.width = tuple(get_elem_list(elem, "width", float))
|
||||
plot.pixels = tuple(get_elem_list(elem, "pixels"))
|
||||
background = get_elem_list(elem, "background")
|
||||
if background is not None:
|
||||
plot._background = tuple(background)
|
||||
|
||||
# Set plot colors
|
||||
colors = {}
|
||||
for color_elem in elem.findall("color"):
|
||||
uid = int(get_text(color_elem, "id"))
|
||||
colors[uid] = tuple(get_elem_list(color_elem, "rgb", int))
|
||||
plot.colors = colors
|
||||
|
||||
# Set masking information
|
||||
mask_elem = elem.find("mask")
|
||||
if mask_elem is not None:
|
||||
plot.mask_components = get_elem_list(mask_elem, "components", int)
|
||||
background = get_elem_list(mask_elem, "background", int)
|
||||
if background is not None:
|
||||
plot.mask_background = tuple(background)
|
||||
|
||||
# show overlaps
|
||||
overlap = get_text(elem, "show_overlaps")
|
||||
if overlap is not None:
|
||||
plot.show_overlaps = (overlap in ('true', '1'))
|
||||
overlap_color = get_elem_list(elem, "overlap_color", int)
|
||||
if overlap_color is not None:
|
||||
plot.overlap_color = tuple(overlap_color)
|
||||
|
||||
# Set universe level
|
||||
level = get_text(elem, "level")
|
||||
if level is not None:
|
||||
plot.level = int(level)
|
||||
|
||||
return plot
|
||||
|
||||
def to_vtk(self, output: PathLike | None = None,
|
||||
openmc_exec: str = 'openmc', cwd: str = '.'):
|
||||
"""Render plot as an voxel image
|
||||
"""Render plot as a voxel image
|
||||
|
||||
This method runs OpenMC in plotting mode to produce a .vti file.
|
||||
|
||||
|
|
@ -1059,10 +1278,6 @@ class Plot(PlotBase):
|
|||
Path of the .vti file produced
|
||||
|
||||
"""
|
||||
if self.type != 'voxel':
|
||||
raise ValueError(
|
||||
'Generating a VTK file only works for voxel plots')
|
||||
|
||||
# Create plots.xml
|
||||
Plots([self]).export_to_xml(cwd)
|
||||
|
||||
|
|
@ -1082,6 +1297,20 @@ class Plot(PlotBase):
|
|||
return voxel_to_vtk(h5_voxel_file, output)
|
||||
|
||||
|
||||
def Plot(plot_id=None, name=''):
|
||||
"""Legacy Plot class for backward compatibility.
|
||||
|
||||
.. deprecated:: 0.15.4
|
||||
Use :class:`SlicePlot` for 2D slice plots or :class:`VoxelPlot` for 3D voxel plots.
|
||||
|
||||
"""
|
||||
warnings.warn(
|
||||
"The Plot class is deprecated. Use SlicePlot for 2D slice plots "
|
||||
"or VoxelPlot for 3D voxel plots.", FutureWarning
|
||||
)
|
||||
return SlicePlot(plot_id, name)
|
||||
|
||||
|
||||
class RayTracePlot(PlotBase):
|
||||
"""Definition of a camera's view of OpenMC geometry
|
||||
|
||||
|
|
@ -1737,16 +1966,16 @@ class SolidRayTracePlot(RayTracePlot):
|
|||
|
||||
|
||||
class Plots(cv.CheckedList):
|
||||
"""Collection of Plots used for an OpenMC simulation.
|
||||
"""Collection of plots used for an OpenMC simulation.
|
||||
|
||||
This class corresponds directly to the plots.xml input file. It can be
|
||||
thought of as a normal Python list where each member is inherits from
|
||||
:class:`PlotBase`. It behaves like a list as the following example
|
||||
demonstrates:
|
||||
|
||||
>>> xz_plot = openmc.Plot()
|
||||
>>> big_plot = openmc.Plot()
|
||||
>>> small_plot = openmc.Plot()
|
||||
>>> xz_plot = openmc.SlicePlot()
|
||||
>>> big_plot = openmc.VoxelPlot()
|
||||
>>> small_plot = openmc.SlicePlot()
|
||||
>>> p = openmc.Plots((xz_plot, big_plot))
|
||||
>>> p.append(small_plot)
|
||||
>>> small_plot = p.pop()
|
||||
|
|
@ -1782,7 +2011,7 @@ class Plots(cv.CheckedList):
|
|||
----------
|
||||
index : int
|
||||
Index in list
|
||||
plot : openmc.Plot
|
||||
plot : openmc.PlotBase
|
||||
Plot to insert
|
||||
|
||||
"""
|
||||
|
|
@ -1903,8 +2132,13 @@ class Plots(cv.CheckedList):
|
|||
plots.append(WireframeRayTracePlot.from_xml_element(e))
|
||||
elif plot_type == 'solid_raytrace':
|
||||
plots.append(SolidRayTracePlot.from_xml_element(e))
|
||||
elif plot_type in ('slice', 'voxel'):
|
||||
plots.append(Plot.from_xml_element(e))
|
||||
elif plot_type == 'slice':
|
||||
plots.append(SlicePlot.from_xml_element(e))
|
||||
elif plot_type == 'voxel':
|
||||
plots.append(VoxelPlot.from_xml_element(e))
|
||||
elif plot_type is None:
|
||||
# For backward compatibility, assume slice if no type specified
|
||||
plots.append(SlicePlot.from_xml_element(e))
|
||||
else:
|
||||
raise ValueError("Unknown plot type: {}".format(plot_type))
|
||||
return plots
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ using mcpl_read_fpt = const mcpl_particle_repr_t* (*)(mcpl_file_t* file_handle);
|
|||
using mcpl_close_file_fpt = void (*)(mcpl_file_t* file_handle);
|
||||
|
||||
using mcpl_hdr_add_data_fpt = void (*)(mcpl_outfile_t* file_handle,
|
||||
const char* key, int32_t ldata, const char* data);
|
||||
const char* key, uint32_t datalength, const char* data);
|
||||
using mcpl_create_outfile_fpt = mcpl_outfile_t* (*)(const char* filename);
|
||||
using mcpl_hdr_set_srcname_fpt = void (*)(
|
||||
mcpl_outfile_t* outfile_handle, const char* srcname);
|
||||
|
|
@ -150,13 +150,20 @@ struct McplApi {
|
|||
load_symbol_platform("mcpl_create_outfile"));
|
||||
hdr_set_srcname = reinterpret_cast<mcpl_hdr_set_srcname_fpt>(
|
||||
load_symbol_platform("mcpl_hdr_set_srcname"));
|
||||
hdr_add_data = reinterpret_cast<mcpl_hdr_add_data_fpt>(
|
||||
load_symbol_platform("mcpl_hdr_add_data"));
|
||||
add_particle = reinterpret_cast<mcpl_add_particle_fpt>(
|
||||
load_symbol_platform("mcpl_add_particle"));
|
||||
close_outfile = reinterpret_cast<mcpl_close_outfile_fpt>(
|
||||
load_symbol_platform("mcpl_close_outfile"));
|
||||
|
||||
// Try to load mcpl_hdr_add_data (available in MCPL >= 2.1.0)
|
||||
// Set to nullptr if not available for graceful fallback
|
||||
try {
|
||||
hdr_add_data = reinterpret_cast<mcpl_hdr_add_data_fpt>(
|
||||
load_symbol_platform("mcpl_hdr_add_data"));
|
||||
} catch (const std::runtime_error&) {
|
||||
hdr_add_data = nullptr;
|
||||
}
|
||||
|
||||
// Try to load mcpl_hdr_add_stat_sum (available in MCPL >= 2.1.0)
|
||||
// Set to nullptr if not available for graceful fallback
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class DistribmatTestHarness(PyAPITestHarness):
|
|||
# Plots
|
||||
####################
|
||||
|
||||
plot1 = openmc.Plot(plot_id=1)
|
||||
plot1 = openmc.SlicePlot(plot_id=1)
|
||||
plot1.basis = 'xy'
|
||||
plot1.color_by = 'cell'
|
||||
plot1.filename = 'cellplot'
|
||||
|
|
@ -81,7 +81,7 @@ class DistribmatTestHarness(PyAPITestHarness):
|
|||
plot1.width = (7, 7)
|
||||
plot1.pixels = (400, 400)
|
||||
|
||||
plot2 = openmc.Plot(plot_id=2)
|
||||
plot2 = openmc.SlicePlot(plot_id=2)
|
||||
plot2.basis = 'xy'
|
||||
plot2.color_by = 'material'
|
||||
plot2.filename = 'matplot'
|
||||
|
|
|
|||
|
|
@ -73,13 +73,13 @@ def pin_model_attributes():
|
|||
tal.scores = ['flux', 'fission']
|
||||
tals.append(tal)
|
||||
|
||||
plot1 = openmc.Plot(plot_id=1)
|
||||
plot1 = openmc.SlicePlot(plot_id=1)
|
||||
plot1.origin = (0., 0., 0.)
|
||||
plot1.width = (pitch, pitch)
|
||||
plot1.pixels = (300, 300)
|
||||
plot1.color_by = 'material'
|
||||
plot1.filename = 'test'
|
||||
plot2 = openmc.Plot(plot_id=2)
|
||||
plot2 = openmc.SlicePlot(plot_id=2)
|
||||
plot2.origin = (0., 0., 0.)
|
||||
plot2.width = (pitch, pitch)
|
||||
plot2.pixels = (300, 300)
|
||||
|
|
@ -911,7 +911,7 @@ def test_setter_from_list():
|
|||
model = openmc.Model(tallies=[tally])
|
||||
assert isinstance(model.tallies, openmc.Tallies)
|
||||
|
||||
plot = openmc.Plot()
|
||||
plot = openmc.SlicePlot()
|
||||
model = openmc.Model(plots=[plot])
|
||||
assert isinstance(model.plots, openmc.Plots)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,11 @@ from openmc.plots import _SVG_COLORS
|
|||
|
||||
@pytest.fixture(scope='module')
|
||||
def myplot():
|
||||
plot = openmc.Plot(name='myplot')
|
||||
plot = openmc.SlicePlot(name='myplot')
|
||||
plot.width = (100., 100.)
|
||||
plot.origin = (2., 3., -10.)
|
||||
plot.pixels = (500, 500)
|
||||
plot.filename = './not-a-dir/myplot'
|
||||
plot.type = 'slice'
|
||||
plot.basis = 'yz'
|
||||
plot.background = 'black'
|
||||
plot.background = (0, 0, 0)
|
||||
|
|
@ -80,8 +79,7 @@ def test_voxel_plot(run_in_tmpdir):
|
|||
geometry.export_to_xml()
|
||||
materials = openmc.Materials()
|
||||
materials.export_to_xml()
|
||||
vox_plot = openmc.Plot()
|
||||
vox_plot.type = 'voxel'
|
||||
vox_plot = openmc.VoxelPlot()
|
||||
vox_plot.id = 12
|
||||
vox_plot.width = (1500., 1500., 1500.)
|
||||
vox_plot.pixels = (200, 200, 200)
|
||||
|
|
@ -97,8 +95,9 @@ def test_voxel_plot(run_in_tmpdir):
|
|||
assert Path('h5_voxel_plot.h5').is_file()
|
||||
assert Path('another_test_voxel_plot.vti').is_file()
|
||||
|
||||
slice_plot = openmc.Plot()
|
||||
with pytest.raises(ValueError):
|
||||
# SlicePlot should not have to_vtk method
|
||||
slice_plot = openmc.SlicePlot()
|
||||
with pytest.raises(AttributeError):
|
||||
slice_plot.to_vtk('shimmy.vti')
|
||||
|
||||
|
||||
|
|
@ -153,14 +152,14 @@ def test_from_geometry():
|
|||
geom = openmc.Geometry(univ)
|
||||
|
||||
for basis in ('xy', 'yz', 'xz'):
|
||||
plot = openmc.Plot.from_geometry(geom, basis)
|
||||
plot = openmc.SlicePlot.from_geometry(geom, basis)
|
||||
assert plot.origin == pytest.approx((0., 0., 0.))
|
||||
assert plot.width == pytest.approx((width, width))
|
||||
assert plot.basis == basis
|
||||
|
||||
|
||||
def test_highlight_domains():
|
||||
plot = openmc.Plot()
|
||||
plot = openmc.SlicePlot()
|
||||
plot.color_by = 'material'
|
||||
plots = openmc.Plots([plot])
|
||||
|
||||
|
|
@ -179,8 +178,8 @@ def test_xml_element(myplot):
|
|||
assert elem.find('pixels') is not None
|
||||
assert elem.find('background').text == '0 0 0'
|
||||
|
||||
newplot = openmc.Plot.from_xml_element(elem)
|
||||
attributes = ('id', 'color_by', 'filename', 'type', 'basis', 'level',
|
||||
newplot = openmc.SlicePlot.from_xml_element(elem)
|
||||
attributes = ('id', 'color_by', 'filename', 'basis', 'level',
|
||||
'meshlines', 'show_overlaps', 'origin', 'width', 'pixels',
|
||||
'background', 'mask_background')
|
||||
for attr in attributes:
|
||||
|
|
@ -200,11 +199,11 @@ def test_to_xml_element_proj(myprojectionplot):
|
|||
|
||||
|
||||
def test_plots(run_in_tmpdir):
|
||||
p1 = openmc.Plot(name='plot1')
|
||||
p1 = openmc.SlicePlot(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 = openmc.SlicePlot(name='plot2')
|
||||
p2.origin = (-3., -3., -3.)
|
||||
plots = openmc.Plots([p1, p2])
|
||||
assert len(plots) == 2
|
||||
|
|
@ -213,7 +212,7 @@ def test_plots(run_in_tmpdir):
|
|||
plots = openmc.Plots([p1, p2, p3])
|
||||
assert len(plots) == 3
|
||||
|
||||
p4 = openmc.Plot(name='plot4')
|
||||
p4 = openmc.VoxelPlot(name='plot4')
|
||||
plots.append(p4)
|
||||
assert len(plots) == 4
|
||||
|
||||
|
|
@ -230,8 +229,7 @@ def test_plots(run_in_tmpdir):
|
|||
|
||||
def test_voxel_plot_roundtrip():
|
||||
# Define a voxel plot and create XML element
|
||||
plot = openmc.Plot(name='my voxel plot')
|
||||
plot.type = 'voxel'
|
||||
plot = openmc.VoxelPlot(name='my voxel plot')
|
||||
plot.filename = 'voxel1'
|
||||
plot.pixels = (50, 50, 50)
|
||||
plot.origin = (0., 0., 0.)
|
||||
|
|
@ -243,7 +241,6 @@ def test_voxel_plot_roundtrip():
|
|||
new_plot = plot.from_xml_element(elem)
|
||||
assert new_plot.name == plot.name
|
||||
assert new_plot.filename == plot.filename
|
||||
assert new_plot.type == plot.type
|
||||
assert new_plot.pixels == plot.pixels
|
||||
assert new_plot.origin == plot.origin
|
||||
assert new_plot.width == plot.width
|
||||
|
|
@ -288,10 +285,9 @@ def test_phong_plot_roundtrip():
|
|||
def test_plot_directory(run_in_tmpdir):
|
||||
pwr_pin = openmc.examples.pwr_pin_cell()
|
||||
|
||||
# create a standard plot, expected to work
|
||||
plot = openmc.Plot()
|
||||
# create a standard slice plot, expected to work
|
||||
plot = openmc.SlicePlot()
|
||||
plot.filename = 'plot_1'
|
||||
plot.type = 'slice'
|
||||
plot.pixels = (10, 10)
|
||||
plot.color_by = 'material'
|
||||
plot.width = (100., 100.)
|
||||
|
|
|
|||
273
tests/unit_tests/test_slice_voxel_plots.py
Normal file
273
tests/unit_tests/test_slice_voxel_plots.py
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
"""Tests for SlicePlot and VoxelPlot classes
|
||||
|
||||
This module tests the functionality of the new SlicePlot and VoxelPlot
|
||||
classes that replace the legacy Plot class.
|
||||
"""
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
import openmc
|
||||
|
||||
|
||||
def test_slice_plot_initialization():
|
||||
"""Test SlicePlot initialization with defaults"""
|
||||
plot = openmc.SlicePlot()
|
||||
assert plot.width == [4.0, 4.0]
|
||||
assert plot.pixels == [400, 400]
|
||||
assert plot.basis == 'xy'
|
||||
assert plot.origin == [0., 0., 0.]
|
||||
|
||||
|
||||
def test_slice_plot_width_validation():
|
||||
"""Test that SlicePlot only accepts 2 values for width"""
|
||||
plot = openmc.SlicePlot()
|
||||
|
||||
# Should accept 2 values
|
||||
plot.width = [10.0, 20.0]
|
||||
assert plot.width == [10.0, 20.0]
|
||||
|
||||
# Should reject 1 value
|
||||
with pytest.raises(ValueError, match='must be of length "2"'):
|
||||
plot.width = [10.0]
|
||||
|
||||
# Should reject 3 values
|
||||
with pytest.raises(ValueError, match='must be of length "2"'):
|
||||
plot.width = [10.0, 20.0, 30.0]
|
||||
|
||||
|
||||
def test_slice_plot_pixels_validation():
|
||||
"""Test that SlicePlot only accepts 2 values for pixels"""
|
||||
plot = openmc.SlicePlot()
|
||||
|
||||
# Should accept 2 values
|
||||
plot.pixels = [100, 200]
|
||||
assert plot.pixels == [100, 200]
|
||||
|
||||
# Should reject 1 value
|
||||
with pytest.raises(ValueError, match='must be of length "2"'):
|
||||
plot.pixels = [100]
|
||||
|
||||
# Should reject 3 values
|
||||
with pytest.raises(ValueError, match='must be of length "2"'):
|
||||
plot.pixels = [100, 200, 300]
|
||||
|
||||
|
||||
def test_slice_plot_basis():
|
||||
"""Test that SlicePlot has basis attribute"""
|
||||
plot = openmc.SlicePlot()
|
||||
|
||||
# Test all valid basis values
|
||||
for basis in ['xy', 'xz', 'yz']:
|
||||
plot.basis = basis
|
||||
assert plot.basis == basis
|
||||
|
||||
# Test invalid basis
|
||||
with pytest.raises(ValueError):
|
||||
plot.basis = 'invalid'
|
||||
|
||||
|
||||
def test_slice_plot_meshlines():
|
||||
"""Test that SlicePlot has meshlines attribute"""
|
||||
plot = openmc.SlicePlot()
|
||||
|
||||
meshlines = {
|
||||
'type': 'tally',
|
||||
'id': 1,
|
||||
'linewidth': 2,
|
||||
'color': (255, 0, 0)
|
||||
}
|
||||
plot.meshlines = meshlines
|
||||
assert plot.meshlines == meshlines
|
||||
|
||||
|
||||
def test_slice_plot_xml_roundtrip():
|
||||
"""Test SlicePlot XML serialization and deserialization"""
|
||||
plot = openmc.SlicePlot(name='test_slice')
|
||||
plot.width = [15.0, 25.0]
|
||||
plot.pixels = [150, 250]
|
||||
plot.basis = 'xz'
|
||||
plot.origin = [1.0, 2.0, 3.0]
|
||||
plot.color_by = 'material'
|
||||
plot.filename = 'test_plot'
|
||||
|
||||
# Convert to XML and back
|
||||
elem = plot.to_xml_element()
|
||||
new_plot = openmc.SlicePlot.from_xml_element(elem)
|
||||
|
||||
# Check all attributes preserved
|
||||
assert new_plot.name == plot.name
|
||||
assert new_plot.width == pytest.approx(plot.width)
|
||||
assert new_plot.pixels == tuple(plot.pixels)
|
||||
assert new_plot.basis == plot.basis
|
||||
assert new_plot.origin == pytest.approx(plot.origin)
|
||||
assert new_plot.color_by == plot.color_by
|
||||
assert new_plot.filename == plot.filename
|
||||
|
||||
|
||||
def test_slice_plot_from_geometry():
|
||||
"""Test creating SlicePlot from geometry"""
|
||||
# Create simple geometry
|
||||
s = openmc.Sphere(r=10.0, boundary_type='vacuum')
|
||||
c = openmc.Cell(region=-s)
|
||||
univ = openmc.Universe(cells=[c])
|
||||
geom = openmc.Geometry(univ)
|
||||
|
||||
# Test all basis options
|
||||
for basis in ['xy', 'xz', 'yz']:
|
||||
plot = openmc.SlicePlot.from_geometry(geom, basis=basis)
|
||||
assert plot.basis == basis
|
||||
assert plot.width == pytest.approx([20.0, 20.0])
|
||||
assert plot.origin == pytest.approx([0.0, 0.0, 0.0])
|
||||
|
||||
|
||||
def test_voxel_plot_initialization():
|
||||
"""Test VoxelPlot initialization with defaults"""
|
||||
plot = openmc.VoxelPlot()
|
||||
assert plot.width == [4.0, 4.0, 4.0]
|
||||
assert plot.pixels == [400, 400, 400]
|
||||
assert plot.origin == [0., 0., 0.]
|
||||
|
||||
|
||||
def test_voxel_plot_width_validation():
|
||||
"""Test that VoxelPlot only accepts 3 values for width"""
|
||||
plot = openmc.VoxelPlot()
|
||||
|
||||
# Should accept 3 values
|
||||
plot.width = [10.0, 20.0, 30.0]
|
||||
assert plot.width == [10.0, 20.0, 30.0]
|
||||
|
||||
# Should reject 2 values
|
||||
with pytest.raises(ValueError, match='must be of length "3"'):
|
||||
plot.width = [10.0, 20.0]
|
||||
|
||||
# Should reject 1 value
|
||||
with pytest.raises(ValueError, match='must be of length "3"'):
|
||||
plot.width = [10.0]
|
||||
|
||||
|
||||
def test_voxel_plot_pixels_validation():
|
||||
"""Test that VoxelPlot only accepts 3 values for pixels"""
|
||||
plot = openmc.VoxelPlot()
|
||||
|
||||
# Should accept 3 values
|
||||
plot.pixels = [100, 200, 300]
|
||||
assert plot.pixels == [100, 200, 300]
|
||||
|
||||
# Should reject 2 values
|
||||
with pytest.raises(ValueError, match='must be of length "3"'):
|
||||
plot.pixels = [100, 200]
|
||||
|
||||
# Should reject 1 value
|
||||
with pytest.raises(ValueError, match='must be of length "3"'):
|
||||
plot.pixels = [100]
|
||||
|
||||
|
||||
def test_voxel_plot_xml_roundtrip():
|
||||
"""Test VoxelPlot XML serialization and deserialization"""
|
||||
plot = openmc.VoxelPlot(name='test_voxel')
|
||||
plot.width = [10.0, 20.0, 30.0]
|
||||
plot.pixels = [100, 200, 300]
|
||||
plot.origin = [1.0, 2.0, 3.0]
|
||||
plot.color_by = 'cell'
|
||||
plot.filename = 'voxel_plot'
|
||||
|
||||
# Convert to XML and back
|
||||
elem = plot.to_xml_element()
|
||||
new_plot = openmc.VoxelPlot.from_xml_element(elem)
|
||||
|
||||
# Check all attributes preserved
|
||||
assert new_plot.name == plot.name
|
||||
assert new_plot.width == pytest.approx(plot.width)
|
||||
assert new_plot.pixels == tuple(plot.pixels)
|
||||
assert new_plot.origin == pytest.approx(plot.origin)
|
||||
assert new_plot.color_by == plot.color_by
|
||||
assert new_plot.filename == plot.filename
|
||||
|
||||
|
||||
def test_plot_deprecation_warning():
|
||||
"""Test that Plot class raises deprecation warning"""
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
openmc.Plot()
|
||||
|
||||
assert len(w) == 1
|
||||
assert issubclass(w[0].category, FutureWarning)
|
||||
assert "deprecated" in str(w[0].message).lower()
|
||||
|
||||
|
||||
def test_plot_returns_slice_plot():
|
||||
"""Test that Plot() returns a SlicePlot instance"""
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
plot = openmc.Plot()
|
||||
|
||||
# Should be an actual SlicePlot instance
|
||||
assert isinstance(plot, openmc.SlicePlot)
|
||||
|
||||
|
||||
def test_plot_type_setter_raises_error():
|
||||
"""Test that setting plot.type raises a helpful error"""
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
plot = openmc.Plot()
|
||||
|
||||
with pytest.raises(TypeError, match="no longer supported"):
|
||||
plot.type = 'voxel'
|
||||
|
||||
with pytest.raises(TypeError, match="no longer supported"):
|
||||
plot.type = 'slice'
|
||||
|
||||
|
||||
def test_plot_type_getter_warns():
|
||||
"""Test that getting plot.type raises a deprecation warning"""
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
plot = openmc.Plot()
|
||||
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
plot_type = plot.type
|
||||
|
||||
assert plot_type == 'slice'
|
||||
assert len(w) == 1
|
||||
assert issubclass(w[0].category, FutureWarning)
|
||||
assert "deprecated" in str(w[0].message).lower()
|
||||
|
||||
|
||||
def test_plots_collection_mixed_types():
|
||||
"""Test Plots collection with different plot types"""
|
||||
slice_plot = openmc.SlicePlot(name='slice')
|
||||
voxel_plot = openmc.VoxelPlot(name='voxel')
|
||||
wireframe_plot = openmc.WireframeRayTracePlot(name='wireframe')
|
||||
|
||||
plots = openmc.Plots([slice_plot, voxel_plot, wireframe_plot])
|
||||
|
||||
assert len(plots) == 3
|
||||
assert isinstance(plots[0], openmc.SlicePlot)
|
||||
assert isinstance(plots[1], openmc.VoxelPlot)
|
||||
assert isinstance(plots[2], openmc.WireframeRayTracePlot)
|
||||
|
||||
|
||||
def test_plots_collection_xml_roundtrip(run_in_tmpdir):
|
||||
"""Test XML export and import with new plot types"""
|
||||
s1 = openmc.SlicePlot(name='slice1')
|
||||
s1.width = [10.0, 20.0]
|
||||
s1.basis = 'xz'
|
||||
|
||||
v1 = openmc.VoxelPlot(name='voxel1')
|
||||
v1.width = [10.0, 20.0, 30.0]
|
||||
|
||||
plots = openmc.Plots([s1, v1])
|
||||
plots.export_to_xml()
|
||||
|
||||
# Read back
|
||||
new_plots = openmc.Plots.from_xml()
|
||||
|
||||
assert len(new_plots) == 2
|
||||
assert isinstance(new_plots[0], openmc.SlicePlot)
|
||||
assert isinstance(new_plots[1], openmc.VoxelPlot)
|
||||
assert new_plots[0].name == 'slice1'
|
||||
assert new_plots[1].name == 'voxel1'
|
||||
assert new_plots[0].basis == 'xz'
|
||||
assert new_plots[0].width == pytest.approx([10.0, 20.0])
|
||||
assert new_plots[1].width == pytest.approx([10.0, 20.0, 30.0])
|
||||
Loading…
Add table
Add a link
Reference in a new issue